У меня проблема с двумя отдельными транзакциями, которые сбрасываются в базу данных в обратном порядке по сравнению с тем, в котором они фактически выполняются.
Вот бизнес-пример: есть RemoteJob-RemoteJobEvent один-к-одному-Множество отношений.Каждый раз, когда создается новое событие, в поле lastModified объекта RemoteJob и RemoteJobEvent получается и устанавливается отметка времени, и две записи сохраняются (одно обновление + одна вставка).
Вот как это выглядит вкод:
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_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]
Это, конечно, создает OptimisticLockException
- он действует одинаково в двух средах: тест с Apache Derby / Tomcat / Atomikos Transaction Essentials и цель с WebSphere7.0 / Oracle 11.
Мой вопрос: как это возможно, что границы транзакции не соблюдаются?Я понимаю, что поставщик JPA может свободно выбирать порядок SQL в пределах одной транзакции, но он не может переупорядочивать целых транзакций, не так ли?
Еще немного информации о нашей среде: представленный код является частью обработчика сообщений Spring 3.0.5 JMS (DefaultMessageListenerContainer);Spring также используется для внедрения бинов, но для управления транзакциями на основе аннотаций используется системный менеджер транзакций (Websphere's / Atomikos, как указано выше), поэтому используются транзакционные аннотации EJB3, а не Spring.
Надеюсь, это вызываетнекоторый интерес, в этом случае я с радостью предоставлю больше информации, если это необходимо.