Предоставляет ли Guice Persist область действия или управляемый приложением EntityManager?
Мы используем Guice Persist для добавления EntityManager в наш проект.
Например
public class MyDao{
@Inject
EntityManager em;
public void someMethod(){
//uses em instance
}
}
Но нам непонятно, как впрыскивают экземпляр EntityManager
собирается быть использованным.
- Какой это тип EntityManager? (см., например: типы менеджеров сущностей). Под капотом Guice Persist создает его с помощью
EntityManagerFactory.createEntityManager()
так что я бы сказал, что это менеджер приложений, управляемый приложением. Но в официальной вики они пишут о стратегии "сквозной транзакции", которая предполагает, что EntityManager (псевдо) ограничен транзакциями. - Должны ли мы вызывать close() для него вручную? Или Guice позаботится об этом?
- Какова область применения кэша первого уровня? Только одна транзакция (как у менеджеров сущностей в области транзакций) или пока я использую один и тот же внедренный экземпляр
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 является прямым:
- Всегда вводить
Provider<EntityManager>
вместоEntityManager
непосредственно. - Для семантики в области транзакций: всегда аннотируйте каждый метод с помощью
@Transactional
(даже методы только для чтения). Таким образом,JpaLocalTxnInterceptor
будет перехватывать вызовы ваших аннотированных методов, обеспечивая не только запуск и фиксацию транзакций, но также установку и сброс экземпляров EM в ThreadLocal. - Для семантики области запроса: используйте
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, вы не должны ничего закрывать....