TransactionAttribute注释(@REQUIRES_NEW)被忽略

MaD*_*aDa 5 java spring jpa transactions

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

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

这是代码中的样子:

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.
}

}
Run Code Online (Sandbox Code Playgroud)

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

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

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]
Run Code Online (Sandbox Code Playgroud)

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

我的问题是:这怎么可能,交易边界不受尊重?据我所知,JPA提供商可以一个事务中自由选择SQL排序,但它不能重新排序整个事务,可以吗?

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

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

MaD*_*aDa 6

我没有读过关于Spring代理如何工作的信息,那些负责基于注释的事务支持的人.

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

我将addEvent()方法移动到一个单独的类,问题消失了.另一种解决方案可能是改变<tx:annotation-driven/>配置的工作方式; 更多信息:Spring Transaction Management参考.