主要内容:
- 悲观锁(工程
hibernate_pessimistic_locking
) - 乐观锁(工程
hibernate_optimistic_locking
)
一、悲观锁
悲观锁的实现通常依赖于数据库机制,在整个过程中将数据锁定,其他任何用户都不能读取或修改。
例如,当两个人同时读取数据库中的数据,读取到的数据是相同的,但是当用户一将相关数据(比如数量减100)改变了,但是用户二在不知道的情况下又将数据改变(数量减100)了,然而此时用户二修改不是在用户一修改的基础上进行的修改,两次修改的结果是:原数量-100。这样就出现了错误,即用户二将用户一的修改覆盖了。此时我们如果使用悲观锁,在用户一将数据从数据库中读取出来的时候,将数据库锁住,此时用户二是根本读不到数据的,这样也根本不会进行相关的修改操作,只有当用户一将锁释放之后,用户二才能进行相关的操作。这种方式的并发性不好。
配置上没有什么特殊,只是我们在使用load方法时使用有三个参数的load方法,最后一个参数值是一个枚举值
LockMode.UPGRADE
。悲观锁在短事务中应用的比较多。
实例:
这里我们模拟一个药品的数量,模拟两个用户同时同时去修改药品数量。
实体类:
Inventory.java
private int itemNo;
private String itemName;
private int quantity;
Inventory.hbm.xml
<?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="cn.itcast.hibernate.Inventory" table="_inventory">
<id name="itemNo">
<generator class="native"/>
</id>
<property name="itemName"/>
<property name="quantity"/>
</class>
</hibernate-mapping>
说明:可以看到相关的映射并没有什么大的变化。
然后我们生成一些数据:
InitData.java
package cn.itcast.hibernate;
import org.hibernate.Session;
public class InitData {
public static void main(String[] args) {
Session session = null;
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Inventory inv = new Inventory();
inv.setItemName("脑白金");
inv.setQuantity(1000);
session.save(inv);
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
}
测试:
PessimisticLockingTest.java
package cn.itcast.hibernate;
import org.hibernate.LockMode;
import org.hibernate.Session;
import junit.framework.TestCase;
public class PessimisticLockingTest extends TestCase {
public void testLoad1() {
Session session = null;
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Inventory inv = (Inventory)session.load(Inventory.class, 1, LockMode.UPGRADE);
System.out.println("itemName=" + inv.getItemName());
System.out.println("quantity=" + inv.getQuantity());
inv.setQuantity(inv.getQuantity() - 200);
session.update(inv);
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, 1, LockMode.UPGRADE);
System.out.println("itemName=" + inv.getItemName());
System.out.println("quantity=" + inv.getQuantity());
inv.setQuantity(inv.getQuantity() - 200);
session.update(inv);
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
}
说明:在模拟的时候可以先在方法一修改数据之前加上一个断点,然后debug运行到这里,然后运行方法二,我们可以看到虽然方法二也会发出sql语句,但是却读不到数据,因为我们设置了悲观锁:
Inventory inv = (Inventory)session.load(Inventory.class, 1, LockMode.UPGRADE);
从这里可以看出当资源被锁住后,其他用户是读取不到任何资源的,只有在用户一使用完将锁释放之后用户二才会读取到数据,这类锁一般用在短事务中。同时使用悲观锁的时候lazy失效。
二、乐观锁
大多数基于数据版本记录机制(version)实现,一般是在数据库表中加入一个version字段,读取数据时将版本号一同读出,之后更新数据时版本号加一,如果版本号小于或等于当前版本号,则认为数据是过期的,否则给予更新。乐观锁也可以使用时间戳的方式。
注意:需要在实体类中加一个版本字段,同时在配置的时候一定要配置在id标签的后面。而还要使用optimistic-lock="version"
属性。这种方式相比悲观锁方式最大的好处是多个用户可以同时读取到数据,只是在修改数据之后会对版本号进行一个比较,以确定能否修改。同时这里还是支持lazy的。
示例:
Inventory.java
private int itemNo;
private String itemName;
private int quantity;
private int version;
Inventory.hbm.xml
<?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="cn.itcast.hibernate.Inventory" table="_inventory" optimistic-lock="version">
<id name="itemNo">
<generator class="native"/>
</id>
<version name="version"/>
<property name="itemName"/>
<property name="quantity"/>
</class>
</hibernate-mapping>
说明:可以看到我们加入了一个版本的字段,同时在配置的时候使用此字段作为一个乐观锁。
同样和上例一样,先存入相关数据,之后进行测试:
OptimisticLockingTest.java
package cn.itcast.hibernate;
import org.hibernate.LockMode;
import org.hibernate.Session;
import junit.framework.TestCase;
public class OptimisticLockingTest extends TestCase {
public void testLoad1() {
Session session = null;
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Inventory inv = (Inventory)session.load(Inventory.class, 1);
System.out.println("itemName=" + inv.getItemName());
System.out.println("version=" + inv.getVersion());
System.out.println("quantity=" + inv.getQuantity());
inv.setQuantity(inv.getQuantity() - 200);
session.update(inv);
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, 1);
System.out.println("itemName=" + inv.getItemName());
System.out.println("version=" + inv.getVersion());
System.out.println("quantity=" + inv.getQuantity());
inv.setQuantity(inv.getQuantity() - 200);
session.update(inv);
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
}
}
说明:同样我们现在方法一处加上断点,然后运行到修改语句之前,运行方法二,可以看到方法二可以读出数据并修改,在数据库中我们可以发现数据变了,同时版本号变为1,然后继续运行方法一,会发现方法一报错,这是因为方法一读取出来的版本号是0,而方法二将版本号更新了,那么方法一就不能继续更改了,必须重新进行读取再修改。