Предоставляет ли Guice Persist транзакцию или управляемую приложениями EntityManager?

Мы используем Guice Persist, чтобы внедрить EntityManager в наш проект.

например.

public class MyDao{
   @Inject
   EntityManager em;

   public void someMethod(){
       //uses em instance
   }
}

Но нам неясно, как будет использоваться введенный экземпляр EntityManager.

  • Какой тип EntityManager это? (см. например: типы менеджеров сущностей) Под капотом Guice Persist создает экземпляр через EntityManagerFactory.createEntityManager(), поэтому я бы сказал, что это менеджер приложений, управляемый приложениями. Но в официальная Wiki они пишут о стратегии seeion-per-transaction, которая предполагает, что EntityManager является (псевдо) транзакционным охватом.
  • Должны ли мы ссылаться на close() на нем вручную? Или Гиис позаботится об этом?
  • Какова область кеш первого уровня? Только одна транзакция (например, в менеджерах сущностей с транзакциями) или до тех пор, пока я использую тот же инъецируемый экземпляр EntityManager (как в менеджерах сущностей, управляемых приложениями)?

Ответ 1

Я провел некоторое исследование исходного кода Guice-persist и прочитал комментарии в вики-страницах Guice-persist, и вот те ответы, которые мне нужны:

1. Управление жизненным циклом EntityManager выглядит некорректно, если оно вводится через @Inject EntityManager. Как указано в одном из комментариев к Wiki:

Я подтверждаю, что непосредственно вставляет EntityManager вместо поставщика может быть опасным. Если вы не находитесь внутри UnitOfWork или метода аннотируется с @Transaction, первая инъекция EntityManager в потоке создаст новый EntityManager, никогда не уничтожит его, и всегда используйте этот конкретный EntityManager для этого потока (EM хранятся токарно-локальный). Это может привести к ужасным проблемам, таким как инъекция dead entityManager (соединение закрыто и т.д.) Поэтому моя рекомендация, если всегда вводить Провайдера или, по крайней мере, вводить непосредственно EntityManager только внутри открытого UnitOfWork.

Итак, пример в моем вопросе - это не самое правильное использование. Он создает одиночный экземпляр EntityManager (per-thread) и будет вводить этот экземпляр всюду: - (.

Однако, если я ввел провайдера и использовал его внутри метода @Transactional, то экземпляр EntityManager был бы за транзакцию. Таким образом, ответ на этот вопрос: , если он введен и используется правильно, диспетчер сущностей связан с транзакциями.

2. Если вы ввели и использовали правильно, мне не нужно вручную закрывать диспетчер сущностей (guice-persist будет обрабатывать это для меня). Если использовать неправильно, закрытие вручную было бы очень плохой идеей (закрытый экземпляр EntityManager будет вставляться в каждое место, когда я @Inject EntityManager)

3. При инъецировании и правильном использовании область кеша L1 представляет собой одинарную транзакцию. Если используется неверно, область действия кеша L1 - это время жизни приложения (EntityManager - одиночный)

Ответ 2

Несмотря на то, что вопрос полностью отвечает на вопрос Петра, я хотел бы добавить несколько практических советов о том, как использовать guice-persist.

У меня были проблемы с ним, которые довольно сложно отлаживать. В моем приложении определенные потоки отображали устаревшие данные, а иногда EntityManager экземпляры оставлялись со старыми соединениями с мертвой базой данных. Основная причина заключалась в том, как я использовал аннотацию @Transactional (я использовал их только для методов, которые обновляют/вставляют/удаляют, а не только для методов только для чтения). guice-persist хранит экземпляры EntityManager в ThreadLocal, как только вы вызываете get() на инъецированном экземпляре Provider<EntityManager> (который я сделал для методов только для чтения). Однако этот ThreadLocal удаляется, если вы также вызываете UnitOfWork.end() (который обычно выполняется перехватчиком, если @Transactional находится в методе). Не делая этого, вы оставите экземпляр EM в ThreadLocal, чтобы в конечном итоге каждый поток в пуле потоков имел старый экземпляр EM с устаревшими кэшированными объектами.

Итак, до тех пор, пока вы придерживаетесь следующих простых правил, использование guice-persist прямо:

  • Всегда вводите Provider<EntityManager> вместо EntityManager напрямую.
  • Для семантики с областью транзакций: всегда комментируйте каждый метод с помощью @Transactional (даже для методов только для чтения). Таким образом, JpaLocalTxnInterceptor перехватит вызовы ваших аннотированных методов, чтобы не только запускать и совершать транзакции, но также устанавливать и удалять экземпляры EM в ThreadLocal.
  • Для семантики с охватом запроса: используйте фильтр сервлета PersistFilter, который поставляется с guice-persist. Он вызовет begin() и end() на UnitOfWork для вас до и после выполнения запроса, тем самым заполняя и очищая ThreadLocal.

Надеюсь, это поможет!

Ответ 3

1. Это зависит от конфигурации модуля. Существуют некоторые основные привязки:

JpaPersistanceService

public class JpaPersistanceService implements Provider<EntityManager> {

  private EntityManagerFactory factory;

  public JpaPersistanceService(EntityManagerFactory factory) {
    this.factory = factory;
  }

  @Override
  public EntityManager get() {
    return factory.createEntityManager();
  }
}

Связывание модулей

EntityManagerFactory factory = Persistence.createEntityManagerFactory(getEnvironment(stage));
bind(EntityManager.class).annotatedWith(Names.named("request")).toProvider(new JpaPersistanceService(factory)).in(RequestScoped.class);
bind(EntityManager.class).annotatedWith(Names.named("session")).toProvider(new JpaPersistanceService(factory)).in(SessionScoped.class);
bind(EntityManager.class).annotatedWith(Names.named("app")).toProvider(new JpaPersistanceService(factory)).asEagerSingleton;

Использование

@Inject @Named("request")
private EntityManager em; //inject a new EntityManager class every request

@Inject @Named("session")
private Provider<EntityManager> emProvider; //inject a new EntityManager class each session
//This is little bit tricky, cuz EntityManager is stored in session. In Stage.PRODUCTION are all injection created eagerly and there is no session at injection time. Session binding should be done in lazy way - inject provider and call emProvider.get() when em is needed;

@Inject @Named("application")
private EntityManager em; //inject singleton

2. Да, вы должны или вы будете использовать JpaPersistModule [javadoc]

3. Ну, речь идет о конфигурации JPA в области persistence.xml и EntityManager

Ответ 4

Я заказываю провайдера.... но я подозреваю, что что-то не так. Когда я пытаюсь повторно развертывать приложение ВСЕГДА я должен рестартировать сервер, потому что классы JPA кэшируются.

Происходит следующая псевдо-ошибка

https://bugs.eclipse.org/bugs/show_bug.cgi?id=326552

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