简体   繁体   English

JPA和Spring交易-请解释

[英]JPA and Spring transactions - please explain

today I have coded a test case for my application, to see how transactions behave. 今天,我已经为我的应用程序编写了一个测试用例,以查看事务的行为。 And I have found that nothing works the way I thought it does. 而且我发现没有任何东西能像我想象的那样起作用。

I have a Spring-based application using Hibernate as JPA provider, backed by MySQL. 我有一个基于Spring的应用程序,使用Hibernate作为JPA提供程序,并由MySQL支持。 I have DAO objects, extending Spring's JpaDaoSupport. 我有DAO对象,扩展了Spring的JpaDaoSupport。 These are covered by Spring's transaction management. 这些都包含在Spring的事务管理中。

The test case I've created works like this: 1) An entity is created, with some counter set to 0. 2) Then two threads are created, which both call a DAO method incrementCounter() in a loop. 我创建的测试用例的工作原理如下:1)创建一个实体,并将其计数器设置为0。2)然后创建两个线程,这两个线程都在循环中调用DAO方法增量计数器()。

I thought that when DAO methods are covered with transaction, then only one thread will be inside it (ie, Spring will take care of synchronization). 我以为,当DAO方法被事务覆盖时,里面只有一个线程(即Spring将负责同步)。 But this has proven to be false assumption. 但这已被证明是错误的假设。

After (temporary) solving this by adding synchronized to the DAO method, I found out that Hibernate does not store the changes made by the DAO method, and the other thread, when find()ing the entity, has old data. 在通过向DAO方法添加synchronized来解决(临时)问题之后,我发现Hibernate不会存储DAO方法所做的更改,并且在对实体进行find()处理时,另一个线程拥有旧数据。 Only explicit call of this.getJpaTemplate().flush(); 仅显式调用this.getJpaTemplate().flush(); helped. 帮助。

I also thought that entity manager would give me the same entity instance from the cache of the persistence context, but this is also false. 我还认为实体管理器会从持久性上下文的缓存中为我提供相同的实体实例,但这也是错误的。 I have checked hashCode() and equals(), and they are fine - based on entity's bussines key. 我已经检查了hashCode()和equals(),它们很好-基于实体的bussines键。

Any comments welcome, as it seems I miss some basic concepts of how JPA / Spring works with transactions. 欢迎任何评论,因为我似乎错过了JPA / Spring如何与事务配合使用的一些基本概念。

  1. Should DAO methods be synchronized ? DAO方法应该synchronized吗?
  2. Should I call flush() at the end of every DAO method? 我应该在每个DAO方法的末尾调用flush()吗?
  3. Is spring responsible for making the calls to DAO methods behave transactionally? spring是否负责使对DAO方法的调用具有事务性? (ie not letting two threads work with the same entity at the same time) (即不要让两个线程同时使用同一实体)
  4. If not, how do I achieve this? 如果没有,我该如何实现?

Note that in my test case, I use a single DAO object, but that should be OK as Spring's beans are singletons - right? 请注意,在我的测试用例中,我使用了一个DAO对象,但这应该没问题,因为Spring的bean是单例的-对吗?

Thanks for any help. 谢谢你的帮助。

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

Without synchronized and flush() , this was giving me output like 没有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

...etc, meaning that one thread has overwriten the changes from the other. ...等等,这意味着一个线程已经覆盖了另一个线程的更改。

  1. Should DAO methods be synchronized? DAO方法应该同步吗? Mine usually are not, because they don't have any state. 我的通常不是,因为他们没有任何状态。 No need. 没必要。
  2. Should I call flush() at the end of every DAO method? 我应该在每个DAO方法的末尾调用flush()吗? No, I don't do that. 不,我不这样做。
  3. Is Spring responsible for making the calls to DAO methods behave transactionally? Spring是否负责使对DAO方法的调用具有事务性? (ie not letting two threads work with the same entity at the same time). (即不要让两个线程同时使用同一实体)。 I think you're confusing transactions, isolation, and synchronization. 我认为您在混淆事务,隔离和同步。
  4. If not, how do I achieve this? 如果没有,我该如何实现? I think you have to worry about synchronization and isolation. 我认为您必须担心同步和隔离。

Your example is not what I would think of as a DAO. 您的示例不是我认为的DAO。 I think your test really isn't the proper idiom. 我认为您的考试确实不是适当的习语。 If I had an object Foo, I'd have a FooDao interface that would declare the CRUD methods for that object: 如果我有一个对象Foo,我将有一个FooDao接口,该接口将声明该对象的CRUD方法:

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

It's possible to write a generic DAO, as you'd guess from the example. 正如您从示例中猜测的那样,可以编写通用DAO。

Spring usually has a service layer that uses domain and persistence objects to implement use cases. Spring通常具有一个服务层,该服务层使用域和持久性对象来实现用例。 That's where transactions need to be declared, because the unit of work is associated with a use case and not a DAO. 那是需要声明事务的地方,因为工作单元是与用例而不是DAO相关的。 A DAO has no way of knowing whether or not it's part of a larger transaction. DAO无法知道它是否是较大事务的一部分。 That's why transactions are usually declared on the service. 这就是通常在服务上声明事务的原因。

I thought that when DAO methods are covered with transaction, then only one thread will be inside it 我认为当DAO方法被事务覆盖时,里面只有一个线程

Based on your description, each call to incrementCounter should be running in its own transaction, but the threads are running concurrently so you would have two transactions running concurrently. 根据您的描述,对crementCounter的每次调用应在其自己的事务中运行,但是线程正在同时运行,因此您将有两个事务在同时运行。

(ie, Spring will take care of synchronization). (即Spring将负责同步)。

It sounds like you're confusing transactions with synchronization. 听起来您好像在混淆同步交易。 Just because two threads are running their own transaction does not mean that the transactions will be serialized with respect to each other, unless your transaction isolation level is set to SERIALIZABLE. 仅仅因为两个线程正在运行各自的事务,并不意味着事务将相对于彼此进行序列化,除非将事务隔离级别设置为SERIALIZABLE。

I also thought that entity manager would give me the same entity instance from the cache of the persistence context 我还认为实体管理器会从持久性上下文的缓存中为我提供相同的实体实例

Yes, within the scope of the persistence context. 是的,在持久性上下文的范围内。 If your persistence context has transaction scope, then you're only guaranteed to get the same entity instance within the same transaction, ie within one invocation of incrementCounter. 如果您的持久性上下文具有事务范围,则只能保证在同一事务内(即在一次incrementCounter内)获得同一实体实例。

Should DAO methods be synchronized? DAO方法应该同步吗?

As mentioned in other posts, typically DAO methods are CRUD methods, and you don't usually synchronize since the DAO is simply selecting/updating/etc. 如其他文章所述,DAO方法通常是CRUD方法,由于DAO只是选择/更新/等,因此您通常不进行同步。 and doesn't know the larger context of what you're doing. 并且不知道您在做什么的更大范围。

Is spring responsible for making the calls to DAO methods behave transactionally? spring是否负责使对DAO方法的调用具有事务性? (ie not letting two threads work with the same entity at the same time) (即不要让两个线程同时使用同一实体)

Again, transaction != synchronization. 同样,事务!=同步。

If not, how do I achieve this? 如果没有,我该如何实现?

As mentioned in other posts, you can do synchronization at the Java level or set your transaction isolation level to SERIALIZABLE. 如其他文章所述,您可以在Java级别进行同步,也可以将事务隔离级别设置为SERIALIZABLE。 Another option would be to use SELECT FOR UPDATE syntax (eg LockMode.UPGRADE in Hibernate) to inform the database that you intend to make a change and the database should lock the rows. 另一个选择是使用SELECT FOR UPDATE语法(例如Hibernate中的LockMode.UPGRADE)通知数据库您打算进行更改,并且数据库应锁定行。

If you're not tied to pessimistic locking, you can implement optimistic locking, eg using Hibernate versioning. 如果您不局限于悲观锁定,则可以实现乐观锁定,例如使用Hibernate版本控制。 You would get a lot of optimistic lock failures with your specific incrementCounter example, but one would assume a real application would not behave like that. 使用您的特定crementCounter示例,您将获得很多乐观的锁定失败,但是人们会假设一个实际的应用程序不会表现出那样的行为。

As an aside in most apps I have worked on, transactions are usually declared at a service layer above DAOs. 除了我开发的大多数应用程序外,事务通常在DAO之上的服务层进行声明。 This means if you're doing a few inserts across different entities it can have transactional semantics. 这意味着,如果您要在不同实体之间进行一些插入操作,则可能具有事务语义。

The answer depends on your application: 答案取决于您的应用程序:

  • If you're expecting a lot of contention over the same entity, you can use pessimistic locking which will obtain a database lock so that another thread can't update over the top. 如果您期望在同一个实体上发生很多争用,则可以使用悲观锁定 ,这将获得数据库锁定,以便另一个线程无法在顶部更新。 Similar to your synchronized DAO this will restrict the overall performance of the application and you'll need to take care to avoid deadlocks 与同步DAO相似,这将限制应用程序的整体性能,并且您需要注意避免死锁
  • If you're expecting this to occur infrequently use optimistic locking which will maintain a version column and if an update is lost an exception is thrown to be handled in application code. 如果您不希望发生这种情况,请使用开放式锁定,该锁定将维护版本列,并且如果更新丢失,则会在应用程序代码中引发异常处理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM