Hibernate Deep Deproxy

A common problem faced with using ORMs that use lazy loading is that the objects returned by the ORM contain (obviously) lazy loading references, so that you need an ORM session to access those objects. For example, if you have a “Person” class, that contains a “mother” property, when you do “person.getMother()”, the ORM will get the mother from the database when it’s requested – not when the person is initialized.

Lazy loading is great, because it means you don’t load a huge amount of data when you really just want one object (say you just want the person’s name, with lazy loading, the person’s mother is never retrieved). However, when you want to do caching, lazy loading can be a serious problem.

For example, let’s say I have a method I call a lot – “personDao.findAll()”. I’d like to cache this entire method, so I don’t need to hit the database or the ORM at all, so I use something like an aspect to do declarative caching on that method. On the second and subsequent calls, the returned list of persons won’t have sessions attached (as they’re still attached to the first caller, which is long gone), so they can’t load their lazy references, and you end up with the famous LazyInitializationException. If you know the list of people isn’t too big, and that it doesn’t refer to too many other objects, you can removed the lazy proxies and load everything at once – then cache that result. But be careful – by doing deep deproxying, all objects that are refered to will be loaded, so if you’re not careful, you can load the entire database, which results is either a loss of performance (due to using all the memory) or an immediate error.

Here’s how I do deep deproxying with Hibernate. I’ve read about many techniques to do this, but this approach works for better than anything I’ve been able to find so far.

    public T deepDeproxy(final Object maybeProxy) throws ClassCastException {
    	if(maybeProxy==null) return null;
		T ret = deepDeproxy(maybeProxy,new HashSet<Object>());
    	return ret;
    }
 
    private T deepDeproxy(final Object maybeProxy,final HashSet<Object> visited) throws ClassCastException {
    	if(maybeProxy==null) return null;
        Class clazz;
        Hibernate.initialize(maybeProxy);
        if (maybeProxy instanceof HibernateProxy) {
                HibernateProxy proxy = (HibernateProxy) maybeProxy;
                LazyInitializer li = proxy.getHibernateLazyInitializer();
                clazz = li.getImplementation().getClass();
        }
        else {
                clazz = maybeProxy.getClass();
        }
        T ret = (T) deepDeproxy(maybeProxy,clazz);
        if(visited.contains(ret)) return ret;
        visited.add(ret);
        for (PropertyDescriptor property : PropertyUtils.getPropertyDescriptors(ret)) {
        	try{
	        	String name = property.getName();
        		if(!"owner".equals(name) &&  property.getWriteMethod()!=null){
        			Object value = PropertyUtils.getProperty(ret, name);
    				boolean needToSetProperty=false;
        			if (value instanceof HibernateProxy) {
    					value = deepDeproxy(value,visited);
    					needToSetProperty=true;
        			}
        			if(value instanceof Object[]){
        				Object[] valueArray = (Object[]) value;
        				Object[] result = (Object[]) Array.newInstance(value.getClass(), valueArray.length);
        				for(int i=0;i<valueArray.length;i++){
        					result[i]=deepDeproxy(valueArray[i],visited);
        				}
        				value=result;
        				needToSetProperty=true;
        			}
        			if(value instanceof Set){
        				Set valueSet = (Set) value;
        				Set result = new HashSet();
        				for(Object o : valueSet){
        					result.add(deepDeproxy(o,visited));
        				}
        				value=result;
    					needToSetProperty=true;
        			}
        			if(value instanceof Map){
        				Map valueMap = (Map) value;
        				Map result = new HashMap();
        				for(Object o : valueMap.keySet()){
        					result.put(deepDeproxy(o, visited),deepDeproxy(valueMap.get(o),visited));
        				}
        				value=result;
    					needToSetProperty=true;
        			}
        			if(value instanceof List){
        				List valueList = (List) value;
        				List result = new ArrayList(valueList.size());
        				for(Object o : valueList){
        					result.add(deepDeproxy(o,visited));
        				}
        				value=result;
    					needToSetProperty=true;
        			}
					if(needToSetProperty) PropertyUtils.setProperty(ret, name, value);
        		}
        	}catch (java.lang.IllegalAccessException e){
				e.printStackTrace();
        	} catch (InvocationTargetException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			}
        }
        return ret;
    }
 
    private <T> T deepDeproxy(Object maybeProxy, Class<T> baseClass) throws ClassCastException {
    	if(maybeProxy==null) return null;
        if (maybeProxy instanceof HibernateProxy){
        	return baseClass.cast(((HibernateProxy) maybeProxy).getHibernateLazyInitializer().getImplementation());
        }else{
           return baseClass.cast(maybeProxy);
        }
     }

CC BY-SA 4.0 Hibernate Deep Deproxy by Craig Andrews is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

3 thoughts on “Hibernate Deep Deproxy

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.