Spring事务与事务传播机制

文章目录

  • Spring事务与事务传播机制
    • 前言
    • 1. Spring中事务的实现
      • 1.1 编程式事务(手动编写代码操作事务)
      • 1.2 声明式事务(注解自动启动和提交事务)
        • 1.2.1 @Transactional的使用
        • 1.2.2 @Transactional工作原理
      • 2. Spring事务的隔离级别
      • 3. 事务传播机制
        • 3.1 事务传播机制分类

Spring事务与事务传播机制

前言

Spring事务与MySQL事务类似,都是将一组操作封装成一个执行单元,要么全部成功,要么全部失败。比如我们A给B转账100元,如果A账号成功扣款100元,但是B账号由于某种原因进款失败,那么A账号就相当于凭空失去了100元。使用事务就能够解决这个问题,整体执行一起成功或者失败。

1. Spring中事务的实现

1.1 编程式事务(手动编写代码操作事务)

SpringBoot 内置了两个对象,DataSourceTransactionManager ⽤来开启事务、提交、回滚事务的,TransactionDefinition 是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从而获得⼀个事务 TransactionStatus,实现代码如下:

@RestController
public class UserController {
@Resource
private UserService userService;
// JDBC 事务管理器
@Resource 
private DataSourceTransactionManager dataSourceTransactionManager;
// 定义事务属性
@Resource
private TransactionDefinition transactionDefinition;
@RequestMapping("/sava")
public Object save(User user) {
// 开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(transactionDefinition);
// 插⼊数据库
int result = userService.save(user);
// 提交事务
dataSourceTransactionManager.commit(transactionStatus);
// // 回滚事务
// dataSourceTransactionManager.rollback(transactionStatus);
return result;
}
}

我们transactionmanager可以有多个,通过new创建,此时我们多个对象中的设置可以不一样。

在实际开发中我们使用的是简单的注解来实现事务的开启提交。

1.2 声明式事务(注解自动启动和提交事务)

特点:

  1. 使用@Transactional注解可以实现事务的自动提交,当程序没有报错时就会自动提交,当程序执行期间报错时会自动回滚事务。
  2. 可以添加在类和方法上。修饰方法时只能应用到public方法上,修饰类时标识该类的public方法都生效。

1.2.1 @Transactional的使用

使用注解的写法如下:

/**
     * 声明式事务
     * @param userinfo
     * @return
     */
    @Transactional //声明是事务自动提交
    @RequestMapping("/insert")
    public Integer insert(Userinfo userinfo){
//        非空校验
        if (userinfo==null||!StringUtils.hasLength(userinfo.getPassword())||!StringUtils.hasLength(userinfo.getUsername())){
            return 0;
        }
        int res = userService.add(userinfo);
        return res;
    }

使用该注解我们也有多种选择设置,通过该注解的参数实现。具体如下:

参数 作用
value 当配置多个事务管理器时,可以指定选择那个
transactionManeger 当配置多个事务管理器时可以指定选哪个
propagation 事务传播行为,默认为Propagation.REQUIRED
isolation 事务隔离级别,默认为Isolation.DEFAULT
timeout 事务超时时间,默认值为-1,如果超时还没完成,自动回滚事务
readOnly 指定事务是否是只读事务,默认为false
rollbackFor 用于指定那个触发事务回滚的异常类型,可以指定多个
rollbackForClassNme 用于指定那个触发事务回滚的异常类型,可以指定多个
noRollbackForClassName 抛出指定异常类型,可以多个
noRollbackFor 抛出指定异常类型,可以多个

注意事项:

  • 我们使用注解时,如果有异常但是使用try-catch捕获异常,我们程序就不会回滚。

解决办法1:我们重新将异常再次抛出异常

 /**
     * 声明式事务
     * @param userinfo
     * @return
     */
    @Transactional //声明是事务自动提交
    @RequestMapping("/insert")
    public Integer insert(Userinfo userinfo){
//        非空校验
        if (userinfo==null||!StringUtils.hasLength(userinfo.getPassword())||!StringUtils.hasLength(userinfo.getUsername())){
            return 0;
        }
//        测试有异常时是否回滚,正常会回滚
//        如果有trycatch就不会回滚,
        try {
            int num= 10/0;
        }catch (Exception e){
            System.out.println(e.getMessage());
            //此时再次抛出异常可以解决事务不会滚的问题
            throw e;
        }

        int res = userService.add(userinfo);
        return res;
    }

解决办法2:手动回滚事务

 /**
     * 声明式事务
     * @param userinfo
     * @return
     */
    @Transactional //声明是事务自动提交
    @RequestMapping("/insert")
    public Integer insert(Userinfo userinfo){
//        非空校验
        if (userinfo==null||!StringUtils.hasLength(userinfo.getPassword())||!StringUtils.hasLength(userinfo.getUsername())){
            return 0;
        }
//        测试有异常时是否回滚,正常会回滚
//        如果有trycatch就不会回滚,
        try {
            int num= 10/0;
        }catch (Exception e){
            System.out.println(e.getMessage());
            //此时再次抛出异常可以解决事务不会滚的问题
           // throw e;
            //手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }

        int res = userService.add(userinfo);
        return res;
    }

1.2.2 @Transactional工作原理

  1. @Transactional 是基于 AOP 实现的,AOP 使⽤动态代理实现。如果⽬标对象实现了接⼝,默认情况下会采用 JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。
  2. @Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。

Spring事务与事务传播机制_第1张图片
Spring事务与事务传播机制_第2张图片

2. Spring事务的隔离级别

与mysql事务隔离级别类似,但是新增了一个默认事务隔离级别,标识事务隔离级别与数据库设置一致。事务隔离级别是保证多个并发事务执⾏的可控性的(稳定性的)。主要有以下几类:

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

Spring事务隔离级别通过@Transactional ⾥的 isolation 属性设置;

3. 事务传播机制

使用Spring事务编写的代码在使用过程中可能存在相互调用的情况,如果传播链中有一个事务出错,那么如何解决呢,回不回滚上一个事务,如果多个事务回滚几个,事务传播机制就是定义了传播的固定模式。 事务隔离级别是保证多个并发事务执⾏的可控性的(稳定性的),⽽事务传播机制是保证⼀个事务在多个调⽤⽅法间的可控性的(稳定性的)。

Spring事务与事务传播机制_第3张图片

3.1 事务传播机制分类

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

  1. Propagation.REQUIRED:默认的事务传播级别,它表示如果调用者存在事务,则被调用者加⼊该事务;如果调用者没有事务,则创建⼀个新的事务。

Spring事务与事务传播机制_第4张图片

  1. Propagation.SUPPORTS:如果调用者存在事务,则被调用者加⼊该事务;如果没有事务,则以非事务的⽅式继续运⾏。

Spring事务与事务传播机制_第5张图片

  1. Propagation.MANDATORY:如果调用者存在事务,则被调用者加⼊该事务;如果没有事务,则抛出异常。

Spring事务与事务传播机制_第6张图片

  1. Propagation.REQUIRES_NEW:表示创建⼀个新的事务执行,如果调用者存在事务,则把该事务挂起。

Spring事务与事务传播机制_第7张图片

  1. Propagation.NOT_SUPPORTED:以非事务方式运行,如果调用者存在事务,则把该事务挂起。

Spring事务与事务传播机制_第8张图片

  1. Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

Spring事务与事务传播机制_第9张图片

  1. Propagation.NESTED:如果调用者存在事务,则被调用者创建⼀个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED(即被调用者创建一个新的事务)。
    Spring事务与事务传播机制_第10张图片

以上事务传播机制可以分为下面三类:支持当前事务、不支持当前事务、嵌套事务

Spring事务与事务传播机制_第11张图片

接下来我们创建一个添加日志的service,并且设置算是异常,当我们添加用户之后进行添加日志,两个事务都设置隔离级别

以下演示传播机制为REQUIRED的代码。可以发现当我们添加日志的事务报错时,我们添加用户的事务也会回滚:

Usercontroller类:

 /**
     * 事务传播机制的应用
     * 添加用户时添加日志,事务传播机制为requried
     * 我们添加日志事务有算数异常,
     * 此时我们添加用户事务也会回滚,
     * 因为相当于加入了事务变成一个事务
     * 当传播机制为nested(嵌套时)
     * 添加用户日志不会会滚
     * @param userinfo
     * @return
     */
    @Transactional //声明是事务自动提交
    @RequestMapping("/insert2")
    public Integer insert2(Userinfo userinfo){
        if (userinfo==null||!StringUtils.hasLength(userinfo.getPassword())||!StringUtils.hasLength(userinfo.getUsername())){
            return 0;
        }
//      添加用户
        int res = userService.add(userinfo);
//        添加日志,
        if(res>0){
            logService.add();
        }


        return res;
    }

LogService类:

package com.example.demo_tr.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

/**
 * @author zq
 * @date 2023-07-22 14:28
 */

@Service
public class LogService {

//    设置事务的传播机制,默认也为这个可以不写
    @Transactional(propagation = Propagation.REQUIRED)
    //@Transactional(propagation = Propagation.NESTED)
    public int add(){
        try {
            int num=10/0;
        }catch (Exception e){
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }

        return 1;
    }
}

UserService类:

package com.example.demo_tr.service;

import com.example.demo_tr.entity.Userinfo;
import com.example.demo_tr.mapper.UserMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * @author zq
 * @date 2023-07-21 20:23
 */

@Service
public class UserService {
//    @Autowired
    @Resource
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    //@Transactional(propagation = Propagation.NESTED)
    public Integer add(Userinfo userinfo){
        int res = userMapper.add(userinfo);
        System.out.println("用户添加"+res);
        return  res;
    }

}

我们可以通过访问地址,查看数据库user info表是否添加用户确定事务是否会滚;当事务传播级别为nested是修改设置级别即可,此时我们会发现嵌套事务是不会回滚添加用户的,即使添加日志出错;

你可能感兴趣的:(spring,java,后端)