Понимание транзакций уровня контейнера EJB3 / JPA и уровня изоляции - PullRequest
6 голосов
/ 09 ноября 2010

Рассмотрим упрощенное представление кода, с которым я работаю:

@Stateless(...)
@Remote(...)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class FirstEjbType {

   @EJB(...)
   private SecondEjbType secondEjb;
   @EJB(...)
   private ThirdEjbType thirdEjb;

   public void doSomething() {
      secondEjb.doSomething();  // WRITES SOMETHING TO THE DATABASE
      thirdEjb.doSomething();  // CAN'T SEE THAT SOMETHING IN THE DATABASE!
}

Я установил аннотацию TransactionAttribute на MANDATORY на уровне класса. Я понимаю, что это означает, что все методы, такие как doSomething(), должны вызываться в предоставленной транзакции. В этом случае мы используем транзакции, управляемые контейнером.

TransactionAttribute вообще не используется в SecondEjbType или ThirdEjbType ... ни на уровне класса, ни на уровне метода. Я понимаю, что это означает, что secondEjb.doSomething() и thirdEjb.doSomething() будут работать в рамках транзакции, предоставленной для firstEjb.doSomething().

Тем не менее, я серьезно упускаю что-то ! Как указано в комментариях к коду ... secondEjb записывает данные в базу данных, а thirdEjb считывает эти данные как часть своей операции. Поскольку все это выполняется в рамках одной транзакции, я не ожидал, что возникнут какие-либо проблемы с уровнями изоляции. Однако, по какой-то причине запись в базу данных secondEjb не видна для thirdEjb.

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


ОБНОВЛЕНИЕ - Дополнительная информация, запрошенная johnstok ниже:

  • Я работаю в GlassFish
  • Я не уверен, что вы подразумеваете под "нестандартным режимом сброса", поэтому я предполагаю, что ответ - нет.
  • Мой файл persistence.xml выглядит следующим образом:

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="pu" transaction-type="JTA">
<jta-data-source>jdbc/datasource</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="toplink.cache.shared.default" value="false"/>
</properties>
</persistence-unit>
</persistence>

Ответы [ 5 ]

15 голосов
/ 10 ноября 2010

Первое, что нужно проверить, это то, что бины два и три используют @PersistenceContext EntityManager для получения EntityManager, а not @PersistenceUnit EntityManagerFactory, за которым следует вызов createEntityManager().

Во-вторых, проверьте, чтоDataSource фактически настроен для участия в транзакциях JTA (autoCommit или связанные свойства должны быть отключены).

Наконец, быстрый и грязный способ проверить ваше распространение - это вызвать метод EntityManager.getDelegate() и проверитьрезультирующий объект одинаков во всей ожидаемой области транзакции.

Вот как все работает под покровом ... EntityManager, внедренный в ваш бин при его создании, является подделкой,простой фасад.Когда вы пытаетесь использовать ссылку EntityManager в транзакции, контейнер фактически начинает копаться в текущей транзакции, находит real EntityManager, который спрятан в области транзакции, и передает ваш вызов that EntityManager (если в транзакции уже нет EntityManager, контейнер создаст его и добавит).Этот настоящий объект будет иметь значение getDelegate().Если значение getDelegate() не совпадает (==) в secondEjb.doSomething() и thirdEjb.doSomething(), то вы не получаете ожидаемое распространение, и каждый из них говорит с различным контекстом персистентности.

Назаметьте, что применение MANDATORY к классу влияет только на методы, определенные в этом классе, а не в суперклассах.Если вы не задаете атрибут @TransactionAttribute для суперкласса, эти методы используют значение по умолчанию независимо от того, как подклассы могут быть аннотированы.Я только упоминаю об этом, поскольку это может повлиять на ваше понимание вашего кода.

12 голосов
/ 10 ноября 2010

Я установил аннотацию TransactionAttribute на ОБЯЗАТЕЛЬНО на уровне класса.Я понимаю, что это означает, что все методы, такие как doSomething (), должны вызываться в предоставленной транзакции.В этом случае мы используем транзакции, управляемые контейнером.

Использование MANDATORY на уровне класса означает, что контейнер должен выдать исключение вызывающей стороне, если не выполняется транзакция при вызове любого метода FirstEjbType.

Из любопытства, кто инициирует транзакцию тогда?Есть ли конкретная причина не использовать значение по умолчанию REQUIRED?

TransactionAttribute вообще не используется в SecondEjbType или ThirdEjbType ... ни на уровне класса, ни на уровне метода.Я понимаю, что это означает, что secondEjb.doSomething () и thirdEjb.doSomething () будут работать в транзакции, предоставленной для firstEjb.doSomething ()

Это означает, что используется атрибут транзакции по умолчанию, т.е.REQUIRED, и метод REQUIRED гарантированно будет выполнен внутри транзакции (контейнер запустится один , если потребуется ).

Так что в вашем случае, secondEjb.doSomething() и thirdEjb.doSomething() должно быть действительно выполнено в рамках транзакции firstEjb.doSomething().

Я пропустил что-то очевидное, или мое концептуальное понимание в основном верно, и проблема может быть в другом месте?

Практическое правило для распространения контекста персистентности (в области транзакции) заключается в том, что контекст персистентности распространяется по мере распространения транзакции JTA.Но есть некоторые ограничения.В спецификации JPA это выглядит следующим образом:

5.6.3 Распространение контекста постоянства

Как описано в разделе 5.1, один контекст постоянства может соответствовать одному или нескольким экземплярам диспетчера сущностей JTA (все связаны с одной и той же фабрикой диспетчера сущностей).

Контекст персистентности распространяется по экземплярам диспетчера сущностей по мере распространения транзакции JTA.

Распространение контекстов персистентности применяется только в пределах местная среда.Контексты постоянства не распространяются на удаленные уровни.

И Sahoo (из команды GlassFish) более подробно пишет о правилах распространения в Распространение контекста постоянства :

3.(правило № 3) Не ожидайте, что компьютер будет распространяться при вызове удаленного EJB, даже если удаленный EJB работает в той же JVM или в том же приложении.

Итак,предполагая, что вы используете JPA везде, я ожидал бы, что бизнес-методы, вызываемые в одной и той же транзакции, наследуют один и тот же контекст постоянства только , если вы не используете Remote интерфейсы (я не знаю,специфичная интерпретация спецификации GlassFish, но Sahoo довольно ясно об этом ограничении).

PS: JPA предполагает уровень изоляции READ_COMMITTED (поэтому может работать оптимистическая блокировка), а стандартный JPA не допускает пользовательских настроек уровней изоляции (ну, некоторые провайдеры разрешают изменять его либо глобально, либо по запросу, ноэто непереносимо).

PPS: Я Я не уверен, что уровни изоляции являются ключом к вашей проблеме здесь.

Ссылки

  • JPA 1.0 Спецификация
    • Раздел 5.6.3 «Распространение контекста постоянства»
2 голосов
/ 09 ноября 2010

Поскольку в тегах упоминается , я полагаю, что контекст постоянства не очищается перед вызовом thirdEjb методов, поэтому изменения не записываются в базу данных.

По умолчанию контекст персистентности JPA сбрасывается перед фиксацией, перед выполнением запроса JPA или вручную с помощью em.flush(). Таким образом, видимость изменений зависит от метода доступа к данным, используемого в thirdEjb - если данные читаются, например, с использованием JDBC, изменения не должны быть видны без em.flush().

1 голос
/ 09 ноября 2010

Вам нужно будет предоставить больше информации, чтобы получить ответ на этот вопрос.

  • Вы работаете в контейнере Java EE?
  • Вы установили нестандартный режим промывки?
  • Можете ли вы опубликовать свой файл persistence.xml?

Имейте в виду, что «контекст постоянства» и жизненный цикл транзакции могут не совпадать.

Сделка

Для EJB 3.0 атрибут транзакции по умолчанию для всех приложений EJB 3.0 - REQUIRED. Документы для типа транзакции находятся здесь: http://download.oracle.com/javaee/6/api/javax/ejb/TransactionAttributeType.html

Возможно, вы используете тип транзакции REQUIRES_NEW, который будет работать в отдельной транзакции?

В частности, попробуйте использовать локальный, а не удаленный интерфейс для 2-го и 3-го EJB .

Промывка

При настройках по умолчанию перед запросом выполняется принудительная очистка (если необходимо), чтобы убедиться в правильности результатов: http://download.oracle.com/javaee/5/api/javax/persistence/FlushModeType.html

Попробуйте позвонить entityManager.setFlushMode(FlushModeType.AUTO);, чтобы убедиться, что перед вашим запросом происходит сброс. Включите ведение журнала SQL в вашем JPA-провайдере, чтобы убедиться, что обновления / вставки действительно отправляются в БД до выбора.

0 голосов
/ 11 ноября 2010

Я узнал тонну из всех ответов здесь, и не могу отблагодарить людей достаточно.Тем не менее, я считаю, что мой вопрос замутил воды до такой степени, что, возможно, было бы лучше начать сначала с другого вопроса.

Не похоже, что прыжки с одного EJB на следующий и обратноделать с чем угодно.Чтобы упростить задачу, я попытался работать с контрольным примером, полностью изолированным от одного EJB.Я вошел в этот метод secondEjb.doSomething(), который сохраняет сущность в базе данных.В конце метода я добавил em.flush() и попытался снова получить сущность с помощью запроса JPA.

Даже если я был , все еще в том же методе, где сущностьтолько что был сохранен , он не был виден для этого последующего запроса.Я провел некоторое дополнительное исследование в другом месте , и похоже, что это может быть просто нормальный режим изоляции для JPA в транзакционном контексте.Когда запущена транзакция, дополнительные запросы в этой транзакции еще не видны для незафиксированных данных.

Если мое резюме связанного обсуждения CodeRanch является точным, тогда "гадость" на JPA!:) В любом случае, я переработал код, чтобы вообще избежать этой проблемы.

...