我一直发现我已经存在的事务已经在标记为@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在什么基础上关闭会话?
更新: 我相信我的上述假设是正确的,但我的观察结果有些偏差。请参阅下面的自我提供的答案。
答案 0 :(得分:20)
我个人不喜欢REQUIRED事务属性,并强烈反对使用它。
懒惰地创建事务(这是必需的事务)导致不知道事务何时何地实际启动以及何时提交事务。这不是一件好事。人们应明确设计交易边界。
您希望使用UserTransaction
非常好并且 使用CMT - 通过容器提供的UserTransaction启动的JTA事务或启动的JTA事务之间没有区别为您服务。
我建议的方法是将所有必需的用法切换为 MANDATORY 。使用MANDATORY,容器将不为您启动交易。相反,它将通过确保无法调用它来保护您的bean,除非事务正在进行中。这是一个惊人的,未充分利用的功能。 MANDATORY是任何想要创建真正确定性的交易应用程序并加以强制执行的人的最好朋友。通过这种设置,您可能会爱上CMT。
在这种情况下你用UserTransactions
启动交易然后容器就像你的大保镖将人踢到路边,除非他们在尝试调用你之前已经适当地启动了一个交易代码。
@Resource UserTransaction
将通过注入java:comp/UserTransaction
将通过查询@TransactionAttribute(MANDATORY)
将影响该确切类的方法(即fooClass.getDecaredMethods()
方法)。超类和子类的方法将默认为@TransactionAttribute(REQUIRED)
,除非这些类也明确注释@TransactionAttribute(MANDATORY)
userTransaction.begin()
和userTransaction.commit()
并执行相关的异常处理,请考虑@TransactionAttribute(REQUIRES_NEW)
。您的交易边界仍将记录在案并明确。RuntimeException
将导致事务被标记为回滚,即使您在调用代码中捕获并处理异常也是如此。使用@ApplicationException
根据具体情况对自定义运行时异常类禁用此功能。有些事情可能导致正在进行的事务停止,暂停或以其他方式不传播到被调用的bean。
EntityTransaction
或java.sql.Connection.commit()
将规避事务管理。答案 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。