事务概述

Hibernate是对JDBC的轻量级封装,其主要功能是操作数据库。在操作数据库过程中,经常会遇到事务处理的问题,接下来就来介绍Hibernate中的事务管理。

事务的并发问题

在实际应用过程中,数据库是要被多个用户所共同访问的。在多个事务同时使用相同的数据时,可能会发生并发的问题,具体如下:

  • 脏读:一个事务读取到另一个事务未提交的数据。
  • 不可重复读:一个事务读到了另一个事务已经提交的update的数据,导致在同一个事务中的多次查询结果不一致。
  • 虚读/幻读:一个事务读到了另一个事务已经提交的insert的数据,导致在同一个事务中的多次查询结果不一致。

事务的隔离级别

为了避免事务并发问题的发生,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同。

  • 读未提交(Read Uncommitted,1级): 一个事务在执行过程中,既可以访问其他事务提交的新插入的数据,又可以访问未提交的修改数据。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。此隔离级别可防止丢失更新。
  • 读已提交(Read committed, 2级): 一个事务在执行过程中,既可以访问其他事务成功提交的新插入的数据,又可以访问成功修改的数据。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。此隔离级别可有效防止脏读。
  • 可重复读(Repeatable Read, 4级): 一个事务在执行过程中,可以访问其他事务成功提交的新插入的数据,但不可以访问成功修改的数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。此隔离级别可有效的防止不可重复读和脏读。
  • 序列化/串行化(Serializable, 8级):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。此隔离级别可有效的防止脏读、不可重复读和幻读。

在使用数据库时候,隔离级别越高,安全性越高,性能越低。

实际开发中,不会选择最高或者最低隔离级别,选择READ_COMMITTED(oracle默认)、REPEATABLE_READ(mysql默认)

Hibernate的事务控制

Hibernate事务代码规范写法

代码结构

1
2
3
4
5
6
7
8
try {
开启事务
提交事务
}catch() {
回滚事务
}finally {
关闭
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Test
public void testTx() {
SessionFactory sessionFactory = null;
Session session = null;
Transaction tx = null;
try {
sessionFactory = HibernateUtils.getSessionFactory();
session = sessionFactory.openSession();
//开启事务
tx = session.beginTransaction();

//添加
User user = new User();
user.setUsername("小马");
user.setPassword("250");
user.setAddress("美国");

session.save(user);

int i = 10/0;
//提交事务
tx.commit();
}catch(Exception e) {
e.printStackTrace();
//回滚事务
tx.rollback();
}finally {
//关闭操作
session.close();
sessionFactory.close();
}
}

Hibernate的事务管理

在Hibernate中,可以通过代码来操作管理事务,如通过Transaction tx=session.beginTransactiong();开启一个事务,持久化操作后,通过tx.commit(); 提交事务;如果事务出现异常,又通过tx.rollback();操作来撤销事务(事务回滚)。

除了在代码中对事务开启,提交和回滚操作外,还可以在hibernate的配置文件中对事务进行配置。配置文件中,可以设置事务的隔离级别。其具体的配置方法是在hibernate.cfg.xml文件中的<session-factory>标签元素中进行的。配置方法如下所示。

1
2
3
4
5
6
7
8
<!--     事务隔离级别
hibernate.connection.isolation = 4
1-Read uncommitted isolation
2-Read committed isolation
4-Repeatable read isolation
8-Serializable isolation
-->
<property name="hibernate.connection.isolation">4</property>

到这里我们已经设置了事务的隔离级别,那么我们在真正进行事务管理的时候,需要考虑事务的应用场景,也就是说我们的事务控制不应该是在DAO层实现的,应该在Service层实现,并且在Service中调用多个DAO实现一个业务逻辑的操作。具体操作如下显示:

img

Hibernate绑定session

在Dao层操作数据库需要用到session对象,在Service控制事务也是使用session对象完成. 我们要确保Dao层和Service层使用的使用同一个session对象。

有两种办法可以实现:

  1. 在业务层获取到Session,并将Session作为参数传递给DAO。

  2. 使用ThreadLocal将业务层获取的Session绑定到当前线程中,然后在DAO中获取Session的时候,都从当前线程中获取。

使用第二种方式肯定是最优方案,具体的实现已经不用我们来完成了,hibernate的内部已经将这个事情做完了。我们只需要在hibernate.cfg.xml中完成一段配置即可。

1
2
3
4
5
6
<!-- 配置session与当前线程绑定 -->
<!-- thread:Session对象的生命周期与本地线程绑定(推荐)
jta:Session对象的生命周期与JTA事务绑定
managed:hibernate委托程序来管理Session对象的生命周期。
-->
<property name="hibernate.current_session_context_class">thread</property>

注意:上述配置一般和sessionFactory.getCurrentSession()这个方法一起配合使用。getCurrentSession()方法用来绑定sessionThreadLocal,而且这个与线程绑定的session可以不用关闭,当事务提交时,session会自动关闭,不要手动调用close关闭。

1
2
3
4
//提供一个方法返回与本地线程绑定的session
public static Session getSession(){
return sessionFactory.getCurrentSession();
}

getCurrentSession获取当前线程的session对象,创建session对象之后不需要调用close方法,在线程结束的时候会自动将session关闭,不需要手动关闭,否则会出现session was closed 错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.itzheng.hibernate.demo01;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import com.itzheng.hibernate.utils.HibernateUtils;
/*
* 测试当前线程绑定的Session
*/
public class HibernateDemo04 {
@Test
public void demo01() {
//会按照配置的好的事务处理方式去存储数据
Session session = HibernateUtils.getCurrentSession();//保证每一个对象在调用该方法的时候都使用的是同一个session
Transaction transaction = session.beginTransaction();
Customer customer = new Customer();
customer.setCust_name("王西");
session.save(customer);
transaction.commit();
//session.close();不需要二次关闭session。因为在当前线程结束的时候就会关闭session对象,也就缓存
}
}