使用两个数据库时出现随机的“Session is closed error”错误的Hibernate问题

6
我有一个需求,需要在单个DAO类中使用两个不同的数据库。其中一个数据库是读写的,另一个是只读的。
我创建了2个数据源、2个会话工厂和2个事务管理器(读写数据库的事务管理器是平台事务管理器)。我在服务方法上使用@Transactional来配置Spring进行事务管理。
当我们在DAO类中调用sessionFactory.getCurrentSession()时,我们会遇到随机的“Session is closed!”异常(我不能总是复现它,有时正常工作,有时出错):
org.hibernate.SessionException: Session is closed!
at org.hibernate.internal.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:133)
at org.hibernate.internal.SessionImpl.setFlushMode(SessionImpl.java:1435)
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:99)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)

我没有使用全局事务(XA)的要求,我只想查询2个不同的数据库。
我已经阅读了这个帖子,它建议在DAO层注入两个单独的session工厂,就像我们现在所做的一样:Session factories to handle multiple DB connections 此外,根据这个答案,AbstractRoutingDataSource不能用于单个Dao类:https://dev59.com/EFvUa4cB1Zd3GeqPqin2#7379048 我的dao示例代码如下:
Criteria criteria = sessionFactory1.getCurrentSession().createCriteria(MyClass.class);
criteria.add(Restrictions.eq("id", id));
criteria.list();

criteria = sessionFactory2.getCurrentSession().createCriteria(MyClass2.class); // generates random "Session is closed!" error.
criteria.add(Restrictions.eq("id", id));
criteria.list();

我也尝试过使用“doInHibernate”方法。但传递给它的会话也随机抛出“Session is closed!”异常:
    @Autowired
    protected HibernateTemplate hibernateTemplate;

    @SuppressWarnings("unchecked")
    protected List<Map<String, Object>> executeStaticQuery(final String sql) {
        HibernateCallback<List<Map<String, Object>>> hibernateCallback = new HibernateCallback<List<Map<String, Object>>>() {
            @Override
            public List<Map<String, Object>> doInHibernate(Session session) throws HibernateException {
                SQLQuery query = session.createSQLQuery(sql);
                query.setResultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP);
                return query.list();
            }
        };
        return hibernateTemplate.execute(hibernateCallback);
    }

你能发布你的代码吗?为什么不使用一个事务性服务来注入两个DAO?出于好奇,你为什么要调用getCurrentSession()? - PaulNUK
@PaulNUK,我已经更新了我的问题并附上了代码。你所说的“拥有单个事务服务注入两个DAO”是什么意思?我有两个数据库,所以我需要两个事务或一个全局事务。XA很难实现,在这种情况下也不是必需的。 - nilgun
5个回答

3

所以你的应用程序中是否有以下代码?如果没有,你应该添加它,这可能是导致问题的原因。

<bean id="transactionManager"   
class="org.springframework.orm.hibernate3.HibernateTransactionManager">    
<property name="sessionFactory" ref="sessionFactory"/>    
</bean>    
<tx:annotation-driven/>

按照下面提到的方法删除该属性

<property name="current_session_context_class">thread</property>

您正在覆盖Spring将其设置为SpringSessionContext.class。这几乎肯定是您问题的一部分。

Spring管理您的会话对象。它所管理的这些会话对象与Spring事务相关联。因此,您收到该错误的原因可能是由于您处理事务的方式。

换句话说,请不要这样做。

Transaction tx = session.beginTransaction();

除非您想要自己管理会话的生命周期,在这种情况下,您需要调用session.open()和session.close()

相反地,请使用框架来处理事务。我建议利用Spring Aspects和声明性方法,如我之前所述,它更加简洁和简单,但是如果您想要实用主义方式,您也可以使用Spring完成。请按照参考手册中概述的示例进行操作。请参见以下链接:

http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/orm.html#orm-hibernate-tx-programmatic


我正在使用 @Transactional,它仅管理平台事务管理器上的事务,而其他事务会抛出错误。我不编写代码手动开始事务。 - nilgun

2
上述错误提示表明,有时您无法获取会话,因为会话已关闭。您可以使用openSession()方法代替getCurrentSession()方法。
Session session = this.getSessionFactory().openSession();
session.beginTransaction();
// Your Code Here.
 session.close();

这种方法的缺点是您需要显式地关闭会话。在单线程环境中,它比getCurrentSession()慢。
还要检查此链接:Hibernate Session is closed

如果我在使用Spring管理的事务时找不到解决方案,我会尝试这个方法。但正如你所说,这种方法的缺点是速度较慢。 - nilgun

2
问题在于您只有一个Hibernate会话和两个数据存储。该会话绑定到事务。如果您向另一个数据库打开新的事务,这将有效地为该数据库和实体管理器打开新会话。
这相当于@Transactional(propagation = Propagation.REQUIRES_NEW)
您需要确保有两个不同的事务/会话分别绑定到向两个数据库进行的每个持久化操作。

当我在我的服务类上使用@Transactional注解时,它只适用于平台事务管理器。我该如何注释我的服务以便对2个不同的事务管理器进行@Transactional注解? - nilgun

2

谢谢你的回答,Hany。实际上我相信我没有错过这个问题,因为当会话关闭时没有抛出错误,查询会在正确的数据库上运行。否则它们将会结束在错误的数据源上。 - nilgun

1

谢谢Vigneshwaran。问题是我无法声明性地将两个事务管理器关联到单个请求。或者我做了,但第二个会话有时会关闭。 - nilgun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接