Hibernate 乐观锁 org.hibernate.StaleObjectStateException 错误
Hibernate 乐观锁 org.hibernate.StaleObjectStateException 异常
Hibernate乐观锁大多是基于数据版本(version)记录机制实现的。所谓的数据版本,就是为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个version字段来实现。
读取数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本信息进行对比,如果提交的数据版本号大于数据库表中的当前版本号,则予以更新,否则认为是过期数据。
乐观锁 测试:
User 类:
User.hbm.xml映射文件:
测试代码:
运行以上代码,会抛出如下异常:
再次运行以上代码,不会抛出异常,会对数据库中记录进行更新。
原因:
在第一次测试之后,会将数据库表中id=1的记录的name属性值设为”AAA”(由于事务tx2先执行)。再次测试,由于Hibernate的缓存(在调用load方法时会将查询到的对象进行缓存),执行事务tx2时不会发出sql语句(因为更新后的值与加载到的值相同),也就意味着数据库表中对应记录的version值没有加1,所以事务tx可以提交成功。如果更新user的name属性,关键要考虑与load得到的对象相比是否有变化,是否需要发出sql语句,如果两个事务都要发出sql语句,就会冲突。Hibernate本身发出sql语句是没有错的,但在关键数据库中会发生冲突。
要注意的是:上述测试都是在“lazy=false”的条件下执行的,如果将“lazy=false”去掉,默认采用延迟加载,那么不会出现冲突问题,发出的sql语句如下:
即每次都是查询更新,因为load时采用延迟加载,得到的只是一个代理对象,当对user进行设置时,会发出sql语句查询,紧接着就是更新语句。所以两次select查询得到的version值不同,不会冲突。
我只是分析了一下原因,没有说必须使用延迟加载,for update是悲观锁机制吧???
Hibernate乐观锁大多是基于数据版本(version)记录机制实现的。所谓的数据版本,就是为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个version字段来实现。
读取数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本信息进行对比,如果提交的数据版本号大于数据库表中的当前版本号,则予以更新,否则认为是过期数据。
乐观锁 测试:
User 类:
public class User implements Serializable{ private int id; private String name; private int version; ... }
User.hbm.xml映射文件:
<hibernate-mapping> <class name="po.User" table="t_user" lazy="false" optimistic-lock="version"> <id name="id"> <generator class="native" /> </id> <version name="version" column="version" type="integer"/> <property name="name" /> </class> </hibernate-mapping>
测试代码:
Session session = sessionFactory.openSession(); Session session2 = sessionFactory.openSession(); User user = (User)session.load(User.class, new Integer(1)); User user2 = (User)session2.load(User.class, new Integer(1)); Transaction tx = session.beginTransaction(); Transaction tx2 = session2.beginTransaction(); user2.setName("AAA"); tx2.commit(); user.setName("BBB"); tx.commit();
运行以上代码,会抛出如下异常:
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
再次运行以上代码,不会抛出异常,会对数据库中记录进行更新。
原因:
在第一次测试之后,会将数据库表中id=1的记录的name属性值设为”AAA”(由于事务tx2先执行)。再次测试,由于Hibernate的缓存(在调用load方法时会将查询到的对象进行缓存),执行事务tx2时不会发出sql语句(因为更新后的值与加载到的值相同),也就意味着数据库表中对应记录的version值没有加1,所以事务tx可以提交成功。如果更新user的name属性,关键要考虑与load得到的对象相比是否有变化,是否需要发出sql语句,如果两个事务都要发出sql语句,就会冲突。Hibernate本身发出sql语句是没有错的,但在关键数据库中会发生冲突。
要注意的是:上述测试都是在“lazy=false”的条件下执行的,如果将“lazy=false”去掉,默认采用延迟加载,那么不会出现冲突问题,发出的sql语句如下:
Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.name as name0_0_ from t_user user0_ where user0_.id=? Hibernate: update t_user set version=?, name=? where id=? and version=? Hibernate: select user0_.id as id0_0_, user0_.version as version0_0_, user0_.name as name0_0_ from t_user user0_ where user0_.id=? Hibernate: update t_user set version=?, name=? where id=? and version=?
即每次都是查询更新,因为load时采用延迟加载,得到的只是一个代理对象,当对user进行设置时,会发出sql语句查询,紧接着就是更新语句。所以两次select查询得到的version值不同,不会冲突。
1 楼
hatedance
2010-08-02
谢谢lz的帖子,写的清晰明了。
2 楼
pclfs1983
2010-08-02
学习了 3q
3 楼
gwpking8419
2010-08-03
只有原因,没有总结吗
4 楼
phenom
2010-08-03
有些使用Select for update解决的.
照LZ的分析,必须使用延迟加载了?
照LZ的分析,必须使用延迟加载了?
5 楼
lijiejava
2010-08-03
phenom 写道
有些使用Select for update解决的.
照LZ的分析,必须使用延迟加载了?
照LZ的分析,必须使用延迟加载了?
我只是分析了一下原因,没有说必须使用延迟加载,for update是悲观锁机制吧???