千山万水之Hibernate(十三)——锁

锁主要是为了解决数据的并发访问问题。悲观锁,通常是由数据库机制实现,在整个过程中把数据锁住(查询时),只要事务不释放,任何用户都不能查看或修改。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]

因此我们可以在程序中捕获此异常,提示用户数据已经过期。进而达到解决并发数据同时更新的问题。


总结

悲观锁是数据库提供的一种数据访问的隔离机制,而乐观锁是由应用程序通过数据版本或时间戳等方式解决数据冲突的手段,两者都是为了应对并发访问的情况。数据的并发访问、更新会造成更新丢失等一系列问题,做好数据与操作的同步、一致,就会用到事务、锁等一些技术点,后面会多多积累、分享这些相关的内容。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-09-15 01:24:23

千山万水之Hibernate(十三)——锁的相关文章

千山万水之Hibernate(四)——关联映射(多对一)

在上一篇文章(<千山万水之Hibernate(三)--基本映射 >)中,我们介绍了怎样通过Hibernate进行最基本的单实体映射以及Hibernate设计的一些基本原理,本篇文章将介绍关联映射中的多对一映射是如何实现的. 原理分析 我们拿学生和班级为示例,学生属于某一个班级,而且多个学生是有可能属于同一个班级的,相应的实体关系图为: Class的映射文件向上一篇我们介绍到的基本映射文件那样编写便可以,而对于Student实体,我们需要从Student中得出对应班级,而Student与Clas

千山万水之Hibernate(七)——关联映射(多对多)

一直认为通过写SQL语句来处理多对多的情况比较复杂,对表关系必须是理解的非常清楚,在学习了Hibernate中的多对多处理后,想想其实多对多也没什么,只不过多了一张表,如果说多了一张表感觉复杂了,Hibernate中我们完全不用去理会他,直接去操作关联实体就可以了,从这点上看,Hibernate为我们做了很多事,确实功不可没. 在有了Hibernate这个帮手后,今天一起看看我们在Hibernate的基础上进行一些操作. 原理分析 我们拿学生和课程为例子来分析,所谓的多对多关系可以这样理解:一个

千山万水之Hibernate(六)——关联映射(一对多)

学习Hibernate的过程中,映射是比较重要的一部分,再说其中哪一种映射比较重要(或可以说是比较常用)呢?那一定很多人会想到一对多关联映射.之所以这样说,是因为在生活中很多这样的实例:班级-学生.企业-员工.文件夹-文件.试卷-题目等.至于生活中为什么会遇到这样大量一对多的情况,似乎是哲学方面的事情了,当然大家可以继续思考,而我们今天主要讨论Hibernate中的一对多. 原理分析 我们仍然拿班级.学生之间的关系做例子:一个班级包含多个学生.相应的实体关系图为: 单向关联 由图可知,由于单向的

千山万水之Hibernate(八)——Component映射

Component映射体现一种封装复用的思想,我们知道数据域模型的设计一般是粗粒度的,而对象模型的设计我们往往遵循细粒度.单一职责.抽象复用的原则,但到了对象模型与数据模型相互转换.对应的时候,我们就需要考虑来怎样实现来同时满足双方的基本设计理念.Hibernate中就提供相关的实现. 原理分析 对象模型: User类与Employee类存有很多相同的属性,为了更好的可维护性与灵活性,进行抽象.复用得出了Contact类,Contact类是不需要映射到数据库中表的,有了这样的需求,Hiberna

千山万水之Hibernate(五)——关联映射(一对一)

知道了多对一关联映射的映射原理,我们再来看一对一关联的情况,一对一分映射有两种实现方案: 一对一主键关联映射 对于其中关联的情况我们又各分为单向.双向两种,而对于一对一,Hibernate采用one-to-one标签进行标识. 原理分析 我们拿人(Person)与身份证件(IdCard)为一对一关联对象的示例,他们的实体关系图为: 采取第一种方案,则Person对应数据库表与IdCard对应数据库表中的主键是一一对应的,不需要添加多余的字段来表示外键.Person关联映射文件中的配置为: <?x

千山万水之Hibernate(八)——继承映射

类之间的关系,我们可以分为四种:关联.依赖.继承.实现.而我们所说的实体类之间的关系往往会想到两种:关联和继承,其他两种为什么会不是很常用?首先类之间的依赖是一种比较弱的关系,在代码上可以理解为在一个类的方法的参数上或方法内部对另一个类有引用或调用,引用类或调用类不属于原始类的变量类型,实体类之间一般不存在方法,也就谈不上依赖了.实现描述的是类与接口的关系,一般接口用于定义方法,也就是相当于定义出一些规范,不进行实现. 在前面几篇文章中,我们了解和学习了如何使用Hibernate进行实体类之间的

千山万水之Hibernate(二)——Hibernate的三态

Session是Hibernate向应用程序提供的操纵数据库的最主要的接口,我们可以通过Session来操作Java对象,完成对应数据库的操作.从根据Session管理的角度来看需要持久化的对象可以分为三种状态:Transient.Persistent.Detached.它们之间的关系如图所示: Transient(瞬时):没有被Session所管理(即不处于Session的缓存中)的持久化对象所处的状态.刚用new语句创建,还没有被持久化. Persistent(持久化):已经被持久化,已经加

hibernate 乐观锁与悲观锁使用

Hibernate支持两种锁机制: 即通常所说的"悲观锁(Pessimistic Locking)"和 "乐观锁(OptimisticLocking)". 悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据). Hibernate的加锁模式有: ? LockMode.NONE : 无锁机制. ? LockMode.WRITE :Hibernate在Ins

Hibernate学习---第十二节:Hibernate之锁机制&amp;乐观锁实现

1.悲观锁 它指的是对数据被外界修改保持保守态度,因些,在整个数据处理过程中,将数据牌锁定状态.悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层的锁机制才能保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据). 一个典型的悲观锁调用示例: select * from account where name = "12345" for update 通过for update子句,这条SQL锁定了account表中所有符合检索条件的记录.本次事务