精通Hibernate——应用程序中的悲观锁和乐观锁
2015-09-09 17:27
344 查看
当数据库采用read commited隔离级别时,会导致不可重复读和第二类丢失更新的并发问题。可以使用悲观锁或者乐观锁来避免这类问题
悲观锁:在应用程序中显式的为数据资源加锁,悲观锁假定当前事务操纵数据资源时,肯定还会有其他事务同时访问该数据,为了避免当前事务操作收到干扰,先锁定资源,但是他会影响性能。
乐观锁:假定当前操纵数据资源时,不会有其他事务同时访问该数据资源,此时,完全依赖数据库的隔离级别来管理锁的工作。应用程序采用版本控制手段来避免可能出现的并发问题。
悲观锁
悲观锁不常用,这里只是简单的提一下,他的实现方式有二:
1、在应用程序中显式指定采用数据库独占锁来锁定数据资源
2、在数据库表中增加一个表明记录状态的LOCK字段,当值为Y时,表示已经锁定,N表示空闲
说到悲观锁我们顺便学习下LockMode
其实LockMode只是在使用Hibernate 中 的session.load()加载数据时指定的模式,也叫悲观锁(模式),然而,悲观锁是为了弥补read-committed 机制的不足,从而解决non-repeatable (不可重复读)和 phantom-read (幻读)问题 ,而non-repeatable 和 phantom-read 这两个问题也只是事务并发是产生的两种问题
锁定模式
LockMode.NONE:如果缓存中存在对象,直接返回该对象的引用,否则通过select语句到数据库中加载该对象,默认值.
LockMode.READ:不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否和数据库中对象版本一致
LockMode.UPGRADE:不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否和数据库中对象的版本一致,如果数据库系统支持悲观锁(如Oracle/MySQL),就执行select…for update语句,如果不支持(如Sybase),执行普通select语句,在程序中如何加上悲观锁呢,很简单,直接在session的load方法中添加一个参数即可,例如:
使用悲观锁查找数据或者更新数据时,默认加上for update ,表示对当前事务加锁,其他事务暂时不能使用,例如 select … for update
LockMode.UPGRADE_NOWAIT和LockMode.UPGRADE:具有同样功能,此外,对于Oracle等支持update nowait的数据库,执行select…for update nowait语句,nowait表明如果执行该select语句的事务不能立即获得悲观锁,那么不会等待其它事务释放锁,而是立刻抛出锁定异常
LockMode.WRITE:保存对象时会自动使用这种锁定模式,仅供Hibernate内部使用,应用程序中不应该使用它
LockMode.FORCE:强制更新数据库中对象的版本属性,从而表明当前事务已经更新了这个对象
利用Hibernate版本控制来实现乐观锁
Hibernate乐观锁,能自动检测多个事务对同一条数据进行的操作,并根据先胜原则,提交第一个事务,其他的事务提交时则抛出org.hibernate.StaleObjectStateException异常。
Hibernate乐观锁是怎么做到的呢?
我们先从Hibernate乐观锁的实现说起。要实现Hibenate乐观锁,我们首先要在数据库表里增加一个版本控制字段,字段名随意,比如就叫version,对应hibernate类型只能为 long,integer,short,timestamp,calendar,也就是只能为数字或timestamp类型。然后在hibernate mapping里作如下类似定义:
Hibernate乐观锁的的使用:
在事务2提交时,因为它提交的数据比事务1提交后的数据旧,所以hibernate会抛出一个org.hibernate.StaleObjectStateException异常。
回到前面的问题,Hibernate怎么知道事务2提交的数据比事务1提交后的数据旧呢?
因为MyEntity有个version版本控制字段。
回头看看上面的源代码中的:
这里,et1.version==et2.version,比如此时version=1,
当事务1提交后,该数据的版本控制字段version=version+1=2,而事务2提交时version=1<2所以Hibernate认为事务2提交的数据为过时数据,抛出异常。
这就是Hibernate乐观锁的原理机制。
对于StaleObjectStateException异常有两种处理方式:
方式一:自动撤销事务,通知信息已被其他事务提交,需要重新开始事务
方式二:通知信息已被其他事务修改,显示最新信息,由用户决定如何继续事务,用户也可以决定立即撤销事务
Hibernate的乐观锁除了使用version,还可以使用timestamp,两者用法类似
理论上version比timestamp更安全,timestamp只能精确到秒
对游离对象进行版本检查
Session的lock方法显式对一个游离对象进行版本检查
lock方法的执行步骤如下:
1、把Account对象与当前Session关联
2、如果设定了LockMode.READ,就比较这个Account对象的版本与accounts表中对应的记录版本是否一致,如果不一致,说明该记录已经被其他事务更改,因此会抛出StaleObjectStateException 异常
悲观锁:在应用程序中显式的为数据资源加锁,悲观锁假定当前事务操纵数据资源时,肯定还会有其他事务同时访问该数据,为了避免当前事务操作收到干扰,先锁定资源,但是他会影响性能。
乐观锁:假定当前操纵数据资源时,不会有其他事务同时访问该数据资源,此时,完全依赖数据库的隔离级别来管理锁的工作。应用程序采用版本控制手段来避免可能出现的并发问题。
悲观锁
悲观锁不常用,这里只是简单的提一下,他的实现方式有二:
1、在应用程序中显式指定采用数据库独占锁来锁定数据资源
2、在数据库表中增加一个表明记录状态的LOCK字段,当值为Y时,表示已经锁定,N表示空闲
说到悲观锁我们顺便学习下LockMode
其实LockMode只是在使用Hibernate 中 的session.load()加载数据时指定的模式,也叫悲观锁(模式),然而,悲观锁是为了弥补read-committed 机制的不足,从而解决non-repeatable (不可重复读)和 phantom-read (幻读)问题 ,而non-repeatable 和 phantom-read 这两个问题也只是事务并发是产生的两种问题
锁定模式
LockMode.NONE:如果缓存中存在对象,直接返回该对象的引用,否则通过select语句到数据库中加载该对象,默认值.
LockMode.READ:不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否和数据库中对象版本一致
LockMode.UPGRADE:不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否和数据库中对象的版本一致,如果数据库系统支持悲观锁(如Oracle/MySQL),就执行select…for update语句,如果不支持(如Sybase),执行普通select语句,在程序中如何加上悲观锁呢,很简单,直接在session的load方法中添加一个参数即可,例如:
Account a = (Account)session.load(Account.class, 1, LockMode.UPGRADE);
使用悲观锁查找数据或者更新数据时,默认加上for update ,表示对当前事务加锁,其他事务暂时不能使用,例如 select … for update
LockMode.UPGRADE_NOWAIT和LockMode.UPGRADE:具有同样功能,此外,对于Oracle等支持update nowait的数据库,执行select…for update nowait语句,nowait表明如果执行该select语句的事务不能立即获得悲观锁,那么不会等待其它事务释放锁,而是立刻抛出锁定异常
LockMode.WRITE:保存对象时会自动使用这种锁定模式,仅供Hibernate内部使用,应用程序中不应该使用它
LockMode.FORCE:强制更新数据库中对象的版本属性,从而表明当前事务已经更新了这个对象
利用Hibernate版本控制来实现乐观锁
Hibernate乐观锁,能自动检测多个事务对同一条数据进行的操作,并根据先胜原则,提交第一个事务,其他的事务提交时则抛出org.hibernate.StaleObjectStateException异常。
Hibernate乐观锁是怎么做到的呢?
我们先从Hibernate乐观锁的实现说起。要实现Hibenate乐观锁,我们首先要在数据库表里增加一个版本控制字段,字段名随意,比如就叫version,对应hibernate类型只能为 long,integer,short,timestamp,calendar,也就是只能为数字或timestamp类型。然后在hibernate mapping里作如下类似定义:
<version name="version" column="VERSION" type="integer"/>
Hibernate乐观锁的的使用:
Session session1 = sessionFactory.openSession(); Session session2 = sessionFactory.openSession(); MyEntity et1 = session1.load(MyEntity.class, id); MyEntity et2 = session2.load(MyEntity.class, id); //这里 et1, et2为同一条数据 Transaction tx1 = session1.beginTransaction(); //事务1开始 et1.setName(“Entity1”); //事务1中对该数据修改 tx1.commit(); session1.close(); //事务1提交 Transaction tx2 = session2.beginTransaction(); //事务2开始 et2.setName(“Entity2”); //事务2中对该数据修改 tx2.commit(); session2.close(); //事务2提交
在事务2提交时,因为它提交的数据比事务1提交后的数据旧,所以hibernate会抛出一个org.hibernate.StaleObjectStateException异常。
回到前面的问题,Hibernate怎么知道事务2提交的数据比事务1提交后的数据旧呢?
因为MyEntity有个version版本控制字段。
回头看看上面的源代码中的:
MyEntity et1 = session1.load(MyEntity.class, id); MyEntity et2 = session2.load(MyEntity.class, id);
这里,et1.version==et2.version,比如此时version=1,
当事务1提交后,该数据的版本控制字段version=version+1=2,而事务2提交时version=1<2所以Hibernate认为事务2提交的数据为过时数据,抛出异常。
这就是Hibernate乐观锁的原理机制。
对于StaleObjectStateException异常有两种处理方式:
方式一:自动撤销事务,通知信息已被其他事务提交,需要重新开始事务
try{ }catch(StaleObjectStateException e){ tx.rollback(); log.info("信息已被其他事务提交,需要重新开始事务"); }
方式二:通知信息已被其他事务修改,显示最新信息,由用户决定如何继续事务,用户也可以决定立即撤销事务
Hibernate的乐观锁除了使用version,还可以使用timestamp,两者用法类似
理论上version比timestamp更安全,timestamp只能精确到秒
对游离对象进行版本检查
Session的lock方法显式对一个游离对象进行版本检查
Session session1 = sessionFactory.openSession(): tx1 = session1.beginTransaction(); Account account = (Account)session1.get(Account.class,new long(1)); tx1.commit(); session1.close(); Session session2 = sessionFactory.openSession(): tx2 = session2.beginTransaction(); session2.lock(account,LockMode.READ); tx2.commit(); session2.close();
lock方法的执行步骤如下:
1、把Account对象与当前Session关联
2、如果设定了LockMode.READ,就比较这个Account对象的版本与accounts表中对应的记录版本是否一致,如果不一致,说明该记录已经被其他事务更改,因此会抛出StaleObjectStateException 异常
相关文章推荐
- python实现在 Mac 10.9 远程桌面截屏抓取
- web架构
- wireshark中带有SLE和SRE的SACK包详解
- 二、CXF与springMVC整合的webService客户端调用
- 如何清除xcode里面的mobileprovision文件
- 0909编译原理
- Andoird Studio 错误: 非法字符: '\ufeff' 解决方案。
- 0909关于编译原理的理解
- 0909 关于编译原理的思考
- Failed to add reference to 'System.Net.Http'. Please make sure that it is in the Global Assembly Cache.
- MariaDB数据库在Linux下的编译安装配置
- HTTP协议传输数据,大小有上限吗?
- svn系列之三服务端配置代码仓库
- Web开发:Struts2 Spring Hibernate整合(二)——Spring的使用
- winform的listbox拖动(拖拽)排序实例,源码在下方免积分下载
- sizeof和strlen
- 纯js与jquery两种方法,获得某元素同级节点内容,进行值替换
- Angular学习
- Json概述以及python对json的相关操作
- JConsole & JVisualVM远程监视Websphere服务器JVM的配置方法