我遇到了两个单独的事务的问题,这些事务以与实际执行的顺序相反的顺序刷新到数据库。
这是业务案例:存在 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()
我们已经处于交易环境中。然后我们将其挂起以生成一个新事务,以在方法中创建 EVENT_CODE_10addEvent()
。此方法返回后,我们应该提交其结果并对每个人可见(但上下文mainMethod()
需要刷新)。最后,我们进入addEventIfNotThere()
方法(又是一个新的交易),结果发现没有人添加 EVENT_CODE_11,所以我们这样做并返回。因此,数据库中应有两个事件。
麻烦来了:OpenJPA 似乎刷新了both添加事件的交易不早于之后 the 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 排序within一笔交易,但不能reorder全程交易可以吗?
有关我们环境的更多信息:所提供的代码是 Spring 3.0.5 JMS 消息处理程序 (DefaultMessageListenerContainer) 的一部分; Spring 也用于 bean 注入,但是基于注释的事务管理使用系统事务管理器(Websphere 的/Atomikos,如上所述),这就是使用 EJB3 而不是 Spring 事务注释的原因。
我希望这能引起一些兴趣,在这种情况下,如果需要,我很乐意提供更多信息。