Hibernate第五章知识点总结——第五章--事务管理
高级映射回顾
组件映射
继承映射
每张表一个类层次、每张表一个子类、每个类一张表
值类型集合映射
Set/Bag/idBag/List/Map
sort/order-by
目标
理解事务的概念及特性
理解并应用事务隔离级别
掌握Hibernate事务API
掌握乐观锁和悲观锁的应用
知识点预览
数据库事务
Hibernate事务
锁
数据库事务
1. 了解数据库事务
a) 数据库事务组合了数据库访问操作。
b) 一个事务要被确保以两种方式终止:提交或回滚。
c) 为了在事务内执行所有的数据库操作,必须标记这个工作单的范围,必须启用事务,在某个时间提交变化。如果出现错识,必须回滚事务,保留数据的一致状态。
2. 事务管理概述
a) 原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)一起称为ACID标准。
b) Atomicity:一个事务中包含的所有都是一个不可分割的工作单元。
c) Consistency:只有合法的数据可以被写入数据库,如果有任何违例(比如数据与字段类型不符),事务应该将其回滚到最初状态。
d) Isolation:事务允许多个用户对同一数据的并发访问,而不破坏数据的正确性和完整性,同时并行事务的修改和其他并行事务的修改相互独立。
e) Durability:一旦事务被提交之后,处理结果被固化(保存到可掉电存储器上)。
3. 事务隔离问题
a) 脏读(Dirty read)–如果一个事务读取另一个事务没有提交的数据。
b) 不可重复读(Unrepeatable read)–一个事务再次读取之前曾读取的数据,两次读取结果不一样。
c) 幻读(Phantom read)–一个事务执行一个查询两次,并且第二个结果集包括第一个结果集中不存在的记录。
4. 事务隔离级别
a) 未提交读(Read uncommitted)–允许脏读取,但不允许丢失更新
b) 提交读(Read committed)–不会读到另一个并行事务已修改但未提交的数据,避免“脏读”,此隔离级别是大多数主流数据库默认隔离级别,同时也适用于大多数应用系统。
c) 可重复读(Repeatable read)–一个事务不可能更新已经由另一个事务读取但未提交的数据,避免不可重复读取和脏读取
d) 串行读(Serializable)–最严格的事务隔离,要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行
e) 隔离级别
f) 4种隔离级别严密程度又前往后依次递增,同时其性能也依次下降,因此采取最高性能隔离级别并不可取,我们需根据实际情况进行取舍,以获得数据合法性和系统性能上最佳平衡。
5. 选择隔离级别
a) 首先排除”未提交读”隔离级别
b) 绝大部分应用都无须使用“序列化”隔离(一般来说,读取幻影数据并不是一个问题),此隔离级别也难以测量(scale poorly)
c) 可重复读隔离级别,消除一个事务在另外一个并发事务过程中覆盖数据的可能性
6. 设置隔离级别
a) 隔离级别值
1 –读取未提交隔离
2 –读取提交隔离
4 –可重复读隔离
8 –串行读隔离
b) Hibernate在配置文件中设置隔离级别
c) Hibernate不会改变从应用服务器获取的数据库连接的隔离级别
7. 设置隔离级别—配置文件
<!-- 设置隔离层次,控制事务的并发,缺省时Read Committed: 2 -->
<property name="connection.isolation">2</property>
Hibernate事务
1. Hibenrate是JDBC的轻量级封装,本身并不具备事务管理能力,因此在事务管理层Hibernate将其委托给底层的JDBC或者JTA,以实现事务的管理与调度;
2. 底层事务实现方式定义在Hibernate配置文件中,Hibernate默认事务处理机制基于JDBCTransaction。
3. 事务实现方式—配置文件
<!-- 配置事务 -->
<!-- 非管理环境:桌面应用,tomcat环境,可以不写 -->
<property name="transacton.factory_class">
org.hibernate.transaction.JDBCTransactionFactory
</property>
<!-- 管理环境,JTA事务 -->
<!-- property name="transacton.factory_class">
org.hibernate.transaction.JTATransactionFactory
</property-->
4. Hibernate事务—基于JDBC实现
a) Hibernate代码片段:
Session s=sessionFactory.openSession();
Transaction tx=s.beginTransaction();
……
tx.commit();
b) JDBC代码片段:
Connection conn=DiverManager.getConnection();
conn.setAutoCommit(false);
……
conn.commit();
5. Hibernate事务—基于JTA实现
JTA事务是有JTA Container维护的,事务的生命周期由JTA Container维护,与具体Connection无关
6. Hiberante事务API
public void transientToPersist(){
Session ses = sf.openSession();
Transaction tx = ses.beginTransaction();
//创建一个transient对象c
Course c = new Course("Core C++", "C++ fundamental");
try{
//将transient 对象c变成了persistent对象c
cid = (Long)ses.save(c);
tx.commit();
}catch(HibernateException he){ tx.rollback();throw he;
}finally{ses.close();}
//persistent对象c变成了detached对象c
}//garbage collection c
7. 提交回滚关闭
调用tx.commit()方法同步Session状态到数据库。
如果s.save(c)抛出一个异常,则必须强制调用tx.rollback()方法回滚事务。
非常重要的是,不管成功与否,都要在finally代码块关闭Session,以确保JDBC连接释放到数据库连接池。
锁
1. 锁
a) 业务逻辑处理中,往往需要数据访问排他性,需要通过一些机制保证这个数据在操作过程中不会被外界修改,锁是预防在并发访问数据资源时,当一个事务对数据加锁后,没有并发事务能读或者修改该数据资源。
b) 悲观锁(Pessimistic Locking)
c) 乐观锁(Optimistic Locking)
2. 悲观锁
a) 悲观锁在读取数据资源进行加锁,直到事务完成该锁被释放掉。
b) 依赖数据库的悲观锁(for update)
select * from t_user where name=‘zz’ for update
3. 锁模式
a) Hibernate内部锁机制:
LockMode.NONE–无锁机制
LockMode.WRITE–Hibernate在Insert和Update记录时会自动获取
LockMode.READ– Hibernate读取记录时会自动获取
b) 数据库锁机制:
LockMode.UPDGRADE–利用数据库的 for update子句加锁
LockMode.UPDGRADE_NOWAIT– Oracle的特定实现,利用oracle的for update nowait子句实现加锁
c) Hibernate的LockMode类可以让你请求一个特定的悲观锁
public void update(){
Session ses = sf.openSession();
Transaction tx = ses.beginTransaction();
try{
ses.lock(c, LockMode.UPGRADE);
c.setName("Core Java");
c.setDescription("Java language");
ses.update(c); //如果使用了lock()方法,没有必要调用update()
tx.commit();
}catch(HibernateException he){tx.rollback();throw he;
}finally{ses.close();}
}
d) 应用事务
i. 假设两个不同的柜员对同一账户进行修改并提交,这时,我们有三种方法可以处理写入数据库的并发
ii. 最晚提交生效(Last commit wins)–两个变更都成功,第二个变更覆盖第一个变更
iii. 最先提交生效(First commit wins)–第一个变更提交,第二个变更提交时会得到一个错误消息
iv. 合并冲突更新(Merge conflicting updates)–第一个变更提交,第二个修改会返回错误信息,用户可以有选择的应用修改
4. 乐观锁
a) Hibernate使用乐观版本管理可以使第二和每三个策略生效
b) 实现方式:
version:版本机制(版本号或时间戳)
dirty:检查被改动的属性
all:检查被保存的所有属性
c) 版本机制
版本管理使用一个增长的版本号或一个当前时间的时间戳
操作员A此时读出数据(version=1),并从账户扣除余额
在A操作过程中,B也读入此用户信息(version=1),并扣除余额
A修改完成之后,增加版本号(version=2),并更新余额信息
B修改完成后,增加版本号(version=2),在数据提交过程中,发现当前版本号不大于数据记录版本号,被驳回。
使用Hibernate版本管理,我们必须在User类添加一个版本属性,并且使用<version>标志或使用@Version注解映射该属性为一个版本号。
d) 使用版本管理—POJO类代码片段
package com.oracle.entity;
public class User {
private Integer userId;
private String userName;
private String userAddress;
private Integer userAge;
private int version;
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
}
e) 使用版本管理—映射文件
<?xml version="1.0" encoding="utf-8"?>
<!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.oracle.entity.User" table="T_USER" optimistic-lock="version">
<id name="userId" type="java.lang.Integer">
<column name="userId" length="32" />
<generator class="native" />
</id>
<version name="version" column="version"></version>
<property name="userName" type="java.lang.String">
<column name="userName" length="200" />
</property>
<property name="userAddress" type="java.lang.String">
<column name="userAddress" length="200" />
</property>
<property name="userAge" type="java.lang.Integer">
<column name="userAge" />
</property>
</class>
</hibernate-mapping>
f) 使用版本管理—持久化代码片段
public class Test {
public static void main(String[] args) {
try {
sf = cfg.buildSessionFactory();
s1 = sf.openSession();
s2=sf.openSession();
User u1=(User) s1.get(User.class, 1);
User u2=(User) s2.get(User.class, 1);
tx1 = s1.beginTransaction();
tx2=s2.beginTransaction();
u1.setUserAge(27);
tx1.commit();
u2.setUserAge(36);
tx2.commit();
} catch (HibernateException e) {
e.printStackTrace();
tx1.rollback();
tx2.rollback();
}finally{
if(s1!=null){s1.close();}if(s2!=null){s2.close();}if(sf!=null){sf.close();}
}
}
}
g) 使用版本管理
i. Hibernate每次更新时,会在SQL的子句中使用版本字段如下 :
ii. update orders set name=‘new name’,version=6 where id=1001 and version=5;
iii. 如果另一个事务读取并更新相同的信息, VERSION字段不是值5,则找不到匹配的行而更新不成功,此时Hibernate会抛出异常
总结
事务管理概述
ACID/三种问题/四种隔离级别
Hibernate事务API
JDBC实现/JTA实现
锁机制
悲观锁/乐观锁
问题
Hibernate的事务隔离级别
乐观锁和悲观锁