Easiest way to add Vehicle to User with JPA/Hibernate using Spring - java

Let's say I had this in my UserDAO class:
#OneToMany(cascade=CascadeType.ALL, mappedBy="owner", fetch=FetchType.EAGER, orphanRemoval=true)
private Set<Vehicle> vehicles = new HashSet<Vehicle>();
Is this the recommended way to add a new vehicle to a User:
User user = userService.findByLoginName("MartinL");
Vehicle newVehicle = new Vehicle();
newVehicle.set(...) // setters omitted
newVehicle.setOwner(user) // is this needed in any case?
user.getVehicles().add(newVehicle) // add the new vehicle to the Set in User class
userService.save(user); // persist the modified user object to database
Is this the best practice or do I miss on anything?

I prefer the direct approach of saving the newVehicle (after setting the owner attribute, of course).
Advantages:
1) Your approach will force the framework to check all the attributes of user and write them back at the DB, and will also write again all of the vehicles.
2) It is more readable, in the sense that it makes more clear that your intent is just to add a new vehicle
3) If you are doing this change, you may not even need to retrieve the user object from database, if you use EntityManager.getReference()

You normally want to manage bidirectional associations from the (JPA defined) dependent side of the relationship, and within the dependent class. Pseudocode:
class User {
private Set<Vehicle> vehicles;
public void addVehicle(Vehicle vehicle) {
if(vehicle == null) return;
vehicle.setOwner(this);
vehicles.add(vehicle);
}
public void removeVehicle(Vehicle vehicle) {
if(vehicle == null) return;
if(vehicles.remove(vehicle)) {
vehicle.setOwner(null);
}
}
}
Managing the relationship outside of the entities leads to bugs and duplicated code.

Related

Spring Data JPA - Deleting a child in one-to-many relationship

I'm implementing a Spring boot application and using Spring Data JPA in it. As you know you don't have to implement the repository interface for just CRUD methods, because Spring Data JPA creates an implementation on the fly. So I have just this:
public interface PersonRepository extends JpaRepository<Person, Long> {}
I'm working with one-to-many relationship, this is in my Person domain:
#OneToMany(cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY,
mappedBy = "person")
private Set<Contact> contacts = new HashSet<>();
I decided to write an integration test for child removal from the parent:
#Test
public void removeFromContacts() {
// given
Person person = new Person ("test person");
Contact contact = new Contact("test#gmail.com", "+123456789");
contact.setPerson(person);
person.getContacts().add(contact);
personRepository.save(person);
Person savedPerson = personRepository.findOne(person.getId());
Contact persistedContact = savedPerson.getContacts().stream().findFirst().orElse(null);
// when
savedPerson.getContacts().remove(persistedContact);
persistedContact.setPerson(null);
Person edited = personRepository.save(savedPerson);
// then
Assert.assertTrue(edited.getContacts().isEmpty());
}
This test fails. The reason is savedPerson.getContacts().remove(persistedContact) line doesn't change anything, remove method returns false. It's pretty strange, because I'm trying to remove an object from a hash set which has only one object with exact same hash code (equals() method returns true as well). According to this answer the contact object could've been altered somehow after adding it to the hash set. The only thing I can think of is it happened after this line: personRepository.save(person).
If I'm right then I'm really confused: how should I remove the contact from a person, and even if I find a way, is it okay for personRepository.save method to cause a set to malfunction? And if I'm wrong I would love to know the right answer.
Thanks in advance.
Class Compte and Class User joind to one-to-one relationship
public void delete(Integer integer){
User user = userRepository.findOne(integer);
Compte compte = user.getCompte();
compte.setUser(null);
compteRepository.save(compte);
user.setCompte(null);
userRepository.save(user);
compteRepository.delete(compte);
userRepository.delete(user);
}

Delete a record on an OneToMany association with Hibernate

I am working on an application using Hibernate and I want to delete some records in the database. The relevant Entities are:
#Entity
public class Product {
private String serialNumber;
private Set<Part> parts = new HashSet<Part>();
#Id
public String getSerialNumber() { return serialNumber; }
void setSerialNumber(String sn) { serialNumber = sn; }
#OneToMany
public Set<Part> getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
...
}
#Entity
public class Part implements Serializable {
#Id
#GeneratedValue
private Long part_id;
private String userCode = "";
//getters and setters
....
}
I have let Eclipse implement equals and hashCode in Entity Part based on part_id and userCode. There is also an Entity Factory from which 'begin' all the associations to the other Entities. Therefore, in order to save all the changes it only necessary to execute the comand:
session.update(factory);
All the changes are saved successfully except from the delete from parts. I do:
products.getParts.remove(part);
The issues comig out are:
1) In some cases is part from the Set not removed although the comparison to a part in the Set with equals true returns (the part is in Set according to equals but it is not removed)
2) Even if the remove in the Set succeeds, the record in the database is not deleted.
Based on the above ascertainments what is the best way to remove the records in this case using not loads of queries?
You need to explicitly remove the child:
session.delete(part);
From Hibernate Docs:
The following code:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
c.setParent(null);
session.flush();
will not remove c from the database. In this case, it will only remove
the link to p and cause a NOT NULL constraint violation. You need to
explicitly delete() the Child.
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
session.delete(c);
session.flush();
When using hibernate to map relationships you must be aware of two main concerns:
Which is the owner of the relationship? The owner is the side of the relation whose changes will be persisted in database. In your case the owner is the Part object.
Is a true parent/child relationship or simply a composition relationship? In your case I think the answer is composition
If you want to manage the relation using the set, you have two options:
use #ElementCollection instead of #OnetoMany
change ownership. Something like this:
#OneToMany
#JoinColumn(name="part_id")
public Set<Part> getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
However, the second option is not recommended here. See section 2.2.5.3.1.2.

Assign existing child entity for parent entity JPA- GAE

I have 2 entity that have a relationship between them.
both of the entities has already created and inserted to the DB.
in some point the user has the ability to connect between them.
when im try doing it im getting:
javax.persistence.PersistenceException: Detected attempt to establish
WannaMeetUser("654321") as the parent of WannaMeetUser("123456") but
the entity identified by WannaMeetUser("123456") has already been
persisted without a parent. A parent cannot be established or changed
once an object has been persisted.
this is a transitive relation (user can have many friends from king of user):
the code is attached:
#Entity
public class WannaMeetUser {
#Id //signifies the primary key
#Column(name = "ID", nullable = false)
private Key id;
#ManyToMany
#Basic
private List<WannaMeetUser> userFriends = new ArrayList<WannaMeetUser>();
}
public void addFriendToWannaMeetUser(#Named("userId") String userId,
#Named("friendId") String friendId) {
EntityManager mgr = getEntityManager();
try
{
WannaMeetUser user = mgr.find(WannaMeetUser.class, WannaMeetServerUtils.getKeyFromString("WannaMeetUser", userId));
WannaMeetUser friend = mgr.find(WannaMeetUser.class, WannaMeetServerUtils.getKeyFromString("WannaMeetUser", friendId));
String coupleId = getcoupleId(userId.toString(), friendId.toString());
if (friend == null || user == null) {
throw new EntityNotFoundException("Object does not exist");
}
WannaMeetCouple couple=mgr.find(WannaMeetCouple.class, coupleId);
if (couple == null) {
couple = createCouple(userId.toString(), friendId.toString());
couple.setId(coupleId);
setUserJoined(couple, userId.toString(), friendId.toString(), true);
}
else {
if (isFriendAllready(couple, userId.toString(), friendId.toString()))
;
setUserJoined(couple, userId.toString(), friendId.toString(), false);
doAddFriend(user, friend, 10, 12321321);
mgr.persist(couple);
mgr.persist(friend);
mgr.persist(user);
}
} catch (Exception e) {
e.printStackTrace();
}
finally{
mgr.close();
}
}
my question is What is the best way to crate such a relationship ?
Thanks
The messages says user 1 has already been persisted without having any parent, but now we try
to persist user 2 as a parent of user 1, but user 1 is known not to have a parent.
try to persist everything in one go using cascading instead. You can declare a cascading relation like this:
#ManyToMany(cascade=CascadeType.ALL)
private List<User> userFriends;
And then persist everything in one go using cascading:
User user1 = new User();
User user2 = new User();
user1.getUserFriends().add(user2);
// this persists the whole tree in one go
entityManager.persist(user1);
Parent-child entities should be used only for relationships that never change (e.g. a user and his photo). For dynamic relationships you have three options:
(A) Store references to other objects as a property (e.g. userFriends property in a User entity). If a relationship is unidirectional (e.g. User A likes User B), then you can update only one entity. For bidirectional relationships, you update both entities.
(B) Create a new entity type Relationship with two properties User A and User B. Create and delete these entities as necessary.
(C) When User A likes User B, create a new entity Relationship as a child entity of User A and use an id of User B as an id for this new Relationship entity.
The choice between these options depends on your data model and data access patterns.

How to persist a JPA autogenerated value before commit?

Hi I am beginner on the JPA world, I have a question on the auto-generated id. We are using OpenJPA, My application requires that one operation which creates bunch of related objects must be inside a single transaction which will be part of global transaction (XA). I am struggling in get the auto-generated id and use it to set values in other object. Here is the snapshot:
#ENTITY
#Table(name="TDepart")
class Department{
private long id;
#GeneratedValue(strategy= GenerationType.TABLE)
public long getId();
}
//And some classes like
class Professor {
void setDepartmentId(long id);
}
Now I have a business operation:
void doSomething()
{
Department depart = new Department();
handleProfessors (depart);
handleStudent (depart);
//and someother rountines need to refer department
}
//sample code which will getId
void handleProfessors(Department depart)
{
Professor p = new Professor ();
p.setDepartmentId(depart.getId);
}
So the Department.getId() will be called several times. The doSomething() will be in a single managed transaction, but the GeneratedValue will use an unmanaged tx. Now may problem is: whenever the getId is called, it will return a new value, and when the department is final persisted, the id is the latest number, so all other objects refer to an non-exists department. Is there anyway to handle this so that the id is (kindof) persist?
I have a loose requirement solution, which will create an dummy department first and persist it, so the ID is not change. The code is similar to this:
void doSomething()
{
Department depart = createEmptyDepartment(); // always new tx so department is created;
try {
reallyDoSomehing(); // tx required so it is part of global tx
}
catch (SomeException e) {
removeEmptyDepartment(depart);
}
Now I do not know how I can set the tx for removeEmptyDepartment(), if is required it will use the global request so it will be rollback as well. If it is new tx it will cause a deadlock since reallyDoSomething() will lock the db row.
Please, give me some ideas on how to solve it.
Thanks,
Howard.
I don't fully understand your issue, but I'm thinking that rather than setting the departmentId in your professor class, you should be setting the Department instead
i.e.
void setDepartmentId(long id);
change to
void setDepartment(Department d);
The id components should be handled automatically by the entity manager

JPA: Eclipselink does not persist bi-directional relationships in database

My domain model in my Java EE 6 application contains bi-directional relationships like the following:
#Entity
public class Users implements PrimaryKeyHolder<String>, Serializable {
#Id
private String username;
#ManyToMany(mappedBy= "users")
private List<Category> categories;
public List<Category> getCategories() {
if (categories == null) {
categories = new ArrayList<Category>();
}
return Collections.unmodifiableList(categories);
}
public void addCategory(Category category) {
if (categories == null) {
categories = new ArrayList<Category>();
}
categories.add(category);
if (!category.getUsers().contains(this)) {
category.addUser(this);
}
}
public void removeCategory(Category category) {
if (categories == null) {
categories = new ArrayList<Category>();
}
categories.remove(category);
if (category.getUsers().contains(this)) {
category.removeUser(this);
}
}
public void setCategories(Collection<Category> categories) {
if (this.categories == null) {
this.categories = new ArrayList<Category>();
}
for (Iterator<Category> it = this.categories.iterator(); it.hasNext();) {
Category category = it.next();
it.remove();
if (category.getUsers().contains(this)) {
category.removeUser(this);
}
}
for (Category category : categories) {
addCategory(category);
}
}
}
#Entity
public class Category implements PrimaryKeyHolder<Long>, Serializable {
#Id
private Long id;
#ManyToMany
private List<User> users;
public List<User> getUsers() {
if (users == null) {
users = new ArrayList<User>();
}
return Collections.unmodifiableList(users);
}
protected void addUser(User user) {
if (users == null) {
users = new ArrayList<User>();
}
users.add(user);
}
protected void removeUser(User user) {
if (users == null) {
users = new ArrayList<User>();
}
users.remove(user);
}
}
UPDATE: I added relationship management code. Relationships are only set on the user side, therefore, the add/remove methods are protected in the Categoriy class. I set the categories on the user via setCategories.
Eclipselink correctly generates a join table CATEGORY_USERS. However, it does not persist any information in it (it only caches the information). E.g. when I execute a find operation on the entity manager (e.g. a user), it returns the complete object graph (including the category relationship). But when I look at the tables, information are not updated (even though the transactions are committed). I also inserted a flush operation in my code, without success. Basic information (like String, Integer, etc. columns) gets correctly persisted and updated. After turning the log level to FINE, I can see that no SQL statements are executed for the relationships and the join table, respectively. But I do see SQL statements for uni-directional relationships.
My datamodel is covered by extensive unit tests, which all pass successfully. I basically do the same operation as in the container, commit the transaction, reload the entities from the db and check if the relationships are correctly set, which they are (I'm using the in-memory derby database for testing).
My app server is Glassfish v3.1-b17.
Any help is appreciated.
Thanks,
Theo
Ensure you are setting both sides of the relationship. The specification requires that the application sets both sides of the relationship as there is no relationship maintenance in JPA.
After endless hours of trying I finally got to a solution: I simply changed the owning side of the relationship, i.e. I put the mappedBy attribute to the category entity like this:
#ManyToMany(mappedBy= "categories")
private List<User> users;
The explanation for this can be found here
Four points:
1.- When you have an error, it's more simple find solution isolating them in an example (Or unit test) that reproduces the error. In your case, you could do an example with more simple getter and setter (for example, removing unmodifiableList use and other innecesary methods for testing actually issue).
2.- I advise you to use pojos for model, without any logic. So, remove logic from pojos.
3.- We are using eclipselink and we do not have problems persisting relations. So, it is more possible that error will be in your code.
4.- Test annoting relation with "cascade = javax.persistence.CascadeType.ALL"
Apology for my poor English :(

Categories

Resources