【问题标题】:hibernate multiple threads prevent multiple save(), JTA necessary?休眠多个线程防止多个save(),JTA必要吗?
【发布时间】:2012-02-09 16:18:27
【问题描述】:

我正在为我的 Web 应用程序使用每个请求模型的休眠会话。我的 jdbc 事务从每个 Web 请求的开头开始,并在最后提交。

// 非托管环境成语

Session sess = factory.openSession();
Transaction tx = null;
try {
    tx = sess.beginTransaction();

    // do some work
    ...

    tx.commit();
}
catch (RuntimeException e) {
    if (tx != null) tx.rollback();
    throw e; // or display error message
}
finally {
    sess.close();
}

我面临的问题是,我根据几个参数测试实体 (A) 是否存在,并且仅在它不存在时才进行插入。

public synchronized myMethod(param1, param2) {
    MyEntityA entity = MyEntityADAO.findEntity(param1, param2)
    if (entity == null) {
        entity = .../create entity
        MyEntityADAO.save(entity);
    }
}

问题是同步没有帮助,因为当当前运行的线程退出方法并释放锁时,对 MyEntityADAO.save() 的调用实际上并没有写入数据库,写入数据库发生在事务完成之后除了少数情况外,这通常是我的应用程序所需要的。上面的代码会导致在多线程环境中使用相同的参数保存多条记录。

我尝试在自己的新会话和事务中执行保存代码:

public synchronized myMethod(param1, param2) {
    MyEntityA entity = MyEntityADAO.findEntity(param1, param2)
    if (entity == null) {
        entity = .../create entity
        Session session = HibernateUtil.createSession();
        MyEntityADAO.save(entity);
        Transaction t = session.beginTransaction();

   }
}

在某些情况下,上述问题会导致 2 个打开的会话使用休眠加载同一个集合。

我是否应该将每个 DAO 调用包含在其自己的事务中,并使用 JTA 进行事务传播?有没有办法避免 JTA?是否可以在调用 MyEntityADAO.save() 之后提交与主会话关联的事务并在之后立即在主会话上调用 beginTransaction 并像现在一样在请求结束时提交事务?

【问题讨论】:

    标签: java hibernate transactions synchronization jta


    【解决方案1】:

    数据库中数据的一致性不应因仅在其自身事务中进行原子更改的某些部分而受到损害。尽管某些同步可能在您的环境中起作用,但如果您需要集群化您的应用程序,或者如果多个应用程序访问数据库,它不会解决问题。

    您应该做的是在[param1 - param2] 上的数据库中放置一个唯一约束。如果存在竞争条件,这将导致两个事务之一回滚。

    如果您选择仍然在其自己的事务中隔离检查/插入代码(因为如果成功并且外部事务失败则不是问题),我看不出 JTA 会有什么问题。假设您使用的是 EJB 或 Spring,只需将此方法放在其自己的 EJB/bean 中,并将该方法标记为事务性,并使用 REQUIRES_NEW 传播。

    因此代码如下所示:

    // some code
    Long id = myBean.checkIfExistOrCreate(param1, param2); // this methos call starts a new transaction
    // now we're sure that the entity exists. Load it in the current session.
    MyEntity e = em.find(MyEntity.class, id);
    

    如果您无法同步checkIfExistOrCreate,请尝试调用它,捕获它可能抛出的任何异常,然后重试调用它:

    Long id = null;
    try {
        id = myBean.checkIfExistOrCreate(param1, param2);
    }
    catch (Exception e) { // a well-defined exception would be better
        // the transaction roled back: retry
        id = myBean.checkIfExistOrCreate(param1, param2);
    }
    // now we're sure that the entity exists. Load it in the current session.
    MyEntity e = em.find(MyEntity.class, id);
    

    【讨论】:

    • 感谢您的意见。我确实设置了约束,但我没有在我的 dao/service 层中捕获任何休眠/jdbc 异常。似乎是个好主意。谢谢。
    • 我正在捕获 ConstraintViolationException 并尝试在 catch 块中获取现有实体并获得 null ..当事务在底层数据库中标记为“已完成”时是否存在一些中间阶段(mysql) 并且表约束已“更新”但数据库不会从表中返回新行?
    • 我已经尝试在 save() 之后的同步方法/块中刷新会话。减少异常但仍然没有解决问题
    • 冲洗无济于事。事务通常彼此隔离运行,并且在传统的默认隔离级别 (READ_COMMITTED) 下,事务 B 在 A 提交之前不会看到事务 A 在数据库中插入的任何内容。
    • 使用 READ_COMMITED 隔离级别以使您的解决方案正常工作,您仍然需要在退出同步块之前提交正在执行 INSERT 的事务,在您的 catch 块中存在的测试将导致 null。
    【解决方案2】:

    适用于我的解决方案和我的特定应用要求,试图避免 JTA 和嵌套事务:

    使用 ManagedSessionContext 因为 org.hibernate.context.ThreadLocalSessionContext 将关闭并为每个事务创建一个新会话。如果您在多个打开的会话中加载这些实体(当您将为一个请求创建多个事务时),您将遇到与集合相关联的实体的问题。

    • 我打开一个休眠会话并将其绑定到我的网络请求开始时的上下文
    • 在插入之前需要测试是否存在的任何服务层方法都标记为已同步,使用插入语句提交全局事务并启动新事务
    • 在请求的最后,绑定到会话的事务被提交

      公共同步 myMethod(param1, param2) {

       MyEntityA entity = MyEntityADAO.findEntity(param1, param2)
       if (entity == null) {
            entity = .../create entity
      
            MyEntityADAO.save(entity);
            HibernateUtil.getCurrentSession().getTransaction().commit();
            HibernateUtil.getCurrentSession().getTransaction().begin();
      
      
       }
      

      }

    我知道它很丑陋,并且不适用于每个场景中的每个人,但是在对事务管理、隔离级别、锁定、版本控制进行了非常深入的搜索之后,这是我发现的唯一对我有用的解决方案。我没有使用 Spring,也没有使用 Java EE 容器,而是使用 Tomcat 6。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-05
      • 1970-01-01
      • 1970-01-01
      • 2015-05-23
      相关资源
      最近更新 更多