🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
- 1. 回忆数据库中的事务
 - 2. Spring中事务的实现
 - 2.1 Spring编程式事务
 - 2.2 Spring声明式事务@Transactional
 - 2.3 @Transactional的作用
 
- 3. @Transactional详解
 - 3.1 rollbackFor
 - 3.2 事务隔离级别
 - 3.2.1 回顾MySQL中的事务隔离级别
 - 3.3.2 Spring事务隔离级别
 
- 3.3 Spring事务传播机制
 - 3.3.1 概念
 - 3.3.2 事务的传播机制分类
 - 3.3.3 Spring事务传播机制代码演示
 
1. 回忆数据库中的事务
https://lilesily12385.blog.csdn.net/article/details/137935719
2. Spring中事务的实现
Spring中的事务操作分为两类:
- 编程式事务(手动写代码操作事务)
 - 声明式事务(使用注解完成)
 
现有一需求: 用户注册,注册时候在日志表中插入一条操作记录
 数据准备:
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;
-- ⽤⼾表 
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (
 `id` INT NOT NULL AUTO_INCREMENT,
 `user_name` VARCHAR (128) NOT NULL,
 `password` VARCHAR (128) NOT NULL,
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now() ON UPDATE now(),
 PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARACTER 
SET = utf8mb4 COMMENT = '⽤⼾表';
-- 操作⽇志表 
DROP TABLE IF EXISTS log_info;
CREATE TABLE log_info (
 `id` INT PRIMARY KEY auto_increment,
 `user_name` VARCHAR ( 128 ) NOT NULL,
 `op` VARCHAR ( 256 ) NOT NULL,
 `create_time` DATETIME DEFAULT now(),
 `update_time` DATETIME DEFAULT now() ON UPDATE now() 
) DEFAULT charset 'utf8mb4';
 
代码准备:
 1. 创建项目,配置文件引入日志打印,驼峰转换,数据库配置等.
spring:
 datasource:
 url: jdbc:mysql://127.0.0.1:3306/trans_test?
characterEncoding=utf8&useSSL=false
 username: root
 password: root
 driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
 configuration: # 配置打印 MyBatis⽇志 
 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
 map-underscore-to-camel-case: true #配置驼峰⾃动转换 
 
创建实体类:
import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
	private Integer id;
	private String userName;
	private String password;
	private Date createTime;
	private Date updateTime;
}
 
mport lombok.Data;
import java.util.Date;
@Data
public class LogInfo {
	private Integer id;
	private String userName;
	private String op;
	private Date createTime;
	private Date updateTime;
}
 
Mapper:
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper {
 @Insert("insert into user_info(`user_name`,`password`)values(#{name},#{password})")
	Integer insert(String name,String password);
}
 
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface LogInfoMapper {
 @Insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")
	Integer insertLog(String name,String op);
}
 
Service:
@Slf4j
@Service
public class UserService {
	@Autowired
	private UserInfoMapper userInfoMapper;
	public void registryUser(String name,String password){
	//插⼊⽤⼾信息 
	userInfoMapper.insert(name,password);
	}
}
 
 @Slf4j
 @Service
 public class LogService {
	@Autowired
	private LogInfoMapper logInfoMapper;
	public void insertLog(String name,String op){
	//记录⽤⼾操作 
	logInfoMapper.insertLog(name,"⽤⼾注册");
	}
 }
 
2.1 Spring编程式事务
Spring手动操作事务主要分为三步,与Sql事务的操作类似:
 - 开启事务
 - 提交事务
 - 或者回滚事务
@RestController
@RequestMapping("/user")
public class TransactionController {
    @Autowired
    private UserService userService;
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;
    @RequestMapping("/registry")
    public String login(String userName,String password){
        //开启事务
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        //用户注册
        userService.registryUser(userName,password);
        //提交事务
        dataSourceTransactionManager.commit(transaction);
        //回滚事务
//        dataSourceTransactionManager.rollback(transaction);
        return "注册成功";
    }
}
 
SpringBoot内置了两个对象:
DataSourceTransactionManager,数据源事务管理器,一般用于事务的开启回滚和提交.TransactionDefinition是事务的属性,一般用于传给事务管理器的getTransaction方法,用于事务的获取与开启.
- 观察事务提交
 
dataSourceTransactionManager.commit(transaction);
 

 
 我们观察到数据库,数据被插入成功.
- 观察事务回滚
 
dataSourceTransactionManager.rollback(transaction);
 

 
 我们看到虽然返回的结果是注册成功,但是数据库中并没有多出任何数据.
 下面我们来学习一种简单而快捷的方法,使用注解声明.
2.2 Spring声明式事务@Transactional
一共有两部操作:
- 添加依赖
 
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>
 
- 在事务的方法上添加
@Transactional注解就可以实现了.无需手动开启和提交事务,在事务全部执行完成之后会自动提交,在中途发生异常的手会自动回滚. 
@RestController
@RequestMapping("/user2")
public class TransactionController2 {
    @Autowired
    private UserService userService;
    @RequestMapping("/registry2")
    @Transactional
    public String registry2(String userName,String password){
        userService.registryUser(userName,password);
        return "注册成功";
    }
}
 
运行程序后,数据插入成功.
 修改程序,使之出现异常:
@RestController
@RequestMapping("/user2")
public class TransactionController2 {
    @Autowired
    private UserService userService;
    @RequestMapping("/registry2")
    @Transactional
    public String registry2(String userName,String password){
        userService.registryUser(userName,password);
        int i = 10/0;
        return "注册成功";
    }
}
 
运行之后,虽然返回了注册成功,但是数据库并没有更新结果.
我们一般写事务的时候会在业务逻辑层来控制事务,因为在业务逻层中,一个业务功能可能会包含多个数据库访问操作,这样就可以把多个访问数据库的操作 合并在同一个事务中.
2.3 @Transactional的作用
@Transactional可以用来修饰方法或者是类.
 修饰方法的时候,只对public修饰的方法生效,修饰其他方法也不会报错,但是也不会生效.
 修饰类的时候,对类中的所有public方法生效.
 在程序出现异常的时候,如果异常没有被捕获,这时候事务就会被回滚,但是如果异常被捕获,就会被认为是正常执行,依然会提交事务.
 修改上述代码:
@RestController
@RequestMapping("/user2")
public class TransactionController2 {
    @Autowired
    private UserService userService;
    @RequestMapping("/registry2")
    @Transactional
    public String registry2(String userName,String password){
        userService.registryUser(userName,password);
        try {
            int i = 10/0;
        }catch (ArithmeticException e) {
            e.printStackTrace();
        }
        return "注册成功";
    }
}
 
运行程序之后,虽然出错了,由于异常得到了捕获,事务便得到了提交.
 以下两种情况,事务依然会回滚:
- 重新抛出异常
 
try {
    int i = 10/0;
}catch (ArithmeticException e) {
    throw e;
}
 
- 手动回滚事务
使用TransactionAspectSupport.currentTransactionStatus()得到当前事务,并使用setRollbackOnly回滚. 
try {
    int i = 10/0;
}catch (ArithmeticException e) {
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
 
3. @Transactional详解
我们主要学习@Transactional注解的三个属性:
 1. rollbackFor:异常回滚属性,指定能够触发事务回滚的异常类型.可以指定多个异常类型.
 2. Isolation:事务的隔离级别,默认是Isolation.DEFAULT.
 3. propagation:事务的传播机制.默认值为propagation.REQUIRED
3.1 rollbackFor
异常回滚的时候,@Transactional默认在遇到Error或者运行时异常时才会回滚.
 
 接下来我们来使用代码验证:
@Transactional
@RequestMapping("/registry3")
public String registry3(String userName,String password) throws IOException {
    userService.registryUser(userName,password);
    if (true){
        throw new IOException();
    }
    return "注册成功";
}
 
向服务器提交数据:
 
 
 我们看到,虽然抛出了异常,但是数据库的数据仍然被修改了.
 如果我们要想指定回滚异常的类型,我们需要通过@Transactional的rollbackFor属性来完成,给属性传入异常的类对象来实现对回滚异常的指定,
@RequestMapping("/registry3")
@Transactional(rollbackFor = Exception.class)
public String registry3(String userName,String password) throws IOException {
    userService.registryUser(userName,password);
    if (true){
        throw new IOException();
    }
    return "注册成功";
}
 
运行程序:
 

 我们看到,事务并没有进行提交,被回滚了,数据库的数据并没有更行.
3.2 事务隔离级别
3.2.1 回顾MySQL中的事务隔离级别
https://lilesily12385.blog.csdn.net/article/details/137935719
3.3.2 Spring事务隔离级别
Spring中事务的隔离级别有5种:
- Isolation.DEFAULT: 以连接数据库的隔离级别为准.
 - Isolation.READ_UNCOMMITTED:读未提交
 - Isolating.READ_COMMITTED:读提交.
 - Isolation.REPEATABLE_READ:可重复读.
 - Isolation.SERIALIZABLE:串行化.
 
public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);
    private final int value;
    private Isolation(int value) {
        this.value = value;
    }
    public int value() {
        return this.value;
    }
}
 
Spring中的隔离级别可以通过@Transactional中的Isolation属性进行设置.
@RequestMapping("/registry3")
@Transactional(isolation = Isolation.DEFAULT)
public String registry3(String userName,String password) throws IOException {
    userService.registryUser(userName,password);
    return "注册成功";
}
 
3.3 Spring事务传播机制
3.3.1 概念
事务的传播机制就是:多个事务方法存在调用关系的时候,事务是如何在这些方法之间进行传播的.事务的传播机制就解决的是一个事务在多个节点上(方法)中的传递.
 
比如有两个方法A和B,他们都被@Transactional修饰,方法A调用了方法B.A方法运行的时候,会开启一个新的事物,当A调用B的时候,B方法本身也有事务,此时B方法运行的时候,是假如A事务,还是创建一个新的事务呢?下面我们就来介绍一下事务的传播机制.
3.3.2 事务的传播机制分类
@Transactional注解支持事务的传播机制的设置,我们可以通过propagation属性来设置传播行为.
- Propagation.REQUIRED:加入事务,默认的事务传播级别.如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务.(加入事务,就是共用一个事务,一个事务发生异常,全部回滚.)
 - Propagation.SUPPORTS:如果当前存在事务,则加入该事务.如果当前没有事务,则以非事务的方式继续运行.
 - Propagation.MANDATORY:强制性,如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常.
 - Propagation.REQUIERS_NEW:新建事务,创建一个新事务如果当前存在事务,则把当前事务挂起.(发生异常,不影响其他事务)也就是不管外部方法是否开启事务,
Propagation.REQUIERS_NEW修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干涉. - Propagation.NOT_SUPPORTED: 以非事务的方式运行,如果当前存在事务则把当前事务挂起.
 - Propagation.NEVER: 不支持当前事务,以非事务的方式运行,如果存在事务,则抛出异常.
 - Propagation.NESTED: 嵌套事务,如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则该取值等价于
Propagation.REQUIRED.
1,4对应,2,5对应,3,6对应. 
public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);
    private final int value;
    private Propagation(int value) {
        this.value = value;
    }
    public int value() {
        return this.value;
    }
}
 
举例说明:一对新人结婚,需要房子
- Propagation.REQUIRES: 如果你有房子,就住你的房子(加入事务),如果你没有房子,我们就一起买房子(创建一个新事务).
 - Propagation.SUPPORTS: 如果你有房子,我们就住你的房子(加入事务),如果没有房子,我们就租房子(以非事务的方式运行).
 - Propagation.MANDATORY: 要求必须有房子(加入事务),如果没有房子,就不结婚(抛出异常)
 - Propagation.REQUIERS_NEW: 必须买新房,不管你有没有房子,必须两个人一起买房(创建一个新事务),即使有房也不住(当前事务挂起).
 - Propagation.NOT_SUPPORTED: 不管你有没有房子,我都不住(挂起当前事务),必须租房(以非事务的方式运行).
 - Propagation.NEVER:不能有房子(当前存在事务),有房子就不结婚(抛出异常).
 - Propagation.NESTED :如果你没房,就⼀起买房.如果你有房,我们就以房子为根据地,做点下生意.(如果如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行.如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED )
 
3.3.3 Spring事务传播机制代码演示
重点关注两个: REQUIRED,REQUIERS_NEW.
- REQUIRED(加入事务)
用户注册,插入一条数据,并记录操作日志. 
@RequestMapping("/r4")
@Transactional
public String registry4(String userName,String password){
    userService.registryUser(userName,password);
    logService.insertLog(userName,"用户注册");
    return "注册成功";
}
@Service
public class UserService {
    @Autowired
    public UserInfoMapper userInfoMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public void registryUser(String name,String password) {
        userInfoMapper.insert(name, password);
    }
}
@Service
public class LogService {
    @Autowired
    private LogInfoMapper logInfoMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertLog(String name,String op){
        int i = 10/0;
        //记录⽤⼾操作
        logInfoMapper.insertLog(name,"⽤户注册");
    }
}
 
我们在执行之后,发现数据库中并没有插入任何数据,这就是因为insertLog方法发生了异常,事务发生回滚,当事务回滚之后,registry4方法也发生了回滚,导致了registryUser也发生了回滚,导致数据库中没有插入数据.
- REQUIRES_NEW(新建事务)
将上面的UserService和LogService的事务传播机制改为Propagation.REQUIRES_NEW. 
@RequestMapping("/r4")
@Transactional
public String registry4(String userName,String password){
    userService.registryUser(userName,password);
    logService.insertLog(userName,"用户注册");
    return "注册成功";
}
@Service
public class LogService {
    @Autowired
    private LogInfoMapper logInfoMapper;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertLog(String name,String op){
        int i = 10/0;
        //记录⽤⼾操作
        logInfoMapper.insertLog(name,"⽤户注册");
    }
}
@Service
public class UserService {
    @Autowired
    public UserInfoMapper userInfoMapper;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void registryUser(String name,String password) {
        userInfoMapper.insert(name, password);
    }
}
 
在执行之后,我们会发现,日志表中并没有插入新的数据,但是用户表中插入了新的数据,这是由于UserService,LogService与registry4属于不同的事务,LogService出现异常回滚之后不会影响registry4和UserService的执行.
- NEVER(不支持当前事务)
把REQUIRED代码的UserService中的对应方法的事务传播机制修改为Propagation.NEVER.并去掉制造的异常. 
@Transactional(propagation = Propagation.NEVER)
public void insertLog(String name,String op){
    //记录⽤⼾操作
    logInfoMapper.insertLog(name,"用户注册");
}
 
运行之后,程序抛出异常,日志表和用户表均没有数据插入.
- NESTED(嵌套事务)
将上述REQUIRED的UserService中的对应方法的事务传播机制修改为Propagation.NESTED. 
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name,String op){
    int i = 10/0;
    //记录用户操作
    logInfoMapper.insertLog(name,"用户注册");
}
 
@Transactional(propagation = Propagation.NESTED)`在这里插入代码片`
public void registryUser(String name,String password) {
    userInfoMapper.insert(name, password);
}
 
运行程序之后,两张表中都没有插入任何数据,如果我们对出现异常的方法进行手动回滚:
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name,String op){
    try {
        int i = 10/0;
    }catch (Exception e){
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    //记录用户操作
    logInfoMapper.insertLog(name,"用户注册");
}
 

 我们看到,用户表插入成功了,但是日志表的数据被回滚了.
 
 
区分REQUIRED和NESTED
REQUIRED:当其中一个事务出现异常的时候,所有事务都会回滚,如果try-catch语句中对事物进行手动回滚,则子事务和父事务全部会被回滚.
NESTED : 当子事务出现异常的时候,子事务对应的父事务也会回滚,但是如果在有异常的子事务中进行try-catch,catch中对事务进行手动回滚,则只有出现异常的事务被回滚,但是另一个没有出现异常的子事务没有被回滚,与REQUIRED最大的区别就是,NESTED可以做到部分回滚,但是REQUIRED只能做到全部回滚.




















