4.1 事务概述

我们在开发企业应用时,通常业务人员的一个是对数据库读写的多个原子操作的结合。由于数据操作在顺序执行的过程中,任何一步都有可能发生异常,进而导致后续操作无法完成,此时由于业务逻辑并未正确的完成,之前成功操作的数据并不可靠,如果要让这个业务正确的执行下去,通常有实现方式:

  • 记录失败的位置,问题修复之后,从上一次执行失败的位置开始继续执行后面要做的业务逻辑。
  • 在执行失败的时候,回退本次执行的所有过程,让操作恢复到原始状态,待问题修复之后,重新执行原来的业务逻辑。

事务就是针对上述方式2的实现。事务(Transaction)一般是指要做的或所做的事情,就是上面所说的业务人员的一个操作。比如电商系统中,一个创建订单的操作包含了创建订单、商品库存的扣减两个基本操作。如果创建订单成功,库存扣减失败,那么就会出现商品超卖的问题,所以最基本的最发就是需要为这两个操作用事务包括起来,保证这两个操作要么都成功,要么都失败。

这样的场景在实际开发过程中非常多,本章讨论Spring Boot中的数据库事务管理。

4.2 Spring Boot数据库事务管理

MyBatis-Spring 允许 MyBatis 参与到 Spring 的事务管理中,而不是创建一个新的专用事务管理器。它借助 Spring 中的 DataSourceTransactionManager 来实现事务管理。而在Spring Boot中,当我们使用了spring-boot-starter-jdbc依赖的时候,框架会自动注入DataSourceTransactionManager。所以我们不需要任何额外配置就可以用@Transactional注解进行事务的使用。

下面是我们在上一章编写的单元测试测代码

  1. @Test
  2. public void testInsert() throws Exception {
  3. userMapper.insert(new UserEntry("aa1", "13701111234"));
  4. userMapper.insert(new UserEntry("bb1", "18601234567"));
  5. userMapper.insert(new UserEntry("cc1", "18801885678"));

可以看到,在这个单元测试用例中,连续创建了3个User实体到数据库中,下面我们人为的来制造一些异常,看看会发生什么情况。

  1. @Test
  2. public void testInsert() throws Exception {
  3. userMapper.insert(new UserEntry("aa1", "13701111234"));
  4. userMapper.insert(new UserEntry("bb1", "18601234567"));
  5. userMapper.insert(new UserEntry("cc1", "188018856781234"));

执行测试用例,可以看到控制台中抛出了如下异常,
image.png
可以看到,测试用例执行到一半之后因为异常中断了,前2条数据正确
image.png
现在我们手工删除53、54两条数据,并且给testInsert方法加上@Transactional注解(需要用IDEA的功能自动导入org.springframework.transaction.annotation.Transactional

  1. @Test
  2. @Transactional
  3. public void testInsert() throws Exception {
  4. userMapper.insert(new UserEntry("aa1", "13701111234"));
  5. userMapper.insert(new UserEntry("bb1", "18601234567"));
  6. userMapper.insert(new UserEntry("cc1", "188018856781234"));

执行该测试用例,在控制台的信息中可以看到在事务开始之后又发生了回滚(Rolled back transaction for test):
image.png
再看数据库中,User表没有aa1和bb1的用户数据,确实成功实现了自动回滚。

这里主要通过单元测试演示了如何使用@Transactional注解来声明一个函数需要被事务管理,通常我们单元测试为了保证每个测试之间的数据独立,会使用@Rollback注解让每个单元测试都能在结束时回滚。比如我们可以写下面这样的测试方法

  1. @Test
  2. @Rollback
  3. @Transactional
  4. public void testInsertRollback() {
  5. userMapper.insert(new UserEntry("aa1", "13701111234"));
  6. userMapper.insert(new UserEntry("bb1", "18601234567"));
  7. userMapper.insert(new UserEntry("cc1", "18801885678"));
  8. Assert.assertEquals(4, userMapper.getAll().size());
  9. }

你可以看到尽管没有任何错误,在方法完成之后还是回滚了
image.png
这是查看数据库,确实只有之前添加的那一条数据。

这需要注意:必须同时使用 @Transactional 注解,单独的 @Rollback 注解不会起作用

在开发业务逻辑时,我们通常在service层接口中使用@Transactional来对各个业务逻辑进行事务管理的配置,以确保数据不会因异常而出现错乱。例如

  1. public interface UserService {
  2. @Transactional
  3. UserEntry update(String name, String password);
  4. }

4.3 事务详解

上面的例子中我们使用了默认的事务配置,可以满足一些基本的事务需求,但是当项目较复杂时(比如有多个数据源等),需要在声明事务时指定不同的事务管理器。为此,需要通过注解的 value 属性指定配置的事务管理器名称。例如:@Transactional(value="transactionManagerPrimary")

除了指定不同的事务管理器之后,还能对事务进行隔离级别和传播行为的控制。

4.3.1 隔离级别

隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。

我们可以看org.springframework.transaction.annotation.Isolation枚举类中定义了五个表示隔离级别的值:

  1. public enum Isolation {
  2. DEFAULT(-1),
  3. READ_UNCOMMITTED(1),
  4. READ_COMMITTED(2),
  5. REPEATABLE_READ(4),
  6. SERIALIZABLE(8);
  7. }
  • DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是:READ_COMMITTED。
  • READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别
  • READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值
  • REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
  • SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能通常情况下也不会用到该级别

具体的,使用isolation属性设置隔离级别,例如:

  1. @Transactional(isolation = Isolation.DEFAULT)

4.3.2 传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。

我们可以看org.springframework.transaction.annotation.Propagation枚举类中定义了6个表示传播行为的枚举值:

  1. public enum Propagation {
  2. REQUIRED(0),
  3. SUPPORTS(1),
  4. MANDATORY(2),
  5. REQUIRES_NEW(3),
  6. NOT_SUPPORTED(4),
  7. NEVER(5),
  8. NESTED(6);
  9. }
  • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。

通过使用propagation属性设置:

  1. @Transactional(propagation = Propagation.REQUIRED)

版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。