TransactionAttribute注释(@REQUIRES_NEW)被忽略

时间:2011-07-11 10:48:12

标签: 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.
}

}

总结一下:在mainMethod()中,我们已经处于事务上下文中。然后,我们将其挂起以生成新事务,以在方法addEvent()中创建EVENT_CODE_10。返回此方法后,我们应该为每个人提交并显示其结果(但需要刷新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]

当然,这会生成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事务注释的原因。

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

2 个答案:

答案 0 :(得分:6)

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

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

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

答案 1 :(得分:1)

另一种选择是使用AspectJ编织Spring的AnnotationTransactionAspect,如Spring documentation

的第11.5.9节所述