JPA: question on impedance mismatch in OneToMany relations - java

I have a question about JPA-2.0 (provider is Hibernate) relationships and their corresponding management in Java. Let's assume i have a Department and an Employee entity:
#Entity
public class Department {
...
#OneToMany(mappedBy = "department")
private Set<Employee> employees = new HashSet<Employee>();
...
}
#Entity
public class Employee {
...
#ManyToOne(targetEntity = Department.class)
#JoinColumn
private Department department;
...
}
Now i know i have to manage the Java relationships myself, as in the following unit test:
#Transactional
#Test
public void testBoth() {
Department d = new Department();
Employee e = new Employee();
e.setDepartment(d);
d.getEmployees().add(e);
em.persist(d);
em.persist(e);
assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
assertNotNull(em.find(Department.class, d.getId()).getEmployees());
}
If i leave out either e.setDepartment(d) or d.getEmployees().add(e) the assertions will fail. So far, so good. What if i commit the database transaction in between?
#Test
public void testBoth() {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Department d = new Department();
Employee e = new Employee();
e.setDepartment(d);
d.getEmployees().add(e);
em.persist(d);
em.persist(e);
em.getTransaction().commit();
em.close();
em = emf.createEntityManager();
em.getTransaction().begin();
assertNotNull(em.find(Employee.class, e.getId()).getDepartment());
assertNotNull(em.find(Department.class, d.getId()).getEmployees());
em.getTransaction().commit();
em.close();
}
Do i still need to manage both sides of the relation? No, as it turns out, i don't have to. With this modification
e.setDepartment(d);
//d.getEmployees().add(e);
the assertions still succeed. However, if i only set the other side:
//e.setDepartment(d);
d.getEmployees().add(e);
the assertions fail. Why? Is is because the Employee is the owning side of the relation? Can i change that behavior by annotating differently? Or is it just always the "One" side of the "OneToMany" that determines when the foreign key field in the database is filled?

I don't know what your test is trying to demonstrate but the fact is you must handle both sides of the association when working with bidirectional associations. Not doing so is incorrect. Period.
Update: While the spec reference mentioned by axtavt is of course accurate, I insist, you definitely must set both sides of a bi-directional association. Not doing so is incorrect and the association between your entities in the first persistence context is broken. The JPA wiki book puts it like this:
As with all bi-directional relationships it is your object model's and application's responsibility to maintain the relationship in both direction. There is no magic in JPA, if you add or remove to one side of the collection, you must also add or remove from the other side, see object corruption. Technically the database will be updated correctly if you only add/remove from the owning side of the relationship, but then your object model will be out of synch, which can cause issues.
In other words, the only correct and safe way to manage your bidirectional association in Java is to set both sides of the link. This is usually done using defensive link management methods, like this:
#Entity
public class Department {
...
#OneToMany(mappedBy = "department")
private Set<Employee> employees = new HashSet<Employee>();
...
public void addToEmployees(Employee employee) {
this.employees.add(employee);
employee.setDepartment(this);
}
}
I repeat, not doing so is incorrect. Your test only works because you're hitting the database in a new persistence context (i.e. a very particular situation, not the general one) but the code would break in many other situations.

Entity relationships in JPA have owning and inverse sides. Database updates are determined by the state of the owning side. In your case Employee is an owning side due to the mappedBy attribute.
From the JPA 2.0 specification:
2.9 Entity Relationships
...
Relationships may be bidirectional or
unidirectional. A bidirectional
relationship has both an owning side
and an inverse (non-owning) side. A
unidirectional relationship has only
an owning side. The owning side of a
relationship determines the updates to
the relationship in the database, as
described in section
3.2.4.
The following rules apply to bidirectional relationships:
The inverse side of a bidirectional
relationship must refer to its owning
side by use of the mappedBy element of
the OneToOne, OneToMany, or ManyToMany
annotation. The mappedBy element
designates the property or field in
the entity that is the owner of the
relationship.
The many side of
one-to-many / many-to-one
bidirectional relationships must be
the owning side, hence the mappedBy
element cannot be specified on the
ManyToOne annotation.
For
one-to-one bidirectional
relationships, the owning side
corresponds to the side that contains
the corresponding foreign key.
For
many-to-many bidirectional
relationships either side may be the
owning side.

The reason why the second test in a new persistence context succeeds if you only update the owning side in a previous context is that the persistence provider obviously can't know that when persisting you did not update the inverse side as well. It only cares about the owning side for persistence purposes. However, when you get persistent objects from a persistence provider, the provider sets the bidirectional associations properly on both sides (it is simply assumed they were persisted properly, too). However, as many others here have already pointed out, it is not the responsibility of the persistence provider to complete newly created bidirectional associations and you should always properly maintain bidirectional associations in your code.

Related

Why is the #JoinTable annotation is always present along with the #OneToMany annotation

Consider a one-to-many association like project and task, where a project can have multiple tasks. If this is modeled using a foreign key clause in the task table which refers to the primary key of the project table, then we put the #JoinColumn annotation in the owning side i.e. the task table as the many side is always the owning side.
But if this is mapped by using a third table project_task, the #JoinTable annotation is being used on the inverse side i.e. on the project entity. But #JoinTable should always be on the owning side.
Approach one with foreign key:
#Entity
public class Project {
#OneToMany(mappedBy = "project")
private Collection<Task> tasks;
}
#Entity
public class Task {
#ManyToOne
#JoinColumn(name="PROJECT_ID")
private Project project;
}
Approach two with third table and #JoinTable Annotation:
#Entity
public class Project {
#JoinTable(name = "MY_JT",
joinColumns = #JoinColumn(
name = "PROJECT_ID",
),
inverseJoinColumns = #JoinColumn(
name = "TASK_ID"
)
#OneToMany
private List<Task> tasks;
}
#Entity
public class Task {
private Project project;
}
Why is the owning side shifted to the project entity when using #JoinTable?
Try to understand owning side as the entity which knows of the other end of the association.
I am the owning entity if i'm able to reach the associated entity so i have informations which allow me to reach the associated entity.
The most used case is to put the foreign key on the many side of the association.
So if you have a relation between Project and Task and we 're telling that 1 Project can have many Tasks.
A project_id column will be add on Task table which will be the owning side of the relation because it held information (Project Fk) which allow it to reach Project Entity from Car.
When you're using #Jointable on OneToMany association you're designing a unidirectional association so car entity don't have anymore the information to reach associated User entity like in a bidirectional association.
This information is now kept by a join table which is in User entity so User entity become the owning side of the relation.
So to answer your question the owning side shifted to Projet because now you can't anymore reach a project from a task and the only way to have tasks associated to a project is to use the join table held by Project entity. So now It's project entity through its joinTable wich keep informations to retrieve the tasks associated to a project.
Hope it's clear.
What do you mean by 'shifted'? The owning side of an association is either the only side of that association, or the side whose association annotation does not have the mappedBy parameter present.
In your second example, Project is the owning side, because the association is unidirectional (there is no mapping of that association in Task).
The owning side of a association is the one that manages the association (i.e. changes to that side will be recorded). Had Task been defined like this:
#Entity
public class Task {
#ManyToOne(mappedBy="tasks")
private Project project;
}
Project would still be the owning side (meaning: changes to Project.tasks would be recorded, while changes to Task.project would be ignored). This is an unusual, yet valid mapping (it typically makes more sense to declare the many side the owning side in a bidirectional one-to-many association, but doing the opposite is also permitted).

What is the role of mappedBy in JPA or inverse="true" in Hibernate?

I have referred to various docs on Hibernate and have finally come to a very simple answer on this question.
I just wanted to clarify whether my final simple answer on this question is correct or not. Please help
Here is my answer
Inverse is a an attribute of tag in hibernate. It is generally used in OneToMany / ManyToOne bidirectional mapping.
inverse="true" means the child entity is the owner of relationship between the two entities and the child entity should take care of persistence operation with the foreign key column in the child table.
You are right with your answer.
The reason for "inverse" is that in a database there is always one side controlling a relation (e.g. the FK side), while in your bidirectional domain model mapping you can make changes to both the one-to-many side (add/remove children) and the child side (setting the parent). That's why both sides need to stay in sync.
Hibernate can't automatically enforce the synchronism constraint, so it's the developer responsibility to make it happen. The only thing Hibernate can do is to observe one side only (in order to translate state transitions into SQL queries).
Therefore it's a good habit to provide add/removeChild like utility methods:
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<Child>();
public List<Child> getChildren() {
return children;
}
public void addChild(Child child) {
children.add(child);
child.setParent(this);
}
public void removeChild(Child child) {
children.remove(child);
child.setParent(null);
}
}
Adding to what #Vlad rightly pointed out, another side effect of how this impacts the DML query (atleast how Hibernate does it ) generated is that the DML for state change of non-inverse side is a single query (the update of foreign key is part of that DML) instead of two. This is well illustrated here.

How to persist two entities at the same time with #OneToOne relationship?

I have two entities Travel and Assurance with #OneToOne relationship. Both entities must be created via the same interface with a Save botton. I use this method:
ManagedBean.java:
public String add(){
newTravel = manager.createTravel(arrivalDate, returnDate, lengthToStay, addToStay, visitPurpose);
newAssurance = manager.createAssurance(company, assuranceStart, assuranceEnd, newTravel);
return "Travellers";
}
In the database, I found the Travel_Id associated to the Assurance but The Assurance_Id is null in the Travels Table.
It seems that your relationship is not bilateral (i.e you do not use the mappedBy annotation property). If you used one, you would have only one column (either Travel_Id or Assurance_Id, depending where you put the mappedBy).
Also consider doing the saving inside the same transaction, e.g by using the same manager method and setting both side of the relationship.

Cascading in JPA+ Hibernate

Bellow are my entities:
public class EntityA {
//...
#OneToMany(mappedBy="entityA")
private Set entitieBs;
}
public class EntityB {
//...
#ManyToOne(cascade=CascadeType.PERSIST)
private EntityA entityA;
}
with accessors methods(getters and setters).
I intend that every time when I save a new EntityB object in the database (with an EntityA object set up as the "parent"), if I call EntityA.getEntityBs() on the parent of new EntityB, to have it added in the result Set. But if I do it as in my example it doesn't work.
Does anybody know where I am wrong?
Thanks!
Here is my java code how I persist the entity:
//...some code
EntityB eb = new EntityB();
eb.setEntityA(entityA);
entityManager.persist(entityB);
I want to make clear that I don't add entityB to entityA's set of entityBs.
This question is asked every two days.
JPA doesn't maintain the coherence of the object graph for you. It's your responsibility to maintain both sides of a bidirectional association. Everything will be as you expect if you commit the transaction, close the session, and reload the entities, because you have initialized the owning side of the association. But if you modify one side of the association in memory, JPA won't modify the other side for you.

I don't update both sides of a JPA relationship, but it works. Ok?

These are my entities
#Entity
public class User {
#ManyToMany(mappedBy="admins")
private Set<Game> adminForGames = new HashSet<Game>();
#Entity
public class Game {
#ManyToMany
#JoinTable(
name="Game_adminUsers",
joinColumns=#JoinColumn(name="Game_id"),
inverseJoinColumns=#JoinColumn(name="User_id")
)
private Set<User> adminUsers = new HashSet<User>();
And this is how I create a new Game:
Game game = new Game(index, name);
game.getAdminUsers().add(someUser);
em.persist(game);
As you can see I don't update the someUser to add the new game to his adminForGames, though according though the JPA spec this is my responsibility.
Still, it works fine, which is logical because the User record doesn't actually need to be written to (because the relationship uses a join table).
Does it work "by chance" (my implementation is Hibernate) or am I actually doing this correctly?
My understanding from the JPA spec is that it is the user's responsibility to set correct relations in memory.
For the database it is sufficient to set the owning side of the relationship (and that is what you did). So (from theory) it should not work if you set the non-owning side only.
This isn't "chance." This is how JPA is expected to work.

Categories