Lazy/Eager Loading Using Hibernate
Today's post will focus on why and how we use the concepts known
as LAZY and EAGER loading in an application and how to use
Spring's hibernate template to load our LAZY entities in an EAGER
fashion.
And of course as the title itself suggests, we will show this by
an example. The scenario is as such;
You are a parent who has a kid with a lot of toys. But the current
issue is whenever you call him (we assume you have a boy), he comes
to you with all his toys as well. Now this is an issue since you do
not want him carrying around his toys all the time.
So being the rationale parent, you go right ahead and define the toys
of the child as LAZY. Now whenever you call him, he just comes to you
without his toys.
But you are faced with another issue. When the time comes for a family
trip, you want him to bring along his toys because the kid will be
bored with the trip otherwise. But since you strictly enforced LAZY
on the child's toy, you are unable to ask him to bring along the toys.
This is where EAGER fetching comes into play. Let us first see our
domain classes.
package com.fetchsample.example.domain;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
/**
* Holds information about the child
*
* @author dinuka.arseculeratne
*
*/
@Entity
@Table(name = "CHILD")
@NamedQuery(name = "findChildByName",
query = "select DISTINCT(chd) from Child chd left join fetch chd.toyList
where chd.childName=:chdName")
public class Child {
public static interface Constants {
public static final String FIND_CHILD_BY_NAME_QUERY = "findChildByName";
public static final String CHILD_NAME_PARAM = "chdName";
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
/**
* The primary key of the CHILD table
*/
private Long childId;
@Column(name = "CHILD_NAME")
/**
* The name of the child
*/
private String childName;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
/**
* The toys the child has. We do not want the child to have the
* same toy more than once, so we have used a set here.
*/
private Set toyList = new HashSet();
public Long getChildId() {
return childId;
}
public void setChildId(Long childId) {
this.childId = childId;
}
public String getChildName() {
return childName;
}
public void setChildName(String childName) {
this.childName = childName;
}
public Set getToyList() {
return toyList;
}
public void addToy(Toy toy) {
toyList.add(toy);
}
}
------------------------------------------------
package com.fetchsample.example.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* Hols data related to the toys a child possess
*
* @author dinuka.arseculeratne
*
*/
@Entity
@Table(name = "TOYS")
public class Toy {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "TOY_ID")
/**
* The primary key of the TOYS table
*/
private Long toyId;
@Column(name = "TOY_NAME")
/**
* The name of the toy
*/
private String toyName;
public Long getToyId() {
return toyId;
}
public void setToyId(Long toyId) {
this.toyId = toyId;
}
public String getToyName() {
return toyName;
}
public void setToyName(String toyName) {
this.toyName = toyName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((toyName == null) ? 0 : toyName.hashCode());
return result;
}
@Override
/**
* Overriden because within the child class we use a Set to
* hold all unique toys
*/
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Toy other = (Toy) obj;
if (toyName == null) {
if (other.toyName != null)
return false;
} else if (!toyName.equals(other.toyName))
return false;
return true;
}
@Override
public String toString() {
return "Toy [toyId=" + toyId + ", toyName=" + toyName + "]";
}
}
So as you can see, we have two simple entities representing the child
and toy. The child has a one-to-many relationship with the toys which
means one child can have many toys (oh how i miss my childhood days).
Afterwards we need to interact with the data, so let us go ahead and
define out DAO(Data access object) interface and implementation.
package com.fetchsample.example.dao;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.fetchsample.example.domain.Child;
/**
* The basic contract for dealing with the {@link Child} entity
*
* @author dinuka.arseculeratne
*
*/
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public interface ChildDAO {
/**
* This method will create a new instance of a child in the child table
*
* @param child
* the entity to be persisted
*/
public void persistChild(Child child);
/**
* Retrieves a child without his/her toys
*
* @param childId
* the primary key of the child table
* @return the child with the ID passed in if found
*/
public Child getChildByIdWithoutToys(Long childId);
/**
* Retrieves the child with his/her toys
*
* @param childId
* the primary key of the child table
* @return the child with the ID passed in if found
*/
public Child getChildByIdWithToys(Long childId);
/**
* Retrieves the child by the name and with his/her toys
*
* @param childName
* the name of the child
* @return the child entity that matches the name passed in
*/
public Child getChildByNameWithToys(String childName);
}
--------------------------------------------
package com.fetchsample.example.dao.hibernate;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.fetchsample.example.dao.ChildDAO;
import com.fetchsample.example.domain.Child;
/**
* The hibernate implementation of our {@link ChildDAO} interface
*
* @author dinuka.arseculeratne
*
*/
public class ChildDAOHibernateImpl extends HibernateDaoSupport implements
ChildDAO {
/**
* {@inheritDoc}
*/
public void persistChild(Child child) {
getHibernateTemplate().persist(child);
}
/**
* {@inheritDoc}
*/
public Child getChildByIdWithoutToys(Long childId) {
return getHibernateTemplate().get(Child.class, childId);
}
/**
* {@inheritDoc}
*/
public Child getChildByIdWithToys(Long childId) {
Child child = getChildByIdWithoutToys(childId);
/**
* Since by default the toys are not loaded, we call the hibernate
* template's initialize method to populate the toys list of that
* respective child.
*/
getHibernateTemplate().initialize(child.getToyList());
return child;
}
/**
* {@inheritDoc}
*/
public Child getChildByNameWithToys(String childName) {
return (Child) getHibernateTemplate().findByNamedQueryAndNamedParam(
Child.Constants.FIND_CHILD_BY_NAME_QUERY,
Child.Constants.CHILD_NAME_PARAM, childName).get(0);
}
}
A simple contract. I have four main methods. The first one of course
just persists a child entity to the database.
The second method retrieves the Child by the primary key passed in,
but does not fetch the toys.
The third method first fetches the Child and then retrieves the
Child's toys using the Hibernate template's initialize() method.
Note that when you call the initialize() method, hibernate will
fetch you LAZY defined collection to the Child proxy you retrieved.
The final method also retrieves the Child's toys, but this time using
a named query. If we go back to the Child entity's Named query, you
can see that we have used "left join fetch". It is the keyword fetch
that actually will initialize the toys collection as well when
returning the Child entity that qualifies. Finally i have my main
class to test our functionality;
package com.fetchsample.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.fetchsample.example.dao.ChildDAO;
import com.fetchsample.example.domain.Child;
import com.fetchsample.example.domain.Toy;
/**
* A test class
*
* @author dinuka.arseculeratne
*
*/
public class ChildTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"com/fetchsample/example/spring-context.xml");
/**
* First we initialize a child
*/
Child child = new Child();
/**
* A cool ben 10 action figure
*/
Toy ben10 = new Toy();
ben10.setToyName("Ben 10 figure");
/**
* A even more cooler spider man action figure
*/
Toy spiderMan = new Toy();
spiderMan.setToyName("Spider man figure");
child.setChildName("John");
/**
* Add the toys to the collection
*/
child.addToy(ben10);
child.addToy(spiderMan);
ChildDAO childDAO = (ChildDAO) context.getBean("childDAO");
childDAO.persistChild(child);
Child childWithoutToys = childDAO.getChildByIdWithoutToys(1L);
// The following line will throw a lazy initialization error since we have
// defined fetch type as LAZY in the Child domain class.
// System.out.println(childWithToys.getToyList().size());
Child childWithToys = childDAO.getChildByIdWithToys(1L);
System.out.println(childWithToys.getToyList().size());
Child childByNameWithToys = childDAO.getChildByNameWithToys("John");
System.out.println(childByNameWithToys.getToyList().size());
}
}
--------------------------------------------
package com.fetchsample.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.fetchsample.example.dao.ChildDAO;
import com.fetchsample.example.domain.Child;
import com.fetchsample.example.domain.Toy;
/**
* A test class
*
* @author dinuka.arseculeratne
*
*/
public class ChildTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"com/fetchsample/example/spring-context.xml");
/**
* First we initialize a child
*/
Child child = new Child();
/**
* A cool ben 10 action figure
*/
Toy ben10 = new Toy();
ben10.setToyName("Ben 10 figure");
/**
* A even more cooler spider man action figure
*/
Toy spiderMan = new Toy();
spiderMan.setToyName("Spider man figure");
child.setChildName("John");
/**
* Add the toys to the collection
*/
child.addToy(ben10);
child.addToy(spiderMan);
ChildDAO childDAO = (ChildDAO) context.getBean("childDAO");
childDAO.persistChild(child);
Child childWithoutToys = childDAO.getChildByIdWithoutToys(1L);
// The following line will throw a lazy initialization error since we have
// defined fetch type as LAZY in the Child domain class.
// System.out.println(childWithToys.getToyList().size());
Child childWithToys = childDAO.getChildByIdWithToys(1L);
System.out.println(childWithToys.getToyList().size());
Child childByNameWithToys = childDAO.getChildByNameWithToys("John");
System.out.println(childByNameWithToys.getToyList().size());
}
}
Defining your base Entity as LAZY is a good practice since
in many occasions, you might not want the collections within
an entity, but just want to interact with the data in your base
entity. But in the event of you needing the data of your collections,
then you can use either of the methods mentioned before.
Referance: Dinuka Arseculeratne (Dzone)
No comments:
Post a Comment