Spring Boot 事务管理

什么是事务?

事务实际上是指一系列操作,程序中的事务大多时候是指数据库事务,那么这一系列操作就是数据库操作,通常包含多个 SQL 语句。

为了保证完整性,事务需要满足 ACID 原则:
(1) 原子性(Atomic)
一个事务无论包含多少操作,要么全部执行,要么全部不执行,若执行期间某个操作失败,则在其之前执行的操作都要回滚到事务执行前状态。
(2) 一致性(Consistency)
一个事务使系统从一个一致状态转换到另一个一致状态。
(3) 隔离性(Isolation)
事务执行过程中的数据变化只存在于该事务中,对外界不产生影响,只有该事务正常执行完毕后,其它事务才能获取到这些变化的数据。
(4) 持久性(Durability)
事务正常执行完毕后对数据的改变是永久性的。

本文重点在于 Spring Boot 中事务的使用,不再赘述事务相关的技术细节。

本文示例基于之前已介绍过的代码,如有不清楚还请参看:
Spring Boot 集成 MyBatis
Spring Boot 集成阿里巴巴 Druid 数据库连接池

Spring 在之前版本中早已提供事务管理的能力,Spring Boot 诞生后进一步简化了事务配置工作。如果添加了 spring-boot-starter-jdbc 依赖,框架会默认自动注入 DataSourceTransactionManager ;如果添加了 spring-boot-starter-data-jpa 依赖,框架会默认自动注入 JpaTransactionManager。无需其它额外配置,直接在需要添加事务处理的方法上使用 @Transactional 注解。

1 定义 Service 接口

package demo.spring.boot.transaction.service;

import demo.spring.boot.transaction.domain.User;

public interface UserService {
    
    void addUser(User user);
}

2 定义 Service 接口实现类

package demo.spring.boot.transaction.service.impl;

import demo.spring.boot.transaction.dao.UserDao;
import demo.spring.boot.transaction.domain.User;
import demo.spring.boot.transaction.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    
    private final UserDao userDao;
    
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }
    
    @Override
    public void addUser(User user) {
        userDao.insert(user);
    }
}

3 编写单元测试

package demo.spring.boot.transaction;

import demo.spring.boot.transaction.domain.User;
import demo.spring.boot.transaction.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.time.LocalDate;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTests {
    
    @Autowired
    private UserService userService;
    
    @Test
    public void testAddUser() {
        User user = new User();
        user.setAccount("test_account");
        user.setName("Test Name");
        user.setBirth(LocalDate.now());
        userService.addUser(user);
    }
}

执行单元测试,测试通过,查询数据库可以看到刚插入的数据

mysql> select * from user \G;
*************************** 1. row ***************************
     id: 24
account: test_account
   name: Test Name
  birth: 2018-08-03
1 row in set (0.00 sec)

4 对 Service 接口实现类的 addUser 方法稍作调整,在插入数据后执行一条肯定会抛出异常的语句,如下

@Override
public void addUser(User user) {
    userDao.insert(user);
    int x = 1 / 0;
}

删除数据库 user 表中记录(因为 account 字段添加了唯一性索引),再次执行单元测试,测试失败,失败原因是被测试方法抛出了异常 java.lang.ArithmeticException: / by zero
但是因为抛出异常在 DAO 执行插入操作之后,所以数据库中还是成功插入了数据,数据库查询结果如下:

mysql> select * from user \G;
*************************** 1. row ***************************
     id: 25
account: test_account
   name: Test Name
  birth: 2018-08-03
1 row in set (0.00 sec)

5 在 Service 接口实现类的 addUser 方法中添加 @Transactional 注解实现事务管理

package demo.spring.boot.transaction.service.impl;

import demo.spring.boot.transaction.dao.UserDao;
import demo.spring.boot.transaction.domain.User;
import demo.spring.boot.transaction.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class UserServiceImpl implements UserService {
    
    private final UserDao userDao;
    
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }
    
    @Override
    @Transactional
    public void addUser(User user) {
        userDao.insert(user);
        int x = 1 / 0;
    }
}

执行和第 4 步相同步骤的单元测试,注意还是需要删除之前插入 user 表中记录,依旧因为异常原因导致单元测试失败,但是查询数据库 user 表无数据记录,说明异常抛出前 DAO 插入的数据已被成功回滚(省略数据库查询结果)。

注意:@Transactional 默认只回滚 Unchecked Exception,即 RuntimeException,所有 Checked Exception 默认是不会滚的

示例:
首先,修改 addUser 方法,将 int x = 1 / 0; 替换成抛出 Checked Exception

package demo.spring.boot.transaction.service.impl;

import demo.spring.boot.transaction.dao.UserDao;
import demo.spring.boot.transaction.domain.User;
import demo.spring.boot.transaction.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;

@Service
public class UserServiceImpl implements UserService {
    
    private final UserDao userDao;
    
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }
    
    @Override
    @Transactional
    public void addUser(User user)
        throws IOException {
        userDao.insert(user);
        throw new IOException();
    }
}

其次,修改单元测试方法

@Test
public void testAddUser()
    throws IOException {
    User user = new User();
    user.setAccount("test_account");
    user.setName("Test Name");
    user.setBirth(LocalDate.now());
    userService.addUser(user);
}

最后,运行单元测试,测试失败,但是查询数据库仍看到抛出异常前插入的数据

mysql> select * from user \G;
*************************** 1. row ***************************
     id: 28
account: test_account
   name: Test Name
  birth: 2018-08-03
1 row in set (0.00 sec)

@Transactional 指定异常回滚

查看 @Transactional 源码,rollbackFor 属性可以指定针对某些异常回滚(其它类似功能属性请参考 API 文档)

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    boolean readOnly() default false;

    Class[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

修改 addUser 方法,添加 @Transactional 注解属性 rollbackFor,指定当抛出 java.io.IOException 异常时回滚

package demo.spring.boot.transaction.service.impl;

import demo.spring.boot.transaction.dao.UserDao;
import demo.spring.boot.transaction.domain.User;
import demo.spring.boot.transaction.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;

@Service
public class UserServiceImpl implements UserService {
    
    private final UserDao userDao;
    
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }
    
    @Override
    @Transactional(rollbackFor = {IOException.class})
    public void addUser(User user)
        throws IOException {
        userDao.insert(user);
        throw new IOException();
    }
}

运行单元测试,测试失败,但是查询数据库 user 表无数据记录,说明异常抛出前 DAO 插入的数据已被成功回滚(省略数据库查询结果)。

如果赋予 rollbackFor 属性其它异常类型,既不是 java.io.IOException 又不是其父类,则运行单元测试后尽管测试失败,但是异常抛出前的数据也会被插入数据库 user 表中,请自行测试。

项目工程目录


Spring Boot 事务管理_第1张图片

POM文件



    4.0.0

    demo.spring.boot
    demo-spring-boot-transaction
    0.0.1-SNAPSHOT
    jar

    demo-spring-boot-transaction
    Demo project for Spring Boot

    
        org.springframework.boot
        spring-boot-starter-parent
        2.0.4.RELEASE
         
    

    
        UTF-8
        UTF-8
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-jdbc
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            1.3.2
        
        
            mysql
            mysql-connector-java
            runtime
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


你可能感兴趣的:(Spring Boot 事务管理)