Hibernate与悲观锁、乐观锁小总结

来源:互联网 发布:c语言的图形函数库 编辑:程序博客网 时间:2024/06/11 17:33

首先需要说明的是悲观锁和乐观锁不是hibernate提出的,而是JDBC里固有的两种事务加锁策略,这点在 javax.persistence.LockModeType 这个类有体现,hibernate实现和扩展了这两种策略。


4.0以上,有一个新特性,就是update语句的优化。

在我的hibernate中,如果修改了持久化对象的属性,hibernate会记录是哪个属性修改了,原理是通过setter,从而在update的时候只更新被修改的属性,而不是全部更新。

但是前提是必须是持久化对象。


悲观锁,网上介绍的很多,百度文库这个文章比较详细一点,以下总结几点

  • 依赖数据库,需要其支持lock机制
  • 需显示lock,会生成一条查询的sql,并且hibernate会将其放入PL(持久化池),放入PL后的对象属性修改(setter)会引起更新操作
  • 对于4.0以上版本,持久化对象的属性修改会被hibernate记录,并在更新时仅仅更新修改过的属性,而不是更新所有属性
  • 无法发现和处理第二类更新丢失,并且多线程并发有性能问题
  • 因上面几点,4.0以后中这种类型的锁已经无多少用途,基本是保留API,保留hibernate可以使用悲观锁的机制

第一类更新丢失:事务A和事务B同时访问同一个数据,事务B先提交修改,事务A回滚操作。导致事务B的修改丢失

第二类更新丢失:事务A和事务B同时访问同一个数据,事务B先提交修改,事务A再提交。导致事务B的修改被覆盖


悲观锁不能发现第二类更新丢失问题,因为当A和B都获取同一行数据后,分别提交修改,因为是瞬时对象,即使merge,也可能出现覆盖的情况,因为merge会先查询数据库,假设这时A已经提交更新,那么就会读出A更新后的数据,然后merge方法会比较每个属性是否一致,不一致就会更新,从而导致更新丢失。


另外,使用merge方法时,如果当前session中已经存在一个id相同的持久化对象时,会抛出NonUniqueObjectException,可以使用session.clean 清除一级缓存。


个人认为,在多线程情况下使用悲观锁时应是如下形式:

Message message = (Message) session.get(Message.class , this.message.getUuid() , LockOptions.UPGRADE);this.message.setTitle(this.message.getTitle()+Math.random());BeanUtils.copyProperties(message, this.message);

利用4.0的特性,load或get一个对象,并加更新锁,然后修改属性,这里使用BeanUtils,当有属性调用setter,并且set一个不同的值时,就会触发该属性的更新操作。


乐观锁,通过程序机制来判断是否可以更新记录,推荐使用版本号验证机制,只是增加一个字段,但是性能较其他机制高很多,因仅需判断版本号就可以实现乐观锁机制了。

乐观锁定这个文章介绍很详细,大家可以去看看。


值得一说的是版本号字段的类型,推荐使用整数型integer,这也是官方推荐和默认的,而不建议使用timestamp,原因是有些数据库timestamp对应的类型精度不够,会产生版本无法匹配的问题和集群安全问题。


当使用乐观锁时,hibernate会生成类似下面的语句:

update        hibernate.t_informations     set        version=?,        title=?,        content=?     where        uuid=?         and version=?
注意到where语句多出一条版本号的匹配,估计hibernate是这样判断的,当使用乐观锁时,如果这条语句更新了一条数据(有id条件,所以最多只能更新一条),就表示版本验证成功,反之不成功,抛出StaleObjectStateException,而且因为根本没有更新数据,所以不必回滚。

以上是版本号实现的乐观锁,估计其它方式也类似,本人没有测试,如果大家有什么不同意见,欢迎留言和讨论~

原创粉丝点击