Monday, December 1, 2008

Hibernate Lazy Entities Cleaner

For those who have problems with lazy loaded entities, you can use the following (it is tested and used by many)

The cleaning method will traverse all objects in the hierarchy and remove lazy entities until it finds an object of instance BaseBean. For better performance let all your beans implement a dummy interface called BaseBean.

--------------------------------------------------------------------------------------------------

/*
* Copyright 2008, Maher Kilani
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.hibernate.proxy.HibernateProxy;


/**
* This class is implemented to solve the hibernate lazy loading problem with
* flex which doesnt support lazy loading in the meantime.
*
* @author Maher Kilani
*
*/
public class HibernateProxyCleaner
{

private static Logger log = Logger.getLogger(HibernateProxyCleaner.class);

/**
* This function would take as a prameter any kind of object and recursively
* access all of its member and clean it from any uninitialized variables.
* The function will stop the recursion if the member variable is not of type
* baseBean (defined in the application) and if not of type collection
*
* @param listObj
* @throws ClassNotFoundException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws InstantiationException
*/
@SuppressWarnings("unchecked")
public static void cleanObject(Object listObj, HashSet visitedBeansSet) throws IllegalArgumentException, IllegalAccessException,
ClassNotFoundException, InstantiationException, InvocationTargetException
{
if(visitedBeansSet == null) visitedBeansSet = new HashSet();
if(listObj == null) return;

// to handle the case of abnormal return consisting of array Object
// case if hybrid bean
if(listObj instanceof Object[])
{
Object[] objArray = (Object[]) listObj;
for(int z = 0; z < objArray.length; z++)
{
cleanObject(objArray[z], visitedBeansSet);
}
}
else
{

Iterator itOn = null;

if(listObj instanceof List)
{
itOn = ((List) listObj).iterator();
}
else
if(listObj instanceof Set)
{
itOn = ((Set) listObj).iterator();
}
else
if(listObj instanceof Map)
{
itOn = ((Map) listObj).values().iterator();
}

if(itOn != null)
{
while(itOn.hasNext())
{
cleanObject(itOn.next(), visitedBeansSet);
}
}
else
{
if(!visitedBeansSet.contains(listObj))
{
visitedBeansSet.add(listObj);
processBean(listObj, visitedBeansSet);
}

}
}
}

/**
* Remove the un-initialized proxies from the given object
*
* @param objBean
* @throws Exception
* @throws IllegalAccessException
* @throws ClassNotFoundException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws InstantiationException
*/
@SuppressWarnings("unchecked")
private static void processBean(Object objBean, HashSet visitedBeans) throws IllegalAccessException, IllegalArgumentException,
ClassNotFoundException, InstantiationException, InvocationTargetException
{
Class tmpClass = objBean.getClass();
Field[] classFields = null;
while(tmpClass != null && tmpClass != BaseBean.class && tmpClass != Object.class)
{
classFields = tmpClass.getDeclaredFields();
cleanFields(objBean, classFields, visitedBeans);
tmpClass = tmpClass.getSuperclass();
}
}

@SuppressWarnings("unchecked")
private static void cleanFields(Object objBean, Field[] classFields, HashSet visitedBeans) throws ClassNotFoundException,
IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException
{
boolean accessModifierFlag = false;
for(int z = 0; z < classFields.length; z++)
{
Field field = classFields[z];
accessModifierFlag = false;
if(!field.isAccessible())
{
field.setAccessible(true);
accessModifierFlag = true;
}

Object fieldValue = field.get(objBean);

if(fieldValue instanceof HibernateProxy)
{
String className = ((HibernateProxy) fieldValue).getHibernateLazyInitializer().getEntityName();
Class clazz = Class.forName(className);
Class[] constArgs = {Integer.class };
Constructor construct = null;
BaseBean baseBean = null;

try
{
construct = clazz.getConstructor(constArgs);
}
catch(NoSuchMethodException e)
{
log.info("No such method for base bean " + className);
}

if(construct != null)
{
baseBean = (BaseBean) construct.newInstance((Integer) ((HibernateProxy) fieldValue).getHibernateLazyInitializer().getIdentifier());
}
field.set(objBean, baseBean);
}
else
{
if(fieldValue instanceof org.hibernate.collection.PersistentCollection)
{
// checking if it is a set, list, or bag (simply if it is a
// collection)
if(!((org.hibernate.collection.PersistentCollection) fieldValue).wasInitialized())
field.set(objBean, null);
else
{
cleanObject((fieldValue), visitedBeans);
}

}
else
{
if(fieldValue instanceof BaseBean || fieldValue instanceof Collection) cleanObject(fieldValue, visitedBeans);
}
}
if(accessModifierFlag) field.setAccessible(false);
}
}
}


--------------------------------------------------------------------------------------------------

10 comments:

  1. Thanks, Maher -- this is working extremely well for me in a Flex project. It handles half the round-trip, e.g. creating an (almost) Domain Object for FLEX without proxies. Still have to disassemble the modified object that FLEX sends back to the Java service layer, but that can be done.

    ReplyDelete
  2. Run into one issue I'd love you to comment on ... the HibernateCleaner only cleans up to the immediate superclass.

    How do you think we might recurse up the hierarchy until we run out of superclasses implementing the marker interface>

    ReplyDelete
  3. Hi Robert, I have updated the code. You can notice now that there is a processbean method that will go through the superclasses and clean all fields. Tell me what you think.

    ReplyDelete
  4. Looks good. I tried something similar (below) but like yours better.

    I'd tried something similar along the lines of:

    @SuppressWarnings("unchecked")
    private static void processBean(Object objBean, HashSet visitedBeans)
    throws IllegalAccessException, IllegalArgumentException,
    ClassNotFoundException, InstantiationException,
    InvocationTargetException {
    Field[] classFields = objBean.getClass().getDeclaredFields();
    cleanFields(objBean, classFields, visitedBeans);
    processClassHierarchy(objBean, objBean.getClass(), visitedBeans);
    }

    /*
    * Walk up the class hierarchy of the objBean, retrieving inherited fields
    * as you go and processing them against the objBean
    */
    private static void processClassHierarchy(Object objBean, Class clazz,
    HashSet visitedBeans) throws ClassNotFoundException,
    IllegalAccessException, InstantiationException,
    InvocationTargetException {
    Field[] classFields;
    if (clazz.getSuperclass() != null
    && clazz.getSuperclass().getDeclaredFields().length > 0) {
    classFields = clazz.getSuperclass()
    .getDeclaredFields();
    cleanFields(objBean, classFields, visitedBeans);
    processClassHierarchy(objBean, clazz.getSuperclass(), visitedBeans);
    }
    }

    Thanks!

    ReplyDelete
  5. where do you put this thing??

    ReplyDelete
  6. you can use an interceptor to call it whenever you are sending back the data to the presentation layer.

    ReplyDelete
  7. Thank you so much for this simple and straight forward solution! I have been wracking my brain for the past few days trying to figure out an elegant solution to this issue. Mnay thanks a million times over!!!

    ReplyDelete
  8. Hi,The option may not be able to provide all of the usually necessary website optimization techniques a professional website designer is obligated to understand and implement for Web Design Cochin, but the savvy novice, with help, may be able to simplify optimization and allow it to be work until business growth allows the hiring of a professional website designer.Thanks......

    ReplyDelete
  9. nice job! thanks!

    ReplyDelete