Tuesday, July 26, 2011

Uni directional,Bi directional,Cascade and MappedBy Hibernate

Unidirectional and Bidirectional relationship

In order to understand this relationship take the case of Book Library.
Book Library contains collection of books. So book contains foreign key of book library
to maintain the relationship.

Unidirectional relationship refers to the case when relationship between two objects can be accessed by one way only. i.e
From Book Library we can access books not vice versa
class Book{
     Long bookId;
     String bookName;
      …........
}

class BookLibrary{
      Long libraryId;
      String libraryName;
       Set<Book> bookSet;
}


Bidirectional relationship refers to the case when relationship between two objects can be accessed both ways. i.e
From Book Library we can access collection of Books and Book Object will have reference to BookLibrary

class Book{
     Long bookId;
     String bookName;
     BookLibrary library;
      …........
}
Though in both the cases for relationship to occur there would be foreign key mapping in Book Table corresponding to Book Library.


Cascade By Attribute

Taking the Book Library example further,lets assume we have a form/web page where we input the information regarding the library and books. So we have a library object containing library information and collection of books. In traditional JDBC approach,we will persist the Book Library first ,get the corresponding primary key and then iterate through book list and persist it one by one by adding the foreign key attribute. All this work has to be done by developer.
 

Similarly for updates and deletes we have to follow the same procedure.
Cascade By attribute provided by Hibernate keeps the above mechanism abstract to the developer. Developer persists/updates/deletes the top level object and changes are
propagated to the associated entities depending upon the value of cascade by attribute.

So taking the above example if we provide the cascade by attribute to the book set as
ALL. Whatever operations are done on BookLibrary object ,it will be passed on to the book collection object also. Developer need not have to code this,it will happen automatically.

So by providing the right cascade attributes we can govern the flow of changes from parent to child entities.

class BookLibrary{
      Long libraryId;
      String libraryName;
       …...
    @Cascade(value = { CascadeType.ALL })
       Set<Book> bookSet;
}


MappedBy Attribute

MappedBy Attribute is the most misunderstood concept. It comes into action when we have bi directional relationship between the entities. In relational database we have foreign key attribute which identifies the parent and child tables.

However when we have bi-directional relationship,in order to provide the identification regarding the same hibernate has come up with mappedBy attribute.

Normally mappedBy attribute is provided on the object which does not contains foreign key.
In hibernate terms,it means “I am not the owner of the relationship,ownership is governed by other entity'.

Its the job of the associated entity on non mappedBy side to maintain the association. In case other entity does not do the association,there will be NULL in the foreign key column.

Inorder to understand it better lets take case of  parent and child.Child belongs to a parent and parent can have more than one child.Its a classical case of one to many relationship.
Here mappedBy attribute is at parent side.So its the responsibilty of child to maintain the realtionship.

SQL Script

create table ORM_MappedBy_Parent(
    Id bigint Identity(1,1) primary key,
    ParentName varchar(50) not null,
    ParentAddr varchar(50)not null
)

create table ORM_MappedBy_Child(
 ChildId bigint identity(1,1) primary key,
 ChildName varchar(50) not null,
 ParentId bigint  null
)

alter table ORM_MappedBy_Child 
ADD FOREIGN KEY (ParentId) REFERENCES ORM_MappedBy_Parent(Id);


Here ORM_MappedBy_Child contains the foriegn key.So  Parent will have collection of child elements.

Model Classes

MappedByParent.java containing collection of Child entities with mappedBy attribute


package com.kunaal.model.mappedBy;

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_MappedBy_Parent")
public class MappedByParent implements BaseModel<Long>{

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="id")
 private Long parentId;
 
 @Column(name="parentName")
 private String name;
 
 @Column(name="parentAddr")
 private String address;

 @OneToMany(cascade=CascadeType.ALL,mappedBy="parent")
 private Set<MappedByChild> childSet=new HashSet<MappedByChild>();
 
 public Long getPrimaryKey() {
  return getParentId();
 }

 /**
  * @return the parentId
  */
 public Long getParentId() {
  return parentId;
 }

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

 /**
  * @return the name
  */
 public String getName() {
  return name;
 }

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

 /**
  * @return the address
  */
 public String getAddress() {
  return address;
 }

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

 /* (non-Javadoc)
  * @see java.lang.Object#hashCode()
  */
 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result
    + ((parentId == null) ? 0 : parentId.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;
  MappedByParent other = (MappedByParent) obj;
  if (parentId == null) {
   if (other.parentId != null)
    return false;
  } else if (!parentId.equals(other.parentId))
   return false;
  return true;
 }

 /**
  * @return the childSet
  */
 public Set<MappedByChild> getChildSet() {
  return childSet;
 }

 /**
  * @param childSet the childSet to set
  */
 public void setChildSet(Set<MappedByChild> childSet) {
  this.childSet = childSet;
 }
 

}


MappedByChild.java which is owner of the relationship


package com.kunaal.model.mappedBy;

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_MappedBy_Child")
public class MappedByChild implements BaseModel<Long>{

 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="childId")
 private Long childId;
 
 @Column(name="childName")
 private String childName;

 @ManyToOne
 @JoinColumn(name="ParentId")
 private MappedByParent parent;
 
 public Long getPrimaryKey() {
  return getChildId();
 }

 /**
  * @return the childId
  */
 public Long getChildId() {
  return childId;
 }

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

 /**
  * @return the childName
  */
 public String getChildName() {
  return childName;
 }

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

 /**
  * @return the parent
  */
 public MappedByParent getParent() {
  return parent;
 }

 /**
  * @param parent the parent to set
  */
 public void setParent(MappedByParent parent) {
  this.parent = parent;
 }

}



MappedDAOImpl.java which extends from BaseDAOImpl and perform basic operations like persist,update,delete,findById and others for the domain object
package com.kunaal.dao.impl;

import com.kunaal.dao.IMappedDAO;
import com.kunaal.model.mappedBy.MappedByParent;

/**
 * @author Kunaal A Trehan
 *
 */
public class MappedDAOImpl extends BaseDAOImpl<MappedByParent, Long> implements IMappedDAO{

 @Override
 public Class<MappedByParent> getEntityType() {
  return MappedByParent.class;
 }

}


Abstract BaseDAOImpl.java containing all basic operations
package com.kunaal.dao.impl;

import java.io.Serializable;
import java.util.List;

import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;

import com.kunaal.dao.IBaseDAO;
import com.kunaal.model.BaseModel;

/**
 * @author Kunaal A Trehan
 *
 */
public abstract class BaseDAOImpl<K extends BaseModel<T>,T> implements IBaseDAO<K , T>{

 private SessionFactory sessionFactory;
 
 public abstract Class<K> getEntityType();
 
 public void persist(K object) {
  Session session = sessionFactory.getCurrentSession();
  session.save(object);
  
 }

 public void update(K object) {
  Session session = sessionFactory.getCurrentSession();
  session.update(object);
 }

 public void delete(K object) {
  Session session = sessionFactory.getCurrentSession();
  session.delete(object);
 }

 public List<K> listAll() {
  Session session = sessionFactory.getCurrentSession();
  return session.createCriteria(getEntityType()).list();
 }

 public K findById(T id) {
  Session session = sessionFactory.getCurrentSession();
  return (K) session.get(getEntityType(), (Serializable) id);
 }

 /**
  * @return the sessionFactory
  */
 public SessionFactory getSessionFactory() {
  return sessionFactory;
 }

 /**
  * @param sessionFactory the sessionFactory to set
  */
 public void setSessionFactory(SessionFactory sessionFactory) {
  this.sessionFactory = sessionFactory;
 }

}



Abstract BaseServiceImpl.java containing all the common methods like persist,delete,update and others
package com.kunaal.service.impl;

import java.util.List;

import com.kunaal.dao.IBaseDAO;
import com.kunaal.model.BaseModel;
import com.kunaal.service.IBaseService;

/**
 * @author Kunaal A Trehan
 *
 */
public abstract class BaseServiceImpl<K extends BaseModel<T>,T> implements 
       IBaseService<K , T>{

 private IBaseDAO<K,T> baseDAO;
 
 public void persist(K object) {
  getBaseDAO().persist(object);
 }

 public void update(K object) {
  getBaseDAO().update(object);
 }

 public void delete(K object) {
  getBaseDAO().delete(object);
 }

 public List<K> listAll() {
  return getBaseDAO().listAll();
 }

 public K findById(T id) {
  return getBaseDAO().findById(id);
 }

 /**
  * @return the baseDAO
  */
 public IBaseDAO<K, T> getBaseDAO() {
  return baseDAO;
 }

 /**
  * @param baseDAO the baseDAO to set
  */
 public void setBaseDAO(IBaseDAO<K, T> baseDAO) {
  this.baseDAO = baseDAO;
 }

}


MappedServiceImpl .java extending from BaseServiceImpl
package com.kunaal.service.impl;

import com.kunaal.model.mappedBy.MappedByParent;
import com.kunaal.service.IMappedService;

/**
 * @author Kunaal A Trehan
 *
 */
public class MappedServiceImpl extends BaseServiceImpl<MappedByParent, Long> implements IMappedService{

}



MappedServiceTest.java

In this test class we have two methods
-insertProperInfo
-insertImProperInfo

In insertProperInfo() ,child entity is attaching itself to parent and thus forming the relationship.
In insertImProperInfo(),child entity is not associating itself to parent.So no relationship is formed.As a result of which foreign key is passed as NULL in the database.


package com.kunaal.service;

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

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.mappedBy.MappedByChild;
import com.kunaal.model.mappedBy.MappedByParent;

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

 private ApplicationContext appCtx;
 
 @Autowired
 private IMappedService mappedService;
 
 public void setApplicationContext(ApplicationContext applicationContext)
   throws BeansException {
  this.appCtx=applicationContext;
 }

 /**
  * In this test case all information and relationships are made proper
  */
 @Test
 @Transactional
 public void insertProperInfo(){
  MappedByParent parent=new MappedByParent();
  MappedByChild child1=new MappedByChild();
  MappedByChild child2=new MappedByChild();
  
  parent.setAddress("Bangalore");
  parent.setName("Parent1");
  
  child1.setChildName("Child1");
  child2.setChildName("child2");
  
  //make associations
  child1.setParent(parent);
  child2.setParent(parent);
  Set<MappedByChild> childSet=new HashSet<MappedByChild>();
  childSet.add(child1);
  childSet.add(child2);
  parent.setChildSet(childSet);
  
  mappedService.persist(parent);
 }
 
 @Test
 @Transactional
 public void insertImProperInfo(){
  MappedByParent parent=new MappedByParent();
  MappedByChild child1=new MappedByChild();
  MappedByChild child2=new MappedByChild();
  
  parent.setAddress("Bangalore");
  parent.setName("Parent2");
  
  child1.setChildName("ImproperChild1");
  child2.setChildName("ImproperChild2");
  
  //make associations
  //HERE I AM NOT PUTTING THE RELATIONSHIP ASSOCIATION SO 
  //RELATIONSHIPS ARE NOT SET.THERE WILL BE NULL IN THE FOREIGN KEY [ParentId]
  //child1.setParent(parent);
  //child2.setParent(parent);
  Set<MappedByChild> childSet=new HashSet<MappedByChild>();
  childSet.add(child1);
  childSet.add(child2);
  parent.setChildSet(childSet);
  
  mappedService.persist(parent);
 }
}


SQL Server screen showing the information after the test case execution.





4 comments:

  1. @OneToMany(cascade=CascadeType.ALL,mappedBy="parent")

    What does "parent" reference? Its not the name of the parent table, and its not the name of the parent class... where is this defined?

    Thanks!

    ReplyDelete
    Replies
    1. 'parent' refers to field
      private MappedByParent parent; in MappedByChild class

      @OneToMany(cascade=CascadeType.ALL,mappedBy="parent") means that there is one to many relationship between me and child entity.However
      onus of maintaining the relationship is on the child side.

      Hope it helps

      Delete
  2. Thanks a lot! You made a new blog entry to answer my question; I really appreciate your time and effort.
    best java training in chennai |
    java training center in chennai

    ReplyDelete