Предоставляет ли Guice Persist область действия или управляемый приложением EntityManager?

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

Например

public class MyDao{
   @Inject
   EntityManager em;

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

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

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

4 ответа

Решение

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

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

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

Так что пример в моем вопросе - не самое правильное использование. Он создает одноэлементный экземпляр EntityManager (для каждого потока) и внедрит этот экземпляр везде:-(.

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

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

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

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

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

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

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

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

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.

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

Бывает следующая псевдо-ошибка

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

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

Другие вопросы по тегам