锁主要是为了解决数据的并发访问问题。悲观锁,通常是由数据库机制实现,在整个过程中把数据锁住(查询时),只要事务不释放,任何用户都不能查看或修改。Hibernate中对悲观锁进行了封装。
测试示例
悲观锁
同时执行两个测试方法,同时采用悲观锁方式访问同一条数据记录。
1.建立测试实体
package com.tgb.hibernate;
/**
* 库存实体
* @author Forrest
*
*/
public class Inventory {
//物料编码
private String itemNo;
//物料名称
private String itemName;
//数量
private int quantity;
public String getItemNo() {
return itemNo;
}
public void setItemNo(String itemNo) {
this.itemNo = itemNo;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
}
2.实体映射文件
<?xml version="1.0"?>
<!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.tgb.hibernate.Inventory" table="t_inventory">
<id name="itemNo">
<generator class="assigned"></generator>
</id>
<property name="itemName" />
<property name="quantity" />
</class>
</hibernate-mapping>
3.建立初始化数据类,进行初始数据的添加
public class InitData {
/**
* @param args
*/
public static void main(String[] args) {
Session session = null;
Transaction tx = null;
try{
session = HibernateUtils.getSession();
tx = session.beginTransaction();
Inventory inventory = new Inventory();
inventory.setItemNo("1001");
inventory.setItemName("吗叮咛");
inventory.setQuantity(1000);
session.save(inventory);
tx.commit();
}catch(Exception e){
e.printStackTrace();
if(tx != null){
tx.rollback();
}
}finally{
HibernateUtils.closeSession(session);
}
}
}
4.编写测试方法
public class PessimisticLockLockingTest extends TestCase {
/**
* 测试悲观锁方法1
*/
public void testPessimisticLock1(){
Session session = null;
Transaction tx = null;
try{
session = HibernateUtils.getSession();
tx = session.beginTransaction();
//采用悲观锁加载数据
Inventory inv = (Inventory)session.load(Inventory.class, "1001", LockMode.UPGRADE);
System.out.println("操作人员1-->itemNo=" + inv.getItemNo());
System.out.println("操作人员1-->itemName=" + inv.getItemName());
System.out.println("操作人员1-->itemQuantity=" + inv.getQuantity());
//在此添加断点,程序运行至此,执行方法2,方法2将会等待方法1执行完再执行。
inv.setQuantity(inv.getQuantity() - 200);
tx.commit();
}catch(Exception e){
e.printStackTrace();
if(tx != null){
tx.rollback();
}
}finally{
HibernateUtils.closeSession(session);
}
}
/**
* 测试悲观锁方法2
*/
public void testPessimisticLock2(){
Session session = null;
Transaction tx = null;
try{
session = HibernateUtils.getSession();
tx = session.beginTransaction();
//由于方法1未提交事务(断点模拟),方法2运行至此等待资源
Inventory inv = (Inventory)session.load(Inventory.class, "1001", LockMode.UPGRADE);
System.out.println("操作人员2-->itemNo=" + inv.getItemNo());
System.out.println("操作人员2-->itemName=" + inv.getItemName());
System.out.println("操作人员2-->itemQuantity=" + inv.getQuantity());
inv.setQuantity(inv.getQuantity() - 200);
tx.commit();
}catch(Exception e){
e.printStackTrace();
if(tx != null){
tx.rollback();
}
}finally{
HibernateUtils.closeSession(session);
}
}
}
testPessimisticLock1运行至断点尚未提交事务时,运行测试方法testPessimisticLock2,我们可以观察到,测试方法2处于等待的状态,没有打印出最终的结果。等到方法1执行完后,释放数据库资源,方法2才获得资源能够继续执行。整个过程便是我们在执行session对象的load方法时添加了一个参数:LockMode.UPGRADE,应用了数据库悲观锁的情况。
当事务周期执行时间过长,别人将无法访问数据库中资源,因此悲观锁影响程序的并发性。可采用乐观锁解决此类情况。
乐观锁
乐观锁其实并不算是“锁”,它是利用数据的版本管理、使用时间戳或比较数据库所有字段的方式解决数据访问冲突的一种手段。下面我们演示Hibernate对乐观锁的支持(基于悲观锁示例的一些修改)。
测试场景:操作员1与操作员2同时读取了数据库中数据,操作员2将先将数据进行了更新,因此数据版本进行了更新,当操作员1也更新数据进行提交时,会检查其数据版本,小于数据库中对应数据的版本,不允许提交。
1.在实体中加入version属性
public class Inventory {
private String itemNo;
private String itemName;
private int quantity;
private int version;
//省略get、set方法。。。
}
2.修改实体映射配置
<hibernate-mapping>
<!-- 乐观锁配置(默认) -->
<class name="com.tgb.hibernate.Inventory" table="t_inventory" optimistic-lock="version">
<id name="itemNo">
<generator class="assigned"></generator>
</id>
<!-- 版本控制字段的映射 -->
<version name="version" />
<property name="itemName" />
<property name="quantity" />
</class>
</hibernate-mapping>
3.编写测试方法
public class PessimisticLockLockingTest extends TestCase {
/**
* 乐观锁测试方法1
*/
public void testPessimisticLock1(){
Session session = null;
Transaction tx = null;
try{
session = HibernateUtils.getSession();
tx = session.beginTransaction();
Inventory inv = (Inventory)session.load(Inventory.class, "1001");
System.out.println("操作员1-->itemNo=" + inv.getItemNo());
System.out.println("操作员1-->itemName=" + inv.getItemName());
System.out.println("操作员1-->itemQuantity=" + inv.getQuantity());
System.out.println("操作员1-->version=" + inv.getVersion());
//在此添加断点,程序运行至此,执行方法2,方法2执行完后再继续执行本方法。
inv.setQuantity(inv.getQuantity() - 200);
tx.commit();
}catch(Exception e){
e.printStackTrace();
if(tx != null){
tx.rollback();
}
}finally{
HibernateUtils.closeSession(session);
}
}
/**
* 乐观锁测试方法2
*/
public void testPessimisticLock2(){
Session session = null;
Transaction tx = null;
try{
session = HibernateUtils.getSession();
tx = session.beginTransaction();
Inventory inv = (Inventory)session.load(Inventory.class, "1001");
System.out.println("操作员2-->itemNo=" + inv.getItemNo());
System.out.println("操作员2-->itemName=" + inv.getItemName());
System.out.println("操作员2-->itemQuantity=" + inv.getQuantity());
System.out.println("操作员2-->version" + inv.getVersion());
inv.setQuantity(inv.getQuantity() - 200);
tx.commit();
}catch(Exception e){
e.printStackTrace();
if(tx != null){
tx.rollback();
}
}finally{
HibernateUtils.closeSession(session);
}
}
}
测试时运行测试方法顺序与悲观锁的测试一致,在最后测试方法1提示事务时报错:
21:31:00,634 ERROR AbstractFlushingEventListener:301 - Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.tgb.hibernate.Inventory#1001]
因此我们可以在程序中捕获此异常,提示用户数据已经过期。进而达到解决并发数据同时更新的问题。
总结
悲观锁是数据库提供的一种数据访问的隔离机制,而乐观锁是由应用程序通过数据版本或时间戳等方式解决数据冲突的手段,两者都是为了应对并发访问的情况。数据的并发访问、更新会造成更新丢失等一系列问题,做好数据与操作的同步、一致,就会用到事务、锁等一些技术点,后面会多多积累、分享这些相关的内容。
版权声明:本文为博主原创文章,未经博主允许不得转载。