当前位置: 首页 > news >正文

Spring_事务

在mysql阶段的文章中,已经介绍过事务了。本篇文章是对mysql事务的总结和对使用Spring框架来实现事务操作的讲解。

事务回顾

什么是事务

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

事务会把所有操作作为一个整体,一起向数据库提交或者撤销操作请求。所以这组操作要么同时成功,要么同时失败。

为什么需要事务

我们在程序开发的时候,会有事务的需求。

比如:转账操作。

第一步:A:-100元

第二步:B:+100元

事务的操作

事务的操作主要有三步:

1、开启事务:start transaction(一组操作开启事务)

2、提交事务:commit(这组操作全部成功,提交事务)

3、回滚事务:rollback(这组操作中间任何一个操作出现异常,回滚事务)

Spring中事务的实现

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

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

2、声明式事务(利用注解自动开启和提交事务)

假设现在有需求:用户注册,注册时在日志表中插入一条操作记录。

数据准备:

DROP TABLE
IFEXISTS 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
IFEXISTS 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、创建项目,引入SpringWeb,Mybatis,mysql等依赖

2、配置文件

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: 5028driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration: # 配置打印 MyBatis⽇志log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true #配置驼峰⾃动转换

实体类:

Userinfo:

@Data
public class Userinfo {private Integer id;private String userName;private String password;private Date createTime;private Date updateTime;
}

Loginfo:

@Data
public class LogInfo {private Integer id;private String userName;private String op;private Date createTime;private Date updateTime;
}

Mapper:

UserinfoMapper:

@Mapper
public interface UserinfoMapper {@Insert("insert into user_info(user_name,password)values (#{userName},#{password})")Integer insert(String userName,String password);
}

LoginfoMapper:

@Mapper
public interface LoginfoMapper {@Insert("insert into log_info(user_name,op) values (#{userName},#{op})")Integer insertLog(String name,String op);
}

Service:

UserService:

@Service
public class UserService {@Autowiredprivate UserinfoMapper userinfoMapper;public Integer insert(String userName,String password){return userinfoMapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;public Integer login(String userName,String op){return loginfoMapper.insertLog(userName,op);}
}

Controller:

UserController:

@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/r1")public boolean login(String userName,String password){userService.insert(userName,password);return true;}
}

Spring编程式事务(了解)

Spring手动操作事务有三个操作步骤:

  • 开启事务
  • 提交事务
  • 回滚事务

SpringBoot内置了两个对象:

  1. DataSourceTransactionManager 事务管理器,用来开启、提交或回滚事务
  2. TransactionDefinition是事务的属性,在获取事务的时候需要将TransactionDefinition传递进去从而获得一个事务TransactionStatus

下面是代码实现:

@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;//JDBC事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;//定义事务属性@Autowiredprivate TransactionDefinition transactionDefinition;@RequestMapping("/r1")public boolean login(String userName,String password){//开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);//用户注册userService.insert(userName,password);//提交事务dataSourceTransactionManager.commit(transactionStatus);//回滚事务
//        dataSourceTransactionManager.rollback(transactionStatus);return true;}
}

使用postMan进行测试:

提交事务:

回滚事务:

刷新之后数据库的数据并没有增加:

Spring声明式事务@Transactional

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

代码实现:


@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用户注册userService.insert(userName,password);return true;}
}

日志:

刷新数据库,发现数据插入成功:

修改程序,使它出现异常:

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用户注册userService.insert(userName,password);//制造异常int a = 10/0;return true;}
}

重新测试:

日志:

刷新数据库,发现并没有数据插入:

对比日志:

那如果我们需要让它发生异常时不发生回滚呢?

此时我们可以使用try-catch将异常捕获住,代码修改如下:

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用户注册userService.insert(userName,password);try {//制造异常int a = 10/0;} catch (Exception e) {e.getMessage();}return true;}
}

重新测试:

日志:

数据库:

那我们如果在异常捕获后需要事务进行回滚呢?有以下两种方式:

1、重新抛出异常

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用户注册userService.insert(userName,password);try {//制造异常int a = 10/0;} catch (Exception e) {//将异常重新抛出throw e;}return true;}
}

测试:

2、手动回滚事务

使用TransationAspectSupport.currentTransactionStatus()得到当前事务,并使用setRollbackOnly设置setRollbackOnly。

代码:

@RequestMapping("/trans")
@RestController
public class TransController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/r1")public boolean login(String userName,String password){//用户注册userService.insert(userName,password);try {//制造异常int a = 10/0;} catch (Exception e) {//手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return true;}
}

测试:

@Transactional详解

1、rollbackFor

上面我们已经知道了,@Transactional注解会开始事务并且自动提交/回滚事务。

我们将异常类型改为IOException再进行测试:

    @Transactional@RequestMapping("/r2")public boolean login2(String userName,String password) throws IOException {//用户注册userService.insert(userName,password);if(true){throw new IOException();}return true;}

测试:

此时我们发现虽然程序已经抛出异常,但是事务仍然提交了:

数据库也新增了一条数据:

咦?这是为什么呢?不是抛出异常后,事务就应该自动回滚吗?

这是因为事务回滚的默认是遇到运行时异常进行回滚,我们上面的算数异常就属于运行时异常的子类。因此,能够正常进行回滚。

如何解决呢?

通过@Transactional中的rollbackfor属性进行解决:

    @Transactional(rollbackFor = Exception.class)@RequestMapping("/r2")public boolean login2(String userName,String password) throws IOException {//用户注册userService.insert(userName,password);if(true){throw new IOException();}return true;}

测试:

可以看到事务发生了回滚:

数据库:

对上面内容的总结:

事务隔离级别

回顾Mysql事务隔离级别

1、读未提交:读未提交,也叫未提交读。该隔离级别的事务可以看到其他事务中未提交的事务。

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

脏读问题:

2、读提交:读已提交,也叫提交读,该隔离级别的事务能读取到已提交事务的数据。

该隔离级别不会有脏读问题,但由于在事务执行中可以读取到其他事务提交的结果,所以在不同的时间的相同sql查询可能会得到不同的结果,这种现象叫做不可重复读(前后多次读取,数据内容不一致)

不可重复读:

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

此隔离级别事务执行时,另一个事务成功插入了某条数据,但因为它每次查询的结果都是一样的(修改能查询到是因为它涉及到了表中的所有数据行),所以会导致查询不到这条数据,这个现象称为幻读(前后多次读取,数据总量不同)

幻读:

4、串行化:序列化,事务最高隔离级别。它会强制事务排序,使之不会发生冲突,从而解决了脏读,不可重复读和幻读问题,但因为执行效率第,所以真正使用的场景并不多。

Spring事务隔离级别

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

1、Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。

2、Isolation.READ_UNCOMMITTED:读未提交,对应SQL标准的READ UNCOMMITTED。

3、Isolation.READ_COMITTED:读已提交,对应SQL标准中的READ COMMITTED。

4、Isolation.REPEATABLE_READ:可重复读,对应SQL标准中REPEATABLE READ。

5、Isolation.SERIALIZABLE:串行化,对应SQL标准中的SERIALIZABLE。

我们可以通过@Transactional中的islation属性设置事务隔离级别:

//设置为读已提交@Transactional(isolation = Isolation.READ_COMMITTED)@RequestMapping("/r3")public boolean login3(String userName,String password) throws IOException {//用户注册userService.insert(userName,password);if(true){throw new IOException();}return true;}

Spring事务传播机制

事务传播机制:是多个事务方法存在调用关系时,事务时如何在这些方法间进行传播的。

比如:Controller中的方法A调用Service中的方法B,它们都是被@Transactional修饰。A方法运行时,会开启事务,当A调用B时,B方法本身也有事务,此时方法B运行时,是加入A的事务还是在创建一个新的事务呢?

这就涉及到了事务的传播机制。

打个比方,公司的流程管理:

执行任务之前需要先写执行文档,任务执行结束,再写总结汇报。

此时A部门有一项工作是和B部门一起干的,此时B部门是直接使用A部门的文档,还是新建一个文档呢?

事务隔离级别解决的是多个事务同时调用一个数据库的问题:

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

事务传播级别有哪些

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

Spring事务传播机制有以下七种:

1、Propagtion.REQUIRED:默认的事务传播级别。如果当前存在事务,则加入该事务。如果没有事务,则创建一个新的事务。

2、Propagtion.SUPPORTS:如果当前存在事务,则加入该事务。如果当前没有事务,则以非事务的方式继续运行。

3、Propagtion.MANDATORY:强制性。如果当前存在事务,则加入该事务。如果当前没有事务,则抛出异常

4、Propagation.REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起,也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法都会开启新的事务且开启的事务相互独立,互不干扰。

5、Propagtion.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起(不使用)。

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

7、Propagtion.NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。如果当前没有事务则该取值等价于Propagtion.REQUIRED。

举例记忆:

事务传播机制场景演示

此时,用户注册不仅要在用户表中添加数据,在日志表中也需要进行登记。

REQUIRE

Controller:


@RequestMapping("/user2")
@RestController
public class UserController2 {@Autowiredprivate UserService userService;@Autowiredprivate LogService logService;@Transactional@RequestMapping("/register")public boolean register(String userName,String password){/*** 用户表和注册表的插入理应再Service完成* 此处为了方便,直接在Controller中完成*/if(!StringUtils.hasLength(userName)||!StringUtils.hasLength(password)){return false;}Integer result = userService.insert(userName,password);System.out.println("result:"+result);//插入日志表Integer insert = logService.insert(userName, "用户注册");System.out.println("insert:"+insert);return true;}
}

Service:

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.REQUIRED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);return result;}
}

UserSerVice:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.REQUIRED)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

mapper:

UserinfoMapper:


@Mapper
public interface UserInfoMapper {@Insert("insert into user_info (user_name,password) values (#{userName},#{password})")Integer insert(String userName,String password);}

LoginfoMapper:

@Mapper
public interface LoginfoMapper {@Insert("insert into log_info(user_name,op) values (#{userName},#{op}) ")Integer insertLog(String userName,String op);
}

我们尝试在其中的一个Service中制造异常:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.REQUIRED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);//制造异常int a = 10/0;return result;}
}

测试:

从日志上可以看出,事务发生了回滚:

总结:

REQUIRE_NEW

修改Service代码即可:

UserService:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);//制造异常int a = 10/0;return result;}
}

再次进行测试,通过日志可以看到,日志表的事务发生了回滚,而用户表的事务提交了:

总结:

NEVER

UserService:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.NEVER)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NEVER)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);return result;}
}

这里我们不制造异常,但是让Controller存在事务进行测试:

可以看到仍然报了500:

NESTED

UserService:

@Service
public class UserService {@Autowiredprivate UserInfoMapper mapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String password) {return mapper.insert(userName,password);}
}

LogService:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);return result;}
}

测试没有异常的情况:

事务得到了提交:

测试有异常发生的情况:

@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);int a = 10/0;return result;}
}

事务回滚:

看起来NESTED传播机制好像跟REQUIRE机制没什么区别:但实际上NESTED可以实现部分回滚,使得其他事务能够被提交。

部分回滚:


@Service
public class LogService {@Autowiredprivate LoginfoMapper loginfoMapper;@Transactional(propagation = Propagation.NESTED)public Integer insert(String userName, String op) {Integer result = loginfoMapper.insertLog(userName, op);try {int a = 10/0;} catch (Exception e) {//部分回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return result;}
}

重新测试:

事务得到提交:

总结:

http://www.dtcms.com/a/318483.html

相关文章:

  • 国产3D大型装配设计新突破①:图纸打开设计双加速 | 中望3D 2026
  • C语言的数组与字符串练习题2
  • 如何快速翻译PPT中的文字(或简繁体转换)
  • 【51单片机2个独立按键2个独立数码管静态显示内容自定】2022-10-22
  • Perforce P4 Plan - DevOps实时规划工具
  • 指挥中心自动化的演变
  • 无人机遥控器波特率技术解析
  • 前端开发_怎么禁止用户复制内容
  • 计算机网络:如何判断B或者C类IP地址是否划分了子网
  • 设备 AI 知识库如何提升管理效率?实测分享
  • 【STM32U385RG 测评】基于VSCode的STM32开发环境搭建
  • 认识河豚毒素!剧毒神经毒素详解!
  • 向量数据库基础夯实:相关概念的详细介绍
  • 淘宝/天猫商品详情API详解(tb.item_get)
  • 一文读懂:什么是CLIP
  • 分布式存储 Ceph 的演进经验 · SOSP 2019
  • 【Web安全】csrf、ssrf和xxe的区别
  • GPT-OSS-20B vs Qwen3-14B 全面对比测试
  • 【大模型系列】gpt-oss系列模型初探
  • ACL 2025 Oral|Evaluation Agent:面向视觉生成模型的高效可提示的评估框架
  • 服务器重启后mysql5.7启动失败问题
  • MySql_忘记了root密码怎么办
  • win服务器系统10060问题解决
  • Kali Linux虚拟机安装和中文配置详细教程(2025版)
  • Sklearn 机器学习 数据聚类 DBSCAN聚类算法的异常点
  • MicrochipSam9x60 PIO寄存器操作流程
  • TypeScript 元组类型精简知识点
  • 网络拨测和业务拨测是什么意思
  • 【Create my OS】8 文件系统
  • 【Go】新版GORM自动字段映射规则