2014-01-09 3 views
6

Мы используем Hibernate (с JPA) и Hibernate Envers для сохранения истории объектов. Веб-приложение запускает много потоков, некоторые из них создаются с помощью вызова метода RMI из других приложений, некоторые из них создаются самим приложением, а некоторые из них создаются для обработки HTTP-запросов (они генерируют представления).Spring + Hibernate + Envers + многопоточность - сеанс закрыт

Мы также используем Open Session в 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, все они EntityManagers впрыскивается весной.

@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 "Планировщик": Сессия закрыта! на org.hibernate.internal.AbstractSessionImpl.errorIfClosed (AbstractSessionImpl.java:129) на org.hibernate.internal.SessionImpl.createQuery (SessionImpl.java:1776) на org.hibernate.envers.tools.query. QueryBuilder.toQuery (QueryBuilder.java:226) на org.hibernate.envers.query.impl.AbstractAuditQuery.buildQuery (AbstractAuditQuery.java:92) на org.hibernate.envers.query.impl.EntitiesAtRevisionQuery.list (EntitiesAtRevisionQuery.java:108) на org.hibernate.envers.query.impl.AbstractAuditQuery.getSingleResult (AbstractAuditQuery.java:110) (...)

Когда код, приведенный выше, запускается потоком, создающим вид, он отлично работает. Кроме того, код non-envers в DAO отлично работает для каждого потока. Например, фрагмент ниже

@Override 
public O load(Long id) { 
    final O find = em.find(getBaseClass(), id); 
    return find; 
} 

может работать от нитей RMI без проблем.

Почему нельзя игнорировать потоки методов вызова в менеджере сущностей без исключений, но не может использовать Envers AuditReaderFactory с этим менеджером имен? Я думал, что, возможно, вызов метода в диспетчере сущностей создает временный сеанс, но это не происходит при использовании Envers, это правда?

Каков наилучший способ исправить эту проблему (чтобы AuditReaderFactory можно было использовать из каждого потока)?

ответ

1

Мы не выяснили, почему в методах не-ui-потоков вызов EntityManagerFactory работал, но метод звонков на AuditReaderFactory не сделал. Во всяком случае, мы нашли способ исправить это.

Решением было аннотировать методы с помощью @Transactional. Если какой-либо метод в цепочке вызовов перед вызовом AuditReaderFactory был отмечен как @Transactional, в не-ui-потоках не было SessionException.

Оказалось, что делать loadByRevision транзакционных было недостаточно.Если объект, возвращенный этим методом, содержал постоянные пакеты с ленивой загрузкой, доступ к ним за пределами области loadByRevision вызвал LazyInitializationException (сеанса не было).

Окончательное решение заключалось в том, чтобы убедиться, что, если какой-либо поток хочет загрузить некоторые данные из базы данных, вся загрузка (получение объекта и доступ к ленивым коллекциям) будет выполняться внутри одного метода, аннотированного @Transactional.