当事务属性为“必需”时,为什么CMT会在退出EJB方法时提交?

时间:2011-08-25 19:42:16

标签: java-ee transactions ejb java-ee-6 ejb-3.1

我一直发现我已经存在的事务已经在标记为@ejb.transaction type="Required"的EJB的任何方法中提交。这可能是正确的吗?

我的期望是,一个EJB“需要”一个事务意味着:如果已经存在一个事务,它将礼貌地保留未提交状态,以便调用begin()的人可以在调用{{1之前继续使用它进行进一步操作}或commit()。 [当然,如果首先没有事务,那么EJB方法将同时调用rollback()begin() / commit()。]

我的期望是错误的,还是我应该寻找配置错误?

可能有必要补充说我在EJB中使用了Hibernate 3。我在调用EJB方法之前获取了UserTransaction。 EJB生成的包装器在退出时调用rollback(),Hibernate挂钩并使用该机会关闭其Session。我得到的错误是一个Hibernate延迟加载异常,因为当我尝试访问Hibernate持久化对象上的getter时会话被关闭。所以从技术上来说,我并不是100%确定我观察到的ServerTransaction.commit()是否必须提交我开始的ServerTransaction.commit()(也许UserTransaction并不总是真正遵循“真正的”提交? ),但如果没有 - 那么Hibernate在什么基础上关闭会话?

更新: 我相信我的上述假设是正确的,但我的观察结果有些偏差。请参阅下面的自我提供的答案。

4 个答案:

答案 0 :(得分:20)

要求可以是邪恶的

我个人不喜欢REQUIRED事务属性,并强烈反对使用它。

懒惰地创建事务(这是必需的事务)导致不知道事务何时何地实际启动以及何时提交事务。这是一件好事。人们应明确设计交易边界。

MANDATORY和UserTransaction是灵魂伴侣

您希望使用UserTransaction非常好并且 使用CMT - 通过容器提供的UserTransaction启动的JTA事务或启动的JTA事务之间没有区别为您服务。

我建议的方法是将所有必需的用法切换为 MANDATORY 。使用MANDATORY,容器将为您启动交易。相反,它将通过确保无法调用它来保护您的bean,除非事务正在进行中。这是一个惊人的,未充分利用的功能。 MANDATORY是任何想要创建真正确定性的交易应用程序并加以强制执行的人的最好朋友。通过这种设置,您可能会爱上CMT。

在这种情况下UserTransactions启动交易然后容器就像你的大保镖将人踢到路边,除非他们在尝试调用你之前已经适当地启动了一个交易代码。

考虑

  • Servlet或BMT EJB可以使用UserTransaction。
  • CMT bean不能使用UserTransaction,但是他们可以参与UserTransaction启动的事务(如上所述,JTA事务是JTA事务)。
  • BMT bean无法参与现有交易。如果在事务正在进行时调用BMT bean,则在调用BMT bean之前,事务将被容器挂起,并在方法完成后恢复。
  • @Resource UserTransaction将通过注入
  • 为您提供用户交易
  • java:comp/UserTransaction将通过查询
  • 为您提供用户交易 在类级别使用的
  • @TransactionAttribute(MANDATORY)将影响该确切类的方法(即fooClass.getDecaredMethods()方法)。超类和子类的方法将默认为@TransactionAttribute(REQUIRED),除非这些类也明确注释@TransactionAttribute(MANDATORY)
  • 如果您厌倦了调用userTransaction.begin()userTransaction.commit()并执行相关的异常处理,请考虑@TransactionAttribute(REQUIRES_NEW)。您的交易边界仍将记录在案并明确。
  • 从CMT bean的方法抛出的
  • RuntimeException将导致事务被标记为回滚,即使您在调用代码中捕获并处理异常也是如此。使用@ApplicationException根据具体情况对自定义运行时异常类禁用此功能。

丢失您的交易上下文

有些事情可能导致正在进行的事务停止,暂停或以其他方式不传播到被调用的bean。

  • BMT bean停止事务传播。如果正在进行的事务调用BMT bean,那么该事务将在调用BMT bean之前暂停,并在bean返回后恢复。 BMT bean可以是交易的起源,但不能参与现有交易。如果传播神秘失败,请确保在事务中期没有无意中调用BMT bean。
  • 除了容器提供的UserTransaction或容器提供的方法(如SessionContext.setRollbackOnly)之外,不要使用任何其他方法来管理事务。使用“资源管理器特定的事务划分API”,例如JPA EntityTransactionjava.sql.Connection.commit() 规避事务管理。
  • 不要开始自己的线程。事务传播基于每个线程进行。 Java EE中有 no 标准API,支持跨多个线程的事务。如果您让线程从启动自己的线程或使用@Asynchronous,您将保留现有的事务。

答案 1 :(得分:1)

仔细检查显示出与上述建议不同的答案。我实际看到的是我启动的UserTransaction仍处于打开状态,但CMT在进入EJB方法时创建了一个新事务,尽管有“Required”属性。

我相信这种情况正在发生,因为我违反了规则。 :)使用CMT时,您不应该访问UserTransaction API。 CMT愉快地忽略了我的UserTransaction并开始了自己的事务,取而代之的是所有事务边界的仲裁者。由于它启动了事务,它也提交了它,当然也没有改变UserTransaction。

对我来说似乎很脆弱和​​愚蠢,也许是一种幼稚的观点,但在我阅读它们时似乎与“规则”一致。我不知道为什么CMT选择不与在更高级别开始的UserTransactions一起玩得很好。也许是为了迫使开发人员“做正确的J2EE事情”并创建另一个会话bean层来处理更广泛的事务上下文。这可以起作用,因为CMT将管理外部事务,因此可以很好地使用任何内部事务,我相信在这种情况下,内部EJB不会提交“伞形”事务; CMT会等到外部事务完成,然后提交整个事务。实际上,它必须。

我没有心情在这个已经臃肿的应用程序中创建更多的会话EJB,但它可能是唯一的解决方案,而不是在一堆我不想触摸的地方扯掉CMT。

答案 2 :(得分:1)

NClark,请考虑我在GlassFish 3.1.1上运行的以下代码。我希望它能以任何方式提供帮助: - )

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class ReusingTransaction {

   // It's BMT - we can't control Tx through context - must use...
   @Resource
   SessionContext sctx;

   // ... the UserTransaction instead.
   @Resource
   UserTransaction utx;

   // This CMT EJB will reuse BMT started transaction
   @EJB
   AnotherBean reuseTx;

   public void testMethod() throws Exception {
      // Begin Tx and check it's status - compare value with:
      // http://java.sun.com/javaee/6/docs/api/constant-values.html#javax.transaction.Status.STATUS_ACTIVE
      utx.begin();
      System.out.println("####testMethod#### Tx status: " + utx.getStatus());

      // Our BMT started a Tx - now invoke CMT and reuse this Tx
      // Notice: AnotherBean has MANDATORY Tx attribute, so if no Tx would
      // exist, the AnotherBean couldn't be even invoked.
      reuseTx.testIt();

      // Check if the CMT AnotherBean affected Tx we started. 
      System.out.println("####testMethod#### Tx status: " + utx.getStatus());

      // Just to prevent exceptions.
      utx.rollback();
   }

   // Implicitly CMT - must reuse the Tx
   @Stateless
   @TransactionAttribute(TransactionAttributeType.MANDATORY)
   public static class AnotherBean {

      // It's CMT, so Tx control is made through it's context.
      @Resource
      SessionContext sctx;

      // Can inject it, but cannot use it - will throw an Exception.
      @Resource
      UserTransaction utx;

      public void testIt() throws Exception {

         // Give a sign that rollback must be made.
         sctx.setRollbackOnly();
         System.out.println("####testIt#### Tx status: " + getTxStatus());
      }
   }

   // Small hack to get the status of current thread JTA Tx
   // http://java.sun.com/javaee/6/docs/api/javax/transaction/TransactionSynchronizationRegistry.html
   private static int getTxStatus() throws Exception {
      InitialContext ctx = new InitialContext();
      TransactionSynchronizationRegistry tsr = (TransactionSynchronizationRegistry)
                               ctx.lookup("java:comp/TransactionSynchronizationRegistry");

      return tsr.getTransactionStatus();
   }
}

可以使用@Startup从Singleton EJB调用此EJB,以立即查看AS的反应。

在Glassfish 3.1.1上,您将得到以下结果:

  

信息:#### testMethod #### Tx状态:0

     

INFO:#### testIt #### Tx状态:1

     

INFO:#### testMethod #### Tx状态:1

干杯!

答案 3 :(得分:0)

这就是CMT管理的交易的工作方式。容器在业务方法返回时自动提交事务。如果您不这样做,请使用BMT而不是CMT。