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
    1chapter.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
    1chapter.getBook()

    will produce an error like:

    109:21:26 ERROR com.ho.controller.MyController handleException handleReadDetails error
    2java.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:

01public class ORMUtils {
02    public static <T> T initializeAndUnproxy(T entity) {
03        if (entity == null) {
04            return null;
05        }
06 
07        if (entity instanceof HibernateProxy) {
08            Hibernate.initialize(entity);
09            entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
10        }
11        return entity;
12    }
13}

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:

01public class Chapter extends AbstractPersistentObject {
02     
03    // ------------------------------------------- ATTRIBUTS
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;
10 
11    // ------------------------------------ GET/SET TERS
12    public void setBook(Book book) {
13        this.book = book;
14    }
15     
16    public Book getBook() {
17        book = ORMUtils.initializeAndUnproxy(book);
18         
19        return book;
20    }
21...
22}

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:

1public final PojoType lazyLoad(final PojoType source, String[] levelsToLoad){
2....
3}

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:

1String[] levelsToLoad = new String[] {"language", "chapters/book", "authors"};
2// This bean is attached to Hibernate Session
3Book hibernateAttachedBean = new Book();
4...
5Book 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{"*&#47*"}); :

Here, the full code of the ‘homemade’ solution ORMLazyLoader:

001package com.ho.orm.loader.util;
002 
003import java.beans.PropertyDescriptor;
004import java.lang.reflect.InvocationTargetException;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.HashSet;
008 
009import org.apache.commons.beanutils.PropertyUtils;
010import org.apache.commons.lang.StringUtils;
011import org.apache.commons.lang.exception.ExceptionUtils;
012import org.hibernate.collection.PersistentList;
013import org.hibernate.collection.PersistentSet;
014 
015import com.ho.orm.introspector.pojo.util.AbstractPersistentObject;
016import com.ho.orm.util.ORMUtils;
017 
018/**
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:
021 *      <code>
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/>
028 *          [...]<br/>
029 *          Book hibernateDetachedBean = (new ORMLazyLoader<Book>()).lazyLoad(hibernateAttachedBean, levelsToLoad);<br/>
030 *      </code>
031 * </p>
032 *
033 * @author Huseyin Ozveren
034 */
035public class ORMLazyLoader<PojoType> {
036    // --------------------------------------------------------------------- ATTRIBUTES
037    // --------------------------------------------------------------- PUBLIC METHODS
038    /**
039     * <p>
040     *      Syntax:
041     *      <ul>
042     *          <li>
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.
049     *          </li>
050     *          <li>
051     *              (new ORMLazyLoader<Book>()).lazyLoad(bean, new String[] {}); :
052     *              load all primitive attributes of the first level.
053     *          </li>
054     *          <li>
055     *              (new ORMLazyLoader<Book>()).lazyLoad(bean, new String[] {"*"}); :
056     *              load all attributes (primitive, collection, hibernate types) of the first level.
057     *          </li>
058     *          <li>
059     *              (new ORMLazyLoader<Book>()).lazyLoad(bean, new String{"*&#47*"}); :
060     *              load all attributes (primitive, collection, hibernate types) of the first and second levels.
061     *          </li>
062     *      </ul>
063     * </p>
064     */
065    @SuppressWarnings("unchecked")
066    public final PojoType lazyLoad(final PojoType source, String[] levelsToLoad) {
067        return (PojoType) deepLazyLoad(source, levelsToLoad, 0);
068    }
069 
070     
071    // ------------------------------------------------------------------ PRIVATE METHODS
072    /**
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/>
076     *      "chapters",<br/>
077     *  returns the sub-levels of the current property:<br/>
078     *      new String[]{"paragraphs", "book/language"},<br/>
079     */
080    private final String[] getRemainingLevelsToLoad(String[] levelsToLoad, String currProperty) {
081 
082        ArrayList<String> output = new ArrayList<String>();
083        if (null != levelsToLoad){
084            for (String currLevel : levelsToLoad) {
085                if (null != currLevel) {
086                    // Check if the currProperty is in the levels to load
087                    int indexOfSlash = currLevel.indexOf(currProperty+"/");
088                    if (-1 != indexOfSlash) {
089                        // Get the sub-levels
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);
095                    }
096                } // end-if
097            } // end-if
098        } // end-if
099         
100        return (String[]) output.toArray((new String[output.size()]));
101    }
102     
103    /**
104     *  Returns true if the property named propertyName must be loaded.
105     */
106    private final boolean isLevelLoadNecessary(String propertyName, String[] levelsToLoad) {
107 
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)) {
113                        return true;
114                    }
115                }
116            } // end-for
117        }
118         
119        return false;
120    }
121     
122    /**
123     * Load a lazy property with a type of hibernate object
124     *
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
130     */
131    private final void lazyLoadHibernateObject(
132                            String currProperty,
133                            String[] askedLevelsToLoad,
134                            Object sourceBean,
135                            Integer currLevel,
136                            String[] remainingLevelsToLoad
137                        ) {
138        try {
139            // Check if the current hibernate property must be loaded according to the asked properties
140            if (isLevelLoadNecessary(currProperty, askedLevelsToLoad)) {
141 
142                // Get the object to detach from the source bean
143                Object propertyToLoad = PropertyUtils.getProperty(sourceBean, currProperty);
144                if (null != propertyToLoad) {
145                    // Load recursively the Hibernate Object
146                    Object loadedObject = deepLazyLoad (propertyToLoad, remainingLevelsToLoad, currLevel+1);
147                     
148                    // Set the property into the result bean
149                    PropertyUtils.setProperty(sourceBean, currProperty, loadedObject);
150 
151                }//end-if
152                 
153            } // end-if
154         
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);
161        } // end-try
162    }
163     
164    /**
165     * Load a lazy property with a type of collection object
166     
167     * @param propertyToLoad
168     * @param currLevel
169     * @param remainingLevelsToLoad
170     * @return
171     */
172    @SuppressWarnings("unchecked")
173    private final Collection lazyLoadCollection(Collection propertyToLoad, Integer currLevel, String[] remainingLevelsToLoad ) {
174 
175        Collection output = null;
176        try {
177            if (null != propertyToLoad) {
178                // Instance of Set
179                if (propertyToLoad instanceof PersistentSet) {
180                    output = new HashSet();
181                 
182                // Instance of List
183                }else  if (propertyToLoad instanceof PersistentList){
184                    output = new ArrayList();
185                 
186                // Other
187                } else {
188                    // Initialization needed in order to not clone the Proxy class of Spring/Hibernate Javassist
189                    propertyToLoad = ORMUtils.initializeAndUnproxy(propertyToLoad);
190                    // Create a new EMPTY instance of the source bean
191                    output = propertyToLoad.getClass().newInstance();
192                } // end-if
193                 
194                 
195                // Initialization needed in order to load the current object
196                propertyToLoad = ORMUtils.initializeAndUnproxy(propertyToLoad);
197                 
198                for (Object currCollectionBean : propertyToLoad) {
199                    logMessage("recursive 'deepLazyLoad' loading of the elements of collection Object " , currLevel+1);
200                     
201                    // Load recursively the elements of collection Object
202                    Object loadedObject = deepLazyLoad (currCollectionBean,  remainingLevelsToLoad, currLevel+1);
203                     
204                    output.add(loadedObject);
205                } // end-for
206            } //end-for
207             
208        } catch (Throwable e) {
209            logErrorMessage("propertyToLoad= " + propertyToLoad , e);
210        } // end-try
211         
212        return output;
213    }
214 
215    /**
216     * Get the attribute with a type of collection and load it.
217     *
218     * @param currProperty
219     * @param askedLevelsToLoad
220     * @param sourceBean
221     * @param currLevel
222     * @param remainingLevelsToLoad
223     */
224    @SuppressWarnings("unchecked")
225    private final void lazyLoadCollectionAttribute(
226                        String currProperty,
227                        String[] askedLevelsToLoad,
228                        Object sourceBean,
229                        Integer currLevel,
230                        String[] remainingLevelsToLoad
231                        ) {
232        try {
233            // Check if the current hibernate property must be loaded according to the asked properties
234            if (isLevelLoadNecessary(currProperty, askedLevelsToLoad)) {
235                logMessage("loop on: " + currProperty, currLevel);
236 
237                // Collection
238                Collection propertyToLoad = (Collection) PropertyUtils.getProperty(sourceBean, currProperty);
239 
240                // Load collection
241                Collection loadedProperty = lazyLoadCollection(propertyToLoad, currLevel, remainingLevelsToLoad);
242                 
243                // Set the collection property into the result object
244                PropertyUtils.setProperty(sourceBean, currProperty, loadedProperty);
245                 
246            } // end-if
247             
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);
254        } // end-try
255    }
256     
257    /**
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.
261     *
262     * @param propertyClass
263     * @return
264     */
265    @SuppressWarnings("unchecked")
266    private final boolean isHibernateObject(Class propertyClass) {
267        return AbstractPersistentObject.class.isAssignableFrom(propertyClass);
268    }
269     
270    /**
271     * Return true if the <code>Class</code> parameter is a 'Collection'.
272     * @param propertyClass
273     * @return
274     */
275    @SuppressWarnings("unchecked")
276    private final boolean isCollection(Class propertyClass) {
277        return Collection.class.isAssignableFrom(propertyClass);
278    }
279     
280    /**
281     * Returns the names of the bean properties for which there is a get / set pair.
282     */
283    private final String[] describe(Object bean) {
284        ArrayList<String> output = new ArrayList<String>();
285         
286        PropertyDescriptor[] properties = PropertyUtils.getPropertyDescriptors(bean); // Properties
287             
288        for (PropertyDescriptor currPropertyDescriptor : properties) {
289            String currProperty = currPropertyDescriptor.getName();
290            if (PropertyUtils.isWriteable(bean, currProperty) && PropertyUtils.isReadable(bean, currProperty)) {
291                output.add(currProperty);
292            } // end-if
293        } //end-for
294             
295             
296        return output.toArray(new String[output.size()]);
297    }
298     
299    /**
300     * This method allows:
301     * - the recursive 'deepLazyLoad' loading of hibernate object;
302     * - recursive 'deepLazyLoad' loading of the elements of collection Object;
303     *
304     * @param sourceBean:  original source bean to load
305     * @param askedLevelsToLoad: the levels asked to load
306     * @param currLevel: current level
307     * @return
308     */
309    @SuppressWarnings("unchecked")
310    private final Object deepLazyLoad (Object sourceBean,  String[] askedLevelsToLoad, Integer currLevel) {
311         
312        try {
313            if(sourceBean==null){
314                return null;
315            }
316             
317            // Collection of objects
318            if (sourceBean instanceof Collection) {
319                lazyLoadCollection((Collection) sourceBean, currLevel, askedLevelsToLoad);
320 
321            } else{
322                // Initialization needed in order to load the current object
323                sourceBean = ORMUtils.initializeAndUnproxy(sourceBean);
324                 
325                // Properties of this object
326                String[] properties = describe(sourceBean); // PropertyUtils.describe(bean);
327 
328                // Range all properties
329                for (String currProperty : properties) {
330 
331                    // If it is a Writeable property
332                    if (PropertyUtils.isWriteable(sourceBean, currProperty)) {
333                        // Class of current property
334                        Class propertyClass = PropertyUtils.getPropertyType(sourceBean, currProperty);
335 
336                        // Levels to load
337                        String[] remainingLevelsToLoad = getRemainingLevelsToLoad(askedLevelsToLoad, currProperty);
338                         
339                        // Hibernate Object 'AbstractPersistentObject'
340                        if (isHibernateObject(propertyClass)) {
341                            lazyLoadHibernateObject(currProperty, askedLevelsToLoad, sourceBean, currLevel, remainingLevelsToLoad);
342                        // Collection property
343                        } else if (isCollection(propertyClass)){
344                            lazyLoadCollectionAttribute(currProperty, askedLevelsToLoad, sourceBean, currLevel, remainingLevelsToLoad);
345                        } // end-if
346                             
347                    } // end-if
348                } // end-for
349                 
350            } // end-if
351             
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);
358        } //end-try
359         
360        return sourceBean;
361    }
362     
363    /**
364     * Log message
365     * @param message
366     * @param level
367     */
368    private final void logMessage(String message, Integer level) {
369        final String tabulation = "\t";
370        System.out.println(StringUtils.repeat(tabulation, level)+ message);
371    }
372     
373    /**
374     * Log error message
375     * @param message
376     * @param level
377     */
378    private final void logErrorMessage(String message, Throwable exception) {
379        String exceptionMessage = ExceptionUtils.getStackTrace(exception);
380        logMessage(message+exceptionMessage, 1);
381    }
382 
383}

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 to Smartphone Android 4.0 ICS 3G pas cher Cancel reply

Your email address will not be published.

Time limit is exhausted. Please reload CAPTCHA.

Related Post