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

    }

}
Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Carlos López Marí
  • 1,432
  • 3
  • 18
  • 45

6 Answers6

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.

MyTwoCents
  • 7,284
  • 3
  • 24
  • 52
  • 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. – cнŝdk Jun 15 '18 at 12:05
  • 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. – MyTwoCents Jun 15 '18 at 12:08
  • @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 ? – mjs Mar 20 '21 at 17:26
  • 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. – cнŝdk Apr 21 '21 at 08:29
  • 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](https://www.laliluna.de/jpa-hibernate-guide/ch13.html) 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. – cнŝdk Apr 21 '21 at 08:29
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.

rwenz3l
  • 237
  • 5
  • 10
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.

cнŝdk
  • 31,391
  • 7
  • 56
  • 78
  • 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. – rwenz3l Jun 15 '18 at 11:43
  • 1
    @rwenz3l 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. – cнŝdk Jun 15 '18 at 11:45
1

I just changed

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

with

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

I was accessing the session of the Factory alredy opened.

Carlos López Marí
  • 1,432
  • 3
  • 18
  • 45
  • 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. – cнŝdk Jun 15 '18 at 12:03
1

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

session.beginTransaction();
-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.

soufrk
  • 825
  • 1
  • 10
  • 24
  • Throwed the same exception but instead of session.beginTransaction(); it was on t = session.beginTransaction(); – Carlos López Marí Jun 15 '18 at 11:33
  • Last attempt. Replace `t = session.beginTransaction();` with `t = session.getTransaction();` – soufrk Jun 15 '18 at 11:36
  • 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. – Carlos López Marí Jun 15 '18 at 11:43
  • 1
    @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. – rwenz3l Jun 15 '18 at 11:45
  • @nwenz3l I did, the problem was on the getCurrentTransaction() instead of the openTransaction i wanted to do. – Carlos López Marí Jun 15 '18 at 12:06
  • 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. – soufrk Jun 15 '18 at 12:16