Spring + Hibernate + Envers + многопоточность – сеанс закрыт
-
21-12-2019 - |
Вопрос
Мы используем Hibernate (с JPA) и Hibernate Envers для сохранения истории объектов.Веб-приложение запускает множество потоков, некоторые из них создаются вызовом метода RMI из других приложений, некоторые создаются самим приложением, а некоторые создаются для обработки http-запросов (они генерируют представления).
Мы также используем шаблон Open Session In View для управления сеансами, поэтому наш файл web.xml содержит:
<filter>
<filter-name>openEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Доступ к базе данных осуществляется с помощью DAO, все они имеют EntityManager, внедренные Spring.
@PersistenceContext
protected EntityManager em;
@PersistenceUnit
protected EntityManagerFactory emf;
Все работало вполне хорошо, прежде чем мы решили использовать Hibernate Envers.Когда какой-либо поток, не являющийся потоком, генерирующим представление, запускает код для получения старой версии объекта, выдается исключение.
@Override
public O loadByRevision(Long revision, Long id) {
@SuppressWarnings("unchecked")
O object = (O) AuditReaderFactory.get(em).createQuery().forEntitiesAtRevision(getBaseClass(), revision.intValue())
.add(AuditEntity.id().eq(id)).getSingleResult();
return object;
}
Исключение в потоке «Планировщик» org.hibernate.SessionException:Сессия закрыта!at org.hibernate.internal.abstractsessionImpl.errorifClosed (AbstractSessionImpl.java:129) в org.hibernate.internal.sessionImpl.createequery (SessionImpl.java:1776) at org.hibernate.envers.tools.query.querybuilder.toquery.toquery.toquery.toquery.toquery .java: 226) в org.hibernate.envers.query.impl.abstractauditquery.buildquery (AbstractaudiTquery.java:92) в org.hibernate.envers.query.impl.entitiesatrevision.list (entitiatrevisionquery.java:108) org. hibernate.envers.query.impl.abstractauditquery.getsingleresult (AbstractAuditQuery.java:110) (...)
Когда приведенный выше код запускается потоком, генерирующим представление, он работает нормально.Кроме того, код без конвертации в DAO отлично работает для каждого потока.Например, фрагмент ниже
@Override
public O load(Long id) {
final O find = em.find(getBaseClass(), id);
return find;
}
может без проблем запускаться потоками RMI.
Почему потоки без просмотра могут вызывать методы диспетчера сущностей без исключений, но не могут использовать AuditReaderFactory Envers с этим менеджером сущностей?Я подумал, что, возможно, вызов метода диспетчера сущностей создает временный сеанс, но при использовании Envers этого не происходит, правда ли это?
Как лучше всего решить эту проблему (чтобы AuditReaderFactory можно было использовать из каждого потока)?
Решение
Мы не выяснили, почему в потоках, не связанных с пользовательским интерфейсом, вызывается метод EntityManagerFactory
сработало, но вызывается метод AuditReaderFactory
не сделал.В любом случае, мы нашли способ это исправить.
Решением было аннотировать методы с помощью @Transactional
.Если какой-либо метод в цепочке вызовов перед вызовом AuditReaderFactory был помечен как @Transactional
, не было SessionException
в потоках, не связанных с пользовательским интерфейсом.
Оказалось, что создание loadByRevision
транзакций было недостаточно.Если объект, возвращаемый этим методом, содержит лениво загруженные постоянные пакеты, доступ к ним за пределами loadByRevision
область метода вызвала LazyInitializationException
(сеанса не было).
Окончательное решение заключалось в том, чтобы убедиться, что если какой-либо поток захочет загрузить некоторые данные из базы данных, вся загрузка (получение объекта и доступ к лениво загруженным коллекциям) будет выполняться внутри одного метода, аннотированного @Transactional
.