I switched an app I am working on from using AspectJ load time weaving to using Spring CGlib proxies and right after I did that there were many parts of the code where I started to get hibernate lazy loading exceptions where in the past there were no exceptions being thrown.
I have been able to resolve these lazy loading exceptions by adding @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
to bunch of previously public methods that did not have any transactional attributes on them but called spring repositories to read data from the database.
Anyone know why adding @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
eliminates the hibernate lazy loading exceptions and why these annotations were not required with AspectJ load time weaving but are are required with out?
Update 2 I believe that removing AspectJ was NOT the issue, but the issue was was that I did not really understand the actual behavior of the SUPPORTS propagation. In particular how SUPPORTS interacted with the JPA EntityManager and so I removed a bunch a SUPPORTS propagation which caused the lazy loading exceptions. After reading through the source code for the Spring Transaction Manager It all became clear as to what to do. The key idea that the spring documentation does not really point out very well is that @Transactional annotations are used as synchronization points that tie life cycle of an EntityManager to the start and end of a transactional method. Also highly recommend this series of articles at http://www.ibm.com/developerworks/java/library/j-ts1/ and this blog post http://doanduyhai.wordpress.com/2011/11/21/spring-persistencecontext-explained/
Update 1
This is not a case of calls to private @Transactional methods not going through the AOP proxy. These issues are happening with public methods that are being called from other services.
Here is an example of the code structure, where I see the problem occurring.
@Service
public class FooService
{
@Autowired
private BarService barService;
public void someMethodThatOnlyReads() {
SomeResult result = this.barService.anotherMethodThatOnlyReads()
// the following line blows up with a HibernateLazyLoadingEcxeption
// unless there is a @Transactional supports annotation on this method
result.getEntity().followSomeRelationship();
}
}
@Service
public class BarService
{
@Autowired
private BarRepository barRepo;
public SomeResult anotherMethodThatOnlyReads()
{
SomeEntity entity = this.barRepo.findSomeEntity(1123);
SomeResult result = new SomeResult();
result.setEntity(entity);
return result;
}
}
@Repository
public class BarRepository
{
@PersistenceContext
private EntityManager em;
public SomeEntity findSomeEntity(id Integer)
{
em.find(SomeEntity.class,id);
}
}
I assume your code is not using OpenSessionInViewFilter
or anything similar.
Without the @Transactional
annotation, the Hibernate session is closed after leaving the BarRepository.findSomeEntity()
method.
When a @Transactional
method is called and the TransactionalInterceptor
is properly bound to the method (through the cglib proxy or whatever other AOP configuration you have in the Spring context), then the Session is held open by Spring for the entirety of the annotated method, thus preventing any lazy loading exceptions.
If you turn up the logging to DEBUG
on the org.springframework.transaction
and org.springframework.orm.hibernate3
(or hibernate4
if you are on Hibernate 4) loggers, particularly the HibernateTransactionManager
class and org.springframework.transaction.support.AbstractPlatformTransactionManager
, you should see exactly at which points in the code flow Spring is deciding it needs to open and close the Hibernate Session. The logs should also show why a session or transaction is opened/closed at each point.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With