Sunday, January 9, 2011

Effect of Select Strategies in Hibernate How and When

STUDY : Hibernate Common Issues and Tips
Effect of Select Strategies in Hibernate (How and When)

Hibernate is one of the most popular ORM tools. In my own experience I have found that in the application development life cycle we often get some common hibernate related issues, be it performance related issues or Exception generated by hibernate.
I have tried to capture the exception or problems we get while  using hibernate in the data access layer. The overall finding is divided in to different scene's.


In Hibernate 3 when we load a particular object from db, only that object is loadedand the associated objects are not loaded.  For example if we have a relationship
like      Director   ---> Movies(many)
If we now load the director all the movies will not be retrieved from data base .
We can control this behavior like whether to load the related objects(movies in this case) using fetching strategies.
By default all the related objects(in our case all the movie objects) would be loaded only when we access the collection by director.getMovies() or similar method.

We can control How to Load and When to load using JPA.
It provide @Fetch annotation which can take three different values (FetchMode.JOIN,FetchMode.SELECT,FetchMode.SUBSELECT) it controls HOW to load
We can also specify the WHEN to load  using attribute like, fetch = FetchType.LAZY or fetch = FetchType.EAGER

FetchMode :: How To Load
1. FetchMode.JOIN = Disable the lazy loading, always load all the collections and entities. It will generate one join statement rather then two seperate SQLS, and will retrieve the object and its associatation from data base.
2. FetchMode.SELECT.  (default) = Lazy load all the collections and entities.
This may lead to N+1 Select Problem
3. batch-size=”N” = Fetching up to ‘N’ collections or entities, *Not record*.
4. FetchMode.SUBSELECT= Group its collection into a sub select statement.


When we specify FetchType as Lazy, the association is not loaded until
accessed [by getter methods or methods like size()].
If we try to access the association loaded lazily we might get LazyInitializationException.

EXCEPTION : org.hibernate.PersistentObjectException: detached entity passed to persist: com.javaexp.hib.jointable.movie.Director
:: When we pass a persistent instance to persist() method it throws the above exception.

HANDSON : Trying to see fetching strategy in hibernate, by examining the query it generates.
Scene Condition :One to Many Bidirectional using Join Table
UML: Director (One) -> Movies(Many)


With
@OneToMany(cascade={CascadeType.ALL},fetch = FetchType.EAGER)the
select this_.directorid as directorid7_1_, this_.directorname as director2_7_1_, movies2_.movieid as movieid3_, movie3_.movieid as directorid3_, movie3_.movieid as movieid6_0_, movie3_.moviename as moviename6_0_, movie3_1_.movieid as movieid8_0_ from moviedirectordb this_ left outer join moviem_directordb movies2_ on this_.directorid=movies2_.movieid left outer join moviedb movie3_ on movies2_.directorid=movie3_.movieid left outer join moviem_directordb movie3_1_ on movie3_.movieid=movie3_1_.directorid whee this_.directorname=?

With
@Fetch(FetchMode.JOIN)
@OneToMany(cascade={CascadeType.ALL},fetch = FetchType.EAGER)
select this_.directorid as directorid7_1_, this_.directorname as director2_7_1_, movies2_.movieid as movieid3_, movie3_.movieid as directorid3_, movie3_.movieid as movieid6_0_, movie3_.moviename as moviename6_0_, movie3_1_.movieid as movieid8_0_ from moviedirectordb this_ left outer join moviem_directordb movies2_ on this_.directorid=movies2_.movieid left outer join moviedb movie3_ on movies2_.directorid=movie3_.movieid left outer join moviem_directordb movie3_1_ on movie3_.movieid=movie3_1_.directorid where this_.directorname=?

With
@Fetch(FetchMode.SELECT)
@OneToMany(cascade={CascadeType.ALL},fetch = FetchType.LAZY)

select this_.directorid as directorid7_0_, this_.directorname as director2_7_0_ from moviedirectordb this_ where this_.directorname=?

With
@Fetch(FetchMode.JOIN)
@OneToMany(cascade={CascadeType.ALL},fetch = FetchType.LAZY)
select this_.directorid as directorid7_1_, this_.directorname as director2_7_1_, movies2_.movieid as movieid3_, movie3_.movieid as directorid3_, movie3_.movieid as movieid6_0_, movie3_.moviename as moviename6_0_, movie3_1_.movieid as movieid8_0_ from moviedirectordb this_ left outer join moviem_directordb movies2_ on this_.directorid=movies2_.movieid left outer join moviedb movie3_ on movies2_.directorid=movie3_.movieid left outer join moviem_directordb movie3_1_ on movie3_.movieid=movie3_1_.directorid where this_.directorname=?



We can overide the fetching strategy defined in the mapping in Criteria.
We can set the fetch mode using criteria.setFetchmode


For example,
In the following method  loadDirectorByName we are telling hibernate to load movies using Join FetchMode Strategy.  So this method will result hibernate to fire Join SQL irrespective of what Strategy has been annotated in the original Hibernate mapping.

public Director loadDirectorByName(String albumName) {

try {
   getSession().beginTransaction();
   Criteria criteria =getSession().createCriteria(Director.class);
   criteria.setFetchMode("movies", FetchMode.JOIN);
   criteria.setComment("Testing ");
   Criterion c = Expression.eq("directorName", albumName);
   criteria.add(c);
   List list = criteria.list();
   getSession().flush();
   getSession().getTransaction().commit();
   return list.get(0);
} catch (Exception e) {
       return null;
   }
}





Findings and Conclusion:
if we specify fetch=FetchType.LAZY [and no @Fetch(FetchMode.JOIN) ], then associated Collection or Single object is not loaded from data base. Association is loaded when we access the association
Like using get method or size() method, hibernate loads the association by firing a join sql like the following.

So when we load a Director object from Database, hibernate will load Director object by firing one single sql,
Next when we do  something like directory.size() or director.getMovies()  hibernate will fire another sql either Select or Join(Based on the FetchMode) to load the association.
 
Like,
  select movies0_.movieid as movieid2_, movies0_.directorid as directorid2_, movie1_.movieid as movieid6_0_, movie1_.moviename as moviename6_0_, movie1_1_.movieid as movieid8_0_, director2_.directorid as directorid7_1_, director2_.directorname as director2_7_1_ from moviem_directordb movies0_ left outer join moviedb movie1_ on movies0_.directorid=movie1_.movieid left outer join moviem_directordb movie1_1_ on movie1_.movieid=movie1_1_.directorid left outer join moviedirectordb director2_ on movie1_1_.movieid=director2_.directorid where movies0_.movieid=?

This lead to two different select statement to be fired, this is also known as
N+1 Select Problem.

If we specify Lazy association and the session is closed, later when we
Try to retrieve or walk though the association we will get
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.javaexp.hib.movie.Director.movies, no session or session was closed

Hibernate provides Hibernate.initialize to initialize the collection or associated objects. It can be used when we have loaded the association lazly but it should be called with in the same hibernate session.


   getSession().beginTransaction();
  Criteria criteria =    getSession().createCriteria(Director.class);
  criteria.setFetchMode("movies", FetchMode.LAZY);
  Criterion c = Expressionn.eq("directorName", dirName);
  criteria.add(c);
  List list = criteria.list();
  getSession().flush();
  getSession().getTransaction().commit();
  getSession().close();

  //Initialize proxy
   Hibernate.initialize(list.get(0).getMovies());

  Hibernate will  either load the whole association(EAGER) using  Join Query or will load only the object without loading the association(Lazy) how ever if we specify  @Fetch(FetchMode.JOIN) then hibernate will fire join query and eventually will load all the data, and is equivalent to  EAGER association fetching.
                      

No comments :

Post a Comment