【问题标题】:JPA and Spring transactions - please explainJPA 和 Spring 事务 - 请解释一下
【发布时间】:2010-11-22 03:22:18
【问题描述】:

今天我为我的应用程序编写了一个测试用例,以了解事务的行为方式。而且我发现没有什么能像我想象的那样起作用。

我有一个基于 Spring 的应用程序,使用 Hibernate 作为 JPA 提供程序,由 MySQL 提供支持。 我有 DAO 对象,扩展了 Spring 的 JpaDaoSupport。这些都包含在 Spring 的事务管理中。

我创建的测试用例是这样的: 1) 创建了一个实体,其中一些计数器设置为 0。 2) 然后创建了两个线程,它们都在循环中调用了一个DAO方法incrementCounter()。

我认为当 DAO 方法被事务覆盖时,其中只有一个线程(即 Spring 将负责同步)。但这已被证明是错误的假设。

通过在DAO方法中添加synchronized(临时)解决这个问题后,我发现Hibernate不存储DAO方法所做的更改,而另一个线程在find()实体时,已经旧数据。只有明确调用 this.getJpaTemplate().flush(); 才有帮助。

我还认为实体管理器会从持久化上下文的缓存中给我相同的实体实例,但这也是错误的。我检查了 hashCode() 和 equals(),它们很好 - 基于实体的业务键。

欢迎任何 cmets,因为我似乎错过了 JPA / Spring 如何处理事务的一些基本概念。

  1. DAO 方法应该是synchronized吗?
  2. 我应该在每个 DAO 方法结束时调用 flush() 吗?
  3. spring 是否负责使对 DAO 方法的调用以事务方式运行? (即不要让两个线程同时处理同一个实体)
  4. 如果没有,我该如何实现?

请注意,在我的测试用例中,我使用单个 DAO 对象,但这应该没问题,因为 Spring 的 bean 是单例 - 对吧?

感谢您的帮助。

public class EntityDaoImpl extends JpaDaoSupport implements EntityDao {

public synchronized void incrementCounter( String znacka ) 
{

  String threadName = Thread.currentThread().getName();
  log.info(threadName + " entering do incrementCounter().");

  Entity ent = this.getJpaTemplate().find( Entity.class, znacka );
  log.info("Found an entity "+ent.getZnacka()+"/"+ent.hashCode()+" - " + ObjectUtils.identityToString( ent ) );
  log.info(threadName + ": Actual count: "+ent.getCount() );

  ent.setCount( ent.getCount() + 5 );

  int sleepTime = threadName.endsWith("A") ? 700 : 50;
  try { Thread.sleep( sleepTime ); }
  catch( InterruptedException ex ) {  }

  ent.setCount( ent.getCount() + 5 );
  this.getJpaTemplate().flush();

  log.info(threadName + " leaving incrementCounter().");
}
}

没有synchronizedflush(),这给了我这样的输出

Thread A: Actual count: 220
...
Thread B: Actual count: 220
...
Thread A: Actual count: 240
...
Thread B: Actual count: 250
...
Thread A: Actual count: 250

...等,意味着一个线程已经覆盖了另一个线程的更改。

【问题讨论】:

    标签: hibernate spring jpa transactions persistence


    【解决方案1】:

    我以为当DAO方法被事务覆盖时,里面只有一个线程

    根据您的描述,对 incrementCounter 的每次调用都应在其自己的事务中运行,但线程是同时运行的,因此您将有两个事务同时运行。

    (即,Spring 将负责同步)。

    听起来您将事务与同步混淆了。仅仅因为两个线程正在运行它们自己的事务并不意味着这些事务将相互序列化,除非您的事务隔离级别设置为 SERIALIZABLE。

    我还认为实体管理器会从持久化上下文的缓存中给我相同的实体实例

    是的,在持久化上下文的范围内。如果您的持久性上下文具有事务范围,那么您只能保证在同一事务中获得相同的实体实例,即在一次增量计数器的调用中。

    是否应该同步 DAO 方法?

    正如其他帖子中提到的,DAO 方法通常是 CRUD 方法,并且您通常不会同步,因为 DAO 只是选择/更新/等。并且不知道您正在做什么的更大背景。

    spring 是否负责对 DAO 方法的调用进行事务处理? (即不让两个线程同时处理同一个实体)

    同样,事务!= 同步。

    如果没有,我该如何实现?

    正如其他帖子中提到的,您可以在 Java 级别进行同步或将事务隔离级别设置为 SERIALIZABLE。另一种选择是使用 SELECT FOR UPDATE 语法(例如 Hibernate 中的 LockMode.UPGRADE)来通知数据库您打算进行更改并且数据库应该锁定行。

    如果你不依赖于悲观锁,你可以实现乐观锁,例如使用 Hibernate 版本控制。在您的具体 incrementCounter 示例中,您会遇到很多乐观锁失败,但人们会假设真实的应用程序不会那样做。

    【讨论】:

      【解决方案2】:

      顺便说一句,在我开发过的大多数应用程序中,事务通常在 DAO 之上的服务层声明。这意味着,如果您在不同的实体中执行一些插入操作,它可能具有事务语义。

      答案取决于您的应用:

      • 如果您预计对同一实体有很多争用,您可以使用pessimistic locking,它将获得一个数据库锁,这样另一个线程就不能在顶部进行更新。与您的同步 DAO 类似,这将限制应用程序的整体性能,您需要注意避免死锁
      • 如果您预计这种情况很少发生,请使用乐观锁定,该锁定将维护版本列,如果更新丢失,则会引发异常以在应用程序代码中处理。

      【讨论】:

        【解决方案3】:
        1. 是否应该同步 DAO 方法?我的通常不是,因为它们没有任何状态。没必要。
        2. 我应该在每个 DAO 方法结束时调用 flush() 吗?不,我不这样做。
        3. Spring 是否负责对 DAO 方法的调用进行事务处理? (即不让两个线程同时使用同一个实体)。我认为您混淆了事务、隔离和同步。
        4. 如果没有,我该如何实现?我认为您必须担心同步和隔离。

        您的示例不是我认为的 DAO。我认为您的测试确实不是正确的成语。如果我有一个对象 Foo,我将有一个 FooDao 接口来声明该对象的 CRUD 方法:

        public interface FooDao
        {
            List<Foo> find();
            Foo find(Serializable id);
            void saveOrUpdate(Foo foo);
            void delete(Foo foo);
        }
        

        可以编写一个通用的 DAO,正如您从示例中猜到的那样。

        Spring 通常有一个服务层,它使用域和持久性对象来实现用例。这就是需要声明事务的地方,因为工作单元与用例相关联,而不是与 DAO 相关联。 DAO 无法知道它是否是更大交易的一部分。这就是通常在服务上声明事务的原因。

        【讨论】:

        • 感谢您的回答。我开始将用例放到 DAO 中,因为有了 JpaDaoSupport,DAO 对象只是一个用于将引用转换为正确类型的薄壳。如果我理解得很好,对于用例的另一层,我最终会使用其他 Spring 管理的 bean,实现用例,使用 Spring 事务而不是 DAO 进行 AOP;所以它在技术上是一样的,不是吗?你会推荐什么方法来处理同步?你会推荐一些 Spring 2.5 + JPA 教程应用程序吗?谢谢
        • 我已经引用了 Spring 的成语给你——我建议你遵循它,特别是如果这是你第一次使用 Spring。通常我在 Tomcat 或 JBOSS 或 WebLogic 等 Java EE 应用服务器上运行 Spring,因此每个请求都在其自己的线程中运行。应用服务器将请求排队等待处理。我推荐 Spring 参考文档。
        • 嗯,我所做的也是在 Tomcat 上运行的 Web 应用程序的后端。 Spring 参考很好而且很广泛,但仍然没有包含同步的技巧——至少我还没有找到。我想 EntityManager#lock(Object entity, LockModeType lockMode) 是我应该看的,对吧?
        • 不,看看事务管理器中的隔离。如果您想了解线程,请阅读 Brian Goetz 的书。我相信你在同步方面已经偏离了基础。您的示例不能代表 Spring 应用程序的工作方式。
        • 我非常了解线程、事务和隔离级别——来自其他(非 Java)技术。我不知道如何使用 Spring 将所有这些组合/配置在一起。谢谢你指点我,我发现有几页要读。如果你手头有一些链接,请分享它。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-03-10
        • 2013-04-10
        • 2010-10-24
        • 2020-09-06
        • 1970-01-01
        • 2017-01-03
        • 1970-01-01
        相关资源
        最近更新 更多