Hibernate: Two or more transactions at the same time: Transaction already active
Asked Answered
F

6

6

I have a REST API and when I make a POST and a GET nearly the same time I get this exception:

 SEVERE: The RuntimeException could not be mapped to a response, re-throwing to the HTTP container
java.lang.IllegalStateException: Transaction already active
    at org.hibernate.engine.transaction.internal.TransactionImpl.begin(TransactionImpl.java:52)
    at org.hibernate.internal.AbstractSharedSessionContract.beginTransaction(AbstractSharedSessionContract.java:409)
    at sun.reflect.GeneratedMethodAccessor89.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.hibernate.context.internal.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:355)
    at com.sun.proxy.$Proxy58.beginTransaction(Unknown Source)
    at utils.HibernateSession.createTransaction(HibernateSession.java:15)
    at api.ConversationsREST.getMessages(ConversationsREST.java:128)

They are on different classes so no global attributes implied.

The line which fails is this one:

HibernateSession hs = new HibernateSession();
hs.createTransaction(); // Crash

Which refers to my class HibernateSession:

public class HibernateSession {

    public Session session;

    public void createTransaction() {

        session = HibernateUtil.getSessionFactory().getCurrentSession(); //THIS WAS WRONG
 //EDIT:session = HibernateUtil.getSessionFactory().openSession(); //THIS IS RIGHT
        session.beginTransaction();
    }

    public void commitclose() {

        session.getTransaction().commit();
        session.close();

    }

    public void rollbackclose() {

        try {
            session.getTransaction().rollback();
            session.close();
        } catch (Exception hibernateexception) {
            hibernateexception.printStackTrace();
        }

    }

}

The exception is actually on the line session.beginTransaction()

I always make a hs.commitclose() and in the catch() blocks and the 404s I always make a rollbackclose();

The problem is that when I make a POST messages like this:

HibernateSession hs = new HibernateSession();
hs.createTransaction();
hs.session.save(whatever);
hs.commitclose();

Returns 200 and it's ok but then the GET may crash with the exception above. As I am making a new instance of HibernateSession, why Hibernate seems to be trying to share that transaction?

This only happens when I make both queries in a very short time (I guess starting a transaction between the beginning and the commit of another one). So I guess Hibernate is thinking that session attribute is static or something like that...

Thank you in advance for your help!

EDIT: As requested, HibernateUtil.java (the problem must not be here but may help to understand):

package utils;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {

    private static SessionFactory sessionFactory;

    public static SessionFactory getSessionFactory() {

        if (sessionFactory == null) {

            sessionFactory = build();

        }
        return sessionFactory;

    }

    private static SessionFactory build() {

        try {

            return new Configuration().configure().buildSessionFactory();

        } catch (Throwable ex) {

            System.err.println("Initial SessionFactory creation failed: " + ex);
            throw new ExceptionInInitializerError(ex);
        }

    }

}
Footstalk answered 15/6, 2018 at 11:0 Comment(3)
Can u add HibernateUtil as well...Artificial
@Ashish451 Of courseApprehensible
I have added solution.. you can tryArtificial
A
9

As per your createTransaction logic, you are getting current session from SessionFactory and starting a transaction from it.

Here is the problem.

Lets assume you have created HibernateSession object and started transaction but it is still in progress. So you haven't close transaction yet.

Now you created another HibernateSession and try to start transaction, doing this will throw exception.

So your this code

public void createTransaction() {
    session = HibernateUtil.getSessionFactory().getCurrentSession();
    session.beginTransaction();
}

has to be this

public void createTransaction() {
    session = HibernateUtil.getSessionFactory().openSession();
    session.beginTransaction();
}

getSessionFactory().openSession() always opens a new session that you have to close once you are done with the operations.

getSessionFactory().getCurrentSession() returns a session bound to a context - you don't need to close this.

Artificial answered 15/6, 2018 at 11:37 Comment(5)
Using getSessionFactory().openSession() will work, but this will open a session for each call, it's a bad practice design, try not to make this. It will just kill the appplication when ther will be many calls.Paramecium
I do agree with you but as per his input he is already creating new instance of HibernateSession. I would rather let Container manage Sessions.Artificial
@cнŝdk spent lots of time now to circumvent it. opensession does not register to threadlocal. horrible design. all of hibernate really. i have solved it though using my own implementations, but i wonder. when you do get a new session using openSession() ...and begin a tranasaction .. will all new code like adding enitties belong to the new transaction? when we close it, do we get back to the previous one ? because who gets the work ? what decides that? is that a stack that depends on when the transaction starts and ends ?Leadwort
Yes, it's a little bit confusing and too complicated part of hibernate, but afak when you close a Transaction, you should open a new one otherwise you won't be able to persist data, but when you open a new Transaction you should make sure to close it as well as yes all the new code changes (adding, editing entities) will be held in that transaction, until you commit it.Paramecium
But always keep in mind that: A session is not thread safe and should only be used by one thread. Usually this is guaranteed by the SessionFactory as stated in Session and Transaction Handling chapter in Hibernate docs. And there will be no need to implement your own design if you are using Spring or EJB, Transaction will be perfectly handled by these Frameworks.Paramecium
A
2

You have a few design related flaws:

  1. You should not use getCurrentSession()

As Stated in the jboss documentation, you should use the following idiom:

 Session sess = factory.openSession();
 Transaction tx;
 try {
     tx = sess.beginTransaction();
     //do some work
     ...
     tx.commit();
 }
 catch (Exception e) {
     if (tx!=null) tx.rollback();
     throw e;
 }
 finally {
     sess.close();
 }
  1. You should follow the CRUD pattern

For each Operation (Create, Read, Update, Delete) create a method in your class. Each Method shall creates it's own session and transaction to work with, because as stated in this answer:

session is not a thread safe object - cannot be shared by multiple threads. You should always use "one session per request" or "one session per transaction"

Your methods are somewhat valid, but also have a look at the DAO Objects created in this tutorial

Solution:

The reason your code is failing is simply because the Transaction is still in progress from your previous interaction with the database. That is why you do a finally{ session.close() } - to make sure that the session is closed when leavin the method. I assume that at some point your commit() was not successfull and left your hibernate in a Transaction/Session that has not been closed since.

To fix that, you should insert a session.close() once in your code, execute it and then implement my suggested try-catch-finally block to make sure it is closed in the future.

Attenborough answered 15/6, 2018 at 11:40 Comment(1)
I changed getCurrentSession() with openSession()Apprehensible
B
1

Actually there are many drawbacks in your actual code:

  • You are creating a new instance of your HibernateSession class each time you call your code, so you will have many instances of it, trying to access the same session.
  • Each of this instances of HibernateSession, will try to create a new transaction when you call hs.createTransaction();, and that's why you are getting the Exception at this line, because there are already open transactions and you are trying to open a new one, beacuse you are calling session.beginTransaction(); each time without calling transaction.close();.

What you can do here is to make your HibernateSessionclass singleton, so only one instance is available, you can check this Singleton Pattern implementation for further details on how to implement it.

And in the createTransaction method, wich should be better named getTransaction instead of just calling session.beginTransaction(); you need to get the current transaction if it exists you can check it using session.getTransaction() method and make sure you wrap your code in a try ...catch block, you can check this Hibernate session tutorial for further details.

Browning answered 15/6, 2018 at 11:40 Comment(2)
As one can see in his edit, he has a Singleton Factory that he can use to instanciate new Sessions, which would be totally fine. His actual error is that he has no finally { session.close() } or even try{ .. } in his committing method to begin with. That left his session open at some point and now he is stuck.Attenborough
@Attenborough Yes I know that the code behind is following a singleton pattern, but as he is making a new class to handle that, he should better make this in his class layer as well.Paramecium
F
1

I just changed

session = HibernateUtil.getSessionFactory().getCurrentSession();

with

session = HibernateUtil.getSessionFactory().openSession();

I was accessing the session of the Factory alredy opened.

Footstalk answered 15/6, 2018 at 12:0 Comment(1)
It will work, but actually this will open a session for each call, it's a bad practice design, try not to make this.Paramecium
E
1

I got the same error. In my case after the session was created, I had forgotten the instruction:

session.beginTransaction();
Eventuate answered 31/8, 2022 at 10:40 Comment(0)
M
-1

Try re-writing the code as

public class HibernateSession {

    public Session session;
    int id;
    Transaction t = null;

    public void createTransaction() {
        session = HibernateUtil.getSessionFactory().getCurrentSession();
        t = session.beginTransaction();
    }

    public void commitclose() {
        t.commit();
        session.close();
    }

    public void rollbackclose() {

        try {
            t.rollback();
            session.close();
        } catch (Exception hibernateexception) {
            hibernateexception.printStackTrace();
        }

    }

}

Off course null-checks are required for every reference of t.

Midis answered 15/6, 2018 at 11:24 Comment(6)
Throwed the same exception but instead of session.beginTransaction(); it was on t = session.beginTransaction();Apprehensible
Last attempt. Replace t = session.beginTransaction(); with t = session.getTransaction();Midis
But I need to begin it. It should be inactive as the first time i make a GET. I can check if is active or not, but that's only a workaround, what I really need is the transaction to be a very new instance every time the class is instanciated.Apprehensible
@CarlosLópez that's why you should use a session factory! You only have one instance of a Hibernate Session and that one got stuck inside a Transaction because you did not close it properly.Attenborough
@nwenz3l I did, the problem was on the getCurrentTransaction() instead of the openTransaction i wanted to do.Apprehensible
There are lot of use-cases where you don't get the privilege of opening a new transaction for every operation. In enterprise environments, lot of times you have to join an a already existing transaction, because a transaction is very costly. It's a workaround indeed. Given your code, I felt it won't hurt that much.Midis

© 2022 - 2024 — McMap. All rights reserved.