今天我为我的应用程序编写了一个测试用例,以了解事务的行为方式。我发现没有什么能像我想象的那样发挥作用。
我有一个基于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(),它们很好 - 基于实体的bussines密钥。
欢迎任何评论,因为我似乎错过了JPA / Spring如何处理事务的一些基本概念。
synchronized
吗?请注意,在我的测试用例中,我使用了一个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().");
}
}
没有synchronized
和flush()
,这给了我输出
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
...等,这意味着一个线程已经覆盖了另一个线程的更改。
答案 0 :(得分:3)
你的例子不是我认为的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无法知道它是否是更大交易的一部分。这就是为什么交易通常在服务上宣布的原因。
答案 1 :(得分:3)
我认为当DAO方法被事务覆盖时,其中只有一个线程
根据您的描述,对incrementCounter的每次调用都应该在自己的事务中运行,但是线程并发运行,因此您可以同时运行两个事务。
(即,Spring将负责同步)。
这听起来像是在混淆交易与同步。仅仅因为两个线程正在运行它们自己的事务并不意味着事务将相互序列化,除非您的事务隔离级别设置为SERIALIZABLE。
我还认为实体管理器会从持久化上下文的缓存中给我相同的实体实例
是的,在持久化上下文的范围内。如果您的持久化上下文具有事务范围,那么您只能保证在同一事务中获得相同的实体实例,即在incrementCounter的一次调用中。
DAO方法应该同步吗?
正如其他帖子中提到的,通常DAO方法是CRUD方法,并且您通常不会同步,因为DAO只是选择/更新/等。并且不知道你正在做的事情的更大背景。
Spring是否负责调用DAO方法的事务处理? (即不要让两个线程同时使用同一个实体)
再次,交易!=同步。
如果没有,我该如何实现?
如其他帖子中所述,您可以在Java级别执行同步或将事务隔离级别设置为SERIALIZABLE。另一种选择是使用SELECT FOR UPDATE语法(例如Hibernate中的LockMode.UPGRADE)来通知数据库您打算进行更改,数据库应该锁定行。
如果你不依赖于悲观锁定,你可以实现乐观锁定,例如:使用Hibernate版本控制。您可以使用特定的incrementCounter示例获得大量乐观锁定失败,但可以假设实际应用程序不会像那样。
答案 2 :(得分:1)
除了我曾经使用的大多数应用程序之外,事务通常在DAO之上的服务层声明。这意味着如果你在不同的实体中进行一些插入,它可以具有事务语义。
答案取决于您的申请: