Tuesday, August 2, 2011

Hibernate and one to many relationship

One to Many Bidirectional relationship

When we have bidirectional relationship,we save one query for maintaining the relationships.Incase of bidirectional relationships


Lets take a use case where one department can have many employees.
Here employee will have foreign key for department mapping.

SQL Script

create table ORM_Relation_Department(
   RelDeptId bigint identity(1,1) primary key,
   RelDeptName varchar(50) not null,
   RelDeptDesc varchar(100)
)

create table ORM_Relation_Employee(
   RelEmpId bigint identity(1,1) primary key,
   RelEmpName varchar(50) not null,
   DeptId bigint
)

alter table ORM_Relation_Employee add  FOREIGN KEY (DeptId) REFERENCES ORM_Relation_Department(RelDeptId);


Model classes

 RelDepartment.java having OneToMany relationship with RelEmployee.java.Here RelDepartment is not the owner of the relationship.

package com.kunaal.model.plainOneToMany;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import com.kunaal.model.BaseModel;

/**
 * 
 * @author Kunaal A Trehan
 *
 */
@Entity
@Table(name="ORM_Relation_Department")
public class RelDepartment implements BaseModel<Long>{
 
 @Id
 @Column(name="RelDeptId")
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long deptId;
 
 @Column(name="RelDeptName")
 private String deptName;
 
 @Column(name="RelDeptDesc")
 private String deptDesc;
 
 /**
  * Relationship mapping between employee and department
  * We have mappedBy attribute which states department is not the owner
  * of the relationship
  * Owner of relationship is RelEmployee class and department attribute 
  * in RelEmployee governs that relationship.
  */
 @OneToMany(cascade=CascadeType.ALL,mappedBy="department")
 private Set<RelEmployee> empSet=new HashSet<RelEmployee>();
 
 public Long getPrimaryKey() {
  return getDeptId();
 }

 /**
  * @return the deptId
  */
 public Long getDeptId() {
  return deptId;
 }

 /**
  * @param deptId the deptId to set
  */
 public void setDeptId(Long deptId) {
  this.deptId = deptId;
 }

 /**
  * @return the deptName
  */
 public String getDeptName() {
  return deptName;
 }

 /**
  * @param deptName the deptName to set
  */
 public void setDeptName(String deptName) {
  this.deptName = deptName;
 }

 /**
  * @return the deptDesc
  */
 public String getDeptDesc() {
  return deptDesc;
 }

 /**
  * @param deptDesc the deptDesc to set
  */
 public void setDeptDesc(String deptDesc) {
  this.deptDesc = deptDesc;
 }

 /**
  * @return the empSet
  */
 public Set<RelEmployee> getEmpSet() {
  return empSet;
 }

 /**
  * @param empSet the empSet to set
  */
 public void setEmpSet(Set<RelEmployee> empSet) {
  this.empSet = empSet;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((deptId == null) ? 0 : deptId.hashCode());
  return result;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#equals(java.lang.Object)
  */
 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  RelDepartment other = (RelDepartment) obj;
  if (deptId == null) {
   if (other.deptId != null)
    return false;
  } else if (!deptId.equals(other.deptId))
   return false;
  return true;
 }

}


RelEmployee.java having department attribute for maintaining the relationship.
JoinColumn specifies the foreign key attribute mapping betwenn these two classes.
RelEmployee is the owner of the relationship.

package com.kunaal.model.plainOneToMany;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import com.kunaal.model.BaseModel;

/**
 * @author Kunaal A Trehan
 * 
 */
@Entity
@Table(name="ORM_Relation_Employee")
public class RelEmployee implements BaseModel<Long>{

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="RelEmpId")
 private Long empId;
 
 @Column(name="RelEmpName")
 private String empName;
 
 /**
  * Relationship mapping,specifying foreign key column mapping 
  * This class governs the relationship
  */
 @ManyToOne
 @JoinColumn(name="DeptId")
 private RelDepartment department;
 
 public Long getPrimaryKey() {
  return getEmpId();
 }

 /**
  * @return the empId
  */
 public Long getEmpId() {
  return empId;
 }

 /**
  * @param empId the empId to set
  */
 public void setEmpId(Long empId) {
  this.empId = empId;
 }

 /**
  * @return the empName
  */
 public String getEmpName() {
  return empName;
 }

 /**
  * @param empName the empName to set
  */
 public void setEmpName(String empName) {
  this.empName = empName;
 }

 /**
  * @return the department
  */
 public RelDepartment getDepartment() {
  return department;
 }

 /**
  * @param department the department to set
  */
 public void setDepartment(RelDepartment department) {
  this.department = department;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((empId == null) ? 0 : empId.hashCode());
  result = prime * result + ((empName == null) ? 0 : empName.hashCode());
  return result;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#equals(java.lang.Object)
  */
 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  RelEmployee other = (RelEmployee) obj;
  if (empId == null) {
   if (other.empId != null)
    return false;
  } else if (!empId.equals(other.empId))
   return false;
  if (empName == null) {
   if (other.empName != null)
    return false;
  } else if (!empName.equals(other.empName))
   return false;
  return true;
 }




}

TestCase where there is a PlainDeptService with transactional attributes as PROPOGATION.REQUIRED for persisting department along with employee information

package com.kunaal.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;

import com.kunaal.model.plainOneToMany.RelDepartment;
import com.kunaal.model.plainOneToMany.RelEmployee;

/**
 * @author Kunaal A Trehan
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring-main.xml"})
@TransactionConfiguration(defaultRollback=false)
public class PlainDeptServiceTest implements ApplicationContextAware{

 private ApplicationContext appCtx;
 
 @Autowired
 private IPlainDeptService plainDeptService;
 
 public void setApplicationContext(ApplicationContext applicationContext)
   throws BeansException {
  this.appCtx=applicationContext;
 }
 
 @Test
 @Transactional
 public void insertInfo(){
  RelDepartment dept=new RelDepartment();
  dept.setDeptDesc("This dept is for test case only");
  dept.setDeptName("Mock department");
  
  RelEmployee emp1=new RelEmployee();
  emp1.setEmpName("Emp-1");
  
  RelEmployee emp2=new RelEmployee();
  emp2.setEmpName("Emp-2");

  //Maintain the relationships
  emp1.setDepartment(dept);
  emp2.setDepartment(dept);
  
  //maintain the cascading effect
  dept.getEmpSet().add(emp1);
  dept.getEmpSet().add(emp2);
  
  plainDeptService.persist(dept);
 }

}


TestCase Execution log statements
It shows there is no extra sql statement for updating the foreign key
Everything happened in one shot because of bi directional relationships and mapped by attribute.
Aug 2, 2011 12:15:54 AM org.springframework.orm.hibernate3.LocalSessionFactoryBean buildSessionFactory
INFO: Building new Hibernate SessionFactory
Aug 2, 2011 12:15:56 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction
INFO: Began transaction (1): transaction manager [org.springframework.orm.hibernate3.HibernateTransactionManager@650646]; rollback [false]
Hibernate: insert into ORM_Relation_Department (RelDeptDesc, RelDeptName) values (?, ?)
Hibernate: insert into ORM_Relation_Employee (DeptId, RelEmpName) values (?, ?)
Hibernate: insert into ORM_Relation_Employee (DeptId, RelEmpName) values (?, ?)
Aug 2, 2011 12:15:56 AM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction
INFO: Committed transaction after test execution for test context [[TestContext@1555185 testClass = PlainDeptServiceTest, locations = array<String>['classpath:spring-main.xml'], testInstance = com.kunaal.service.PlainDeptServiceTest@5b28c9, testMethod = insertInfo@PlainDeptServiceTest, testException = [null]]]


Database view after running the test case

One to Many Unidirectional relationship.

Lets extend the above use and make it OneToMany Unidirectional.We will remove  bi directional mapping between employee and department.So when we try to persist the information.Inorder to maintain the relationship an extra update query is fired for foreign key mapping

Unidirectional model classes

RelUniDepartment.java containing collection of RelUniEmployee and foreign key mapping.
.There is no mappedBy attribute

package com.kunaal.model.plainOneToMany;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import com.kunaal.model.BaseModel;

/**
 * @author Kunaal A Trehan
 *
 */
@Entity
@Table(name="ORM_Relation_Department")
public class RelUniDepartment implements BaseModel<Long>{
 @Id
 @Column(name="RelDeptId")
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Long deptId;
 
 @Column(name="RelDeptName")
 private String deptName;
 
 @Column(name="RelDeptDesc")
 private String deptDesc;
 
 /**
  * Relationship mapping between employee and department
  * Since its a uni directional relationship.
  * Foreign key column present in employee table is defined using JoinColumn
  */
 @OneToMany(cascade=CascadeType.ALL)
 @JoinColumn(name="DeptId")
 private Set<RelUniEmployee> empSet=new HashSet<RelUniEmployee>();
 
 public Long getPrimaryKey() {
  return getDeptId();
 }

 /**
  * @return the deptId
  */
 public Long getDeptId() {
  return deptId;
 }

 /**
  * @param deptId the deptId to set
  */
 public void setDeptId(Long deptId) {
  this.deptId = deptId;
 }

 /**
  * @return the deptName
  */
 public String getDeptName() {
  return deptName;
 }

 /**
  * @param deptName the deptName to set
  */
 public void setDeptName(String deptName) {
  this.deptName = deptName;
 }

 /**
  * @return the deptDesc
  */
 public String getDeptDesc() {
  return deptDesc;
 }

 /**
  * @param deptDesc the deptDesc to set
  */
 public void setDeptDesc(String deptDesc) {
  this.deptDesc = deptDesc;
 }

 /**
  * @return the empSet
  */
 public Set<RelUniEmployee> getEmpSet() {
  return empSet;
 }

 /**
  * @param empSet the empSet to set
  */
 public void setEmpSet(Set<RelUniEmployee> empSet) {
  this.empSet = empSet;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((deptId == null) ? 0 : deptId.hashCode());
  return result;
 }

 /* (non-Javadoc)
  * @see java.lang.Object#equals(java.lang.Object)
  */
 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  RelUniDepartment other = (RelUniDepartment) obj;
  if (deptId == null) {
   if (other.deptId != null)
    return false;
  } else if (!deptId.equals(other.deptId))
   return false;
  return true;
 }

}


RelUniEmployee.java without any department attribute.Employee is unaware of department
package com.kunaal.model.plainOneToMany;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import com.kunaal.model.BaseModel;

/**
 * Unidirectional employee mapping
 * 
 * @author Kunaal A Trehan
 *
 */
@Entity
@Table(name="ORM_Relation_Employee")
public class RelUniEmployee implements BaseModel<Long>{
 
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="RelEmpId")
 private Long empId;
 
 @Column(name="RelEmpName")
 private String empName;
 

 public Long getPrimaryKey() {
  return getEmpId();
 }


 /**
  * @return the empId
  */
 public Long getEmpId() {
  return empId;
 }


 /**
  * @param empId the empId to set
  */
 public void setEmpId(Long empId) {
  this.empId = empId;
 }


 /**
  * @return the empName
  */
 public String getEmpName() {
  return empName;
 }


 /**
  * @param empName the empName to set
  */
 public void setEmpName(String empName) {
  this.empName = empName;
 }


 /* (non-Javadoc)
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((empId == null) ? 0 : empId.hashCode());
  result = prime * result + ((empName == null) ? 0 : empName.hashCode());
  return result;
 }


 /* (non-Javadoc)
  * @see java.lang.Object#equals(java.lang.Object)
  */
 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  RelUniEmployee other = (RelUniEmployee) obj;
  if (empId == null) {
   if (other.empId != null)
    return false;
  } else if (!empId.equals(other.empId))
   return false;
  if (empName == null) {
   if (other.empName != null)
    return false;
  } else if (!empName.equals(other.empName))
   return false;
  return true;
 }

}


Test Case for uni directional relationship.Here department is adding the employee in the corresponding collection and test execution report clearly shows extra update statement for maintaing the relationship.

/**
  * Test case showing unidirectional relationships 
  * and sql queries
  */
 @Test
 @Transactional
 public void insertUniInfo(){
  RelUniDepartment dept=new RelUniDepartment();
  dept.setDeptDesc("Unidirectional test case department");
  dept.setDeptName("Uni department");
  
  RelUniEmployee emp1=new RelUniEmployee();
  emp1.setEmpName("Uni-Emp-1");
  
  RelUniEmployee emp2=new RelUniEmployee();
  emp2.setEmpName("Uni-Emp-2");

  //maintain the cascading effect
  dept.getEmpSet().add(emp1);
  dept.getEmpSet().add(emp2);
  
  plainUniDeptService.persist(dept);
 }


Test case execution output report showing queries fired

INFO: Building new Hibernate SessionFactory
Aug 2, 2011 11:38:22 PM org.springframework.test.context.transaction.TransactionalTestExecutionListener startNewTransaction
INFO: Began transaction (1): transaction manager [org.springframework.orm.hibernate3.HibernateTransactionManager@1cab18]; rollback [false]
Hibernate: insert into ORM_Relation_Department (RelDeptDesc, RelDeptName) values (?, ?)
Hibernate: insert into ORM_Relation_Employee (RelEmpName) values (?)
Hibernate: insert into ORM_Relation_Employee (RelEmpName) values (?)
Hibernate: update ORM_Relation_Employee set DeptId=? where RelEmpId=?
Hibernate: update ORM_Relation_Employee set DeptId=? where RelEmpId=?
Aug 2, 2011 11:38:22 PM org.springframework.test.context.transaction.TransactionalTestExecutionListener endTransaction
INFO: Committed transaction after test execution for test context [[TestContext@1dd7736 testClass = PlainDeptServiceTest, locations = array<String>['classpath:spring-main.xml'], testInstance = com.kunaal.service.PlainDeptServiceTest@bd93cd, testMethod = insertUniInfo@PlainDeptServiceTest, testException = [null]]]



No comments:

Post a Comment