为什么使用 @Transactional(propagation = Propagation.SUPPORTS, readOnly = true) 可以解决 Hibernate 懒加载异常问题?

18

我将正在开发的应用程序从使用AspectJ Load Time Weaving切换到使用Spring CGlib代理,然后,在以前没有抛出任何异常的情况下,代码的许多部分开始出现Hibernate延迟加载异常。

我已经通过在以前没有事务属性但调用Spring仓库从数据库读取数据的一些公共方法上添加@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)来解决这些延迟加载异常。

有人知道为什么添加@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)会消除Hibernate延迟加载异常,以及为什么这些注释在AspectJ Load Time Weaving中不需要但在没有AspectJ时需要吗?

更新2 我相信删除AspectJ并不是问题所在,问题在于我并没有真正理解SUPPORTS传播的实际行为。特别是SUPPORTS如何与JPA EntityManager交互,因此我删除了一堆SUPPORTS传播,这导致了延迟加载异常。阅读Spring事务管理器的源代码后,所有问题都变得清晰明了。关键思想是,Spring文档并没有很好地指出@Transactional注释是作为同步点使用的,将EntityManager的生命周期与事务性方法的开始和结束联系起来。也强烈推荐查看http://www.ibm.com/developerworks/java/library/j-ts1/中的这一系列文章和此博客文章

更新1

这不是私有@Transactional方法调用未通过AOP代理的情况。这些问题发生在从其他服务调用的公共方法中。

以下是代码结构示例,我在其中看到问题出现。

@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);
   }
}
2个回答

10

我假设您的代码没有使用OpenSessionInViewFilter或类似的内容。

如果没有@Transactional注解,离开BarRepository.findSomeEntity()方法后,Hibernate会话将关闭。

当调用@Transactional方法且TransactionalInterceptor正确绑定到方法(通过cglib代理或Spring上下文中的任何其他AOP配置)时,则Spring在整个被注释的方法期间保持Session处于打开状态,从而防止任何延迟加载异常。

如果将org.springframework.transactionorg.springframework.orm.hibernate3(或hibernate4如果您使用Hibernate 4)日志记录级别提高到DEBUG,特别是HibernateTransactionManager类和org.springframework.transaction.support.AbstractPlatformTransactionManager,您应该能够看到Spring决定何时需要打开和关闭Hibernate Session的代码流程的确切位置。日志还应显示为什么在每个点打开/关闭会话或事务。


通过打开调试并编写一些测试程序以及阅读Spring源代码,我澄清了我对Spring事务行为的所有误解,特别是SUPPORTS传播的行为,在主要的Spring文档中没有很好地记录。 - ams
@ams 只是好奇,你学到了 SUPPORTS 的实际行为是什么,它与你最初的印象有何不同? - matt b
2
我针对这个问题写了一个相当不错的总结 https://dev59.com/SWPVa4cB1Zd3GeqP9MwJ#16557225 - ams

8
我不完全确定为什么会发生这种情况,但我的理论是以下。
当您从AspectJ编织转移到CGLIB代理时,放置在从同一对象调用的方法上的@Transactional注释将停止生效。这意味着这些方法内部的代码将以非事务方式执行(除非您在调用堆栈中有另一个@Transactional方法,其中@Transactional真正生效)。 Propagation.SUPPORTS的Javadoc说:
引用: 请注意:对于具有事务同步的事务管理器,PROPAGATION_SUPPORTS与根本没有事务略有不同,因为它定义了同步将应用于的事务范围。因此,相同的资源(JDBC连接、Hibernate会话等)将共享整个指定范围。请注意,这取决于事务管理器的实际同步配置。

因此,当您的代码在非事务性环境下执行时,用于加载对象的Hibernate Session将无法用于后续懒加载属性的初始化。当您在代码堆栈中的顶层方法上注释@Transactional(propagation = Propagation.SUPPORTS)时,Hibernate Session 将可用,直到您离开该方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接