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
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
will produce an error like:
1 | 09:21:26 ERROR com.ho.controller.MyController handleException handleReadDetails error |
2 | 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:
01 | public class ORMUtils { |
02 | public static <T> T initializeAndUnproxy(T entity) { |
07 | if (entity instanceof HibernateProxy) { |
08 | Hibernate.initialize(entity); |
09 | entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation(); |
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:
01 | public class Chapter extends AbstractPersistentObject { |
04 | private Book book = null ; |
05 | private Calendar creationDate = null ; |
06 | private Calendar modifDate = null ; |
07 | private String title = null ; |
08 | private int number = 0 ; |
09 | private String content = null ; |
12 | public void setBook(Book book) { |
16 | public Book getBook() { |
17 | book = ORMUtils.initializeAndUnproxy(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:
1 | 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:
1 | String[] levelsToLoad = new String[] { "language" , "chapters/book" , "authors" }; |
3 | Book hibernateAttachedBean = new Book(); |
5 | 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.
1 | ( new ORMLazyLoader<Book>()).lazyLoad(bean, new String{ "language" , "chapters/book" , "authors" }); |
- load all primitive attributes of the first level:
1 | ( new ORMLazyLoader<Book>()).lazyLoad(bean, new String[] {}); : |
- load all attributes (primitive, collection, hibernate types) of the first level:
1 | ( new ORMLazyLoader<Book>()).lazyLoad(bean, new String[] { "*" }); : |
- load all attributes (primitive, collection, hibernate types) of the first and second levels:
1 | ( new ORMLazyLoader<Book>()).lazyLoad(bean, new String{ "*/*" }); : |
Here, the full code of the ‘homemade’ solution ORMLazyLoader:
001 | package com.ho.orm.loader.util; |
003 | import java.beans.PropertyDescriptor; |
004 | import java.lang.reflect.InvocationTargetException; |
005 | import java.util.ArrayList; |
006 | import java.util.Collection; |
007 | import java.util.HashSet; |
009 | import org.apache.commons.beanutils.PropertyUtils; |
010 | import org.apache.commons.lang.StringUtils; |
011 | import org.apache.commons.lang.exception.ExceptionUtils; |
012 | import org.hibernate.collection.PersistentList; |
013 | import org.hibernate.collection.PersistentSet; |
015 | import com.ho.orm.introspector.pojo.util.AbstractPersistentObject; |
016 | import com.ho.orm.util.ORMUtils; |
019 | * This class is used to load the lazy attributes of Hibernate or Collection types in a POJO Hibernate. |
020 | * Example of using this class: |
022 | * // <b>This function receives as input a bean attached to Hibernate session</b> |
023 | * // <b>The below line specifies the attributes to be loaded when the bean is attached to the collections and hibernate beans</b> |
024 | * // <b>this avoids loading all the elements of the bean in memory.</b> |
025 | * String[] levelsToLoad = new String[] {"language", "chapters/book", "authors"};<br/> |
026 | * // This bean is attached to Hibernate Session<br/> |
027 | * Book hibernateAttachedBean = new Book();<br/> |
029 | * Book hibernateDetachedBean = (new ORMLazyLoader<Book>()).lazyLoad(hibernateAttachedBean, levelsToLoad);<br/> |
033 | * @author Huseyin Ozveren |
035 | public class ORMLazyLoader<PojoType> { |
043 | * (new ORMLazyLoader<Book>()).lazyLoad(bean, new String{"language", "chapters/book", "authors"}); : |
044 | * load all primitive attributes of: |
045 | * - "language" attribute of first level, |
046 | * - each instance "chapter" in "chapters" attribute of first level, |
047 | * - each instance "book" of second level in above "chapters/chapter" attribute of first level, |
048 | * - each instance "author" in "authors" of first level. |
051 | * (new ORMLazyLoader<Book>()).lazyLoad(bean, new String[] {}); : |
052 | * load all primitive attributes of the first level. |
055 | * (new ORMLazyLoader<Book>()).lazyLoad(bean, new String[] {"*"}); : |
056 | * load all attributes (primitive, collection, hibernate types) of the first level. |
059 | * (new ORMLazyLoader<Book>()).lazyLoad(bean, new String{"*/*"}); : |
060 | * load all attributes (primitive, collection, hibernate types) of the first and second levels. |
065 | @SuppressWarnings ( "unchecked" ) |
066 | public final PojoType lazyLoad( final PojoType source, String[] levelsToLoad) { |
067 | return (PojoType) deepLazyLoad(source, levelsToLoad, 0 ); |
073 | * For given levels in the form:<br/> |
074 | * new String[]{"chapters/paragraphs", "chapters/book/language", "language", "authors/language"},<br/> |
075 | * and a current property:<br/> |
077 | * returns the sub-levels of the current property:<br/> |
078 | * new String[]{"paragraphs", "book/language"},<br/> |
080 | private final String[] getRemainingLevelsToLoad(String[] levelsToLoad, String currProperty) { |
082 | ArrayList<String> output = new ArrayList<String>(); |
083 | if ( null != levelsToLoad){ |
084 | for (String currLevel : levelsToLoad) { |
085 | if ( null != currLevel) { |
087 | int indexOfSlash = currLevel.indexOf(currProperty+ "/" ); |
088 | if (- 1 != indexOfSlash) { |
090 | String remainingLevel = currLevel.substring(indexOfSlash + (currProperty+ "/" ).length()); |
091 | output.add(remainingLevel); |
092 | } else if (currLevel.indexOf( "*/" )== 0 ){ |
093 | String remainingLevel = currLevel.substring( 0 +( "*/" ).length()); |
094 | output.add(remainingLevel); |
100 | return (String[]) output.toArray(( new String[output.size()])); |
104 | * Returns true if the property named propertyName must be loaded. |
106 | private final boolean isLevelLoadNecessary(String propertyName, String[] levelsToLoad) { |
108 | if ( null != levelsToLoad){ |
109 | for (String currLevel : levelsToLoad) { |
110 | if ( null != currLevel) { |
111 | String authorizedLevel = currLevel.split( "/" )[ 0 ]; |
112 | if (authorizedLevel.equals( "*" ) || authorizedLevel.equals(propertyName)) { |
123 | * Load a lazy property with a type of hibernate object |
125 | * @param currProperty: the name of property to load |
126 | * @param askedLevelsToLoad: original levels asked |
127 | * @param sourceBean: original source bean to detach |
128 | * @param currLevel: current level |
129 | * @param remainingLevelsToLoad: sub-levels of the current property to load |
131 | private final void lazyLoadHibernateObject( |
133 | String[] askedLevelsToLoad, |
136 | String[] remainingLevelsToLoad |
140 | if (isLevelLoadNecessary(currProperty, askedLevelsToLoad)) { |
143 | Object propertyToLoad = PropertyUtils.getProperty(sourceBean, currProperty); |
144 | if ( null != propertyToLoad) { |
146 | Object loadedObject = deepLazyLoad (propertyToLoad, remainingLevelsToLoad, currLevel+ 1 ); |
149 | PropertyUtils.setProperty(sourceBean, currProperty, loadedObject); |
155 | } catch (IllegalAccessException e) { |
156 | logErrorMessage( "sourceBean= " + sourceBean + " propertyName = " +currProperty, e); |
157 | } catch (InvocationTargetException e) { |
158 | logErrorMessage( "sourceBean= " + sourceBean + " propertyName = " +currProperty, e); |
159 | } catch (NoSuchMethodException e) { |
160 | logErrorMessage( "sourceBean= " + sourceBean + " propertyName = " +currProperty, e); |
165 | * Load a lazy property with a type of collection object |
167 | * @param propertyToLoad |
169 | * @param remainingLevelsToLoad |
172 | @SuppressWarnings ( "unchecked" ) |
173 | private final Collection lazyLoadCollection(Collection propertyToLoad, Integer currLevel, String[] remainingLevelsToLoad ) { |
175 | Collection output = null ; |
177 | if ( null != propertyToLoad) { |
179 | if (propertyToLoad instanceof PersistentSet) { |
180 | output = new HashSet(); |
183 | } else if (propertyToLoad instanceof PersistentList){ |
184 | output = new ArrayList(); |
189 | propertyToLoad = ORMUtils.initializeAndUnproxy(propertyToLoad); |
191 | output = propertyToLoad.getClass().newInstance(); |
196 | propertyToLoad = ORMUtils.initializeAndUnproxy(propertyToLoad); |
198 | for (Object currCollectionBean : propertyToLoad) { |
199 | logMessage( "recursive 'deepLazyLoad' loading of the elements of collection Object " , currLevel+ 1 ); |
202 | Object loadedObject = deepLazyLoad (currCollectionBean, remainingLevelsToLoad, currLevel+ 1 ); |
204 | output.add(loadedObject); |
208 | } catch (Throwable e) { |
209 | logErrorMessage( "propertyToLoad= " + propertyToLoad , e); |
216 | * Get the attribute with a type of collection and load it. |
218 | * @param currProperty |
219 | * @param askedLevelsToLoad |
222 | * @param remainingLevelsToLoad |
224 | @SuppressWarnings ( "unchecked" ) |
225 | private final void lazyLoadCollectionAttribute( |
227 | String[] askedLevelsToLoad, |
230 | String[] remainingLevelsToLoad |
234 | if (isLevelLoadNecessary(currProperty, askedLevelsToLoad)) { |
235 | logMessage( "loop on: " + currProperty, currLevel); |
238 | Collection propertyToLoad = (Collection) PropertyUtils.getProperty(sourceBean, currProperty); |
241 | Collection loadedProperty = lazyLoadCollection(propertyToLoad, currLevel, remainingLevelsToLoad); |
244 | PropertyUtils.setProperty(sourceBean, currProperty, loadedProperty); |
248 | } catch (IllegalAccessException e) { |
249 | logErrorMessage( "sourceBean= " + sourceBean + " currProperty=" + currProperty , e); |
250 | } catch (InvocationTargetException e) { |
251 | logErrorMessage( "sourceBean= " + sourceBean + " currProperty=" + currProperty , e); |
252 | } catch (NoSuchMethodException e) { |
253 | logErrorMessage( "sourceBean= " + sourceBean + " currProperty=" + currProperty , e); |
258 | * Return true if the abstract class 'AbstractPersistentObject' is either the same as, |
259 | * or is a superclass or superinterface of, the class or interface represented by the specified |
260 | * <code>Class</code> parameter. |
262 | * @param propertyClass |
265 | @SuppressWarnings ( "unchecked" ) |
266 | private final boolean isHibernateObject(Class propertyClass) { |
267 | return AbstractPersistentObject. class .isAssignableFrom(propertyClass); |
271 | * Return true if the <code>Class</code> parameter is a 'Collection'. |
272 | * @param propertyClass |
275 | @SuppressWarnings ( "unchecked" ) |
276 | private final boolean isCollection(Class propertyClass) { |
277 | return Collection. class .isAssignableFrom(propertyClass); |
281 | * Returns the names of the bean properties for which there is a get / set pair. |
283 | private final String[] describe(Object bean) { |
284 | ArrayList<String> output = new ArrayList<String>(); |
286 | PropertyDescriptor[] properties = PropertyUtils.getPropertyDescriptors(bean); |
288 | for (PropertyDescriptor currPropertyDescriptor : properties) { |
289 | String currProperty = currPropertyDescriptor.getName(); |
290 | if (PropertyUtils.isWriteable(bean, currProperty) && PropertyUtils.isReadable(bean, currProperty)) { |
291 | output.add(currProperty); |
296 | return output.toArray( new String[output.size()]); |
300 | * This method allows: |
301 | * - the recursive 'deepLazyLoad' loading of hibernate object; |
302 | * - recursive 'deepLazyLoad' loading of the elements of collection Object; |
304 | * @param sourceBean: original source bean to load |
305 | * @param askedLevelsToLoad: the levels asked to load |
306 | * @param currLevel: current level |
309 | @SuppressWarnings ( "unchecked" ) |
310 | private final Object deepLazyLoad (Object sourceBean, String[] askedLevelsToLoad, Integer currLevel) { |
313 | if (sourceBean== null ){ |
318 | if (sourceBean instanceof Collection) { |
319 | lazyLoadCollection((Collection) sourceBean, currLevel, askedLevelsToLoad); |
323 | sourceBean = ORMUtils.initializeAndUnproxy(sourceBean); |
326 | String[] properties = describe(sourceBean); |
329 | for (String currProperty : properties) { |
332 | if (PropertyUtils.isWriteable(sourceBean, currProperty)) { |
334 | Class propertyClass = PropertyUtils.getPropertyType(sourceBean, currProperty); |
337 | String[] remainingLevelsToLoad = getRemainingLevelsToLoad(askedLevelsToLoad, currProperty); |
340 | if (isHibernateObject(propertyClass)) { |
341 | lazyLoadHibernateObject(currProperty, askedLevelsToLoad, sourceBean, currLevel, remainingLevelsToLoad); |
343 | } else if (isCollection(propertyClass)){ |
344 | lazyLoadCollectionAttribute(currProperty, askedLevelsToLoad, sourceBean, currLevel, remainingLevelsToLoad); |
352 | } catch (IllegalAccessException e) { |
353 | logErrorMessage( "sourceBean= " + sourceBean , e); |
354 | } catch (InvocationTargetException e) { |
355 | logErrorMessage( "sourceBean= " + sourceBean , e); |
356 | } catch (NoSuchMethodException e) { |
357 | logErrorMessage( "sourceBean= " + sourceBean , e); |
368 | private final void logMessage(String message, Integer level) { |
369 | final String tabulation = "\t" ; |
370 | System.out.println(StringUtils.repeat(tabulation, level)+ message); |
378 | private final void logErrorMessage(String message, Throwable exception) { |
379 | String exceptionMessage = ExceptionUtils.getStackTrace(exception); |
380 | 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
Related
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