【Hibernate】悲观锁和乐观锁

谈到悲观锁和乐观锁,就要谈到数据库的并发问题,数据库的隔离级别越高并发性就越差
并发性:
当前系统进行了序列化后,你读取数据库后,别人查询不了,称为并发性不好

1.悲观锁

具有排它性(我锁住当前数据后,比人看不到此数据),悲观锁一般是由数据库机制来做到的

悲观锁的实现:

通常依赖于数据库机制,在整修过程中将数据库锁定,其它任何用户都不能读取或修改

悲观锁的适用场景:

悲观锁一般适合短事物比较多(如某一个数据取出后加1,立即释放)


实例:

【Hibernate】悲观锁和乐观锁

用户1,用户2同时读取到数据,但是用户2先-200,这时数据库里的是800,现在用户1也开始-200,可以用户1刚才读取到的数据是1000,现在用户用刚一开始读取的数据1000-200,而用户1在更新时数据库里的数据是800,按理说用户1应该是800-200=600,这样就造成更新丢失。这种情况下可采用两种方式解决:悲观锁、乐观锁。
悲观锁:用户1读取数据后,用锁将其读取的数据锁上,这时用户2是读取不到数据的,只有用户1释放锁后用户2才可以读取,同样用户2读取数据的数据也锁上,这样就可以解决更新丢失了。

实体类:

[html] view plain copy
  1. public class Inventory {  
  2.     private int itemNo;   
  3.     private String itemName;      
  4.     private int quantity;  
  5.     public int getItemNo() {  
  6.         return itemNo;  
  7.     }  
  8.     public void setItemNo(int itemNo) {  
  9.         this.itemNo = itemNo;  
  10.     }  
  11.     public String getItemName() {  
  12.         return itemName;  
  13.     }  
  14.     public void setItemName(String itemName) {  
  15.         this.itemName = itemName;  
  16.     }  
  17.     public int getQuantity() {  
  18.         return quantity;  
  19.     }  
  20.     public void setQuantity(int quantity) {  
  21.         this.quantity = quantity;  
  22.     }     
  23. }  

映射文件:

[html] view plain copy
  1. <hibernate-mapping>  
  2.     <class name="com.cn.hibernate.Inventory" table="t_inventory">  
  3.         <id name="itemNo">  
  4.             <generator class="native"/>  
  5.         </id>  
  6.         <property name="itemName"/>  
  7.         <property name="quantity"/>  
  8.     </class>  
  9. </hibernate-mapping>  

悲观锁的使用:

如果要使用悲观锁,肯定在加载数据时就要锁住,通常采用for update语句

Hibernate使用load进行悲观锁加载


Session.load(Class arg(),Serializable arg1,LockMode arg2)throws HibernateException

LockMode:悲观锁模式(一般使用LockMode.UPGRADE)

[html] view plain copy
  1. session = HibernateUtils.getSession();  
  2.             tx = session.beginTransaction();  
  3.             Inventory inv = (Inventory)session.load(Inventory.class, 1, LockMode.UPGRADE);  
  4.             System.out.println(inv.getItemName());  
  5.             inv.setQuantity(inv.getQuantity()-200);  
  6.               
  7.             session.update(inv);  
  8.             tx.commit();  
如果使用悲观锁,那么lazy(懒加载)无效


2.乐观锁

乐观锁:不是锁,是一种冲突检测机制,乐观锁的并发性较好,因为我改的时候,别人可随便修改乐观锁的实现方式:常用的是版本的方式(每个数据表中有一个版本字段version,某一个用户更新数据库后,版本号+1,另一个用户修改后再+1,当用户更新发现数据库当前版本号与读取数据时版本号不一致,等于或小于数据库版本号则更新不了)

Hibernate使用乐观锁需要在映射文件中配置才可生效

实体类

[html] view plain copy
  1. <pre name="code" class="html">public class Inventory {  
  2.     private int itemNo;   
  3.     private String itemName;      
  4.     private int quantity;     
  5.     private int version;//<span style="color:#FF0000;">Hibernate用户实现版本方式乐观锁,但需要在映射文件中配置</span>  
  6.     public int getItemNo() {  
  7.         return itemNo;  
  8.     }  
  9.     public void setItemNo(int itemNo) {  
  10.         this.itemNo = itemNo;  
  11.     }  
  12.     public String getItemName() {  
  13.         return itemName;  
  14.     }  
  15.     public void setItemName(String itemName) {  
  16.         this.itemName = itemName;  
  17.     }  
  18.     public int getQuantity() {  
  19.         return quantity;  
  20.     }  
  21.     public void setQuantity(int quantity) {  
  22.         this.quantity = quantity;  
  23.     }  
  24.     public int getVersion() {  
  25.         return version;  
  26.     }  
  27.     public void setVersion(int version) {  
  28.         this.version = version;  
  29.     }  
  30. }  
  31. </pre>  
  32. <pre></pre>  
  33. <p></p>  
  34. <pre></pre>  
  35. <pre></pre>  
  36. <pre></pre>  
  37. <pre></pre>  
  38.     

使用悲观锁解决事务并发问题

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

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

在Hibernate使用悲观锁十分容易,但实际应用中悲观锁是很少被使用的,因为它大大限制了并发性:

【Hibernate】悲观锁和乐观锁

图为Hibernate3.6的帮助文档Session文档的get方法截图,可以看到get方法第三个参数"lockMode"或"lockOptions",注意在Hibernate3.6以上的版本中"LockMode"已经不建议使用。方法的第三个参数就是用来设置悲观锁的,使用第三个参数之后,我们每次发送的SQL语句都会加上"for update"用于告诉数据库锁定相关数据。

【Hibernate】悲观锁和乐观锁

LockMode参数选择该选项,就会开启悲观锁。

【Hibernate】悲观锁和乐观锁

  T1,T2时刻取款事务和转账事务分别开启,T3事务查询ACCOUNTS表的数据并用悲观锁锁定,T4转账事务也要查询同一条数据,数据库发现该记录已经被前一个事务使用悲观锁锁定了,然后让转账事务等待直到取款事务提交。T6时刻取款事务提交,T7时刻转账事务获取数据。

 

使用乐观锁解决事务并发问题

  相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个"version"字段来实现。
  乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

Hibernate为乐观锁提供了3中实现:

1. 基于version

2. 基于timestamp

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

配置基于version的乐观锁:

【Hibernate】悲观锁和乐观锁
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
<class name="com.suxiaolei.hibernate.pojos.People" table="people">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>

<!-- version标签用于指定表示版本号的字段信息 -->
<version name="version" column="version" type="integer"></version>

<property name="name" column="name" type="string"></property>

</class>
</hibernate-mapping>
【Hibernate】悲观锁和乐观锁

配置基于timestamp的乐观锁:

【Hibernate】悲观锁和乐观锁
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
<class name="com.suxiaolei.hibernate.pojos.People" table="people">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>

<!-- timestamp标签用于指定表示版本号的字段信息 -->
<timestamp name="updateDate" column="updateDate"></timestamp>

<property name="name" column="name" type="string"></property>

</class>
</hibernate-mapping>
【Hibernate】悲观锁和乐观锁

遗留项目,由于各种原因无法为原有的数据库添加"version"或"timestamp"字段,这时不可以使用上面两种方式配置乐观锁,Hibernate为这种情况提供了一个"optimisitic-lock"属性,它位于<class>标签上:

【Hibernate】悲观锁和乐观锁
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
<class name="com.suxiaolei.hibernate.pojos.People" table="people" optimistic-lock="all">
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>

<property name="name" column="name" type="string"></property>
</class>
</hibernate-mapping>
【Hibernate】悲观锁和乐观锁

将该属性的值设置为all,让该记录所有的字段都为版本控制信息。