Комментарий TransactionAttribute (@REQUIRES_NEW) игнорируется - PullRequest
5 голосов
/ 11 июля 2011

У меня проблема с двумя отдельными транзакциями, которые сбрасываются в базу данных в обратном порядке по сравнению с тем, в котором они фактически выполняются.

Вот бизнес-пример: есть 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.

Надеюсь, это вызываетнекоторый интерес, в этом случае я с радостью предоставлю больше информации, если это необходимо.

Ответы [ 2 ]

6 голосов
/ 15 июля 2011

Я стал жертвой того, что не читал о том, как работают прокси-серверы Spring, отвечающие за поддержку транзакций на основе аннотаций.

Оказывается, аннотация REQUIRES_NEW addEvent игнорируется, когдаметод вызывается из одного и того же класса. Транзакционный прокси-сервер Spring в этом случае не работает, поэтому код выполняется в текущей транзакции, что совершенно неверно, поскольку заканчивается (long) после вызоваhelper.addEventIfNotThere() завершено.Последний метод, с другой стороны, вызывается из другого класса, поэтому REQUIRES_NEW действительно запускается и фиксируется как отдельная транзакция.

Я переместил метод addEvent() в отдельный класси проблема исчезла.Другим решением может быть изменение способа работы конфигурации <tx:annotation-driven/>;больше информации здесь: Ссылка на Spring Transaction Management .

1 голос
/ 06 октября 2011

Другим вариантом было бы сплетение Spring AnnotationTransactionAspect с использованием AspectJ, как описано в разделе 11.5.9 документации Spring

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...