【问题标题】:Threading issues with HibernateHibernate 的线程问题
【发布时间】:2013-03-26 06:32:42
【问题描述】:

我在使用 Hibernate 时遇到了一种奇怪的行为。一段时间以来,我一直在用头撞墙,并将奖励任何导致 +100 赏金解决方案的答案。

我有一个 JAX-RS (Jersey) REST 服务器,带有一个过滤器,每个请求关联一个 Hibernate 会话。

在一个请求中,客户端使用一个会话(和一个事务)发布一些存储在数据库中的数据。在随后的调用中,客户端尝试获取此实体,但 Hibernate 找不到它。

一些观察:

  • 如果我同时运行多个客户端,我只能重现此问题。我从来没有通过一次运行一个客户端来重现它。)

  • 可以在数据库中看到实体 ID,如果我重新启动服务器,则 Hibernate 会正常找到该实体。

  • 如果我使用大小为 1 的线程池(无论我同时运行多少个客户端),则不会发生错误。

这是代码,带有一些日志记录:

chargeables.setId(new SecureRandom().nextLong());

System.out.printf("%s,  session: %s  [%s]%n",
                  Thread.currentThread(),
                  System.identityHashCode(session),
                  "session.beginTransaction()");

session.beginTransaction();


System.out.printf("%s,  session: %s  [%s]%n",
                  Thread.currentThread(),
                  System.identityHashCode(session),
                  "session.save(id = "+chargeables.getId()+")");

session.save(chargeables);


System.out.printf("%s,  session: %s  [%s]%n",
                  Thread.currentThread(),
                  System.identityHashCode(session),
                  "session.getTransaction().commit()");

session.getTransaction().commit();

获取实体的代码:

System.out.printf("%s,  session: %s  [%s]%n",
                  Thread.currentThread(),
                  System.identityHashCode(session),
                  "session.get("+id+")");

Chargeables entity = (Chargeables) session.get(Chargeables.class, id);

if (entity == null)
    System.out.printf("%s,  session: %s  [%s]%n",
                      Thread.currentThread(),
                      System.identityHashCode(session),
                      "ENTITY NOT FOUND!");

现在这里是结果日志的摘录(带有一些额外的打开/关闭会话输出):

Thread[Grizzly(5),5,main],  session:  2041842357  [factory.openSession()]
Thread[Grizzly(5),5,main],  session:  2041842357  [session.beginTransaction()]
Thread[Grizzly(5),5,main],  session:  2041842357  [session.save(id = 7939229356942262438)]
Thread[Grizzly(5),5,main],  session:  2041842357  [session.getTransaction().commit()]
Thread[Grizzly(5),5,main],  session:  2041842357  [session.close()]
[...]
Thread[Grizzly(7),5,main],  session:  1717445911  [factory.openSession()]
Thread[Grizzly(7),5,main],  session:  1717445911  [session.get(7939229356942262438)]
Thread[Grizzly(7),5,main],  session:  1717445911  [ENTITY NOT FOUND!]
Thread[Grizzly(7),5,main],  session:  1717445911  [session.close()]

我到底为什么要联系ENTITY NOT FOUND!


休眠版本:4.1.9.Final
MySQL 版本:14.14 Distrib 5.5.29


Chargeables 的映射文件:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
       "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
       "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping
   default-cascade="all"
   package="se.package.common.chargeables"
   default-lazy="false">


    <class name="Chargeables">

        <id name="id" type="long">
            <generator class="assigned"/>
        </id>

        <property name="startTimeStamp" />
        <property name="endTimeStamp" />

        <list name="chargeables">
            <key column="chargeableId" />
            <list-index column="pos" />
            <many-to-many class="Chargeable"/>
        </list>

    </class>


    <class name="Chargeable">

        <id column="id" type="long">
            <generator class="native"/>
        </id>

        <discriminator />

        <property name="timestamp" />

    </class>


    <subclass name="DataTransfer" extends="Chargeable">
        <property name="bytesSent" />
        <property name="bytesReceived" />
    </subclass>


    <subclass name="TelephonyChargeable" extends="Chargeable">
        <many-to-one name="num" />
    </subclass>

    <subclass name="Call" extends="TelephonyChargeable">
        <property name="duration" />        
    </subclass>

    <subclass name="OutgoingCall" extends="Call" />
    <subclass name="IncomingCall" extends="Call" />

    <subclass name="Message" extends="TelephonyChargeable" />

    <subclass name="Sms" extends="Message" />
    <subclass name="IncomingSms" extends="Sms" />
    <subclass name="OutgoingSms" extends="Sms" />

    <subclass name="Mms" extends="Message" />
    <subclass name="IncomingMms" extends="Mms" />
    <subclass name="OutgoingMms" extends="Mms" />


</hibernate-mapping>

【问题讨论】:

  • 您是否为 Hibernate 开启了 SQL 日志记录?愚蠢的问题:您对该实体的 ID 生成策略是什么?
  • 我还没有打开 SQL 日志记录。这显然是个好主意。生成策略是“分配的”。
  • 代码看起来不错。请向我们展示您对 Chargeables 的注释或提供映射文件。看看你有什么。
  • 由于我的网络限制无法访问,请在此处添加。
  • 使用 mapping-xml 更新答案。

标签: multithreading hibernate session concurrency transactions


【解决方案1】:

恕我直言,这是一个隔离问题。您的第二个会话在第一个事务提交之前开始。由于默认休眠隔离级别为 read_commited,因此第二个会话无法检索实体。
您如何在 2 个线程之间传递 id?

【讨论】:

  • 客户端发布一些数据(实体),这些数据持久保存在服务器上。这篇文章的响应是持久化实体的 ID。在第二个请求中,客户端尝试通过提供从前一个请求中收到的 ID 来获取该实体。如果是隔离问题,是否可以更改隔离级别以便确认或排除?
  • 这不是真正的标准:stackoverflow.com/questions/5234240/…。不知道 Mysql 支持哪些隔离级别。关于您上面的评论,隔离问题似乎不太可能
  • 如果这是一个隔离级别问题,我认为在 READ COMMITTED 事务等待另一个事务完成时会发生超时。请参阅我写过的关于类似问题的帖子:theoptimizerdiary.com/2013/03/31/…
  • 从未见过这种情况,我想这取决于底层数据库的隔离实现。理论上它不应该等待任何东西,而是返回缺少未提交记录的东西
【解决方案2】:

如果你能看到数据库中的commit,那不可能是MySQL级别的隔离问题:控制台操作也是客户端操作,运行时你没有特殊权限,所以符合你的隔离策略选择。

在寻找解决方案时,我发现Hibernate provide some facilities to cache DB results: 实际上在那里实现了 2 级缓存。也许您的休眠安装捆绑了缓存机制?

第一个级别在每个会话的基础上工作,这与您在一个线程上运行代码时观察到的行为一致,并且似乎恰好在默认情况下被激活。我不精通 Hibernate,我不能肯定地说,但我认为您的设置将第一级缓存设置为只读模式,而您希望它处于读写模式。

【讨论】:

    猜你喜欢
    • 2011-06-25
    • 2011-01-20
    • 2011-02-15
    相关资源
    最近更新 更多