Suggested Pages

Sunday, June 10, 2012

JPA: Bidirectional Association

Bidirectional associations must be maintained consistent


Bidirectional associations must be maintained consistent in memory as possible, so when you navigate the associations of entities, you can access data perfectly consistent. Suppose you have a typical relationship between two entities as Person and Contact Probably you are interested into navigate from an instance of Contact to an instance of Person and the inverse. But this depends on your application domain and the use cases you have to implement.
Suppose these are your entities:
Person.java

@Entity
public class Person {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private int id;
 
 
 @OneToMany(mappedBy="person",fetch=FetchType.EAGER)
 private List<Contact> contacts;
 
        ...
}
  
Contact.java
 
....
@Entity
public class Contact {

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 
 @ManyToOne
 private Person person;
       
         ...
}

To maintain consistent in memory you usually do this:
 
       contact.setPerson(person);
       person.getContacts().add(contact);
       entityManager.persist(contact);
  
As you know, a bidirectional one-to-many relationship is designed with two tables:
  • Owner Side (Contact): the side of @ManyToOne annotation which is the owner of the relationship because it has a foreign key with Person table;
  • Inverse Side (Person): the side of @OneToMany annotation.

Just two considerations:
  • person.getContacts().add(contact); is used to maintain consistence in memory. Consider the situation in which a Person entity has a huge amount of Contact entity, this can lead to poor performance. So you must pay attention on your uses case and be sure you need to maintain this consistent. If you don't need it, you can avoid this operation.
  • contact.setPerson(person); is used to persist contact entity and write on Contact table the personID of person. Retrieving a person entity for only this reason, may be not enough to perform a retrieving from the database of person entity. In JPA you can use getReference method of EntityManager interface. This method doesn't perform a hit to the database but it just returns a proxy object having the id necessary to persist the contact entity
The previous code can be written at this way.
[

     Person readPerson = entityManager.getReference(Person.class, personid);
     contact.setPerson(readPerson);
     entityManager.persist(contact);

Only changes to the owner side will be reflected in the database


Another important thing to know is the following JPA/Hibernate behaviour:
  • If you modify the owner side, changes will be reflected in the database;
  • If you modify the inverse side, JPA/Hibernate doesn't care.
Now suppose you have the following bidirectional association between Parent and Child entities.
Parent.java

 ...
@Entity
public class Parent {
 
 @Id
 private int parentId;
 
 @Column
 private String name;

 @OneToMany(cascade=CascadeType.PERSIST,mappedBy="parent")
 private List<Child> children;
 ...
}
    

Child.java
 ...
@Entity
public class Child {
 
 @Id
 private int childId;
 
 @Column
 private String name;
 
 @ManyToOne
 private Parent parent;
 
 ...
}
And the following unit test class.
Child.java

....

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(value = "DAOTest-context.xml")
public class DAOTest {

 @Autowired
 private GenericDAO genericDAO;

 
 @Test
 public void testSaveInverseSide() {
  System.out.println("------------testSaveInverseSide(): start -------");
  Child child=new Child();
  child.setChildId(1);
  child.setName("child1");
  Parent parent = new Parent();
  parent.setParentId(1);
  parent.setName("parent1");
  parent.getChildren().add(child); // OneToMany association
  genericDAO.save(child);
  genericDAO.save(parent);
  System.out.println("------------testSaveInverseSide(): end -------");
 }

 @Test
 public void testSaveManyToOne() {
  System.out.println("------------testSaveManyToOne(): start -------");
  Child child=new Child();
  child.setChildId(2);
  child.setName("child2");
  Parent parent = new Parent();
  parent.setParentId(2);
  parent.setName("parent2");
  child.setParent(parent); // ManyToOne association
  genericDAO.save(parent);
  genericDAO.save(child);
  System.out.println("------------testSaveManyToOne(): end -------");
 }
 
 
 public void testSaveBothSide() {
  System.out.println("------------testSaveBothSide(): start -------");
  Child child=new Child();
  child.setChildId(3);
  child.setName("child3");
  Parent parent = new Parent();
  parent.setParentId(3);
  parent.setName("parent3");
  parent.getChildren().add(child);  // OneToMany association
  child.setParent(parent);    // ManyToOne association
  genericDAO.save(parent);
  genericDAO.save(child);
  System.out.println("------------testSaveBothSide(): end -------");
 }
 
}
  • testSaveInverseSide(): perform the following SQL statement
    • insert into Child (name, parent_parentId, childId) values ('child1', '', 1)
    • insert into Parent (name, parentId) values ('parent1', 1)
  • testSaveManyToOne():
    • insert into Parent (name, parentId) values ('parent2', 2)
    • select parent_.parentId, parent_.name as name1_ from Parent parent_ where parent_.parentId=2
    • insert into Child (name, parent_parentId, childId) values ('child2', 2, 2)
  • testSaveBothSide():
    • insert into Parent (name, parentId) values ('parent3', 3)
    • select parent_.parentId, parent_.name as name1_ from Parent parent_ where parent_.parentId=3
    • insert into Child (name, parent_parentId, childId) values ('child3', 3, 3)

As you can see only the second and the third test method perform the right SQL insert into DB. I mean, only when the owner side (Child table) is changed, the foreign key column with the inverse side (Parent table) will be updated.

No comments :

Post a Comment

Suggested Pages