Hibernate乐观锁和悲观锁详解

悲观锁:
悲观锁是对数据库而言的,数据库悲观了,他感觉每一个对他操作的程序都有可能产生并发。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
一个最简单的数据库悲观锁:

select * from tablename for update;

我们在hibernate中设置悲观锁:

session.load(objname.class,id,LockMode.UPGRADE);

生成的sql:

Hibernate:
select
book0_.bid as bid1_0_,
book0_.version as version1_0_,
book0_.bookname as bookname1_0_
from
t_book book0_
where
book0_.bid=? for update

Hibernate 的加锁模式有:

模式 解释
LockMode.NONE 无锁机制。
LockMode.WRITE Hibernate 在 Insert 和 Update 记录的时候会自动获取。
LockMode.READ Hibernate 在读取记录的时候会自动获取。

以上这三种锁机制一般由 Hibernate 内部使用,如Hibernate 为了保证 Update过程中对象不会被外界修改,会在 save 方法实现中自动为目标对象加上 WRITE 锁。
LockMode.UPGRADE :利用数据库的 for update 子句加锁。
LockMode. UPGRADE_NOWAIT : Oracle 的特定实现,利用 Oracle 的 for update nowait 子句实现加锁。
上面这两种锁机制是我们在应用层较为常用的。
get方法第三个参数"lockMode"或"lockOptions",注意在Hibernate3.6以上的版本中"LockMode"已经不建议使用。

乐观锁:
乐观锁并不是真正意义上的锁,大多数情况下是采用数据版本(version)的方式实现,一般在数据库中加入一个version字段,在读取数据的时候就将version读取出来,在保存数据的时候判断version的值是否小于数据库的version值,如果小于则不予更新,否则给予更新。
设置乐观锁的方法:

  1. 基于version

  2. 基于timestamp

  3. 为遗留项目添加添加乐观锁

第一种设置:
1.在对象设置
private int version;并且给与get 和set的方法
2.设置对象配置文件
在id字段后面设置:

然后就可以了。
注意:1.version必须写在id后面 2.数据库的version不能手动去操作或者不能手动去设置setVersion 否则乐观锁失效

例子:

public void test1() {
		Session s1 = sf.openSession();
		Session s2 = sf.openSession();
		Book b1 = (Book) s1.load(Book.class, 1);
		Book b2 = (Book) s2.load(Book.class, 1);
		System.out.println("初始version:"+b1.getVersion()+"\t"+b2.getVersion());
		b1.setBookname("bookxx");
		s1.update(b1);
		s1.beginTransaction().commit();
		System.out.println("s1保存后version:"+b1.getVersion()+"\t\t"+b2.getVersion());
		b2.setBookname("bookxxx");
		s2.update(b2);
		s2.beginTransaction().commit();
	}

控制台:

Hibernate: select book0_.bid as bid1_1_, book0_.version as version1_1_, book0_.bookname as bookname1_1_, author1_.aid as aid0_0_, author1_.aname as aname0_0_ from t_book book0_, t_author author1_ where book0_.bid=author1_.aid(+) and book0_.bid=?
Hibernate: select book0_.bid as bid1_1_, book0_.version as version1_1_, book0_.bookname as bookname1_1_, author1_.aid as aid0_0_, author1_.aname as aname0_0_ from t_book book0_, t_author author1_ where book0_.bid=author1_.aid(+) and book0_.bid=?
初始version:2 2
Hibernate: update t_book set version=?, bookname=? where bid=? and version=?
s1保存后version:3 2
Hibernate: update t_book set version=?, bookname=? where bid=? and version=?

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [hibernatesql.demo.eg1entity.Book#1]

数据库:
在这里插入图片描述
这里我们模拟了多个session同时操作一个数据库中的对象,第一次修改的时候成功了,第二次因为版本号不同,所以没有执行update语句;

注意:
要注意的是,由于乐观锁定是使用系统中的程式来控制,而不是使用资料库中的锁定机制,因而如果有人特意自行更新版本讯息来越过检查,则锁定机制就会无效,例如在上例中自行更改b2的version属性,使之与数据库中的版本号相同的话就不会有错误,像这样版本号被更改,或是由于数据是由外部系统而来,因而版本资讯不受控制时,锁定机制将会有问题,设计时必须注意。
如果手工设置stu.setVersion()自行更新版本以跳过检查,则这种乐观锁就会失效,应对方法可以将Student.Java的setVersion设置成private。

第二种设置方法:
1.在对象设置
private Timestamp timestamp; //java.sql.Timestamp
2.设置对象配置文件

这种方式使用时间戳来提供乐观锁解决方式。
例子:

public void test3() {
		Session s1 = sf.openSession();
		Session s2 = sf.openSession();
		Book b1 = (Book) s1.load(Book.class, 1);
		Book b2 = (Book) s2.load(Book.class, 1);
		System.out.println("初始timestamp:"+b1.getTimestamp()+"\t"+b2.getTimestamp());
		b1.setBookname("bookxx");
		s1.update(b1);
		s1.beginTransaction().commit();
		System.out.println("s1保存后version:"+b1.getTimestamp()+"\t\t"+b2.getTimestamp());
		b2.setBookname("bookxxx");
		s2.update(b2);
		s2.beginTransaction().commit();
	}

控制台:

Hibernate: select book0_.bid as bid1_1_, book0_.timestamp as timestamp1_1_, book0_.bookname as bookname1_1_, author1_.aid as aid0_0_, author1_.aname as aname0_0_ from t_book book0_, t_author author1_ where book0_.bid=author1_.aid(+) and book0_.bid=?
Hibernate: select book0_.bid as bid1_1_, book0_.timestamp as timestamp1_1_, book0_.bookname as bookname1_1_, author1_.aid as aid0_0_, author1_.aname as aname0_0_ from t_book book0_, t_author author1_ where book0_.bid=author1_.aid(+) and book0_.bid=?
初始timestamp:2019-04-08 17:28:53.0 2019-04-08 17:28:53.0
Hibernate: update t_book set timestamp=?, bookname=? where bid=? and timestamp=?
s1保存后version:2019-04-08 17:32:42.425 2019-04-08 17:28:53.0
Hibernate: update t_book set timestamp=?, bookname=? where bid=? and timestamp=?

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [hibernatesql.demo.eg1entity.Book#1]

数据库:
在这里插入图片描述
我们看到,时间戳的显示是精确到毫秒的,所以算上网络延迟是很难很难碰撞在一起的。

第三种设置方法:
这种方法主要是用在老项目添加乐观锁
在对象配置文件class节点上设置
optimistic-lock=“version”
注:optimistic-lock(乐观锁定)(可选,默认是 version):决定乐观锁定的策略。
在 元素为类定义:
optimi(10)stic-lock=“true|false”
注:optimistic-lock(可选 — 默认为 true):指定这个属性在做更新时是否需要获得乐观锁定(optimistic lock)。换句话说,它决定这个属性发生脏数据时版本(version)的值是否增长。

遗留系统的数据库 Schema 通常是静态的,不可修改的。或者,其他应用程序也可能访问同一数据库,根本无法得知如何处理版本号,甚至时间戳。在以上的所有场景中,实现版本化不能依靠数据库表的某个特定列。在 的映射中设置 optimistic-lock=“all” 可以在没有版本或者时间戳属性映射的情况下实现版本检查,此时 Hibernate 将比较一行记录的每个字段的状态。请注意,只有当 Hibernate 能够比较新旧状态的情况下,这种方式才能生效,也就是说,你必须使用单个长生命周期 Session 模式,而不能使用 session-per-request-with-detached-objects 模式。
有些情况下,只要更改不发生交错,并发修改也是允许的。当你在 的映射中设置 optimistic-lock=“dirty”,Hibernate 在同步的时候将只比较有脏数据的字段。

总结:
乐观锁和悲观锁区别:
1.乐观锁主要借助于version
2.悲观锁主要借助于数据库锁

使用悲观锁时,会悲观锁定当前数据或对象,在当前事务操作完成之前,其它人无法对这些数据进行操作。
相当于select * from t_user for update
设置 session.load(studnet,1,LockMode.UPGRADE); 在hibernate3.6之前可以进行悲观锁定

使用乐观锁参照前面的3种设置方法,只要当前versionid一直就没问题,如果有且并发有一个version提交version+1;如果并发
发现两个version不一致就会抛出异常。


您可以通过点击 文章下方的输入框 来对文章内容作出评价, 也可以通过左上方的 关注按钮 来关注我的博客的最新动态。

如果文章内容对您有帮助, 不要忘记点击右上角的 喜欢按钮 来支持一下哦 !

如果您对文章内容有任何疑问, 可以通过评论方式联系我;

如果需要转载,请注明出处,谢谢!!

猜你喜欢

转载自blog.csdn.net/weixin_44071408/article/details/89099896