间接的Hibernate/JPA方法调用会导致事务丢失。

3

我正在使用Spring/JPA2/hibernate编写以下代码:

class A {
  @Autowired
  B b;

  @RequestMapping("/test")
  public void test(final HttpServletRequest r1, HttpServletResponse r2) throws ... {

    b.inner();   // Works

    b.outer();   // javax.persistence.TransactionRequiredException: 
                 // no transaction is in progress ... :|
}

@Component
class B {
   @PersistenceContext
   EntityManager em;

   public void outer() { inner(); }

   @Transactional
   public void inner() { em.flush(); }
}

为什么直接调用 inner() 会导致事务丢失?
2个回答

5

http://static.springsource.org/spring/docs/current/reference/transaction.html#transaction-declarative-annotations

在代理模式下(默认情况下),只有通过代理进入的外部方法调用会被拦截。这意味着自我调用,实际上是目标对象中的一个方法调用另一个目标对象的方法,即使调用的方法标记为@Transactional,在运行时也不会导致实际事务发生。
如果您希望自我调用也被包装在事务中,请考虑使用AspectJ模式(请参见下表中的mode属性)。在这种情况下,首先不会有代理;相反,目标类将被编织(即,其字节码将被修改),以便在任何类型的方法上将@Transactional转换为运行时行为。
@Autowired引用B b(在类A内部)被Spring AOP事务感知代理包装。
当调用b.inner()时,您正在事务感知实例上调用它,并将其标记为@Transactional。因此,启动了由Spring管理的事务。
当调用b.outer()时,它也位于事务感知实例上,但它不是@Transactional。因此,不会启动由Spring管理的事务。
一旦你在outer()的调用中,对inner()的调用不会通过事务感知代理进行,而是直接被调用。这与this.inner()相同。由于你是直接调用它,而不是通过代理,所以它没有Spring事务感知语义。
由于没有启动任何事务,因此会导致TransactionRequiredException
可能的解决方案包括使方法outer() @Transactional
   @Transactional
   public void outer() { inner(); }

或者将整个类标记为@Transactional

@Transactional   
@Component
class B {
   @PersistenceContext
   EntityManager em;

我的目标是创建一个后台程序,每次提交一小部分。因此,将outer()注释为单个事务并不是我的意图。我现在正在更改代理模式以使用aspect-j模式。 - Cojones
如果 B.outer() 中的逻辑需要在 B.inner() 之前发生,但不涉及事务/持久性,则它可能不属于您的持久性层类(DAL/DAO 等)。考虑重构设计以分离关注点,可能通过使用中间对象来实现。Object intermediate = b.outer(); b.inner(intermediate);。然后进行重构,使其不再是持久性层类的成员。Object intermediate = someOtherUtilityOrSupportClassInstance.outer(); b.inner(intermediate); - Dan
我现在已经切换到LTW了,但是现在在任何调用中都没有事务:( 所有配置都添加了context:annotation-config/, <tx:annotation-driven mode="aspectj".., <context:load-time-weaver aspectj-weaving="on"以及TomcatInstrumentableClassLoader等。你确定这是可能的吗?这篇文章认为JPA/Hibernate/LTW不可行: https://dev59.com/VUzSa4cB1Zd3GeqPqNtJ “不应该使用load-time weaver与Hibernate,这是针对Toplink的”或者我需要编译时织入? - Cojones
我个人从未使用过LTW(由于限制性安全要求和使用Java代理/仪表),因此需要其他人参与,或者您应该提出一个新问题。从派生的示例中仍然不清楚为什么您的设计不应该使用代理/拦截器方法;只要适当地重构您的事务范围,或删除嵌套的事务代码,这看起来(至少从示例中)是代码异味。 - Dan

0
事务性上下文持续整个 Spring Bean 生命周期。@Transactional 注释具有整个组件的作用域,您应该将 @Component 注释为 @Transactional。
@Transactional
@Component
class B {
    @PersistenceContext 
    EntityManager em;

    public inner() { }
    public outer() { }
}

方法“inner”和“outer”应实现各自的工作单元。如果您需要一些辅助函数或其他内容,那很好,但需要事务性边界的工作单元应限定在每个方法范围内。请参阅Spring文档中的@Transactional http://static.springsource.org/spring/docs/3.0.x/reference/transaction.html

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