【Java】Spring中事务的隔离级别和事务的传播机制

Spring 中事务的实现

Spring 中的事务操作分为两类:

  1. 编程式事务(手动写代码操作事务)
  2. 声明式事务(利用注解自动开启和提交事务)

编程式事务

Spring 手动操作事务和MySQL 操作事务类似,它也是有 3 个重要操作步骤:

– 开启事务 start transaction;
//业务执行
– 提交事务 commit;
– 回滚事务 rollback;

SpringBoot 内置了两个对象,DataSourceTransactionManager 用来获取事务(开启事务)、提交或回滚事务的,而TransactionDefinition 是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从而获得⼀个事务transaction。

@Slf4j
@RequestMapping("/trans")
@RestController
public class TransactionController {
    @Autowired
    private UserService userService;
    //数据库的事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    private TransactionDefinition transactionDefinition;
    @RequestMapping("/addUser")
    public Integer addUser(String username,String password){
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        User user  = new User(username,password);
        Integer result =  userService.insert(user);
        log.info("影响行数" + result);
        //事务提交
        dataSourceTransactionManager.commit(transaction);
        //事务回滚
//        dataSourceTransactionManager.rollback(transaction);
        return result;
    }
}

声明式事务

以上代码虽然可以实现事务,但操作很繁琐。而声明式事务只需要在需要的方法上添加 @Transactional 注解就可以实现了,⽆需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务

@Slf4j
@RequestMapping("/trans2")
@RestController
public class TransactionController2 {
    @Autowired
    private UserService userService;

    @Transactional
    @RequestMapping("/addUser")
    public Integer addUser(String username,String password){
        User user  = new User(username,password);
        Integer result =  userService.insert(user);
        log.info("影响行数" + result);
        //执行了异常代码
        int a = 10/0;
        return result;
    }
}

在上述代码中,添加@Transactional注解,如果程序执行成功,自动提交事务,如果程序执行异常,则自动回滚事务。如果不加注解,在代码执行异常时不会回滚。

@Transactional的作用范围

修饰方法:需要注意只能应用到 public 方法上,否则不生效
修饰类:表明该注解对该类中所有的 public 方法都生效。

@Transactional的参数

在这里插入图片描述
比如在上述代码中,可以指定出现算数异常时,不回滚

    @Transactional(noRollbackFor = ArithmeticException.class)
    @RequestMapping("/addUser")
    public Integer addUser(String username,String password){
        User user  = new User(username,password);
        Integer result =  userService.insert(user);
        log.info("影响行数" + result);
        //执行了异常代码
        int a = 10/0;
        return result;
    }

注意:@Transactional默认只有Error和运行时异常(RuntimeException及其子类)才会自动回滚,其他异常不会自动回滚。所以如果想要在所有异常情况下都进行回滚,可以指定参数rollbackFor=Excepion.class

注意事项

@Transactional 在异常被捕获的情况下,不会进行事务自动回滚。验证以下代码是否会发生事务回滚:

    @Transactional
    @RequestMapping("/addUser3")
    public Integer addUser3(String username,String password){
        User user  = new User(username,password);
        Integer result =  userService.insert(user);
        log.info("影响行数" + result);
        try{
            //执行了异常代码
            int a = 10/0;
        }catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }

打印日志可以发现事务没有自动回滚,而是提交了:
在这里插入图片描述
解决方案:
方案1:对于捕获的异常,事务是会自动回滚的,因此解决方案1就是可以将异常重新抛出;
方案2:手动回滚事务,在方法中使用 TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法 setRollbackOnly 就可以实现回滚了。

事务隔离级别

事务隔离级别解决的是多个事务同时调用⼀个数据库的问题,为了防止其他的事务影响当前事务执行的⼀种策略。如下图所示:
在这里插入图片描述

MySQL的事务隔离级别

在MySQL中,事务的隔离级别有4种:
在这里插入图片描述

Spring中事务的隔离级别

Spring 中事务隔离级别包含以下 5 种:

1.Isolation.DEFAULT以连接的数据库的事务隔离级别为主
2.Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。
3.Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。
4.Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。
5.Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低。

相比于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了⼀个Isolation.DEFAULT(以所连接的数据库的事务隔离级别为主)。Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进行设置,具体操作如下图所示:

@Transactional(isolation = Isolation.SERIALIZABLE)

Spring事务传播机制

Spring 事务传播机制定义了多个包含了事务的方法相互调用时,事务是如何在这些⽅法间进行传递的。事务传播机制是保证⼀个事务在多个调用方法间的可控性的(稳定性的)。 所以事务传播机制解决的是⼀个事务在多个节点(方法)中传递的问题,如下图所示:

在这里插入图片描述

事务传播机制有哪些

Spring 事务传播机制包含以下 7 种:

  1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建⼀个新的事务。
  2. Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  4. Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  5. Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  6. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  7. Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED。

以上 7 种传播机制,可以根据是否支持当前事务分为以下 3 类:

在这里插入图片描述

Spring 事务传播机制使用和各种场景演示

以用户注册功能为例:

  1. 插入用户表数据
  2. 插入日志表数据

支持当前事务:REQUIRED

先开启事务先成功插⼊⼀条用户数据,然后再执行异常的日志:
UserService:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    public List<User> selectAllUser() {
        return userMapper.queryAll();
    }
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer insert(User user){
        return  userMapper.insert(user);
    }
}

UserLogService:

@Service
public class UserLogService {
    @Resource
    private UserLogMapper userLogMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer insertLog(UserLog userLog){
        Integer result = userLogMapper.insertLog(userLog);
        //出现异常
        int a = 10/0;
        return result;
    }
}

Controller:

@Slf4j
@RequestMapping("/trans3")
@RestController
public class TransactionController3 {

    @Resource
    private UserService userService;
    @Resource
    private UserLogService userLogService;

    @Transactional
    @RequestMapping("/addUser")
    public boolean addUser( String username,String password){
        //1.插入用户表
        User user = new User(username,password);
        userService.insert(user);
        //2.插入日志表
        UserLog userLog = new UserLog(username);
        userLogService.insertLog(userLog);
        return true;
    }
}

执行结果:程序报错,数据库没有插入任何数据。
执行流程描述:
1.UserService 中的保存⽅法正常执行完成。
2.UserLogService 插入日志程序报错,因为使用的是 Controller 中的事务,所以整个事务回滚。
3.数据库中没有插入任何数据,也就是步骤 1 中的用户插⼊方法也回滚了。

不支持当前事务:REQUIRES_NEW

将Controller 类中的代码不变,将添加用户和添加日志的方法修改为 REQUIRES_NEW,表示 不支持当前事务,重新创建事务,观察执行结果:
UserService:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    public List<User> selectAllUser() {
        return userMapper.queryAll();
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer insert(User user){
        return  userMapper.insert(user);
    }
}

UserLogService:

@Service
public class UserLogService {
    @Resource
    private UserLogMapper userLogMapper;
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer insertLog(UserLog userLog){
        Integer result = userLogMapper.insertLog(userLog);
        //出现异常
        int a = 10/0;
        return result;
    }
}

通过日志可以发现,用户表中成功插入了数据,日志表执行失败,但没影响 UserController 中的事务。原因在于当UserLog出现异常时,造成了事务回滚,但由于此时重新创建了事务,所以不影响userinfo表。
在这里插入图片描述

不支持当前事务:NEVER 抛异常

将UserService中的事务的传播机制改为Propagation.NEVER,其余不变:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    public List<User> selectAllUser() {
        return userMapper.queryAll();
    }
    @Transactional(propagation = Propagation.NEVER)
    public Integer insert(User user){
        return  userMapper.insert(user);
    }
}

执行结果直接抛出异常:
在这里插入图片描述

NESTED 嵌套事务

UserService 实现代码如下:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    public List<User> selectAllUser() {
        return userMapper.queryAll();
    }
    @Transactional(propagation = Propagation.NESTED)
    public Integer insert(User user){
        return  userMapper.insert(user);
    }
}

UserLogService实现如下:

@Service
public class UserLogService {
    @Resource
    private UserLogMapper userLogMapper;
    @Transactional(propagation = Propagation.NESTED)
    public Integer insertLog(UserLog userLog){
        Integer result = userLogMapper.insertLog(userLog);
        //出现异常
        int a = 10/0;
        return result;
    }
}

Controller:

@Slf4j
@RequestMapping("/trans3")
@RestController
public class TransactionController3 {

    @Resource
    private UserService userService;
    @Resource
    private UserLogService userLogService;

    @Transactional
    @RequestMapping("/addUser")
    public boolean addUser( String username,String password){
        //1.插入用户表
        User user = new User(username,password);
        userService.insert(user);
        //2.插入日志表
        UserLog userLog = new UserLog(username);
        userLogService.insertLog(userLog);
        return true;
    }
}

从结果来看,当执行没有异常时,会正常在用户表和日志表中插入数据;而执行存在异常时,两个表中都不会插入数据(日志中没有commit)。从结果上来看,貌似嵌套事务和加入事务没有区别。
在这里插入图片描述
分析执行流程描述:
1.UserController 中调用了 UserService 的添加方法,UserService 使用 NESTED 修饰循环嵌套了UserController 的事务,并成功执行了添加方法。
2.UserService 中调用了 UserLogService 的添加方法,UserLogService 使用 NESTED 修饰循环嵌套了上⼀个调用类的事务,执行了添加方法,但在执行的过程中出现了异常,回顾当前事务,日志添加失败。
3.因为是嵌套事务,所以日志添加回顾之后,往上找调用它的方法和事务回滚了用户添加,所以用户添加也失败了,所以最终的结果是用户表和日志表都没有添加任何数据。

嵌套事务和加入事务的区别

在UserLogService中,当出现异常时,手动回滚事务:

@Service
public class UserLogService {
    @Resource
    private UserLogMapper userLogMapper;
    @Transactional(propagation = Propagation.NESTED)
    public Integer insertLog(UserLog userLog){
        Integer result = userLogMapper.insertLog(userLog);
        try {
            //出现异常
            int a = 10/0;
        }catch (Exception e){
            //回滚当前事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return result;
    }
}

从日志的打印结果来看,只有一个commit。最终的执行结果是用户表中成功添加数据,而日志表中没有添加数据。这说明:日志中的事务已经回滚,但是嵌套事务不会回滚嵌套之前的事务,也就是说嵌套事务可以实现部分事务回滚
在这里插入图片描述
嵌套事务只所以能够实现部分事务的回滚,是因为事务中有⼀个保存点(savepoint)的概念,嵌套事务进入之后相当于新建了⼀个保存点,而回滚时只回滚到当前保存点,因此之前的事务是不受影响。而 REQUIRED 是加入到当前事务中,并没有创建事务的保存点,因此出现了回滚就是整个事务回滚。 在 MySQL 的官方文档汇总找到相应的资料。

嵌套事务(NESTED)和加⼊事务(REQUIRED )的区别:
1.整个事务如果全部执行成功,⼆者的结果是⼀样的;
2.如果事务执行到⼀半失败了,那么REQUIRED 会让整个事务全部回滚;而嵌套事务会局部回滚,不会影响上⼀个方法中执行的结果。


继续加油~
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
事务隔离级别是指多个事务并发执行时,一个事务对其他事务的可见性和影响程度的控制。Spring框架支持四个事务隔离级别: 1. 未提交读(READ UNCOMMITTED):最低级别,一个事务可以读取未提交的数据,会导致脏读,不可重复读和幻读问题的出现。 2. 提交读(READ COMMITTED):一个事务只能读取已提交的数据,可以避免脏读问题,但可能导致不可重复读和幻读问题。 3. 可重复读(REPEATABLE READ):在一个事务多次读取同一数据时,结果保持一致,避免了不可重复读问题。但仍然可能存在幻读问题。 4. 串行化(SERIALIZABLE):最高级别,通过确保事务串行执行来避免脏读、不可重复读和幻读问题。但会降低并发性能。 传播机制是指在调用多个事务方法时,如何处理事务传播。Spring框架提供七种传播行为: 1. REQUIRED:如果当前没有事务,就创建一个新事务;如果已存在事务,则加入该事务。 2. SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方法执行。 3. MANDATORY:强制要求存在当前事务,如果没有事务就抛出异常。 4. REQUIRES_NEW:创建一个新事务,并暂停当前事务(如果有)。 5. NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。 6. NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。 7. NESTED:在当前事务的控制下执行一个嵌套事务,如果不存在当前事务,则创建一个新事务。嵌套事务可以独立提交或回滚,但在外部事务提交时才会生效。 通过选择合适的事务隔离级别传播机制,可以确保事务的数据一致性、安全性和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值