Статьи

Типы менеджеров сущностей: EntityManager, управляемый приложением

Спецификация JPA определяет несколько типов контекстов EntityManager / Persistence. Мы можем иметь:

  • расширенные и транзакционные EntityManager,
  • EntityManager, управляемый контейнером или приложением.
  • JTA или ресурс-локальный EntityManager,

Помимо вышеуказанного различия, у нас также есть два основных контекста, в которых может существовать EntityManager / Persistence Context — Java EE и Java SE . Не все опции доступны для Java EE, и не все возможны в Java SE. В оставшейся части поста я имею в виду среду Java EE .

Итак, прежде чем мы перейдем к реальной теме этого поста — поведению EntityManager в Java EE, созданному вручную с помощью EntityManagerFactory, — давайте кратко опишем вышеупомянутые типы EM.

Расширенная или транзакционная область

Эта функция сообщает нам, могут ли операции EntityManager охватывать несколько транзакций. По умолчанию используется контекст сохраняемости транзакций, который означает, что все изменения сбрасываются, и все управляемые объекты отключаются при фиксации текущей транзакции. Расширенная область доступна только для Stateful EJB; это имеет смысл, так как SFSB могут сохранять состояние, поэтому завершение одного бизнес-метода не обязательно означает завершение транзакции. С SLSB история другая — у нас есть бизнес-метод, который должен заканчиваться, когда бизнес-метод завершается, потому что при следующем вызове у нас нет идеи, в каком экземпляре EJB мы закончим. Один метод = одна транзакция; для SLSB разрешен только EntityManager в транзакционной области. Вы можете контролировать, является ли EntityManager расширенным или транзакционным во время внедрения EntityManager:

1
2
@PersistenceContext(type=javax.persistence.PersistenceContextType.EXTENDED)
EntityManager em;

По умолчанию это javax.persistence.PersistenceContextType.TRANSACTION . Как примечание — использование расширенного EntityManager может позволить вам создать некоторые интересные решения; взгляните на компонент без транзакций Адама Бина с приемом транзакционного метода сохранения . Он использует этот подход для автоматического сброса всех изменений, когда транзакция начинается и заканчивается (и он фактически делает это, вызывая специальный искусственный метод). Расширенные возможности и область действия транзакции PersistenceContext разрешены только в случае управляемых контейнером EntityManager .

Управляемый контейнером или управляемый приложением

В подавляющем большинстве приложений Java EE вы просто внедряете EntityManager с @PersistenceContext следующим образом:

1
2
@PersistenceContext
EntityManager em;

На самом деле это означает, что вы позволяете контейнеру внедрить EntityManager для вашего (контейнер создает его из EntityManagerFactory за кулисами.) Это означает, что ваш EntityManager управляется контейнером. Кроме того, вы можете создать EntityManager самостоятельно — из EntityManagerFactory. Вы можете получить его путем инъекции:

1
2
@PersistenceUnit
EntityManagerFactory emf;

Затем, чтобы получить EntityManager, вам нужно вызвать emf.createEntityManager() . И вот оно — вы теперь используете управляемый приложением EntityManager . Приложение (вы) отвечает за создание и удаление EntityManager. Каждый управляемый приложением постоянный контекст имеет расширенную область действия.

Вы можете использовать его, если хотите управлять созданным EM — например, если вы хотите установить какое-либо свойство для базового разработчика JPA или просто подключить себя, прежде чем бизнес-метод возьмет его в свои руки. Однако вам потребуется переместить созданный EntityManager между несколькими компонентами, участвующими в транзакции — контейнер не сделает это за вас, и каждый раз, когда вы вызываете emf.createEntityManager() вы создаете EntityManager, который подключен к новому PersistenceContext. , Вы можете использовать CDI для общего доступа к EntityManager, но это тема одного из последних разделов.

JTA против локального ресурса

Это свойство определяет, хотите ли вы, чтобы JTA управлял транзакциями вашего EntityManager или если вы хотите использовать его прямой API для запуска и фиксации. Если вы используете управляемый контейнером EntityManager, это автоматически означает, что вы должны использовать JTA EntityManager . Если вы используете управляемый приложением EntityManager, вы можете использовать JTA или локальный ресурс. В реальной жизни это означает, что если вы используете JTA EntityManager, вы позаботитесь только об управлении транзакциями более высокого уровня:

  • декларативно; используя аннотации или атрибуты транзакций XML JTA или,
  • программно; используя javax.transaction.UserTransaction .

Если вы используете локальный ресурс EntityManager, вам нужно пойти немного глубже и использовать EntityManager.getTransaction() который возвращает javax.persistence.EntityTransaction и вызывать commit(-) , begin(-) , rollback() и т. Д. определите эту функцию в persistence.xml используя атрибут transaction-type :

1
2
3
4
<?xml version='1.0' encoding='UTF-8'?>
<persistence ...>
    <persistence-unit transaction-type='RESOURCE_LOCAL' ... >
</persistence>

Другое возможное значение (и значение по умолчанию, когда тип транзакции не определен) — JTA .

Управляемый приложением JTA EntityManager в Java EE

Как бы ни звучал этот подзаголовок, зная все предыдущие типы EntityManager и PersistenceContexts, вы должны точно понимать, на что ссылается if.

  • «Управляемый приложением» означает, что мы будем вводить @PersistenceUnit EntityManagerFactory вместо EntityManager,
  • «Java EE», потому что эти примеры (опубликованные на github ) должны использоваться только на сервере приложений Java EE ,
  • «JTA», потому что мы будем использовать уровень транзакций JTA, поэтому мы не будем использовать javax.persistence.EntityTransaction .

Теперь первое, что вам нужно понять, это то, как вы используете транзакции JTA. Существует два типа управления транзакциями JTA — контейнер (CMT) и управляемый компонентом (BMT). Управляемые контейнером JTA-транзакции ( CMT ) означают, что вы используете javax.ejb.TransactionAttribute для определения, должен ли tx быть активным и где находятся границы транзакции. Это тип управления JTA по умолчанию. В качестве альтернативы вы можете выбрать разграничение транзакций JTA самостоятельно. Это называется Bean-управляемыми JTA-транзакциями ( BMT ). Приложение (вы) отвечает за запуск, откат или совершение транзакции. Как вы можете контролировать транзакцию JTA? Вы делаете это с помощью javax.transaction.UserTransaction .

Как вы получаете один? Ну, есть как минимум 3 способа сделать это:

  • он связан с JNDI в частном пространстве имен компонента ( java:comp/UserTransaction ), так что вы можете просто посмотреть его с помощью InitialContext или SessionContext ,
  • вы можете использовать SessionContext для доступа к нему — SessionContext#getUserTransaction() ,
  • поскольку оно связано с известным именем JNDI, вы можете позволить контейнеру внедрить его, используя: @Resource UserTransaction utx; ,

Если у вас есть UserTransaction вы можете начать разграничивать то, что должно быть выполнено в транзакции. Обратите внимание, что вы все еще контролируете транзакции JTA — вы даже не касаетесь локальных транзакций ресурса EntityManager.

Когда EntityManager находится в транзакции JTA?

Без предыдущего введения вы могли бы подумать, что управляемый приложением EntityManager означает, что вы в одиночестве для всего — создания, совместного использования EntityManager, начала передачи, фиксации, закрытия. Однако, зная все вышеперечисленные различия, вы знаете, что вы можете использовать транзакции JTA в EntityManager, управляемом приложением . Но вопрос — как сделать так, чтобы он знал об активной транзакции JTA? Если у нас есть EntityManager, управляемый контейнером, мы знаем, что контейнер управляет всем этим, но если мы сами — как мы это делаем? На самом деле это зависит от того, где EntityManager был создан нами. Ниже приведены некоторые примеры (полный код можно найти в моей учетной записи на github :

Случай 1: мы вызываем следующий код без активной транзакции (поэтому у нас есть TransactionAttribute.NEVER или TransactionAttribute.NOT_SUPPORTED в случае CMT или мы не UserTransaction.begin() в случае BMT:

1
2
EntityManager em = emf.createEntityManager(); 
em.persist(new Customer(firstName, lastName));

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

Случай 2: мы вызываем следующий код, используя BMT:

1
2
3
4
5
6
7
utx.begin();
 
EntityManager em = emf.createEntityManager();
 
em.persist(new Customer(firstName, lastName));
 
utx.commit();

Результаты: новые данные правильно сохраняются во время принятия JTA (в последней строке.)

Случай 3: Мы вызываем следующий код, используя BMT:

1
2
3
4
5
6
EntityManager em = emf.createEntityManager();
utx.begin();
 
em.persist(new Customer(firstName, lastName));
 
utx.commit();

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

В случае второго примера вы можете спросить себя — возможно ли сначала создать EntityManager, затем запустить транзакцию и, наконец, каким-то образом заставить EntityManager узнать об окружающем tx? И если факт да, вы можете сделать это, и это именно то, для чего предназначен метод EntityManager#joinTransaction() . Следующие два случая должны показать вам, как это можно использовать:

Случай 4: Мы вызываем следующий код, используя BMT:

1
2
3
4
5
6
7
EntityManager em = emf.createEntityManager(); 
utx.begin();
em.joinTransaction();
 
em.persist(new Customer(firstName, lastName));
 
utx.commit();  

Результаты: здесь мы явно указали EntityManager присоединиться к активной транзакции JTA. В результате EntityManager сбросит все свои изменения во время принятия JTA .

Случай 5: Мы вызываем следующий код, используя BMT:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
EntityManager em = emf.createEntityManager();
utx.begin();
 
em.joinTransaction();
 
em.persist(new Customer(firstName, lastName));
 
utx.commit();
 
utx.begin();
 
em.joinTransaction();
 
em.persist(new Customer(firstName, lastName));
 
utx.commit();   

Результаты: обе операции EntityManager правильно сохранены. Здесь мы показали, что управляемый приложением контекст сохраняемости может охватывать несколько транзакций JTA (обратите внимание, что мы не создали другой EntityManager, а просто повторно использовали тот, который использовался в предыдущей транзакции.) И здесь вы можете увидеть, что спецификации JPA (JPA 2.0 Final Release) рассказывает о управляемом приложением постоянстве контекста:

7.7. Контексты управляемости приложениями

Когда используется диспетчер объектов, управляемый приложением JTA, если менеджер сущностей создается вне области текущей транзакции JTA, приложение обязано связать менеджера сущностей с транзакцией (при желании), вызвав EntityManager.joinTransaction , Если менеджер сущностей создается вне области транзакции JTA, он не ассоциируется с транзакцией, если не вызывается EntityManager.joinTransaction.
Совместное использование EntityManager с использованием CDI

Как уже упоминалось, если вы хотите поделиться своим EntityManager между компонентами, которые составляют одну транзакцию, вы должны передать его вручную (эй, после того, как все это «управляется приложением».) CDI может быть решением здесь. Вы могли бы создать EntityManager в области запроса и внедрить его в любой компонент, который вам нужен. Это может выглядеть так (в реальной жизни вам также необходимо позаботиться об утилизации ЭМ):

01
02
03
04
05
06
07
08
09
10
public class Resources {
 
    @PersistenceUnit
    EntityManagerFactory emf;
 
    @Produces @RequestScoped
    public EntityManager createEntityManager() {
        return emf.createEntityManager();
    }
}

Теперь в каждом бобе мы можем иметь:

1
2
3
4
5
6
@Stateless
public class MyBean {
 
    @Inject
    EntityManager em;
}

Кажется, это очень чистый способ разделения контекста персистентности, управляемого приложением, между различными компонентами, составляющими транзакцию. Однако я боялся: зная, что поведение транзакций EntityManager, управляемое приложением, зависит от места, где оно было создано, такой подход может иногда давать вам неприятные результаты. Возьмем следующий код в качестве примера (он также доступен в моем проекте Github , этот класс именно здесь ):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Stateless
@TransactionAttribute(TransactionAttributeType.NEVER)
public class BeanABoundary {
 
    @Inject
    private EntityManager em;
 
    @EJB
    BeanB beanB;
 
    public void invoke() {
        em.getProperties();
        beanB.invoke();
}

Обратите внимание, что BeanA является нетранзакционным ресурсом. Также обратите внимание, что мы внедрили и вызвали некоторую операцию в EntityManager (это делает внедрение фактически выполненным.) Теперь, если BeanB является транзакционным, а также внедряет и использует EntityManager — мы закончим нетранзакционным EntityManager, который не будет выбрасывать любое исключение и не будет сохранять какие-либо изменения в базе данных .

В случае старого @PersistenceContext мы были бы в транзакции, потому что EntityManager будет управляться контейнером, и контейнер будет знать о текущей активной транзакции. Контейнер отвечает за совместное использование EntityManager между границами транзакции. В случае показанного метода производителя CDI CDI не знает о запущенной транзакции и просто совместно использует EntityManager.

Конечно, можно использовать CDI и создать @Produces @PersistenceContext EntityManager em а затем использовать @Inject EntityManager . Это будет работать точно так же, как @PersistenceContext EntityManager но позволяет нам определить, например, имя модуля персистентности в одном месте, которое создает EntityManager. Это, однако, не вариант, если мы хотим иметь управляемый приложением EntityManager.

Ссылка: Типы менеджеров сущностей: EntityManager, управляемый приложением, от нашего партнера по JCG Петра Новицки в блоге домашней страницы Петра Новицкого .