TransactionAttribute注释(@REQUIRES_NEW)被忽略 [英] TransactionAttribute annotation (@REQUIRES_NEW) ignored

查看:1375
本文介绍了TransactionAttribute注释(@REQUIRES_NEW)被忽略的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了两个单独事务的问题,这些事务以与实际执行它们相反的顺序刷新到数据库。

I have a problem with two separate transactions that are flushed to the database in a reverse order to that in which they're actually executed.

这是业务案例:有一个RemoteJob-RemoteJobEvent一对多关系。每次创建新事件时,都会获取时间戳并在RemoteJob和RemoteJobEvent的lastModified字段中设置,并且持久保存两个记录(一次更新+一次插入)。

Here's the business case: there's a RemoteJob-RemoteJobEvent one-to-many relation. Every time a new event is created, a timestamp is obtained and set in the lastModified field of both RemoteJob and RemoteJobEvent, and two records are persisted (one update + one insert).

这是代码中的样子:

class Main {

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void mainMethod(...) {
  RemoteJob job = remoteJobDAO.findById(...);
  // ...         
  addEvent(job, EVENT_CODE_10);
  // Here the separate transaction should have ended and its results
  // permanently visible in the database. We refresh the job then
  // to update it with the added event:
  remoteJobDAO.refresh(job); // calls EntityManager.refresh()
  // ...
  boolean result = helper.addEventIfNotThere(job);
}

// Annotation REQUIRES_NEW here to enforce a new transaction; the
// RemoteJobDAO.newEvent() has REQUIRED.
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void addEvent(RemoteJob job, RemoteJobEvent event) {
  remoteJobDAO.newEvent(job, event);
}

}

class Helper {
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public boolean addEventIfNotThere(RemoteJob job) {
  // This loads the job into the persistence context associated with a new transaction.
  job = remoteJobDAO.findById(job.getId());
  // Locking the job record – this method is using as a semaphore by 2 threads,
  // we need to make sure only one of them completes it.
  remoteJobDAO.lockJob(job, LockModeType.WRITE);
  // Refreshing after locking to be certain that we have current data.
  remoteJobDAO.refresh(job);

  // ... here comes logic for checking if EVENT_CODE_11 is not already there
  if (/* not yet present */) {
    remoteJobDAO.newEvent(job, EVENT_CODE_11);
  }

  return ...; // true - event 11 was there, false - this execution added it.
}

}

总结:in mainMethod()我们已经在事务上下文中。然后我们挂起它来生成一个新事务,在方法 addEvent()中创建EVENT_CODE_10。返回此方法后,我们应该为每个人提交并显示其结果(但需要刷新 mainMethod()的上下文)。最后,我们进入 addEventIfNotThere()方法(再次进行新事务),结果发现没有人添加EVENT_CODE_11,所以我们这样做并返回。因此,数据库中应该有两个事件。

To sum up: in mainMethod() we are already in a transaction context. We then hang it to spawn a new transaction to create EVENT_CODE_10 in the method addEvent(). After this method returns, we should have its results committed and visible for everyone (but the context of mainMethod() needs to be refreshed). Finally, we step into the addEventIfNotThere() method (a new transaction again), it turns out nobody added the EVENT_CODE_11, so we do it and return. As a result, two events should be in the database.

这就是麻烦:OpenJPA似乎刷新了两个事件添加事务不久之后 c> addEventIfNotThere()完成了!更重要的是,它以错误的顺序执行,并且版本列值清楚地表明第二个事务没有前一个事务的结果信息,即使第一个应该已经提交(注意日志顺序,lastModified字段值)和事件代码):

Here's the trouble: OpenJPA seems to flush both event-adding transactions no sooner than after the addEventIfNotThere() completes! What's more, it does it in a wrong order, and version column values clearly show that the second transaction has no information of the results of the preceding one, even though the first one should have been committed (note the log order, lastModified field values and event codes):

2011-07-08T10:45:51.386 [WorkManager.DefaultWorkManager : 7] TRACE [openjpa.jdbc.SQL] - <t 2080472065, conn 1753966731> executing prepstmnt 1859546838 INSERT INTO RemoteJobEvent (id, eventCode, lastModified, version, remotejobid) VALUES (?, ?, ?, ?, ?) [params=(long) 252, (short) 11, (Timestamp) 2011-07-08 10:45:51.381, (int) 1, (long) 111]  
2011-07-08T10:45:51.390 [WorkManager.DefaultWorkManager : 7] TRACE [openjpa.jdbc.SQL] - <t 2080472065, conn 1753966731> executing prepstmnt 60425114 UPDATE RemoteJob SET lastModified = ?, version = ? WHERE id = ? AND version = ? [params=(Timestamp) 2011-07-08 10:45:51.381, (int) 3, (long) 111, (int) 2]  
2011-07-08T10:45:51.401 [WorkManager.DefaultWorkManager : 7] TRACE [openjpa.jdbc.SQL] - <t 2080472065, conn 815411354> executing prepstmnt 923940626 INSERT INTO RemoteJobEvent (id, eventCode, lastModified, version, remotejobid) VALUES (?, ?, ?, ?, ?) [params=(long) 253, (short) 10, (Timestamp) 2011-07-08 10:45:51.35, (int) 1, (long) 111]  
2011-07-08T10:45:51.403 [WorkManager.DefaultWorkManager : 7] TRACE [openjpa.jdbc.SQL] - <t 2080472065, conn 815411354> executing prepstmnt 1215645813 UPDATE RemoteJob SET lastModified = ?, version = ? WHERE id = ? AND version = ? [params=(Timestamp) 2011-07-08 10:45:51.35, (int) 3, (long) 111, (int) 2]

当然,这会产生 OptimisticLockException - 它在两个环境中的行为方式相同:使用Apache Derby / Tomcat /进行测试Atomikos Transaction Essentials,以及WebSphere 7.0 / Oracle 11的目标。

This, of course, produces an OptimisticLockException -- it acts the same way in two environments: test with Apache Derby/Tomcat/Atomikos Transaction Essentials, and target with WebSphere 7.0/Oracle 11.

我的问题是:这怎么可能,交易边界不受尊重?我了解JPA提供商可以在一次交易中自由选择 中的SQL订单,但它不能重新订购整个交易,可以吗?

My question is: how is this possible, that transaction borders are not respected? I understand that a JPA provider is free to choose SQL ordering within one transaction, but it cannot reorder whole transactions, can it?

有关我们环境的更多信息:提供的代码是Spring 3.0.5 JMS消息处理程序(DefaultMessageListenerContainer)的一部分; Spring也用于bean注入,但基于注释的事务管理使用系统事务管理器(Websphere的/ Atomikos,如上所述),这就是使用EJB3而不使用Spring事务注释的原因。

Some more info about our environment: the presented code is a part of a Spring 3.0.5 JMS message handler (DefaultMessageListenerContainer); Spring is also used for bean injections, but the annotation-based transaction management uses the system transaction manager (Websphere's/Atomikos, as above), that's why EJB3 and not Spring transactional annotations are used.

我希望这引起一些兴趣,在这种情况下,如果需要,我很乐意提供更多信息。

I hope this raises some interest, in which case I'll gladly supply more info, if needed.

推荐答案

我没有读过Spring代理如何工作的受害者,负责基于注释的交易支持。

I fell victim to not having read up on how Spring proxies work, the ones responsible for annotation-based transaction support.

结果是 addEvent 在同一个类中调用方法时,将忽略REQUIRES_NEW注释。在这种情况下Spring事务代理不起作用,因此代码运行在当前的交易—这是完全错误的,因为它在调用 helper.addEventIfNotThere()完成后结束(长)。另一方面,后一种方法从另一个类调用,因此REQUIRES_NEW真正启动并作为单独的事务提交。

It turns out the addEvent's REQUIRES_NEW annotation is ignored when the method is called from within the same class. The Spring transactional proxy does not come to work in this case, so the code runs in the current transaction — which is totally wrong, as it ends (long) after the call to helper.addEventIfNotThere() completes. The latter method, on the other hand, is called from another class, so the REQUIRES_NEW really starts and commits as a separate transaction.

I将 addEvent()方法移动到一个单独的类中,问题就消失了。另一种解决方案可能是改变< tx:annotation-driven /> 配置的工作方式;更多信息:春季交易管理参考

I moved the addEvent() method to a separate class and the problem disappeared. Another solution could be changing the way the <tx:annotation-driven/> configuration works; more info here: Spring Transaction Management reference.

这篇关于TransactionAttribute注释(@REQUIRES_NEW)被忽略的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆