Spring 事务 - 实践

目录

前言

一、事务

1. 事务的概念

2. Spring 中的事务

1. 编程式事务

2. 声明式事务

二、 @Transactional

1. rollbackFor

2. isolation

3. Spring 事务的传播机制

4. Spring 事务传播机制演示

1. required

2. requires_new

3. nested

4. required 和 nested 的区别


前言

本文介绍了Spring框架中的事务管理机制。主要内容包括:1. 事务基本概念,即一组操作的原子性执行;2. Spring提供的两种事务实现方式:编程式事务(手动管理)和声明式事务(通过@Transactional注解);3. @Transactional注解的核心属性:rollbackFor(异常回滚规则)、isolation(隔离级别)和propagation(传播机制);4. 重点讲解了7种事务传播机制及其应用场景,特别是REQUIRED、REQUIRES_NEW和NESTED的区别。文章通过代码示例展示了不同传播机制下的行为差异,帮助开发者根据业务需求选择合适的事务管理策略。


一、事务

1. 事务的概念

事务是一组操作的集合,是一个不可分割的操作;

事务会把组内的操作视作一个整体,如果所有操作全部成功,事务进行提交,如果有一个或者多个步骤失败,就会将成功的操作也进行回滚,实现整体上全部成功,或者全部不成功的功能;

事务的操作有 3 个步骤:

  • 1. 开启事务;
  • 2. 提交事务;
  • 3. 回滚事务;

2. Spring 中的事务

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

  • 编程式事务:手动编程,操作事务;
  • 声明式事务:利用注解,自动开启,提交或者回滚事务;

1. 编程式事务

DataSourceTransactionManager:事务管理器,用来获取事务,开启事务,提交事务,回滚事务;

TransactionDefinition:事务的属性,在获取事务的时候,将 TransactionDefinition 传递进去,从而获得一个事务 TransactionStatus;

手动实现事务的提交和回滚:

package com.example.trans.controller;
import com.example.trans.service.UserInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserInfoController {
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;
    @Autowired
    private UserInfoService userInfoService;
    @RequestMapping("/register")
    public Boolean register(String userName, String password){
        // 1. 参数校验
        log.info("/user/register接收到参数:userName: {}, password: {}", userName, password);
        if(!StringUtils.hasLength("userName") || !StringUtils.hasLength(password)){
            return false;
        }
        // 2. 开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        // 3. 调用 service
        try{
            Integer result = userInfoService.insertUserInfo(userName, password);
            if(result <= 0){
                return false;
            }
        }catch (Exception e){
            return false;
        }
        // 4. 提交事务
//        dataSourceTransactionManager.commit(transactionStatus);
        // 5. 回滚事务
        dataSourceTransactionManager.rollback(transactionStatus);
        return true;
    }
}

2. 声明式事务

使用  @Transactional 注解,自动实现事务的开启,提交和回滚;

package com.example.trans.controller;
import com.example.trans.service.UserInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@Slf4j
@RestController
@RequestMapping("/user2")
public class UserInfoController2 {
    @Autowired
    private UserInfoService userInfoService;
    // @Transactional 自动实现事务的开启,提交或者回滚
    @Transactional
    @RequestMapping("/register")
    public Boolean register(String userName, String password) throws IOException {
        // 1. 参数校验
        log.info("/user/register接收到参数:userName: {}, password: {}", userName, password);
        if(!StringUtils.hasLength("userName") || !StringUtils.hasLength(password)){
            return false;
        }
        // 2. 调用 service
        try{
            Integer result = userInfoService.insertUserInfo(userName, password);
            int n = 10 / 0;
            if(result <= 0){
                return false;
            }
        }catch (Exception e){
            // 手动回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
//            return false;
        }
        return true;
    }
}

 @Transactional 可以用来修饰方法和类:

  • 修饰方法时,只有修饰被 public 修饰的方法才生效;
  • 修饰类时,对类中的所有被 public 修饰的方法生效;

事务提交和回滚的情况分析:

当所有操作执行成功,事务提交;

当发生运行时异常或者错误时,且异常没有被捕获,事务会自动回滚;

如果发生运行时异常且异常被捕获,则事务提交;

如果发生的是受查异常,则事务会提交;

如果发生运行时异常后,捕获到了异常,仍然想要实现回滚:可以再次抛出异常,也可以手动回滚;

如果发生的是受查异常,仍然想要回滚,可以设置 @Transactional 的属性 rollbackFor 为 Exception.class 类型;

二、 @Transactional

@Transactional 常见的三个属性:

rollbackFor:异常回滚属性,能够指定多个异常类型,发生指定类型的异常时,事务会实现回滚;

Isolation:事务的隔离级别;

propagation:事务的传播机制;

1. rollbackFor

package com.example.trans.controller;
import com.example.trans.service.UserInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@Slf4j
@RestController
@RequestMapping("/user2")
public class UserInfoController2 {
    @Autowired
    private UserInfoService userInfoService;
    // @Transactional 自动实现事务的开启,提交或者回滚
    @Transactional(rollbackFor = Exception.class)
    @RequestMapping("/register")
    public Boolean register(String userName, String password) throws IOException {
        // 1. 参数校验
        log.info("/user/register接收到参数:userName: {}, password: {}", userName, password);
        if(!StringUtils.hasLength("userName") || !StringUtils.hasLength(password)){
            return false;
        }
        // 2. 调用 service
        try{
            Integer result = userInfoService.insertUserInfo(userName, password);
            int n = 10 / 0;
            if(result <= 0){
                return false;
            }
        }catch (Exception e){
            throw new IOException();
        }
        return true;
    }
}

设置 rollbackFor 的类型为 Exception.class 后,只要发生异常,就会实现回滚;

2. isolation

MySQL 有 4 种隔离级别,分别是:读未提交,读已提交,可重复度和串行化;

读未提交:事务 A 写数据时,事务 B 可以读到,如果事务 A 之后又修改了数据,事务 B 读到的就是脏数据,也称为脏读;

读已提交:当事务 A 提交后,事务 B 能读到,事务 C 又修改了当初事务 A 提交的数据,此时事务 B 再读,发现数据前后不一致了,称为不可重复读;

可重复读:当事务 A 提交后,事务 B 不能读到事务 A 产生的数据,但是当 事务 B 想要插入数据时,发现数据行的 ID 已经被占用,事务 B 能感知到数据发生变化,但是读不到,称为幻读;

串行化: 把事务进行排序,一个一个执行,完全没有并发;

MySQL 的默认隔离界别时可重复读;Spring 的默认隔离级别是采用数据库设置的隔离级别;

package org.springframework.transaction.annotation;
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;
    }
}

3. Spring 事务的传播机制

传播机制:当多个事务方法存在调用关系,事务在这些方法的传播方式;

事务的隔离机制解决的是多个事务同时调用一个数据的问题;

事务的传播机制解决的是一个事务在多个方法中传递的问题;

@Transactional 可以设置 propagation 属性,设置事务的传播机制;

传播机制有 7 种:

package org.springframework.transaction.annotation;
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.REQUIRED:默认事务传播级别,如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务;

Propagation.REQUIRES_NEW:不管当前是否存在事务(如果有事务则挂起),都要创建一个新的事务;

Propagation.NESTED:如果当前存在一个事务,则创建一个新的事务,作为嵌套事务运行,如果嵌套事务发生异常,且异常被 catch 住了,则不影响原有事务,如果当前不存在事务,则创建一个新的事务运行;

Propagation.SUPPORTS:如果当前存在事务,则加入该事务,如果当前不存在事务,则以非事务的方式运行;

Propagation.NOT_SUPPORTED:如果当前存在事务,则挂起,以非事务的方式运行;

Propagation.MANDATORY:如果当前不存在事务,则抛出异常;

Propagation.NEVER:如果当前存在事务,则抛出异常;

4. Spring 事务传播机制演示

1. required

用户注册接口:

package com.example.trans.controller;
import com.example.trans.service.LogInfoService;
import com.example.trans.service.UserInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@Slf4j
@RestController
@RequestMapping("/user2")
public class UserInfoController2 {
    @Autowired
    private UserInfoService userInfoService;
    @Autowired
    private LogInfoService logInfoService;
    // @Transactional 自动实现事务的开启,提交或者回滚
    @Transactional(rollbackFor = Exception.class, isolation = Isolation.REPEATABLE_READ)
    @RequestMapping("/register")
    public Boolean register(String userName, String password) throws IOException {
        // 1. 参数校验
        log.info("/user/register接收到参数:userName: {}, password: {}", userName, password);
        if(!StringUtils.hasLength("userName") || !StringUtils.hasLength(password)){
            return false;
        }
        // 2. 调用 service
        try{
            Integer result = userInfoService.insertUserInfo(userName, password);
            Integer result2 = logInfoService.insertLogInfo(userName, "用户注册");
            if(result <= 0 || result2 <= 0){
                return false;
            }
        }catch (Exception e){
            // 手动回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return true;
    }
}

UserInfoService:

package com.example.trans.service;
import com.example.trans.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserInfoService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer insertUserInfo(String userName, String password) {
        return userInfoMapper.insertUserInfo(userName, password);
    }
}

LogInfoService:

package com.example.trans.service;
import com.example.trans.mapper.LogInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class LogInfoService {
    @Autowired
    private LogInfoMapper logInfoMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer insertLogInfo(String userName, String op) {
        return logInfoMapper.insertLogInfo(userName, op);
    }
}

成功运行时:

LogInfoService 抛出异常:事务不会被提交

package com.example.trans.service;
import com.example.trans.mapper.LogInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class LogInfoService {
    @Autowired
    private LogInfoMapper logInfoMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer insertLogInfo(String userName, String op) {
        int n = 10 / 0;
        return logInfoMapper.insertLogInfo(userName, op);
    }
}

2. requires_new

 注册接口代码不变,更改 UserInfoService 和 LogInfoService 的传播机制为 requires_new:

UserInfo 提交成功,LogInfo 没有提交;

3. nested

注册接口代码不变,更改 UserInfoService 和 LogInfoService 的传播机制为 nested;

捕获 LogInfoService 中的异常,并手动回滚事务:

package com.example.trans.service;
import com.example.trans.mapper.LogInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class LogInfoService {
    @Autowired
    private LogInfoMapper logInfoMapper;
    @Transactional(propagation = Propagation.NESTED)
    public Integer insertLogInfo(String userName, String op) {
        try{
            int n = 10 / 0;
        }catch(Exception e){
            // 手动回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        return logInfoMapper.insertLogInfo(userName, op);
    }
}

测试:事务提交,LogInfo 实现了手动回滚;

4. required 和 nested 的区别

如果事务全部执行成功,二者是相同的;

如果事务一部分执行成功,required 加入事务会使整个事务全部回滚,nested 如果 catch 住异常,能手动实现部分回滚,不影响整个事务的提交;


posted @ 2025-11-10 22:15  yxysuanfa  阅读(10)  评论(0)    收藏  举报