Most of us who work with Hibernate framework are pretty much familiar with this obnoxious LazyInitializationException. And you’ve probably spent a couple of hours debugging the Hibernate framework and digging to the root of the problem. In short, this exception occurs when we try to use an uninitialized lazy collection outside of a hibernate session. The easiest solution to this problem is to keep the session open for the length of request, which is definitely not the best way to go, as we don’t know how long this request will take.
It’s easy to reattach an uninitialized collection when you have control over the code that is trying to access the collection: just create a new session, then use lock() or update() to attach the object to the session, and then Hibernate.initialize(persistent collection).
But what if you don’t have access to the code that is making the call to the uninitialized collection (e.g. BeanMapper mapping a lazy collection outside of the session), or you want to separate the code for collection initialization from your main code?
One of the solutions I found to be the best for this type of problem is to use @Around Aspect to wrap the method returning the lazy collection with the active session and initialize the collection just before returning it from the method. Now let’s get to implementation details:
Here is the hibernate configuration for User domain that defines “friends” as a lazy collection:
View CodeXML | |
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.cinsay.domain.User" table="users" lazy="false"> ...... <property name="username" column="username" unique="true" type="string" length="255"/> <property name="password" column="password" type="string" length="255"/> <property name="email" column="email" type="string" length="255" unique="true"/> <property name="firstName" column="first_name" type="string" length="255"/> <property name="lastName" column="last_name" type="string" length="255"/> <property name="birthdayDate" column="birthday" type="date"/> <set name="friends" table="friends_relations" lazy="false" cascade="save-update,delete"> <cache usage="read-write"/> <key column="user_id"/> <element column="friend_id" type="string"/> </set> ....... </class> </hibernate-mapping> | |
To make the lazy collection automatically reattach to the session, in the domain class we simply annotate the method returning the collection with @Lazy annotation. This annotation will be used by AspectJ during compilation phase to find methods that need to be weaved.
Also we need to add transaction support for User.getFriends() method.
Since this method is not always managed by Spring transactions, we have added @Transactional annotation that will be used by AspectJ’s AnnotationTransactionAspect to weave the class. This aspect will wrap the hibernate session around the method.
View CodeJAVA | |
import org.aspectj.lang.annotation.*; import org.aspectj.lang.ProceedingJoinPoint; import org.hibernate.*; import org.hibernate.impl.SessionImpl; import org.hibernate.collection.AbstractPersistentCollection; public class User implements UserDetails, Serializable { @Lazy @Transactional(propagation = Propagation.REQUIRED) public Set getFriends() { return friends; } } | |
@Lazy annotation is very simple:
View CodeJAVA | |
public @interface Lazy { } | |
Aspect that will be used to weave methods that have @Lazy annotation:
View CodeJAVA | |
import org.aspectj.lang.annotation.*; import org.aspectj.lang.ProceedingJoinPoint; import org.hibernate.*; import org.hibernate.impl.SessionImpl; import org.hibernate.collection.AbstractPersistentCollection; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashSet; import java.io.Serializable; @Aspect public class ReattachSessionAspect { private static final Logger log = LoggerFactory.getLogger( ReattachSessionAspect.class ); private SessionFactory sessionFactory; private HashSet classes = new HashSet(); public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } @Around("methodsToBeReattached()") public Object reattachSession(ProceedingJoinPoint pjp) throws Throwable { Object obj = null; obj = pjp.proceed(); if (obj instanceof AbstractPersistentCollection) { AbstractPersistentCollection ps = (AbstractPersistentCollection) obj; if (!ps.wasInitialized() && ps.getSession() == null && !classes.contains(ps.getKey())) { log.info("reattaching Hibernate session to " + pjp.toString()); Session session = sessionFactory.getCurrentSession(); SessionImpl source = (SessionImpl) session; classes.add(ps.getKey()); try { source.update(pjp.getTarget()); Hibernate.initialize(ps); } catch (Exception ex) { log.error("Error in ReattachSessionAspect", ex); } finally { classes.remove(ps.getKey()); } } } return obj; } @Pointcut("execution(@com.ninem.commons.aspectj.Lazy * *(..))") public void methodsToBeReattached() { } } | |
The reason why I use !classes.contains(ps.getKey()) is to filter recursive calls to persistent collections when they’re being accessed from within Hibernate framework. Now, before we move to AspectJ weaving, let’s stop for a second and look at the following code:
View CodeJAVA | |
private SessionFactory sessionFactory; public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } | |
This setter is used by Spring framework to pass sessionFactory to aspect, in order for aspect to have access to currentSession. As I said before, the Hibernate session is created by AnnotationTransactionAspect (@Transaction).
To make ReattachSessionAspect manageable by Spring framework and eligible for beans injection we need to add the following bean configuration to applicationContext.xml:
View CodeXML | |
<bean id="reattachSessionAspect" class="com.cinsay.aspectj.ReattachSessionAspect" factory-method="aspectOf"> <property name="sessionFactory"> <ref local="sessionFactory"/> </property> </bean> | |
Also we need to make AnnotationTransactionAspect manageable by spring in order to be able to set transaction manager:
View CodeXML | |
<bean class="org.springframework.transaction.aspectj.AnnotationTransactionAspect" factory-method="aspectOf"> <property name="transactionManager"> <ref local="transactionManager"/> </property> </bean> | |
These beans are required because we are using AspectJ compile time weaving, rather than Spring AOP to weave classes with aspects. We could have used Spring AOP, but it requires defining interfaces for all methods that need to be weaved, and sometimes we just can’t provide interfaces for certain methods.
The last step is to weave User class with ReattachSessionAspect and AnnotationTransactionAspect aspects. The easiest way is to use aspectj-maven-plugin.
In the build section of your pom file just add aspectj-maven-plugin and it will automatically find existing aspects and weave classes which are in the scope of aspects’ pointcuts.
View CodeXML | |
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> <XnoInline>true</XnoInline> <aspectLibraries> <aspectLibrary> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> </aspectLibrary> </aspectLibraries> <showWeaveInfo>true</showWeaveInfo> </configuration> <executions> <execution> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> </executions> </plugin> | |
You can find more information on AspectJ weaving, here:
http://www.eclipse.org/aspectj/doc/released/devguide/ltw.html
The solution described above is pretty complex, but right now it's the easiest way I'm aware of. Hopefully the next version of Hibernate will include a better mechanism for LazyInitializationException handling.
If you have any suggestions on other ways to filter collections during recursive calls besides !classes.contains(ps.getKey()), please share your ideas!
Hi Konstantin,
This is a great post, which saved me a lot of time.
A few suggestions:
I’ve modified the @Around Aspect method to have a try {} catch () finally {} block in case there was a hibernate exception because we would miss to remove the target key from classes set (removal is now part of the finally block).
One more thing I changed is source.lock() – I used update, because we had issues with lock – “reattached object has a dirty collection” message.
Vanja,
Thanks for your suggestions.
try-catch-finally block is definitely a better way to go.
if you will have any other changes or suggestions for this solution, please, feel free to share them.
this seems to be the best solution i ever found
thx
but can the
annotation
@Transactional(propagation = Propagation.REQUIRED)
put above the @Around in the apectj class?
e.g.
@Transactional(propagation = Propagation.REQUIRED)
@Around(“methodsToBeReattached()”)
public Object reattachSession(ProceedingJoinPoint pjp) throws Throwable {
…
i tried on a app domain and orm settings are all Annotation
e.g
@Entity
@Table….
public class Test(){
@Id
@Column(…)
String id;
@OneToMany
…
..
List list;
@Lazy
public List getList(){
return list;
}
}
And the AOP pointcut do not work on this…
Do you have any suggestion thx
ros, I tried to add @Transactional to aspect but this didn’t work for me. I think there’s now way to weave one aspect with another. Even if you manage to do that, you will have to use only one propagation type for all of your lazy methods.
Are you getting any exception when you’re trying to use annotation configuration? Do you have transaction around lazy method?
First i found
i can use
Session session = sessionFactory.openSession() //session should be //close in finnaly
to replace
Session session = sessionFactory.getCurrentSession();
so no need to add @Transactional above the getter class
Second
I still cannot directly add @Lazy above the getter class to perform pointcut. I just use a service manager to do aop for testing
e.g
testManager.getList(test);
//////////////////////////
public class TestManager{
@Lazy
public List getList(Test test){
return test.getList();
}
}