hibernate多线程防止多次save(),JTA有必要吗?

发布于 2025-01-03 16:40:46 字数 1423 浏览 4 评论 0原文

我正在为我的 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) 是否存在,并且仅在实体 (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,并像现在一样在请求结束时提交事务?

I'm using a hibernate session per request model for my web application. My jdbc transaction begins at the beginning of each web request and commited at the end.

// Non-managed environment idiom

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

I'm faced with the problem where I am testing for existence of an entity (A) based on several parameters and doing an insert only if it doesn't exist.

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

the problem is that synchronization does not help because the call to MyEntityADAO.save() does not actually write to the database when the currently running thread exits the method and releases the lock, the write to the database occurs after the transaction is commited which is generally what I need for my application except for a few scenarios. The code above causes multiple records saved with same parameters in a multithreaded environment.

I've tried to execute the save code in its own new session and transaction:

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

   }
}

the above causes problems with 2 open sessions loading the same collection with hibernate in some instances.

Should I enclose every DAO call in its own transaction and use transaction propagation with JTA? Is there a way to avoid JTA? Is it alright to commit transaction associated with the main session after the call to MyEntityADAO.save() and call beginTransaction on the main session right after and have the transaction commited at the end of the request as it does now?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

溺孤伤于心 2025-01-10 16:40:46

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

您应该做的是在数据库中对[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);

The coherence of the data in database should not be compromised by doing only some part of an atomic change in its own transaction. And although some synchronization might work on your environment, if you need to cluster your app, or if several applications acces the database, it won't solve the problem.

What you should do is to put a unique constraint in the database on [param1 - param2]. That will cause one of the two transactions to rollback if there is a race condition.

If you choose to still isolate the check/insert code in its own transaction (because it's not a problem if that succeeds and the outer transaction fails), I don't see how JTA would be a problem. Supposing you're using EJBs or Spring, just put this method in its own EJB/bean, and mark the method as transactional, with the REQUIRES_NEW propagation.

The code would thus look like this:

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

If you can't synchronize checkIfExistOrCreate, then try calling it, catch any exception that it could throw, and retry calling it:

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);
入画浅相思 2025-01-10 16:40:46

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

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

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

    公共同步 myMethod(param1, param2) {

     MyEntityA 实体 = MyEntityADAO.findEntity(param1, param2)
     如果(实体==空){
          实体 = .../创建实体
    
          MyEntityADAO.save(实体);
          HibernateUtil.getCurrentSession().getTransaction().commit();
          HibernateUtil.getCurrentSession().getTransaction().begin();
    
    
     }
    

    }

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

The solution that worked for me and my particular app requirements trying to avoid JTA and nested transactions:

Using ManagedSessionContext because org.hibernate.context.ThreadLocalSessionContext will close and create a new session for each transaction. You will run into problems with entities that have collections associated if you load those entities in multiple open sessions (when you will create multiple transactions for one request).

  • I open a hibernate session and bind it to the context in the beginning of my web request
  • Any service layer method that needs test for existence prior to insert is marked synchronized, the global transaction is commited with the insert statement and a new transaction is started
  • At the end the request the transaction bound to the session is commited

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

    }

I know its ugly and will not work for everybody in every scenerio, but after doing a very intense search on transaction management, isolation levels, locking, versioning that is the only solution I have found that worked for me. I am not using Spring, and I'm not using a Java EE container, using Tomcat 6.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文