6

I have a requirement to use 2 different databases within single DAO class. One of the databases is read/write while the other is read only.

I have created 2 data sources, 2 session factories and 2 transaction managers (transaction manager for the read/write database is the platform transaction manager) for these databases. I am using @Transactional on the service method to configure Spring for transaction management.

We are getting random Session is closed! exceptions when we call sessionFactory.getCurrentSession() in the DAO class ( I can not always produce it, it sometimes works ok, sometimes gets error) :

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)

I don't have a requirement to use global transaction (XA), I just want to query 2 different databases.

I have read this thread, it suggests injecting two separate session factories in the DAO layer as we do now: Session factories to handle multiple DB connections

Also AbstractRoutingDataSource does not work for single Dao class as per this answer: https://stackoverflow.com/a/7379048/572380

Example code from my dao looks like this:

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();

I have also tried using "doInHibernate" method. But the session passed to it is also randomly throwing "Session is closed!" exceptions:

    @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);
    }
nilgun
  • 10,460
  • 4
  • 46
  • 57
  • Can you post your code ? Why not have a single transactional service that injects two DAOs ? Out of interest why are you calling getCurrentSession() ? – PaulNUK Mar 06 '18 at 14:38
  • @PaulNUK I have updated the question with my code. What you mean by "having single transactional service that injects two DAO's"? I have 2 databases so I need 2 transactions or 1 global transaction. XA is hard to achieve and is not required in this case. – nilgun Mar 10 '18 at 22:01

5 Answers5

3

So you do have the below code in your application? If you don't you should add it,might be it is causing the problem.

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

Remove this property as mentioned below

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

You are overriding Spring which sets this to SpringSessionContext.class. This is almost certainly at least part of your problem.

Spring manages your session objects. These session objects that it manages are tied to Spring transactions. So the fact that you are getting that error means to me that it is most likely due to how you are handling transactions.

in other words don't do this

Transaction tx = session.beginTransaction();

unless you want to manage the life cycle of the session yourself in which case you need to call session.open() and session.close()

Instead use the framework to handle transactions. I would take advantage of spring aspects and the declarative approach using @Transactional like I described earlier its both cleaner and more simple, but if you want to do it pragmatically you can do that with Spring as well. Follow the example outlined in the reference manual. See the below link:

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

Sheel
  • 847
  • 2
  • 8
  • 20
  • I am using @Transactional and it manages transactions only for the platform transaction manager and the other throws error. I don't write code to manually beginTransaction. – nilgun Mar 12 '18 at 13:40
2

Above error suggest, you are not able to get the session as session is closed sometimes. You can use openSession() method instead of getCurrentSession() method.

Session session = this.getSessionFactory().openSession();
session.beginTransaction();
// Your Code Here.
 session.close();

Drawback with this approach is you will explicitly need to close the session. In single threaded environment it is slower than getCurrentSession().

Check this Link Also:- Hibernate Session is closed

  • I will try that if I dont find a solution with spring managed transactions. This has the drawback of being slow as you stated. – nilgun Mar 12 '18 at 05:54
2

The problem is that you have a single hibernate session and two data stores. The session is bound to the transaction. If you open a new transaction towards the other database this will effectively open a new session for this database and this entity manager.

this is equivalent to @Transactional(propagation = Propagation.REQUIRES_NEW)

You need to ensure that there are two different transactions/sessions bound to each of the persistent operations towards the two databases.

Alexander Petrov
  • 9,204
  • 31
  • 70
  • When I annotate my service class with `@Transactional` it only works for the platform transaction manager. How can I annotate my service to be @Transactional for 2 different transaction managers? – nilgun Mar 14 '18 at 06:25
2

If all configurations are correct, then every thing should work fine without error

I think you missed @Qualifier(value="sessionFactory1") and @Qualifier(value="sessionFactory2") at your DAO

kindly look at those examples

Hibernate configuring multiple datasources and multiple session factories

https://medium.com/@joeclever/using-multiple-datasources-with-spring-boot-and-spring-data-6430b00c02e7

Hany Sakr
  • 2,591
  • 28
  • 27
  • Thanks for the answer Hany. Actually I believe I didnt missed this because when the session closed error is not thrown the queries are run on the correct databases. Otherwise they would have ended on the wrong data sources. – nilgun Mar 14 '18 at 06:28
1

HibernateTemplate usage is discouraged already. The clear explanation is given here https://stackoverflow.com/a/18002931/1840818

As stated over there, declarative transaction management has to be used.

Vigneshwaran
  • 387
  • 1
  • 2
  • 14
  • Thanks Vigneshwaran. The point is I couldn't declaratively associate 2 transaction managers to a single request. Or I did but second session is sometimes closed. – nilgun Mar 14 '18 at 06:21