【SpringBoot】Spring事务--详解@Transactional:rollbackFor、事务隔离级别(isolation)、事务传播机制(propagation))

0.介绍

上篇文章讲解@Transactional的使用,这篇主要讲解 @Transactional 注解当中的三个常⻅属性及使用:

  1. rollbackFor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型
  2. Isolation: 事务的隔离级别. 默认值为 Isolation.DEFAULT
  3. propagation: 事务的传播机制. 默认值为 Propagation.REQUIRED

注:使用的是该文章中的表格和基本代码

1.rollbackFor

1.1 介绍

@Transactional 默认只在遇到运⾏时异常(RuntimeException)和Error时才会回滚, 其他情况下不回滚. 即在Exception的⼦类中, 除了RuntimeException及其⼦类,其他的都不回滚。

在这里插入图片描述

1.2 演示

1.2.1 抛IO异常–提交

Controller接口:

@RequestMapping("/user")
@RestController
public class UserInfoController {
    @Autowired
    private UserInfoService userInfoService;

    @RequestMapping("/update")
    public boolean  updateUserInfo(@RequestBody LogInfo logInfo) throws IOException {
        boolean flag =  userInfoService.updateUserInfo(logInfo);

        return flag;
    }
}

Service接口:

@Slf4j
@Service
public class UserInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Autowired
    private UserInfoMapper userInfoMapper;

    @Transactional
    public boolean updateUserInfo(LogInfo logInfo) throws IOException {

        // 处理 from 向 to 转账 10

        // 事务逻辑

        // 1.from 的账户 -10
        userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());

        // 2.to 的账户 +10
        userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());

        // 3. 记录日志
        logInfoMapper.insert(logInfo);

        // 故意设置抛异常
        if(true){
            throw new IOException();
        }

        return true;

    }
}

使用postman测试:
在这里插入图片描述

运行结果:
在这里插入图片描述
前端接收到的数据报错,但是后端已经把事务提交了。

MySQL中数据(原始数据 zangsan–70;lisi–130):
在这里插入图片描述
可以看到,数据库中的数据发生了改变

1.2.2 rollbackFor 指定所有的异常回滚

如果我们需要所有异常都回滚, 需要来配置 @Transactional 注解当中的 rollbackFor 属性, 通过 rollbackFor 这个属性指定出现何种异常类型时事务进⾏回滚

Service接口代码:

@Slf4j
@Service
public class UserInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Autowired
    private UserInfoMapper userInfoMapper;

    //rollbackFor = Exception.class 指定所有异常回滚
    @Transactional(rollbackFor = Exception.class)
    public boolean updateUserInfo(LogInfo logInfo) throws IOException {

        // 处理 from 向 to 转账 10

        // 事务逻辑

        // 1.from 的账户 -10
        userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());

        // 2.to 的账户 +10
        userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());

        // 3. 记录日志
        logInfoMapper.insert(logInfo);

        // 故意设置抛异常

        if(true){
            throw new IOException();
        }

        return true;

    }
}

使用postman测试:
在这里插入图片描述

运行结果:
在这里插入图片描述
通过运行结果可以发现,只是把会话释放了,没有提交事务

MySQL:
在这里插入图片描述
MySQL中的数据没有发生改变

1.3 结论

结论:
• 在Spring的事务管理中,默认只在遇到运⾏时异常RuntimeException和Error时才会回滚.
• 如果需要回滚指定类型的异常, 可以通过rollbackFor属性来指定.

2.事务隔离级别

2.1 MySQL 事务隔离级别(回顾)

SQL 标准定义了四种隔离级别, MySQL 全都⽀持. 这四种隔离级别分别是:

  1. 读未提交(READ UNCOMMITTED): 读未提交, 也叫未提交读. 该隔离级别的事务可以看到其他事务中未提交的数据.

因为其他事务未提交的数据可能会发⽣回滚, 但是该隔离级别却可以读到, 我们把该级别读到的数据称之为脏数据, 这个问题称之为脏读.
比特就业课

  1. 读提交(READ COMMITTED): 读已提交, 也叫提交读. 该隔离级别的事务能读取到已经提交事务的数据.

该隔离级别不会有脏读的问题.但由于在事务的执⾏中可以读取到其他事务提交的结果, 所以在不同时间的相同 SQL 查询可能会得到不同的结果, 这种现象叫做不可重复读

  1. 可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别.

⽐如此级别的事务正在执⾏时, 另⼀个事务成功的插⼊了某条数据, 但因为它每次查询的结果都是⼀样的, 所以会导致查询不到这条数据, ⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因). 明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去, 这个现象叫幻读.

  1. 串⾏化(SERIALIZABLE): 序列化, 事务最⾼隔离级别. 它会强制事务排序, 使之不会发⽣冲突, 从⽽解决了脏读, 不可重复读和幻读问题, 但因为执⾏效率低, 所以真正使⽤的场景并不多
事务隔离级别脏读不可重复读幻读
读未提交 (READ UNCOMMITTED)
读已提交 (READ COMMITTED)×
可重复读 (REPEATABLE READ)××
串行化 (SERIALIZABLE)×××

2.2 Spring事务隔离级别

Spring 中事务隔离级别有5 种:

  1. Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主.
  2. Isolation.READ_UNCOMMITTED : 读未提交, 对应SQL标准中 READ UNCOMMITTED
  3. Isolation.READ_COMMITTED : 读已提交,对应SQL标准中 READ COMMITTED
  4. Isolation.REPEATABLE_READ : 可重复读, 对应SQL标准中 REPEATABLE READ
  5. Isolation.SERIALIZABLE : 串⾏化, 对应SQL标准中 SERIALIZABLE

2.3 ioslation 属性

Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置

赋值时需要使用Isolation枚举类:

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;
    }
}

举例:

@Slf4j
@Service
public class UserInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Autowired
    private UserInfoMapper userInfoMapper;

    // Isolation.READ_COMMITTED 设置读已提交
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public boolean updateUserInfo(LogInfo logInfo) throws IOException {

        // 处理 from 向 to 转账 10

        // 事务逻辑

        // 1.from 的账户 -10
        userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());

        // 2.to 的账户 +10
        userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());

        // 3. 记录日志
        logInfoMapper.insert(logInfo);

        // 故意设置抛异常

        if(true){
            throw new IOException();
        }

        return true;
    }
}

3.Spring事务传播机制

3.1 什么是事务传播机制?

事务传播机制就是: 多个事务⽅法存在调⽤关系时, 事务是如何在这些⽅法间进⾏传播的.

⽐如有两个⽅法A, B都被 @Transactional 修饰, A⽅法调⽤B⽅法
A⽅法运⾏时, 会开启⼀个事务. 当A调⽤B时, B⽅法本⾝也是事务, 此时B⽅法运⾏时, 是加⼊A的事务, 还是创建⼀个新的事务呢?
这个就涉及到了事务的传播机制.

⽐如公司流程管理
执⾏任务之前, 需要先写执⾏⽂档, 任务执⾏结束, 再写总结汇报,此时A部⻔有⼀项⼯作, 需要B部⻔的⽀援, 此时B部⻔是直接使⽤A部⻔的⽂档, 还是新建⼀个⽂档呢?
在这里插入图片描述

事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题:
在这里插入图片描述
⽽事务传播机制解决的是⼀个事务在多个节点(⽅法)中传递的问题:
在这里插入图片描述

3.2 事务的传播机制有哪些

@Transactional 注解⽀持事务传播机制的设置, 通过 propagation 属性来指定传播⾏为

Spring 事务传播机制有以下 7 种:
1.Propagation.REQUIRED: 默认的事务传播级别. 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则创建⼀个新的事务.
2. Propagation.SUPPORTS : 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则以非事务的⽅式继续运行.
3. Propagation.MANDATORY :强制性. 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则抛出异常.
4. Propagation.REQUIRES_NEW : 创建⼀个新的事务. 如果当前存在事务, 则把当前事务挂起. 也就是说不管外部⽅法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部⽅法都会新开
启⾃⼰的事务, 且开启的事务相互独⽴, 互不⼲扰.
5. Propagation.NOT_SUPPORTED : 以⾮事务⽅式运⾏, 如果当前存在事务, 则把当前事务挂起(不⽤).
6. Propagation.NEVER : 以⾮事务⽅式运⾏, 如果当前存在事务, 则抛出异常.
6. Propagation.NESTED : 如果当前存在事务, 则创建⼀个事务作为当前事务的嵌套事务来运⾏.如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED

枚举类Propagation:

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;
    }
}

3.3 Spring 事务传播机制使用

对于以上事务传播机制,我们重点关注以下两个就可以了:

  1. REQUIRED(默认值)
  2. REQUIRES_NEW

3.3.1 Propagation.REQUIRED(加入事务)

程序设计:
在controller中依次调用两个service,传播机制都设置为Propagation.REQUIRED。

(1)Controller层:

@RequestMapping("/user")
@RestController
public class UserInfoController {
    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private LogInfoService logInfoService;

    @RequestMapping("/update1")
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean  updateUserInfo(@RequestBody LogInfo logInfo) {

        // 1. 更新用户信息
        boolean flag1 =  userInfoService.updateUserInfo(logInfo);

        // 2.插入日志
        boolean flag2 = logInfoService.insert(logInfo);

        if(flag1 && flag2){
            return true;
        }

        return false;
    }
}

(1)Service:

@Slf4j
@Service
public class UserInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Autowired
    private UserInfoMapper userInfoMapper;


    @Transactional(propagation = Propagation.MANDATORY)
    public boolean updateUserInfo(LogInfo logInfo) {

        // 处理 from 向 to 转账 10

        // 事务逻辑

        // 1.from 的账户 -10
        userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());

        // 2.to 的账户 +10
        userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());

        // 3. 记录日志
        // 在LogInfoService实现
        
        return true;

    }
}

(2)Service(故意在该方法中设置一个错误):

@Service
public class LogInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public boolean insert(LogInfo logInfo){

		//错误点
        int n = 10/0;

        //记录日志
        int flag = logInfoMapper.insert(logInfo);

        if(flag>0 && n>=0){
            return true;
        }

        return  false;
    }
}

使用postman测试:
在这里插入图片描述

运行结果:
在这里插入图片描述
在运行结果中可以看到,会话只是释放了没有提交

MySQL中的数据(原来的数据:zangsan–55;lisi–145):
在这里插入图片描述
可以看到数据没有发生改变

事务的流程:
运⾏程序, 发现数据库没有插⼊任何数据.
流程描述:
(1)Controller.updateUserInfo方法开始事务
(2) userInfoService.updateUserInfo(logInfo)(执⾏成功) (和Controller.updateUserInfo 使⽤同⼀个事务)
(3)记录操作⽇志, 插⼊⼀条数据(出现异常, 执⾏失败) (和Controller.updateUserInfo 使⽤同⼀个事务)
(5)因为步骤3出现异常, 事务回滚;步骤2和3使⽤同⼀个事务, 所以步骤2的数据也回滚了.

3.3.2 Propagation.REQUIRES_NEW(新建事务)

程序设计:
在controller中依次调用两个service,传播机制都设置为Propagation.REQUIRES_NEW。

(1)ontroller层:

@RequestMapping("/user")
@RestController
public class UserInfoController {
    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private LogInfoService logInfoService;

    @RequestMapping("/update1")
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public boolean  updateUserInfo(@RequestBody LogInfo logInfo) {

        // 1. 更新用户信息
        boolean flag1 =  userInfoService.updateUserInfo(logInfo);

        // 2.插入日志
        boolean flag2 = logInfoService.insert(logInfo);

        if(flag1 && flag2){
            return true;
        }

        return false;
    }
}

(1)Service:

@Slf4j
@Service
public class UserInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Autowired
    private UserInfoMapper userInfoMapper;


    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public boolean updateUserInfo(LogInfo logInfo) {

        // 处理 from 向 to 转账 10

        // 事务逻辑

        // 1.from 的账户 -10
        userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());

        // 2.to 的账户 +10
        userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());

        // 3. 记录日志
        // 在LogInfoService实现
        
        return true;

    }
}

(2)Service(故意在该方法中设置一个错误):

@Service
public class LogInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public boolean insert(LogInfo logInfo){

		//错误点
        int n = 10/0;

        //记录日志
        int flag = logInfoMapper.insert(logInfo);

        if(flag>0 && n>=0){
            return true;
        }

        return  false;
    }
}

使用postman测试:
在这里插入图片描述

运行结果:
在这里插入图片描述

在运行结果中可以看到,有一个会话提交了

MySQL中的数据(原来的数据:zangsan–55;lisi–145):
在这里插入图片描述
可以看到user_info中两个用户的数据发生改变,但是log_info中的数据没有发生改变。

流程描述:
(1)Controller.updateUserInfo方法开始事务
(2) userInfoService.updateUserInfo(logInfo)(开新的事物并执⾏成功) (和Controller.updateUserInfo 使用的不是同⼀个事务)
(3)记录操作⽇志, 插⼊⼀条数据(开新的事物,出现异常, 执⾏失败,该事物回滚)
(5)步骤3出现异常, 事务回滚,但是步骤2和3不使用同⼀个事务, 两个事物相互独立,步骤二的事物提交。

3.3.3 Propagation.NEVER (不⽀持当前事务, 抛异常)

在UserInfoService中的方法中把传播机制改为 Propagation.NEVER,其他的传播机制都设置为Propagation.REQUIRES_NEW。

UserInfoService:

@Slf4j
@Service
public class UserInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Autowired
    private UserInfoMapper userInfoMapper;


//    @Transactional(propagation = Propagation.REQUIRES_NEW)

    @Transactional(propagation = Propagation.NEVER)
    public boolean updateUserInfo(LogInfo logInfo) {

        // 处理 from 向 to 转账 10

        // 事务逻辑

        // 1.from 的账户 -10
        userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());

        // 2.to 的账户 +10
        userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());

        // 3. 记录日志
        // 在LogInfoService实现

        return true;

    }
}

LogInfoService:

@Service
public class LogInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public boolean insert(LogInfo logInfo) {

        //记录日志
        int flag = logInfoMapper.insert(logInfo);

        if (flag > 0) {
            return true;
        }

        return false;
    }
}

使用postman测试:
在这里插入图片描述
运行结果:
在这里插入图片描述
执行程序报错,没有数据插入

3.3.4 Propagation.NESTED(嵌套事务)

将上述UserService 和LogService 中相关⽅法事务传播机制改为 Propagation.NESTED
(1)Controller层:

@RequestMapping("/user")
@RestController
public class UserInfoController {
    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private LogInfoService logInfoService;

    @RequestMapping("/update1")
    @Transactional(propagation = Propagation.REQUIRED)
    public boolean  updateUserInfo(@RequestBody LogInfo logInfo) {

        // 1. 更新用户信息
        boolean flag1 =  userInfoService.updateUserInfo(logInfo);

        // 2.插入日志
        boolean flag2 = logInfoService.insert(logInfo);

        if(flag1 && flag2){
            return true;
        }

        return false;
    }
}

(1)Service:

@Slf4j
@Service
public class UserInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Autowired
    private UserInfoMapper userInfoMapper;


    @Transactional(propagation = Propagation.NESTED)
    public boolean updateUserInfo(LogInfo logInfo) {

        // 处理 from 向 to 转账 10

        // 事务逻辑

        // 1.from 的账户 -10
        userInfoMapper.updateDelete(logInfo.getFrom(),logInfo.getNum());

        // 2.to 的账户 +10
        userInfoMapper.updateAdd(logInfo.getTo(),logInfo.getNum());

        // 3. 记录日志
        // 在LogInfoService实现
        
        return true;

    }
}

(2)Service(故意在该方法中设置一个错误):

@Service
public class LogInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.NESTED)
    public boolean insert(LogInfo logInfo){

		//错误点
        int n = 10/0;

        //记录日志
        int flag = logInfoMapper.insert(logInfo);

        if(flag>0 && n>=0){
            return true;
        }

        return  false;
    }
}

使用postman测试:
在这里插入图片描述

运行结果:
在这里插入图片描述

在运行结果中可以看到,会话只是释放了没有提交

MySQL中的数据(原来的数据:zangsan–40;lisi–160):
在这里插入图片描述

可以看到数据没有发生改变

事务的流程:
运⾏程序, 发现数据库没有插⼊任何数据.
流程描述:
(1)Controller.updateUserInfo方法开始事务
(2) userInfoService.updateUserInfo(logInfo)(开启一个事务,嵌套在Controller事务中,执⾏成功) (和Controller.updateUserInfo 使⽤同⼀个大事务)
(3)记录操作⽇志, 插⼊⼀条数据(开启一个事务,嵌套在Controller事务中,出现异常, 执⾏失败) (和Controller.updateUserInfo 使⽤同⼀个大事务)
(5)因为步骤3出现异常, 事务回滚;步骤2和3使⽤同⼀个事务, 所以步骤2的数据也回滚了.

3.4 REQUIRED和NESTED的区别

3.4.1 NESTED–某一嵌套事务回滚

Controller和Service都使用NESTED

logInfoService嵌套事务异常回滚:

@Slf4j
@Service
public class LogInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.NESTED)
    public boolean insert(LogInfo logInfo){

         try {

             int n = 10/0;

             //记录日志
             int flag = logInfoMapper.insert(logInfo);

             if(flag>0 && n>=0){
                 return true;
             }
         }catch (Exception e){
             //手动回滚
             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
             log.info("日志事务回滚");
             return true;
         }

        return  false;
    }
}

使用postman测试:
在这里插入图片描述
运行结果:
在这里插入图片描述

MySQL数据:
在这里插入图片描述
log_info中的数据没有发生改变,但是user_info数据发生了改变

流程描述:
(1)Controller.updateUserInfo方法开始事务
(2) userInfoService.updateUserInfo(logInfo)(开启一个事务,嵌套在Controller事务中,执⾏成功) (和Controller.updateUserInfo 使⽤同⼀个大事务)
(3)记录操作⽇志, 插⼊⼀条数据(开启一个事务,嵌套在Controller事务中,出现异常并捕获,手动回滚事务) (和Controller.updateUserInfo 使⽤同⼀大个事务)
(5)步骤3出现异常, 但是捕获了并手动回滚事务,没有发生异常;步骤2和3使⽤同⼀个事务, 步骤3相当于来说执行成功,所以步骤2的数据提交了。

3.4.2 REQUIRED–部分事务回滚

Controller和Service都使用REQUIRED

logInfoService部分事务异常回滚:

@Slf4j
@Service
public class LogInfoService {

    @Autowired
    private LogInfoMapper logInfoMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public boolean insert(LogInfo logInfo){

         try {

             int n = 10/0;

             //记录日志
             int flag = logInfoMapper.insert(logInfo);

             if(flag>0 && n>=0){
                 return true;
             }
         }catch (Exception e){
             //手动回滚
             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
             log.info("日志事务回滚");
             return true;
         }

        return  false;
    }
}

使用postman测试:
在这里插入图片描述

运行结果:
在这里插入图片描述
事务回滚后没有提交事务

MySQL数据:
在这里插入图片描述

log_info和user_info中的数据没有发生改变

流程描述:
(1)Controller.updateUserInfo方法开始事务
(2) userInfoService.updateUserInfo(logInfo)(加入Controller事务中,执⾏成功) (和Controller.updateUserInfo 使⽤同⼀个事务)
(3)记录操作⽇志, 插⼊⼀条数据(加入Controller事务中,出现异常并捕获,手动回滚事务) (和Controller.updateUserInfo 使⽤同⼀个事务)
(5)步骤3出现异常, 但是捕获了并手动回滚事务,没有发生异常;步骤2和3使⽤同⼀个事务, 在同一个事务中不能实现部分回滚,所以步骤2也回滚了。

3.4.3 总结

NESTED和REQUIRED区别:
(1)整个事务如果全部执⾏成功, ⼆者的结果是⼀样的.
(2)如果事务⼀部分执⾏成功, REQUIRED加⼊事务会导致整个事务全部回滚. NESTED嵌套事务可以实现局部回滚, 不会影响上⼀个⽅法中执⾏的结果.
嵌套事务之所以能够实现部分事务的回滚, 是因为事务中有⼀个保存点(savepoint)的概念, 嵌套事务进⼊之后相当于新建了⼀个保存点, 而滚回时只回滚到当前保存点.
资料参考: MySQL官方文档

4. 总结

  1. Spring中使⽤事务, 有两种⽅式: 编程式事务(⼿动操作)和声明式事务. 其中声明式事务使⽤较多,在⽅法上添加 @Transactional 就可以实现了
  2. 通过 @Transactional(isolation = Isolation.SERIALIZABLE) 设置事务的隔离级别. Spring 中的事务隔离级别有 5 种
  3. 通过 @Transactional(propagation = Propagation.REQUIRED) 设置事务的传播机制, Spring 中的事务传播级别有 7 种, 重点关注 REQUIRED (默认值) 和 REQUIRES_NEW
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值