Hibernate中的悲观锁和乐观锁

前言

 

    我们都知道,事务具有四个特性:原子性、隔离性、一致性、持久性。对于数据库的操作也应该具有事务的性质,如果执行正确则返回结果,如果中间出了差错,应该回滚,这样才能保证数据库的数据的真实性。但是在现实操作过程中,往往数据量不是一条两条,而是很多条数据同时操作,这是就要防止并发问题。通常来说,数据库的隔离级别越大,并发性就越差。防止数据库的并发,就有了锁机制的出现。

 

为什么要用锁?

 

    假如有这么一个情况,甲和乙两个用户分别操作同一条数据库记录,假如甲先从数据库中读出了数据,并在原数据的基础上做了更改,但是在更新的同时乙读出了数据。按照一般的逻辑,乙应该读出的是甲更新完的数据,但是因为二者可以同时操作,所以乙读出的是旧的数据,也就造成了脏读。当乙对这条记录更改后,更新到数据库后,就覆盖了甲对数据的操作,也就造成了甲的更新丢失。

 

    这些问题原因就在于多个事务同时操作数据,造成了数据的不统一。这些现象都是可以避免的,这也就是锁机制。当一个事务操作数据时,对这条数据进行加锁,别的事务等待。当这个事务执行完之后,解锁,别的事务才可以对这条数据进行操作。

 

    数据库锁的形式有两种,一种是悲观锁,一种是乐观锁。

 

悲观锁

 

    悲观锁之所以叫悲观锁,是因为使用悲观锁的事务具有悲观的心态。它总是害怕中间会出什么岔子,所以在它一读取完数据之后,马上就对这条记录加锁,以防止别的事务操作,所以这种锁叫做悲观锁。悲观锁在SQL语句中的体现是for update,在SQL语句末尾加上for update,就会开启数据库的悲观锁。

 

    在Hibernate中,因为它封装了JDBC的操作过程,所以只需要在执行事务时,开启悲观锁就好。开启悲观锁的方式,就是在load方法中设置LockMode的值。LockMode就是锁模式,当它的值被设置为UPGRADE时,就是开启悲观锁。

 

 

public void testLoad1() {
	Session session = null;
	try {
		session = HibernateUtils.getSession();
		session.beginTransaction();
		Inventory inv = (Inventory)session.load(Inventory.class, "1001", LockMode.UPGRADE);
		System.out.println("opt1-->itemNo=" + inv.getItemNo());
		System.out.println("opt1-->itemName=" + inv.getItemName());
		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);
	}
}

 

 

LockMode还有其他的属性值,例如:

    LockMode.NONE:不开启锁模式

    LockMode.WRITE:Hibernate在进行Insert和Update的操作时会自动开启

    LockMode.READ:Hibernate在读取数据时会自动开启

    LockMode.UPGRADE是利用数据库的for updte语句进行加锁,另外还有LockMode.UPGRADE_NOWAIT是专门为Oracle数据库实现的,利用for update nowait来加锁。

 

    因为悲观锁是利用的数据库底层实现,而且一旦一个事务执行的时间很长,那么其他事务就需要一直等着,这样一来Hibernate使用悲观锁会大大降低它的效率,所以通常用的比较少。那么就还有另外一种锁机制,也就是乐观锁。

 

乐观锁

 

    乐观锁比悲观锁实现起来要轻松,它并不是对数据库进行了什么操作,而是采用了曲线救国的方式。它给每条数据添加了一个字段,叫做version,也就是版本。当更新数据时会检查版本号是否是最新的,如果不是,则不操作数据,如果是最新的,就提交事务。

 

    乐观锁的工作原理是,读取数据时将该数据的版本号一块读出来,对数据进行操作之后将版本号加一,然后存入数据库。更新数据库时对该版本号和数据库中的版本号进行对比,如果该版本号大于数据库中的版本号,则予以更新,否则认为数据过期。

 

 

public class Inventory {

	private String itemNo;
	
	private String itemName;
	
	private int quantity;
	
	private int version;

	public String getItemName() {
		return itemName;
	}

	public void setItemName(String itemName) {
		this.itemName = itemName;
	}

	public String getItemNo() {
		return itemNo;
	}

	public void setItemNo(String itemNo) {
		this.itemNo = itemNo;
	}

	public int getQuantity() {
		return quantity;
	}

	public void setQuantity(int quantity) {
		this.quantity = quantity;
	}

	public int getVersion() {
		return version;
	}

	public void setVersion(int version) {
		this.version = version;
	}
}

	
		
			
		
		
		
		
	

 

 

    乐观锁实现时,要在数据库表中加入version字段,同时映射文件也要添加该字段,并设置optimistic-lock为version,也就是乐观锁实现在version字段上。

 

总结

 

    面对大数据,并发问题变得不可忽视。为了解决并发问题,数据库出现了锁机制,在一个事务执行时,其他事务不能对相同数据进行操作。数据库的锁机制主要分为两种,悲观锁和乐观锁,悲观锁是在数据加载时就加锁,乐观锁则是利用了数据版本的原理。数据库运用悲观锁时,如果事务执行时间过长,就增长了其他事务的等待时间,导致效率降低,由此来看,乐观锁的实现还是比较人性化的。不过,任何事物都是只有合适,没有最好,不同情况不同用法罢了。

你可能感兴趣的:(ORM框架)