悲观锁
为了避免读写数据不及时,导致脏数据产生的问题,hibernate引入了悲观锁和乐观锁。
下面先介绍悲观锁
悲观锁
新建一个java项目,结构如图:
实体类User代码:
package com.robert.pojo;
public class User {
private int id ;
private String name ;
private String pwd ;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- This mapping demonstrates content-based discrimination for the table-per-hierarchy
mapping strategy, using a formula discriminator. -->
<!-- package:声明pojo类所在的包,如果不写,那么在class中的name里需要指明pojo类所在的包;
schema:指数据库的模式,一个模式下可以有多张表。
-->
<hibernate-mapping package="com.robert.pojo">
<!-- class:指映射一个pojo类,
1)提供了公共的无参构造方法,通过反射产生对象。
2)属性用private修饰,并且生成对应的set/get方法。
3)类不能用final来修饰,hibernate会产生代理类(通过cglib)。
name:表示pojo类名;
table:表示pojo类对应的数据库中的表名;如果不写,默认是类名。
-->
<class name="User" table="user">
<!--
id:表示实体类的标识(OID),对应数据库表的主键;
name:指实体类的标识属性名;
column:表示对应数据库表的列名,如果不写,则和属性名一致;
length:表示数据库表中对应数据类型的长度,如果不写,有默认长度;
type:表示类型,如果不写,hibernate会找对应pojo类的属性类型;
-->
<id name="id" column="id">
<!-- 主键生成策略
increment:用于为long,short或者int类型生成唯一标识,
只有在没有其他进程往同一张表中插入数据时才能使用。在集群下不要使用。
(可以使用的数据库是:mysql,ms sql)。
identity:对DB2,MySQL,MS SQL Server,Sybase和HypersonicSQL的内置标识字段提供,
返回的标识符是long, short, 或者int类型的。
sequence:在支持序列的数据库中使用,使用数据库是oracle。
例子:
<generator class="sequence" >
注释:param中的值是数据库sequence的名称
<param name="sequence">user_seq</param>
</generator>
uuid: UUID被编码为一个32位的16进制数字的字符串;
native:根据底层的数据库能力选择,identity,sequence或者hilo中的一个;
assigned:自己指定主键。
-->
<generator class="native" />
</id>
<!--
实体类的属性
name:指明pojo类属性名称(区分大小写);
column:实体类属性对应的数据库表列名;
-->
<property name="name" >
<column name="name"></column>
</property>
<property name="pwd" />
</class>
</hibernate-mapping>
HIbernateUtil代码
package com.robert.util;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static Configuration cfg = null;
private static SessionFactory factory = null;
private static Session session = null ;
static {
init();
}
public static void init() {
cfg = new Configuration().configure();
factory = cfg.buildSessionFactory(new StandardServiceRegistryBuilder()
.applySettings(cfg.getProperties()).build());
}
public static Session getSession() {
if (factory != null){
session = factory.openSession();
return session ;
}
init();
session = factory.openSession();
return session;
}
public static void closeSession() {
if(session!=null && session.isOpen())
session.close();
}
}
hibernate.cfg.xml代码:
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 配置数据库连接信息 -->
<property name="connection.driver_class">
com.mysql.jdbc.Driver
</property>
<property name="connection.url">jdbc:mysql:///hibernate4</property>
<property name="connection.username">root</property>
<property name="connection.password">root</property>
<!-- 数据库方言 -->
<property name="hibernate.dialect">
org.hibernate.dialect.MySQL5Dialect
</property>
<!-- 是否打印sql语句 -->
<property name="show_sql">true</property>
<!-- 格式化sql语句 -->
<property name="format_sql">true</property>
<!-- 数据库更新方式:
1、create:每次更新都先把原有数据库表删除,然后创建该表;
2、create-drop:使用create-drop时,在显示关闭SessionFacroty时(sessionFactory.close()),将drop掉数据库Schema(表)
3、validate:检测;
4、update(常用):如果表不存在则创建,如果存在就不创建
-->
<property name="hbm2ddl.auto">update</property>
<!-- 加载User实体类对应的配置文件 -->
<mapping resource="com/robert/pojo/User.hbm.xml" />
</session-factory>
</hibernate-configuration>
HibernateTest测试类代码:
package com.robert.test;
import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.metamodel.domain.Hierarchical;
import org.junit.Test;
import com.robert.pojo.User;
import com.robert.util.HibernateUtil;
public class HibernateTest {
/**
* session的save方法,保存数据 session中状态改变:瞬时-->持久-->游离
*/
@Test
public void testSave() {
Session session = null;
Transaction tx = null;
User user = null;
try {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
// 构造对象--瞬时状态
user = new User();
user.setName("小明2");
user.setPwd("22221");
// 持久状态,user被session管理,并且id有值--oid
session.save(user);
// 在持久化状态下:hibernate会执行脏数据检查(之前的数据成为脏数据)
// 当提交事务,或清理缓存时,发现session中数据和之前要放入数据库中数据(此时数据仍然在session中,并未真正在数据库中)不一致时,
// 将会把session中的数据更新到数据库中。
user.setName("小友");
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
throw new HibernateException(e.getCause());
} finally {
HibernateUtil.closeSession();
}
// user处于游离状态,但是在内存中仍然存在
System.out.println("name=" + user.getName());
}
/**
* 测试悲观锁
*/
@Test
public void testGetPessimistic() {
Session session = null;
Transaction tx = null;
try {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
User user = (User) session.get(User.class, 1, LockOptions.UPGRADE) ;
System.out.println("name=" + user.getName());
user.setName("张三") ;
session.update(user) ;
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
HibernateUtil.closeSession();
}
}
/**
* 测试悲观锁
*/
@Test
public void testGetPessimistic2() {
Session session = null;
Transaction tx = null;
try {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
//LockOptions设置锁定策略
User user = (User) session.get(User.class, 1, LockOptions.UPGRADE) ;
System.out.println("name=" + user.getName());
user.setName("张三") ;
session.update(user) ;
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
HibernateUtil.closeSession();
}
}
}
悲观锁书写方式:
/**
* 测试悲观锁
*/
@Test
public void testGetPessimitic() {
Session session = null;
Transaction tx = null;
try {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
//LockOptions设置锁定策略
User user = (User) session.get(User.class, 1, LockOptions.UPGRADE) ;
System.out.println("name=" + user.getName());
user.setName("张三") ;
session.update(user) ;
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
HibernateUtil.closeSession();
}
}
悲观锁一共有一下几种形式:
上面的测试代码中有两个测试悲观锁的方法,有两个session,如果同时启动,就相当于同时有两个线程启动,当第一个session执行了
User user = (User) session.get(User.class, 1, LockOptions.UPGRADE) ;
这句话后,后面的线程就需要等待,直到这个session关闭之后,其他的线程才能继续执行,以此类推。
乐观锁:
乐观锁和悲观锁不同的是,乐观锁不是排他性,而是使用了一个版本version来记录,当数据被更改后,version会更改。
下面新建一个java项目,结构如下:
实体类User代码:
package com.robert.pojo;
public class User {
private int id ;
private String name ;
private String pwd ;
private int version ;
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
由代码看出,实体类User中多了一个version属性,这个属性就是用来记录版本号的
User.hbm.xml代码:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- This mapping demonstrates content-based discrimination for the table-per-hierarchy
mapping strategy, using a formula discriminator. -->
<!-- package:声明pojo类所在的包,如果不写,那么在class中的name里需要指明pojo类所在的包;
schema:指数据库的模式,一个模式下可以有多张表。
-->
<hibernate-mapping package="com.robert.pojo">
<!-- class:指映射一个pojo类,
1)提供了公共的无参构造方法,通过反射产生对象。
2)属性用private修饰,并且生成对应的set/get方法。
3)类不能用final来修饰,hibernate会产生代理类(通过cglib)。
name:表示pojo类名;
table:表示pojo类对应的数据库中的表名;如果不写,默认是类名。
-->
<class name="User" table="user">
<id name="id" column="id">
<generator class="native" />
</id>
<version name="version" />
<property name="name" >
<column name="name"></column>
</property>
<property name="pwd" />
</class>
</hibernate-mapping>
实体类对应的配置文件中增加了一个version属性
HIbernateUtil和hibernate.cfg.xml代码和上面悲观锁中的代码相同,这里就不贴了
HibernateTest测试类中的代码:
package com.robert.test;
import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.metamodel.domain.Hierarchical;
import org.junit.Test;
import com.robert.pojo.User;
import com.robert.util.HibernateUtil;
public class HibernateTest {
/**
* session的save方法,保存数据 session中状态改变:瞬时-->持久-->游离
*/
@Test
public void testSave() {
Session session = null;
Transaction tx = null;
User user = null;
try {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
// 构造对象--瞬时状态
user = new User();
user.setName("小明2");
user.setPwd("22221");
session.save(user);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
throw new HibernateException(e.getCause());
} finally {
HibernateUtil.closeSession();
}
// user处于游离状态,但是在内存中仍然存在
System.out.println("name=" + user.getName());
}
@Test
public void testUpdate() {
Session session = null;
Transaction tx = null;
User user = null;
try {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
user = (User) session.get(User.class, 1) ;
user.setName("罗伯特") ;
session.update(user) ;
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
throw new HibernateException(e.getCause());
} finally {
HibernateUtil.closeSession();
}
// user处于游离状态,但是在内存中仍然存在
System.out.println("name=" + user.getName());
}
}
先运行testSave()方法,保存数据,
控制台打印的sql语句如下:
Hibernate:
insert
into
user
(version, name, pwd)
values
(?, ?, ?)
name=小明2
数据库表中的数据如图所示:
执行testUpdate()方法,控制台打印的sql语句如下:
Hibernate:
select
user0_.id as id1_0_0_,
user0_.version as version2_0_0_,
user0_.name as name3_0_0_,
user0_.pwd as pwd4_0_0_
from
user user0_
where
user0_.id=?
Hibernate:
update
user
set
version=?,
name=?,
pwd=?
where
id=?
and version=?
name=罗伯特
由sql语句,可以看出更新条件有两个,一个是id,另一个是version
数据库表数据,如图:
由图中可以看到version由0变为了1,
接下来再测试一个同事开启两个session,看哪个数据可以更新
测试代码:
/**
* 测试乐观锁
*/
@Test
public void testOptimistic() {
Session session = null;
Transaction tx = null;
User user = null;
try {
session = HibernateUtil.getSession();
tx = session.beginTransaction();
user = (User) session.get(User.class, 1) ;
user.setName("罗伯特") ;
Session session1 = HibernateUtil.getSession() ;
Transaction tx1 = session1.beginTransaction() ;
User user1 = (User) session.get(User.class, 1) ;
user1.setName("珠海") ;
session1.update(user1) ;
tx1.commit() ;
session.update(user) ;
tx.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
HibernateUtil.closeSession();
}
}
控制台打印信息:
Hibernate:
select
user0_.id as id1_0_0_,
user0_.version as version2_0_0_,
user0_.name as name3_0_0_,
user0_.pwd as pwd4_0_0_
from
user user0_
where
user0_.id=?
Hibernate:
update
user
set
version=?,
name=?,
pwd=?
where
id=?
and version=?
Hibernate:
update
user
set
version=?,
name=?,
pwd=?
where
id=?
and version=?
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.robert.pojo.User#1]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2541)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3285)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3183)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3525)
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:159)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:465)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1258)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
at com.robert.test.HibernateTest.testOptimistic(HibernateTest.java:95)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
数据库表数据如图:
由数据库中数据得知是user1数据更新到了数据库中,user的数据没有更新进去,报错了,
这是因为user和user1都从数据库中查询出了version是1的数据,但是user1先更新了数据,然后提交到了数据中,
此时,数据库中该数据的version变为了2,等到user也更新数据时,where条件中的version没有找到version是1的对应的数据,所以就报错了。
总结:
悲观锁 :优点是安全,缺点是并发效率低;
乐观锁 :安全性比悲观锁低,并发效率高;
如果数据需要大量修改,适用悲观锁。
如果数据时用来读取的,适用乐观锁。