【问题标题】:Hibernate: Two or more transactions at the same time: Transaction already active休眠:同时有两个或多个事务:事务已处于活动状态
【发布时间】:2018-11-25 05:16:26
【问题描述】:

我有一个 REST API,当我几乎同时进行 POST 和 GET 时,我得到了这个异常:

 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)

它们位于不同的类中,因此没有隐含的全局属性。

失败的线路是这一行:

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

Wich 指的是我的类 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();
        }

    }

}

异常居然在线session.beginTransaction()

我总是做一个 hs.commitclose() 并且在 catch() 块和 404 中我总是做一个 rollbackclose();

问题是当我发出这样的 POST 消息时:

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

返回 200 并没关系,但随后 GET 可能会崩溃,但上述异常除外。 当我创建一个新的 HibernateSession 实例时,为什么 Hibernate 似乎试图共享该事务?

这只发生在我在很短的时间内进行两个查询时(我猜想在另一个人的开始和提交之间开始一个事务)。所以我猜 Hibernate 认为会话属性是静态的或类似的东西......

提前感谢您的帮助!

编辑:应要求,HibernateUtil.java(问题一定不在这里,但可能有助于理解):

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

    }

}

【问题讨论】:

  • 你也可以添加 HibernateUtil...
  • @Ashish451 当然
  • 我已经添加了解决方案..你可以试试

标签: java hibernate rest jersey


【解决方案1】:

尝试将代码重写为

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

    }

}

t 的每个引用都需要进行非正常空检查。

【讨论】:

  • 抛出了同样的异常,但不是 session.beginTransaction();它在 t = session.beginTransaction();
  • 最后一次尝试。将t = session.beginTransaction(); 替换为t = session.getTransaction();
  • 但我需要开始。当我第一次进行 GET 时,它应该处于非活动状态。我可以检查是否处于活动状态,但这只是一种解决方法,我真正需要的是每次实例化类时事务都是一个非常新的实例。
  • @CarlosLópez 这就是你应该使用会话工厂的原因!您只有 一个 的 Hibernate Session 实例,并且该实例卡在 Transaction 中,因为您没有没有正确关闭它。
  • @nwenz3l 我做到了,问题出在 getCurrentTransaction() 而不是我想做的 openTransaction 上。
【解决方案2】:

根据您的 createTransaction 逻辑,您正在从 SessionFactory 获取当前会话并从中开始事务。

这就是问题所在。

假设您已经创建了HibernateSession 对象并开始了事务,但它仍在进行中。所以你还没有关闭交易。

现在您创建了另一个HibernateSession 并尝试启动事务,这样做会引发异常。

所以你的这个代码

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

应该是这样的

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

getSessionFactory().openSession() 总是会打开一个新会话,一旦您完成操作就必须关闭该会话。

getSessionFactory().getCurrentSession() 返回绑定到上下文的会话 - 您无需关闭它。

【讨论】:

  • 使用getSessionFactory().openSession() 会起作用,但这会为每个呼叫打开一个会话,这是一个不好的实践设计,尽量不要这样做。当调用很多时,它只会杀死应用程序。
  • 我同意你的观点,但根据他的意见,他已经在创建 HibernateSession 的新实例。我宁愿让 Container 管理 Sessions。
  • @cнŝdk 现在花了很多时间来规避它。 opensession 没有注册到 threadlocal。可怕的设计。真的所有的冬眠。我虽然使用自己的实现解决了它,但我想知道。当您使用 openSession() 获得一个新会话时......并开始一个事务......所有新代码(如添加实体)都属于新事务吗?当我们关闭它时,我们会回到前一个吗?因为谁得到了工作?是什么决定的?那是一个取决于事务何时开始和结束的堆栈吗?
  • 是的,它是hibernate的一个有点混乱和太复杂的部分,但是当你关闭Transaction时,你应该打开一个新的,否则你将无法持久化数据,但是当您打开一个新的Transaction 时,您应该确保将其关闭,是的,所有新的代码更改(添加、编辑实体)都将保留在该事务中,直到您commit 它。
  • 但请始终牢记:会话不是线程安全的,只能由一个线程使用。通常这是由 SessionFactory 保证的,如 Hibernate 文档中的Session and Transaction Handling chapter 中所述。如果您使用 SpringEJB,则无需实现自己的设计,Transaction 将由这些框架完美处理。
【解决方案3】:

其实你的实际代码有很多缺点:

  • 每次调用代码时,您都会创建 HibernateSession 类的新实例,因此您将拥有许多实例,试图访问同一个会话。
  • HibernateSession 的每个实例都会在您调用 hs.createTransaction(); 时尝试创建一个新事务,这就是您在此行收到异常的原因,因为已经有打开的事务并且您正在尝试打开一个新的,因为你每次都打电话给session.beginTransaction();,而没有打电话给transaction.close();

您可以在这里做的是使您的HibernateSessionclass 单例,因此只有一个实例可用,您可以查看Singleton Pattern implementation 以获取有关如何实现它的更多详细信息。

createTransaction 方法中,最好将其命名为getTransaction 而不是仅仅调用session.beginTransaction(); 您需要获取当前事务(如果存在)您可以使用session.getTransaction() 方法检查它并确保包装try ...catch 块中的代码,您可以查看此 Hibernate session tutorial 了解更多详细信息。

【讨论】:

  • 从他的编辑中可以看出,他有一个单例工厂,可以用来实例化新的会话,这完全没问题。他的实际错误是他的提交方法中没有finally { session.close() } 甚至try{ .. }。这让他的会话在某个时候打开了,现在他被卡住了。
  • @rwenz3l 是的,我知道后面的代码遵循单例模式,但是当他正在创建一个新类来处理这个问题时,他最好在他的类层中也这样做。
【解决方案4】:

你有一些设计相关的缺陷:

  1. 你不应该使用getCurrentSession()

如 jboss documentation 中所述,您应该使用以下成语:

 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. 您应该遵循 CRUD 模式

为每个操作(创建、读取、更新、删除)在您的类中创建一个方法。每个方法都应创建自己的自己的会话和事务以使用,因为如this answer 所述:

session 不是线程安全的对象 - 不能被多个共享 线程。您应该始终使用“每个请求一个会话”或“一个 每个事务的会话”

你的方法有些有效,但也看看this tutorial中创建的DAO对象

解决方案:

您的代码失败的原因仅仅是因为您之前与数据库的交互仍在进行中。这就是为什么您执行finally{ session.close() } - 以确保在离开该方法时会话已关闭。我假设在某些时候您的commit() 没有成功,并且您的休眠状态一直处于此后未关闭的事务/会话中。

要解决这个问题,您应该在代码中插入一次 session.close(),执行它,然后实现我建议的 try-catch-finally 块,以确保它在将来被关闭。

【讨论】:

  • 我用 openSession() 改变了 getCurrentSession()
【解决方案5】:

我刚刚换了

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

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

我正在访问已打开的工厂会话。

【讨论】:

  • 它会起作用,但实际上这会为每个呼叫打开一个会话,这是一个不好的实践设计,尽量不要这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-19
  • 2012-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-14
  • 2018-12-31
相关资源
最近更新 更多