我遇到了两个单独事务的问题,这些事务以与实际执行它们相反的顺序刷新到数据库。
这是商业案例:有一个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事务注释的原因。
我希望这引起一些兴趣,在这种情况下,如果需要,我很乐意提供更多信息。
答案 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节所述