• 【Hibernate 9】悲观锁和乐观锁


    一、锁的基本简介

    1.1,为什么需要锁

    首先,锁的概念产生,主要是为了解决并发性的问题。什么是并发性问题呢,比如:

    Angel现在银行有个账号,里面有存款1000块。现在,Angel的账户,在两个地方分别执行操作。首先,Angel妈妈拿着主卡在四川给Angel账户存钱,读取出Angel的余额是1000块。其次,Angel拿着副卡在廊坊从账户里取钱,读取出angel 的余额是1000块。这时候,angel取了200块钱,余额变为1000-200=800块。可是angel妈妈执行存钱1000块操作,Angel余额变为1000+1000=2000(Angel妈妈最初读取出的余额为1000)这时候,Angel的余额就出现了问题,按照正常逻辑,账户余额为1000-200+1000=1800块。这个问题,就叫做更新丢失。Angel的账户,丢失了取款200的更新。为了解决这一个问题,我们需要引入锁的概念

    1.2,悲观锁

    悲观锁:通常是有数据库机制实现的,在整个过程中把数据锁住(查询),只要事务不释放(提交 / 回滚),那么任何用户都不能查看或修改。

    正如上面的例子,Angel妈妈在四川,读取出了Angel的余额,那么angel的账户就被锁定了。angel就不能在廊坊对其账户进行操作,只有等到angel妈妈对这个账户的更新结束,也就是正常更新余额1000+1000=2000的业务结束,angel才能在廊坊执行取款操作。那么,悲观锁对外界的修改持保守态度,有效的保证了数据的一致性。但是,优点:它不适合多个用户并发访问。当一个锁住的资源不被释放掉的时候,这个资源永远不会被其他用户进行修改,容易造成无限期的等待,也就是等待超时。(想象一下,angel妈妈一直没有执行完存钱操作,angel取钱的道路该是多么的艰辛。。。或者说angel一直没有执行完取钱操作,angel妈妈的存钱道路该有多么心酸。。。)

    那么,怎样能保证数据的一致性,又不会导致无期限的等待呢?

    1.3,乐观锁

    乐观锁,从本质上来说并不是一种锁,它大多数的使用是采用数据版本的方式(version)实现,一般在数据库中加入一个version字段,在读取数据的时候将version读取出来,在保存数据的时候判断version的值是否小于数据库中的version值,如果小于不予更新,否则给予更新。

    如果运用乐观锁的实现机制去解决Angel的取款问题,则会发生什么呢?首先,angel妈妈在四川读出angel的余额1000,和其数据的版本号1;同时angel在廊坊也读取出了余额1000,和版本号1。这时候angel执行了取款操作200,更新余额为1000-200=800,同时将数据版本号更新为2。这时候angel妈妈执行存钱操作,而版本号1<2,所以不予以执行1000+1000=2000的更新操作。若是要存款,只能再次执行业务流程,这样,保证了数据的一致性。

    1.4,Hibernate的加锁模式

    A: LockMode.NONE : 无锁机制。
    B:LockMode.WRITE : Hibernate 在 Insert 和 Update 记录的时候会自动获取。 
    C:LockMode.READ : Hibernate 在读取记录的时候会自动获取。
    以上这三种锁机制一般由 Hibernate 内部使用,如 Hibernate 为了保证 Update过程中对象不会被外界修改,会在 save 方法实现中自动为目标对象加上 WRITE 锁。
    D: LockMode.UPGRADE :利用数据库的 for update 子句加锁。
    E: LockMode. UPGRADE_NOWAIT : Oracle 的特定实现,利用 Oracle 的 for update nowait 子句实现加锁。


    二、实例分析Hibernate的锁机制

    2.1,乐观锁

    <span style="font-family:KaiTi_GB2312;font-size:18px;"><?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.angel.hibernate.Inventory" table="t_inventory" <span style="color:#ff0000;">optimistic-lock="version"</span>>
    		<id name="itemNo">
    			<generator class="assigned"/>
    		</id>
    		<version name="version"/>
    		<property name="itemName"/>
    		<property name="quantity"/>
    	</class>
    </hibernate-mapping></span>

    测试类:

    <span style="font-family:KaiTi_GB2312;font-size:18px;">package test.com.angel.hibernate;
    
    import junit.framework.TestCase;
    
    import org.hibernate.Session;
    
    import com.angel.hibernate.HibernateUtils;
    import com.angel.hibernate.Inventory;
    
    public class OptimisticLockingTest extends TestCase {
    
    	public void testLoad1() {
    		Session session = null;
    		try {
    			session = HibernateUtils.getSession();
    			session.beginTransaction();
    			Inventory inv = (Inventory) session.load(Inventory.class, "1002");
    			System.out.println("opt1-->itemNo=" + inv.getItemNo());
    			System.out.println("opt1-->itemName=" + inv.getItemName());
    			System.out.println("opt1-->version=" + inv.getVersion());
    			System.out.println("opt1-->quantity=" + inv.getQuantity());
    
    			inv.setQuantity(inv.getQuantity() - 200);
    
    			session.getTransaction().commit();
    		} catch (Exception e) {
    			e.printStackTrace();
    			session.getTransaction().rollback();
    		} finally {
    			HibernateUtils.closeSession(session);
    		}
    	}
    
    	public void testLoad2() {
    		Session session = null;
    		try {
    			session = HibernateUtils.getSession();
    			session.beginTransaction();
    			Inventory inv = (Inventory) session.load(Inventory.class, "1002");
    			System.out.println("opt2-->itemNo=" + inv.getItemNo());
    			System.out.println("opt2-->itemName=" + inv.getItemName());
    			System.out.println("opt2-->version=" + inv.getVersion());
    			System.out.println("opt2-->quantity=" + inv.getQuantity());
    
    			inv.setQuantity(inv.getQuantity() + 200);
    
    			session.getTransaction().commit();
    		} catch (Exception e) {
    			e.printStackTrace();
    			session.getTransaction().rollback();
    		} finally {
    			HibernateUtils.closeSession(session);
    		}
    	}
    
    }
    </span>
    当我们在方法1读取数据结束,但是未提交事务之前,紧接着执行方法2的时候,由于version字段的值被更改,所以会导致方法1执行不通过,从而保证了数据的一致性。

    2.2,悲观锁

    <span style="font-family:KaiTi_GB2312;font-size:18px;">package test.com.angel.hibernate;
    
    import junit.framework.TestCase;
    
    import org.hibernate.LockMode;
    import org.hibernate.Session;
    
    import com.angel.hibernate.HibernateUtils;
    import com.angel.hibernate.Inventory;
    
    public class OptimisticLockingTest extends TestCase {
    
    	public void testLoad1() {
    		Session session = null;
    		try {
    			session = HibernateUtils.getSession();
    			session.beginTransaction();
    			Inventory inv = (Inventory) session.load(Inventory.class, "1002",LockMode.UPGRADE);
    			System.out.println("opt1-->itemNo=" + inv.getItemNo());
    			System.out.println("opt1-->itemName=" + inv.getItemName());
    			System.out.println("opt1-->version=" + inv.getVersion());
    			System.out.println("opt1-->quantity=" + inv.getQuantity());
    
    			inv.setQuantity(inv.getQuantity() - 200);
    
    			session.getTransaction().commit();
    		} catch (Exception e) {
    			e.printStackTrace();
    			session.getTransaction().rollback();
    		} finally {
    			HibernateUtils.closeSession(session);
    		}
    	}
    
    	public void testLoad2() {
    		Session session = null;
    		try {
    			session = HibernateUtils.getSession();
    			session.beginTransaction();
    			Inventory inv = (Inventory) session.load(Inventory.class, "1002",LockMode.UPGRADE);
    			System.out.println("opt2-->itemNo=" + inv.getItemNo());
    			System.out.println("opt2-->itemName=" + inv.getItemName());
    			System.out.println("opt2-->version=" + inv.getVersion());
    			System.out.println("opt2-->quantity=" + inv.getQuantity());
    
    			inv.setQuantity(inv.getQuantity() + 200);
    
    			session.getTransaction().commit();
    		} catch (Exception e) {
    			e.printStackTrace();
    			session.getTransaction().rollback();
    		} finally {
    			HibernateUtils.closeSession(session);
    		}
    	}
    
    }
    </span>
    当我们在方法1读取数据结束,但是未提交事务之前,紧接着执行方法2的时候,由于添加悲观锁的缘故,方法2无法执行,只有当方法1进行了提交,方法2才能继续执行。


    三、总结

    加锁可以有效的解决并发问题,但是,这也得根据应用程序的具体情况而定。如果开发 的应用程序都是单人操作,那么根本就不必引入锁的概念。在这里,顺便总结一下数据中锁的基本分类:

    共享 (S) 用于不更改或不更新数据的操作(只读操作),如 SELECT 语句。 
    更新 (U) 用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。 
    排它 (X) 用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。

  • 相关阅读:
    SpringBoot 创建 console程序
    SpringBoot 参数检查 Controller中检查参数是否合法
    SpringBoot 使用maven创建springboot项目
    idea 社区版本创建javaweb项目 使用jetty
    idea 社区版本创建javaweb项目 使用tomcat
    mysql 主从 设置
    windows 使用nginx
    Linux 安装Nginx
    闭包小应用
    js小程序写法优化
  • 原文地址:https://www.cnblogs.com/hhx626/p/6010312.html
Copyright © 2020-2023  润新知