hibernate多线程阻止多个save(),JTA必要吗?

时间:2012-02-09 16:18:27

标签: java hibernate transactions synchronization jta

我正在为我的Web应用程序使用每个请求模型的hibernate会话。我的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个打开的会话在某些情况下使用hibernate加载相同的集合。

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

2 个答案:

答案 0 :(得分:0)

数据库中数据的一致性不应仅通过在自己的事务中进行原子更改的某些部分来破坏。虽然某些同步可能对您的环境起作用,但如果您需要对应用程序进行集群,或者如果有多个应用程序访问数据库,则无法解决问题。

您应该做的是在[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);

答案 1 :(得分:0)

对我有用的解决方案和我的特定应用程序要求试图避免JTA和嵌套事务:

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

  • 我打开一个hibernate会话并将其绑定到我的Web请求开头的上下文
  • 在插入之前需要测试存在的任何服务层方法都标记为synchronized,全局事务使用insert语句提交并启动新事务
  • 结束时,请求绑定到会话的事务被提交

    public synchronized 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,而且我没有使用Tomcat 6的Java EE容器。