`
langgufu
  • 浏览: 2288855 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Hibernate锁机制(悲观锁,乐观锁)

阅读更多

锁(locking)

业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在金融系统的日终结算处理中,我们希望针对某个cut-off时间点的数据进行处理,而不希望在结算进行过程中(可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需要通过一些机制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,在这里,也就是所谓的“锁”,即给我们选定的目标数据上锁,使其无法被其他程序修改。

Hibernate支持两种锁机制:即通常所说的“悲观锁(Pessimistic Locking)”和“乐观锁(Optimistic Locking)”。

悲观锁(Pessimistic Locking) 
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

一个典型的依赖数据库的悲观锁调用: 
select * from user where name=”Erica” for update 
这条sql 语句锁定了user 表中所有符合检索条件(name=”Erica”)的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。

Hibernate的悲观锁,也是基于数据库的锁机制实现。 
下面的代码实现了对查询记录的加锁:

String hqlStr ="from TUser as user where user.name='Erica'"; 
Query query = session.createQuery(hqlStr); 
query.setLockMode("user",LockMode.UPGRADE); //加锁 
List userList = query.list();//执行查询,获取数据 
query.setLockMode对查询语句中,特定别名所对应的记录进行加锁(我们为TUser类指定了一个别名“user”),这里也就是对返回的所有user记录进行加锁。 
观察运行期Hibernate生成的SQL语句: 
select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id 
as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex 
from t_user tuser0_ where (tuser0_.name='Erica' ) for update 
这里Hibernate通过使用数据库的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子句实现加锁。 
上面这两种锁机制是我们在应用层较为常用的,加锁一般通过以下方法实现: 
Criteria.setLockMode 
Query.setLockMode 
Session.lock

注意,只有在查询开始之前(也就是Hiberate 生成SQL 之前)设定加锁,才会 
真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含for update 
子句的Select SQL加载进来,所谓数据库加锁也就无从谈起。

乐观锁(Optimistic Locking) 
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。

乐观锁机制在一定程度上解决了这个问题。

 

乐观锁:

乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个version字段,当前值为1;而当前帐户余额字段(balance)为$100。

1 操作员A 此时将其读出(version=1),并从其帐户余额中扣除$50 
($100-$50)。 
2 在操作员A操作的过程中,操作员B也读入此用户信息(version=1),并 
从其帐户余额中扣除$20($100-$20)。 
3 操作员A完成了修改工作,将数据版本号加一(version=2),连同帐户扣除后余额(balance=$50),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录version更新为2。 
4 操作员B完成了操作,也将版本号加一(version=2)试图向数据库提交数据(balance=$80),但此时比对数据库记录版本时发现,操作员B提交的数据版本号为2,数据库记录当前版本也为2,不满足“提交版本必须大于记录当前版本才能执行更新“的乐观锁策略,因此,操作员B 的提交被驳回。这样,就避免了操作员B 用基于version=1 的旧数据修改的结果覆盖操作员A的操作结果的可能。

从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员A和操作员B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。Hibernate 在其数据访问引擎中内置了乐观锁实现。如果不用考虑外部系统对数 
据库的更新操作,利用Hibernate提供的透明化乐观锁实现,将大大提升我们的 
生产力。 
Hibernate中可以通过class描述符的optimistic-lock属性结合version描述符指定。

现在,我们为之前示例中的TUser加上乐观锁机制。

1.首先为TUser的class描述符添加optimistic-lock属性: 
<hibernate-mapping> 
<class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true" optimistic-lock="version" > 
…… 
</class> 
</hibernate-mapping>

optimistic-lock属性有如下可选取值: 
none:无乐观锁 
version:通过版本机制实现乐观锁 
dirty:通过检查发生变动过的属性实现乐观锁 
all:通过检查所有属性实现乐观锁

其中通过version实现的乐观锁机制是Hibernate官方推荐的乐观锁实现,同时也是Hibernate中,目前唯一在数据对象脱离Session发生修改的情况下依然有效的锁机制。因此,一般情况下,我们都选择version方式作为Hibernate乐观锁实现机制。

2. 添加一个Version属性描述符 
<hibernate-mapping>

<class name="org.hibernate.sample.TUser" table="t_user" dynamic-update="true" dynamic-insert="true" optimistic-lock="version" >

<id name="id" column="id" type="java.lang.Integer" >

<generator class="native"></generator>

</id>

<version column="version" name="version" type="java.lang.Integer" />

……

</class>

</hibernate-mapping> 
注意version 节点必须出现在ID 节点之后。 
这里我们声明了一个version属性,用于存放用户的版本信息,保存在TUser表的version字段中。 
此时如果我们尝试编写一段代码,更新TUser表中记录数据,如: 
Criteria criteria = session.createCriteria(TUser.class); 
criteria.add(Expression.eq("name","Erica")); 
List userList = criteria.list(); 
TUser user =(TUser)userList.get(0); 
Transaction tx = session.beginTransaction(); 
user.setUserType(1); //更新UserType字段 
tx.commit(); 
每次对TUser进行更新的时候,我们可以发现,数据库中的version都在递增。而如果我们尝试在tx.commit 之前,启动另外一个Session,对名为Erica 的用户进行操作,以模拟并发更新时的情形: 
Session session= getSession(); 
Criteria criteria = session.createCriteria(TUser.class); 
criteria.add(Expression.eq("name","Erica")); 
Session session2 = getSession(); 
Criteria criteria2 = session2.createCriteria(TUser.class); 
criteria2.add(Expression.eq("name","Erica")); 
List userList = criteria.list(); 
List userList2 = criteria2.list();TUser user =(TUser)userList.get(0); 
TUser user2 =(TUser)userList2.get(0); 
Transaction tx = session.beginTransaction(); 
Transaction tx2 = session2.beginTransaction(); 
user2.setUserType(99); 
tx2.commit(); 
user.setUserType(1); 
tx.commit(); 
执行以上代码,代码将在tx.commit()处抛出StaleObjectStateException异常,并指出版本检查失败,当前事务正在试图提交一个过期数据。通过捕捉这个异常,我们就可以在乐观锁校验失败时进行相应处理。

分享到:
评论

相关推荐

    Hibernate锁机制_悲观锁和乐观锁

    Hibernate锁机制_悲观锁和乐观锁

    hibernate乐观锁和悲观锁学习

    其中通过 version 实现的乐观锁机制是 Hibernate 官方推荐的乐观锁实现,同时也是 Hibernate 中,目前唯一在数据对象脱离 Session 发生修改的情况下依然有效的锁机制。因此,一般情况下,我们都选择 version 方式...

    数据库事务、hibernate悲观锁和乐观锁

    NULL 博文链接:https://cdxs2.iteye.com/blog/1938245

    Hibernate实现悲观锁和乐观锁代码介绍

    主要介绍了Hibernate实现悲观锁和乐观锁的有关内容,涉及hibernate的隔离机制,以及实现悲观锁和乐观锁的代码实现,需要的朋友可以了解下。

    hibernate乐观锁

    求助编辑百科名片相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库 性能的大量开销,特别是对长事务而言,...

    Hibernate锁策略

    Hibernate锁的基本机制!主要是针对Hiberante乐观锁悲观锁的介绍以及相对应的数据库本身的处理机制,包括和SPRING相结合部分的锁处理机制

    MySQL数据库锁机制原理解析

    在并发访问情况下,很有可能出现不可重复读等等读现象。为了更好的应对高并发,封锁...乐观锁和悲观锁不仅在关系数据库里应用,在Hibernate、Memcache等等也有相关概念。 悲观锁:也即悲观并发控制,Pessimistic Concur

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

     21.5 利用Hibernate的版本控制来实现乐观锁  21.5.1 使用元素  21.5.2 使用元素  21.5.3 对游离对象进行版本检查  21.5.4 强制更新版本  21.6 实现乐观锁的其他方法  21.7 小结  21.8 思考题 第22章 管理...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part4

     21.5 利用Hibernate的版本控制来实现乐观锁  21.5.1 使用元素  21.5.2 使用元素  21.5.3 对游离对象进行版本检查  21.5.4 强制更新版本  21.6 实现乐观锁的其他方法  21.7 小结  21.8 思考题 第22章 管理...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part3

     21.5 利用Hibernate的版本控制来实现乐观锁  21.5.1 使用元素  21.5.2 使用元素  21.5.3 对游离对象进行版本检查  21.5.4 强制更新版本  21.6 实现乐观锁的其他方法  21.7 小结  21.8 思考题 第22章 管理...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part1.rar

     21.5 利用Hibernate的版本控制来实现乐观锁  21.5.1 使用元素  21.5.2 使用元素  21.5.3 对游离对象进行版本检查  21.5.4 强制更新版本  21.6 实现乐观锁的其他方法  21.7 小结  21.8 思考题 第22章 管理...

    涵盖了90%以上的面试题

    乐观锁和悲观锁 偏向锁 轻量级锁 自旋锁 自适应自旋锁 重量级锁 synchronized 可重入锁 土方法实现可重入锁 使用AQS类实现可重入锁 CAS MySQL 中的行级锁、表级锁和页级锁 java中的死锁 公平锁和非公平锁 锁的总结 ...

    低清版 大型门户网站是这样炼成的.pdf

    5.7.4 乐观锁 328 5.8 hibernate的缓存机制 332 5.8.1 hibernate的缓存分类 332 5.8.2 hibernate的缓存范围 332 5.8.3 hibernate的缓存管理 333 5.8.4 hibernate二级缓存的并发访问策略 333 5.8.5 hibernate的...

    Java常见面试题208道.docx

    176.说一下乐观锁和悲观锁? 177.mysql 问题排查都有哪些手段? 178.如何做 mysql 的性能优化? 十八、Redis 179.redis 是什么?都有哪些使用场景? 180.redis 有哪些功能? 181.redis 和 memecache 有什么区别? ...

    Java学习笔记-个人整理的

    \contentsline {chapter}{Contents}{2}{section*.1} {1}Java基础}{17}{chapter.1} {1.1}基本语法}{17}{section.1.1} {1.2}数字表达方式}{17}{section.1.2} {1.3}补码}{19}{section.1.3} {1.3.1}总结}{23}{...

    jdbc基础和参考

    (自动脏检查机制) Detached(托管状态): 1.和Session失去关联 2.数据库中有对应记录存在 3.对托管对象的更动,在托管期间不会影响数据库,但是将托管状态重新和数据库进行关联的时候会将托管对象重新变为持久态,...

Global site tag (gtag.js) - Google Analytics