Monday, July 28, 2014

Relationship Mapping in Hibernate

Hibernate relationship mapping takes care of mapping the relationship in the relational world. The relationship that exist in the relational world are:
  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany
Though the notion of directionality is not present in relational world, it is relevant for the Java world. So we will have to handle in the Java world whether we want to build unidirectional or bidirectional relation. Please note the the need of unidirectional or bidirectional relationship should be governed by
your need of domain. For example if we have a Student and Address table with One to One relationship between them.
One To One
Let's say we have Address and Student in its own table and we expose Address also as an entity.
@Entity

public class AddressEntity implements Serializable{

    protected Long  id;

    ….
    @Id
    @GeneratedValue
    @Column(name="ADDRESS_ID")
    public Long getId() {
        return id;
    }

Map the relationship in the Student entity with directionality build from Student side

In Student class

...

private AddressEntity address;

//Cascade Type defines that if the owning entity in this case Student is saved, than

//whether the AddressEntity will also get saved.

@OneToOne(cascade={CascadeType.ALL})

@JoinColumn(name="ADDRESS_ID")
public AddressEntity getAddressEntity() {
        return address;
}
...

Also note the cascade type here. The cascade type defines whether the AddressEntity will get saved when we save Student. For example let's say the mapping in Student class, if we remove the Cascade type will be:
...

private AddressEntity address;

@OneToOne   // comment out - (cascade={CascadeType.ALL})

@JoinColumn(name="ADDRESS_ID")

public AddressEntity getAddressEntity() {

        return address;
}
...

Now let's try to save a Student and AddressEntity
Student student = new Student();

AddressEntity add = new AddressEntity();

student.setAddressEntity(add);

//If you try to save student, it will throw an exception as 

//it finds AddressEntity wired to it but AddressEntity is transient. 

//To persist the transient AddressEntity with the Student uncomment the CascadeType

//session.save(student);
We can make the primary key for both the related table same. It means that in the Address table the primary key and foreign key are same.

In the Student class

@OneToOne(cascade={CascadeType.ALL})

@PrimaryKeyJoinColumn

public AddressEntity getAddressEntity() {

    return addressEntity;
}

In the above case the AddressEntity can be reached from Student but from AddressEntity you cannot reach to Student. Map the Student entity in the AddressEntity class
...

protected Student student;

@OneToOne(mappedBy="bankAccount")

public Student getStudent() { return student; }    

...

Note that this relationship in the Java world does not makes any change to the relational world.
In the case of bidirectional association, there is a notion of owning entity. The owning entity governs in what case the relationship will materialized in the database.The owning entity is the entity which is on the opposite or inverse side of mappedBy. In the case as the mappedBy is on the AddressEntity, the Student entity becomes the owning type. Now look at following piece of code
Student student = new Student();

AddressEntity add = new AddressEntity();

//Map the relationship from AddressEntity side

add.setStudent(student);

//we will assume that CascadeType is All.

session.save(student);

If we look into the database, the Student and AddressEntity both are created in the database but the relationship is not build between Student and AddressEntity table. To fix the problem change the above code to
Student student = new Student();

AddressEntity add = new AddressEntity();

//Map the relationship from Student side

student.setAddressEntity(add);

//we will assume that CascadeType is All.

session.save(student);

Now the relationship will be properly created as we have made the relationship from owning side. It's a good price to build the relationship from both side whenever the relationship is effected from one side.
Student student = new Student();

AddressEntity add = new AddressEntity();

//Good Practice: Map the relationship on both side 

student.setAddressEntity(add);

add.setStudent(student);

//we will assume that CascadeType is All.

session.save(student);

For owning entity, you have to take care of following:
  • If a AddressEntity has to be assigned to a different student, than unset the addressEntity on the original student and set it into the new student.
  • For deleting a AddressEntity, unset it from the student object and than remove it from database calling remove method on entity manager.
One To Many
Let's have PhoneEntity table with one to Many relationship from Student.
Student Table
STUDENT_ID //Primary Key

...

PhoneEntity table
PHONE_ID //Primary Key

STUDENT_ID  //Foreign Key

...

Now the PhoneEntity class looks like
@Entity

public class PhoneEntity implements Serializable{

    protected int id;

    protected String number;
    @Id

    @GeneratedValue

    @Column(name="PHONE_ID")

    public int getId() {
There is no reference to Student in PhoneEntity class, though the relationship is maintained in PhoneEntity table in database. Now to build the One to Many relationship in Student
@OneToMany(cascade={CascadeType.ALL})

public Collection<PhoneEntity> getPhoneEntityList() {

   return phoneEntityList;

}        
To save phone Entity the code would look like
Collection<PhoneEntity> phoneList = new ArrayList<PhoneEntity>();

PhoneEntity p1 = new PhoneEntity();

p1.setNumber("100");

phoneList.add(p1);
PhoneEntity p2 = new PhoneEntity();

p2.setNumber("200");

phoneList.add(p2);

student.setPhoneEntityList(phoneList);
We can tell hibernate that if an element is removed from a collection than delete it from database as this is the only reference to that entity.
  @OneToMany(cascade={CascadeType.ALL})   @org.hibernate.annotations.Cascade(value=org.hibernate.annotations.CascadeType.DELETE_ORPHAN    )   

    public Collection<PhoneEntity> getPhoneEntityList() {

        return phoneEntityList;

    }  
      
If we want the relationship data to be managed in a join table

@OneToMany(cascade={CascadeType.ALL})

@JoinTable(name="STUDENT_PHONE",joinColumns={@JoinColumn(name="STUDENT_ID")}, 

           inverseJoinColumns={@JoinColumn(name="PHONE_ID")})

public Collection<PhoneEntity> getPhoneEntityList() {
        return phoneEntityList;
}

Now let's see Many To One unidirectional relationship. Let's say we have a country table and Student is mapped to Country with Many to One relationship.
Student Table
STUDENT_ID //Primary Key

COUNTRY_ID //foreign Key

...

Country Table
COUNTRY_ID //Primary Key

...

Country Entity
@Entity

public class Country implements Serializable{

    protected long id;

    protected String name;
    @Id

    @GeneratedValue

    @Column(name="COUNTRY_ID")

    public long getId() {
        return id;
    }
...

Student Entity
private Country country;

@ManyToOne

@JoinColumn(name="COUNTRY_ID")

public Country getCountry() {
    return country;
}

Note that we are not using cascade here as normally the country is a master data and should not be created or deleted in the context of a Student. To save a Student
Country country = new Country();

country.setName("India");

session.save(country);

student.setCountry(country);
Many to one and One to many are two sides of the same relationship. Let’s convert Student and Country into bidirectional relationship.
//the owning side of relationship is Many To One

@OneToMany(mappedBy="country")

public Collection<Student> getStudentList() {

   return studentList;
}
       
For persistence to work we have to call student.setCountry. If we just call country.getStudentList().add(student), The relationship will not change in the database. As a good practice

Always wire both side of relationship.
Many To Many
ManyToMany relationship happens when both side maintains collection based relationship.Let’s take an example of Student and Language they speak. A student can speak many language. Similarly a language can be spoken by many students

Language Entity bean

@Entity

public class Language implements Serializable{

   protected long id;

   protected String name;
Student Bean
@ManyToMany

@JoinTable(name=“A_B",joinColumns={@JoinColumn(name=“C")},

           inverseJoinColumns={@JoinColumn(name=“D")})

public Collection<Language> getLanguageList() {
      return languageList;
}

For Many to Many bidirectional

Language Bean

@ManyToMany(mappedBy="languageList")

public Collection<Student> getStudentList() {

 return studentList;

}
For modifying the same ownership rule applies as we have seen in other  bidirectional relationships.

The collection can be fetched in certain order. E.g if we want to fetch the list of languages in certain order we can say on the Student side of relationship

@ManyToMany

@OrderBy(“name  ASC”)

@JoinTable(name="STUDENT_LANGUAGE",

        joinColumns={@JoinColumn(name="STUDENT_ID")},
        inverseJoinColumns={@JoinColumn(name="LANGUAGE_ID")})
public List<Language> getLanguageList() {
        return languageList;
}

For descending order use name=DESC

No comments:

Post a Comment