Hello,
To continue with Hibernate/ORM posts, I would submit a common problem faced with the use of lazy loading in ORM: the objects returned by the ORM layer contain lazy loading references (its type has a suffix as ‘_$$_javassist_11[….]’), and so the need of have an ORM session to access these objects.
Origin of lazy exceptions
Indeed, some object attributes are loaded as proxies due to lazy loading during a Hibernate session. For obvious performance reasons, it is not advisable to turn off the lazy loading: if an object with lazy loaded attributes is manipulated in persistence layer: no problem, the proxy will load the lazy loaded attribute once its necessary; during its ask. For example, if we have a “Chapter” class, that contains a “Book” property, when we do “chpater.getBook()”, the proxies will get the book from the database or Hibernate caches (1st and 2nd levels) when it’s requested – not when the chapter instance is initialized in the persistence layer.
However, there are still error scenarios:
- Problem 1: out of the persistence layer, in presentation layer (for example in a Spring MVC controller, Struts action), a access to a lazy loaded attribute
chapter.getBook()
will end up with the famous org.hibernate.LazyInitializationException because the Hibernate Session is closed and this lazy attribute don’t have the session attached and can’t load their lazy references;
- Problem 2: more, in some case, it could be necessary to manipulate directly the real object and not the proxy for example during object’s conversion, parsing object to xml or introspection/reflection and an access to a lazy loaded attribute
chapter.getBook()
will produce an error like:
09:21:26 ERROR com.ho.controller.MyController handleException handleReadDetails error java.lang.ClassCastException: com.ho.data.Book_$$_javassist_11 cannot be cast to com.ho.data.Book
Bad solution
A simple ‘bad’ solution could be to have dedicated POJOs for each layer: a POJO ‘Book4Model’ for the persistence layer, a POJO ‘Book4Service’ for service layer and a POJO ‘Book4View’ for presentation layer. So, it will be necessary to maintain all conversions of these POJOs between layers.
Good solution
A other other solution is to ‘de-proxy’ the object due to a utility method like:
public class ORMUtils { public static <T> T initializeAndUnproxy(T entity) { if (entity == null) { return null; } if (entity instanceof HibernateProxy) { Hibernate.initialize(entity); entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation(); } return entity; } }
The offcial documentation http://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/Hibernate.html#initialize(java.lang.Object) explains that the method ‘initialize(Object proxy)’ ensures intialization of a proxy object or collection; it is not guaranteed that the elements INSIDE the collection will be initialized/materialized.
This utility method could be used directly inside the POJO of persistence layer:
public class Chapter extends AbstractPersistentObject { // ------------------------------------------- ATTRIBUTS private Book book = null; private Calendar creationDate = null; private Calendar modifDate = null; private String title = null; private int number = 0; private String content = null; // ------------------------------------ GET/SET TERS public void setBook(Book book) { this.book = book; } public Book getBook() { book = ORMUtils.initializeAndUnproxy(book); return book; } ... }
ORMLazyLoader
However, this previous good solution resolves only the problem n°2 concerning the need of manipulate directly the real object and not the proxy. But the problem n° 1 about the access to a lazy loaded attribute between application’s layers is not target.
So, our complete ‘homemade’ solution is a utility named ORMLazyLoader based on previous ORMUtils class.
This unique Java class proposes an unique fonctionnality:
public final PojoType lazyLoad(final PojoType source, String[] levelsToLoad){ .... }
This lazyLoad fonctionnality will:
- be used in the persistence layer in order to load the lazy attributes of Hibernate or Collection types in a POJO Hibernate,
- receive as inputs a bean attached to Hibernate session and a list of levels to load,
- return the bean with attributes (Hibernate, Collection) detached from Hibernate session depending on specified levels,
Notes: The attributes (Hibernate, Collection) of levels not specified, will remain attached to the Hibernate session. So, if in the presentation layer, we try to access to an attribute attached to Hibernate Session, we will have the famous LazyInitializationException. More, a bean, whose the lazy attributes have been detached, can be used again in the persistence layer without load it again.
Uses of ORMLazyLoader
Simple example:
String[] levelsToLoad = new String[] {"language", "chapters/book", "authors"}; // This bean is attached to Hibernate Session Book hibernateAttachedBean = new Book(); ... Book hibernateDetachedBean = (new ORMLazyLoader<Book>()).lazyLoad(hibernateAttachedBean, levelsToLoad);
Other concrete examples:
- load all primitive attributes of:
– “language” attribute of first level,
– each instance “chapter” in “chapters” attribute of first level,
– each instance “book” of second level in above “chapters/chapter” attribute of first level,
– each instance “author” in “authors” of first level.(new ORMLazyLoader<Book>()).lazyLoad(bean, new String{"language", "chapters/book", "authors"});
- load all primitive attributes of the first level:
(new ORMLazyLoader<Book>()).lazyLoad(bean, new String[] {}); :
- load all attributes (primitive, collection, hibernate types) of the first level:
(new ORMLazyLoader<Book>()).lazyLoad(bean, new String[] {"*"}); :
- load all attributes (primitive, collection, hibernate types) of the first and second levels:
(new ORMLazyLoader<Book>()).lazyLoad(bean, new String{"*/*"}); :
Here, the full code of the ‘homemade’ solution ORMLazyLoader:
package com.ho.orm.loader.util; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.hibernate.collection.PersistentList; import org.hibernate.collection.PersistentSet; import com.ho.orm.introspector.pojo.util.AbstractPersistentObject; import com.ho.orm.util.ORMUtils; /** * This class is used to load the lazy attributes of Hibernate or Collection types in a POJO Hibernate. * Example of using this class: * <code> * // <b>This function receives as input a bean attached to Hibernate session</b> * // <b>The below line specifies the attributes to be loaded when the bean is attached to the collections and hibernate beans</b> * // <b>this avoids loading all the elements of the bean in memory.</b> * String[] levelsToLoad = new String[] {"language", "chapters/book", "authors"};<br/> * // This bean is attached to Hibernate Session<br/> * Book hibernateAttachedBean = new Book();<br/> * [...]<br/> * Book hibernateDetachedBean = (new ORMLazyLoader<Book>()).lazyLoad(hibernateAttachedBean, levelsToLoad);<br/> * </code> * </p> * * @author Huseyin Ozveren */ public class ORMLazyLoader<PojoType> { // --------------------------------------------------------------------- ATTRIBUTES // --------------------------------------------------------------- PUBLIC METHODS /** * <p> * Syntax: * <ul> * <li> * (new ORMLazyLoader<Book>()).lazyLoad(bean, new String{"language", "chapters/book", "authors"}); : * load all primitive attributes of: * - "language" attribute of first level, * - each instance "chapter" in "chapters" attribute of first level, * - each instance "book" of second level in above "chapters/chapter" attribute of first level, * - each instance "author" in "authors" of first level. * </li> * <li> * (new ORMLazyLoader<Book>()).lazyLoad(bean, new String[] {}); : * load all primitive attributes of the first level. * </li> * <li> * (new ORMLazyLoader<Book>()).lazyLoad(bean, new String[] {"*"}); : * load all attributes (primitive, collection, hibernate types) of the first level. * </li> * <li> * (new ORMLazyLoader<Book>()).lazyLoad(bean, new String{"*/*"}); : * load all attributes (primitive, collection, hibernate types) of the first and second levels. * </li> * </ul> * </p> */ @SuppressWarnings("unchecked") public final PojoType lazyLoad(final PojoType source, String[] levelsToLoad) { return (PojoType) deepLazyLoad(source, levelsToLoad, 0); } // ------------------------------------------------------------------ PRIVATE METHODS /** * For given levels in the form:<br/> * new String[]{"chapters/paragraphs", "chapters/book/language", "language", "authors/language"},<br/> * and a current property:<br/> * "chapters",<br/> * returns the sub-levels of the current property:<br/> * new String[]{"paragraphs", "book/language"},<br/> */ private final String[] getRemainingLevelsToLoad(String[] levelsToLoad, String currProperty) { ArrayList<String> output = new ArrayList<String>(); if (null != levelsToLoad){ for (String currLevel : levelsToLoad) { if (null != currLevel) { // Check if the currProperty is in the levels to load int indexOfSlash = currLevel.indexOf(currProperty+"/"); if (-1 != indexOfSlash) { // Get the sub-levels String remainingLevel = currLevel.substring(indexOfSlash + (currProperty+"/").length()); output.add(remainingLevel); }else if(currLevel.indexOf("*/")==0){ String remainingLevel = currLevel.substring(0+("*/").length()); output.add(remainingLevel); } } // end-if } // end-if } // end-if return (String[]) output.toArray((new String[output.size()])); } /** * Returns true if the property named propertyName must be loaded. */ private final boolean isLevelLoadNecessary(String propertyName, String[] levelsToLoad) { if (null != levelsToLoad){ for (String currLevel : levelsToLoad) { if (null != currLevel) { String authorizedLevel = currLevel.split("/")[0]; if (authorizedLevel.equals("*") || authorizedLevel.equals(propertyName)) { return true; } } } // end-for } return false; } /** * Load a lazy property with a type of hibernate object * * @param currProperty: the name of property to load * @param askedLevelsToLoad: original levels asked * @param sourceBean: original source bean to detach * @param currLevel: current level * @param remainingLevelsToLoad: sub-levels of the current property to load */ private final void lazyLoadHibernateObject( String currProperty, String[] askedLevelsToLoad, Object sourceBean, Integer currLevel, String[] remainingLevelsToLoad ) { try { // Check if the current hibernate property must be loaded according to the asked properties if (isLevelLoadNecessary(currProperty, askedLevelsToLoad)) { // Get the object to detach from the source bean Object propertyToLoad = PropertyUtils.getProperty(sourceBean, currProperty); if (null != propertyToLoad) { // Load recursively the Hibernate Object Object loadedObject = deepLazyLoad (propertyToLoad, remainingLevelsToLoad, currLevel+1); // Set the property into the result bean PropertyUtils.setProperty(sourceBean, currProperty, loadedObject); }//end-if } // end-if } catch (IllegalAccessException e) { logErrorMessage("sourceBean= " + sourceBean + " propertyName = " +currProperty, e); } catch (InvocationTargetException e) { logErrorMessage("sourceBean= " + sourceBean + " propertyName = " +currProperty, e); } catch (NoSuchMethodException e) { logErrorMessage("sourceBean= " + sourceBean + " propertyName = " +currProperty, e); } // end-try } /** * Load a lazy property with a type of collection object * * @param propertyToLoad * @param currLevel * @param remainingLevelsToLoad * @return */ @SuppressWarnings("unchecked") private final Collection lazyLoadCollection(Collection propertyToLoad, Integer currLevel, String[] remainingLevelsToLoad ) { Collection output = null; try { if (null != propertyToLoad) { // Instance of Set if (propertyToLoad instanceof PersistentSet) { output = new HashSet(); // Instance of List }else if (propertyToLoad instanceof PersistentList){ output = new ArrayList(); // Other } else { // Initialization needed in order to not clone the Proxy class of Spring/Hibernate Javassist propertyToLoad = ORMUtils.initializeAndUnproxy(propertyToLoad); // Create a new EMPTY instance of the source bean output = propertyToLoad.getClass().newInstance(); } // end-if // Initialization needed in order to load the current object propertyToLoad = ORMUtils.initializeAndUnproxy(propertyToLoad); for (Object currCollectionBean : propertyToLoad) { logMessage("recursive 'deepLazyLoad' loading of the elements of collection Object " , currLevel+1); // Load recursively the elements of collection Object Object loadedObject = deepLazyLoad (currCollectionBean, remainingLevelsToLoad, currLevel+1); output.add(loadedObject); } // end-for } //end-for } catch (Throwable e) { logErrorMessage("propertyToLoad= " + propertyToLoad , e); } // end-try return output; } /** * Get the attribute with a type of collection and load it. * * @param currProperty * @param askedLevelsToLoad * @param sourceBean * @param currLevel * @param remainingLevelsToLoad */ @SuppressWarnings("unchecked") private final void lazyLoadCollectionAttribute( String currProperty, String[] askedLevelsToLoad, Object sourceBean, Integer currLevel, String[] remainingLevelsToLoad ) { try { // Check if the current hibernate property must be loaded according to the asked properties if (isLevelLoadNecessary(currProperty, askedLevelsToLoad)) { logMessage("loop on: " + currProperty, currLevel); // Collection Collection propertyToLoad = (Collection) PropertyUtils.getProperty(sourceBean, currProperty); // Load collection Collection loadedProperty = lazyLoadCollection(propertyToLoad, currLevel, remainingLevelsToLoad); // Set the collection property into the result object PropertyUtils.setProperty(sourceBean, currProperty, loadedProperty); } // end-if } catch (IllegalAccessException e) { logErrorMessage("sourceBean= " + sourceBean + " currProperty=" + currProperty , e); } catch (InvocationTargetException e) { logErrorMessage("sourceBean= " + sourceBean + " currProperty=" + currProperty , e); } catch (NoSuchMethodException e) { logErrorMessage("sourceBean= " + sourceBean + " currProperty=" + currProperty , e); } // end-try } /** * Return true if the abstract class 'AbstractPersistentObject' is either the same as, * or is a superclass or superinterface of, the class or interface represented by the specified * <code>Class</code> parameter. * * @param propertyClass * @return */ @SuppressWarnings("unchecked") private final boolean isHibernateObject(Class propertyClass) { return AbstractPersistentObject.class.isAssignableFrom(propertyClass); } /** * Return true if the <code>Class</code> parameter is a 'Collection'. * @param propertyClass * @return */ @SuppressWarnings("unchecked") private final boolean isCollection(Class propertyClass) { return Collection.class.isAssignableFrom(propertyClass); } /** * Returns the names of the bean properties for which there is a get / set pair. */ private final String[] describe(Object bean) { ArrayList<String> output = new ArrayList<String>(); PropertyDescriptor[] properties = PropertyUtils.getPropertyDescriptors(bean); // Properties for (PropertyDescriptor currPropertyDescriptor : properties) { String currProperty = currPropertyDescriptor.getName(); if (PropertyUtils.isWriteable(bean, currProperty) && PropertyUtils.isReadable(bean, currProperty)) { output.add(currProperty); } // end-if } //end-for return output.toArray(new String[output.size()]); } /** * This method allows: * - the recursive 'deepLazyLoad' loading of hibernate object; * - recursive 'deepLazyLoad' loading of the elements of collection Object; * * @param sourceBean: original source bean to load * @param askedLevelsToLoad: the levels asked to load * @param currLevel: current level * @return */ @SuppressWarnings("unchecked") private final Object deepLazyLoad (Object sourceBean, String[] askedLevelsToLoad, Integer currLevel) { try { if(sourceBean==null){ return null; } // Collection of objects if (sourceBean instanceof Collection) { lazyLoadCollection((Collection) sourceBean, currLevel, askedLevelsToLoad); } else{ // Initialization needed in order to load the current object sourceBean = ORMUtils.initializeAndUnproxy(sourceBean); // Properties of this object String[] properties = describe(sourceBean); // PropertyUtils.describe(bean); // Range all properties for (String currProperty : properties) { // If it is a Writeable property if (PropertyUtils.isWriteable(sourceBean, currProperty)) { // Class of current property Class propertyClass = PropertyUtils.getPropertyType(sourceBean, currProperty); // Levels to load String[] remainingLevelsToLoad = getRemainingLevelsToLoad(askedLevelsToLoad, currProperty); // Hibernate Object 'AbstractPersistentObject' if (isHibernateObject(propertyClass)) { lazyLoadHibernateObject(currProperty, askedLevelsToLoad, sourceBean, currLevel, remainingLevelsToLoad); // Collection property } else if (isCollection(propertyClass)){ lazyLoadCollectionAttribute(currProperty, askedLevelsToLoad, sourceBean, currLevel, remainingLevelsToLoad); } // end-if } // end-if } // end-for } // end-if } catch (IllegalAccessException e) { logErrorMessage("sourceBean= " + sourceBean , e); } catch (InvocationTargetException e) { logErrorMessage("sourceBean= " + sourceBean , e); } catch (NoSuchMethodException e) { logErrorMessage("sourceBean= " + sourceBean , e); } //end-try return sourceBean; } /** * Log message * @param message * @param level */ private final void logMessage(String message, Integer level) { final String tabulation = "\t"; System.out.println(StringUtils.repeat(tabulation, level)+ message); } /** * Log error message * @param message * @param level */ private final void logErrorMessage(String message, Throwable exception) { String exceptionMessage = ExceptionUtils.getStackTrace(exception); logMessage(message+exceptionMessage, 1); } }
This source code ORMLazyLoader is in the ZIP file attachement. This project needs the following librairies commons-beanutils-1.7.0.jar, commons-lang-2.4.jar, commons-logging-1.1.1.jar, hibernate-core-3.3.1.GA.jar, junit-4.1.jar and spring-2.5.5.jar.
Download: test_ORMTools.zip
That’s all!
Best regards,
Huseyin OZVEREN
Nice post. I was checking constantly this blog and I am impressed!
Very helpful information specifically the last part 🙂
I care for such information much. I was looking for this
particular info for a long time. Thank you and good luck.
Thanks, for your comment and encouragement…I had decided to write this blog in order to share my exprerience, and to create a knowledge base (base de connaissances) for developers..
Hùseyin
Hello there! Do you know if they make any plugins to help with SEO?
I’m trying to get my blog to rank for some targeted keywords but I’m not seeing very good success. If you know of any please share. Appreciate it!
What is the relationship between your comment and this article?
First way, concerning a plugin for wordpress (CMS), I advise the famous plugin named “all-in-one-seo-pack” (see http://wordpress.org/extend/plugins/all-in-one-seo-pack/).
Second way, you can add links in others sites or blog to your web site like your previous comment… 😉
Huseyin