JavaBlog.fr / Java.lu DEVELOPMENT,Hibernate,Java Java/Hibernate: Converting Hibernate proxy to real object and ORMLazyLoader

Java/Hibernate: Converting Hibernate proxy to real object and ORMLazyLoader

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{"*&#47*"}); :
    

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{"*&#47*"}); :
	 * 				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

4 thoughts on “Java/Hibernate: Converting Hibernate proxy to real object and ORMLazyLoader”

  1. 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.

    1. 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

Leave a Reply

Your email address will not be published.

Time limit is exhausted. Please reload CAPTCHA.

Related Post