🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
目录
1. 回忆数据库中的事务
https://lilesily12385.blog.csdn.net/article/details/137935719
2. Spring中事务的实现
Spring中的事务操作分为两类:
- 编程式事务(手动写代码操作事务)
- 声明式事务(使用注解完成)
现有一需求: 用户注册,注册时候在日志表中插入一条操作记录
数据准备:
DROPDATABASEIFEXISTS trans_test;CREATEDATABASE trans_test DEFAULTCHARACTERSET utf8mb4;-- ⽤⼾表 DROPTABLEIFEXISTS user_info;CREATETABLE user_info (`id`INTNOTNULLAUTO_INCREMENT,`user_name`VARCHAR(128)NOTNULL,`password`VARCHAR(128)NOTNULL,`create_time`DATETIMEDEFAULTnow(),`update_time`DATETIMEDEFAULTnow()ONUPDATEnow(),PRIMARYKEY(`id`))ENGINE=INNODBDEFAULTCHARACTERSET= utf8mb4 COMMENT='⽤⼾表';-- 操作⽇志表 DROPTABLEIFEXISTS log_info;CREATETABLE log_info (`id`INTPRIMARYKEYauto_increment,`user_name`VARCHAR(128)NOTNULL,`op`VARCHAR(256)NOTNULL,`create_time`DATETIMEDEFAULTnow(),`update_time`DATETIMEDEFAULTnow()ONUPDATEnow())DEFAULTcharset'utf8mb4';
代码准备:
- 创建项目,配置文件引入日志打印,驼峰转换,数据库配置等.
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/trans_test?
characterEncoding=utf8&useSSL=falseusername: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:# 配置打印 MyBatis⽇志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case:true#配置驼峰⾃动转换
创建实体类:
importlombok.Data;importjava.util.Date;@DatapublicclassUserInfo{privateInteger id;privateString userName;privateString password;privateDate createTime;privateDate updateTime;}
mport lombok.Data;importjava.util.Date;@DatapublicclassLogInfo{privateInteger id;privateString userName;privateString op;privateDate createTime;privateDate updateTime;}
Mapper:
importorg.apache.ibatis.annotations.Insert;importorg.apache.ibatis.annotations.Mapper;@MapperpublicinterfaceUserInfoMapper{@Insert("insert into user_info(`user_name`,`password`)values(#{name},#{password})")Integerinsert(String name,String password);}
importorg.apache.ibatis.annotations.Insert;importorg.apache.ibatis.annotations.Mapper;@MapperpublicinterfaceLogInfoMapper{@Insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")IntegerinsertLog(String name,String op);}
Service:
@Slf4j@ServicepublicclassUserService{@AutowiredprivateUserInfoMapper userInfoMapper;publicvoidregistryUser(String name,String password){//插⼊⽤⼾信息
userInfoMapper.insert(name,password);}}
@Slf4j@ServicepublicclassLogService{@AutowiredprivateLogInfoMapper logInfoMapper;publicvoidinsertLog(String name,String op){//记录⽤⼾操作
logInfoMapper.insertLog(name,"⽤⼾注册");}}
2.1 Spring编程式事务
Spring手动操作事务主要分为三步,与Sql事务的操作类似:
- 开启事务
- 提交事务
- 或者回滚事务
@RestController@RequestMapping("/user")publicclassTransactionController{@AutowiredprivateUserService userService;@AutowiredprivateDataSourceTransactionManager dataSourceTransactionManager;@AutowiredprivateTransactionDefinition transactionDefinition;@RequestMapping("/registry")publicStringlogin(String userName,String password){//开启事务TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);//用户注册
userService.registryUser(userName,password);//提交事务
dataSourceTransactionManager.commit(transaction);//回滚事务// dataSourceTransactionManager.rollback(transaction);return"注册成功";}}
SpringBoot内置了两个对象:
DataSourceTransactionManager,数据源事务管理器,一般用于事务的开启回滚和提交.TransactionDefinition是事务的属性,一般用于传给事务管理器的getTransaction方法,用于事务的获取与开启.
- 观察事务提交
dataSourceTransactionManager.commit(transaction);


我们观察到数据库,数据被插入成功.
- 观察事务回滚
dataSourceTransactionManager.rollback(transaction);


我们看到虽然返回的结果是注册成功,但是数据库中并没有多出任何数据.
下面我们来学习一种简单而快捷的方法,使用注解声明.
2.2 Spring声明式事务@Transactional
一共有两部操作:
- 添加依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId></dependency>
- 在事务的方法上添加
@Transactional注解就可以实现了.无需手动开启和提交事务,在事务全部执行完成之后会自动提交,在中途发生异常的手会自动回滚.
@RestController@RequestMapping("/user2")publicclassTransactionController2{@AutowiredprivateUserService userService;@RequestMapping("/registry2")@TransactionalpublicStringregistry2(String userName,String password){
userService.registryUser(userName,password);return"注册成功";}}
运行程序后,数据插入成功.
修改程序,使之出现异常:
@RestController@RequestMapping("/user2")publicclassTransactionController2{@AutowiredprivateUserService userService;@RequestMapping("/registry2")@TransactionalpublicStringregistry2(String userName,String password){
userService.registryUser(userName,password);int i =10/0;return"注册成功";}}
运行之后,虽然返回了注册成功,但是数据库并没有更新结果.
我们一般写事务的时候会在业务逻辑层来控制事务,因为在业务逻层中,一个业务功能可能会包含多个数据库访问操作,这样就可以把多个访问数据库的操作 合并在同一个事务中.
2.3 @Transactional的作用
@Transactional
可以用来修饰方法或者是类.
修饰方法的时候,只对public修饰的方法生效,修饰其他方法也不会报错,但是也不会生效.
修饰类的时候,对类中的所有public方法生效.
在程序出现异常的时候,如果异常没有被捕获,这时候事务就会被回滚,但是如果异常被捕获,就会被认为是正常执行,依然会提交事务.
修改上述代码:
@RestController@RequestMapping("/user2")publicclassTransactionController2{@AutowiredprivateUserService userService;@RequestMapping("/registry2")@TransactionalpublicStringregistry2(String userName,String password){
userService.registryUser(userName,password);try{int i =10/0;}catch(ArithmeticException e){
e.printStackTrace();}return"注册成功";}}
运行程序之后,虽然出错了,由于异常得到了捕获,事务便得到了提交.
以下两种情况,事务依然会回滚:
- 重新抛出异常
try{int i =10/0;}catch(ArithmeticException e){throw e;}
- 手动回滚事务 使用
TransactionAspectSupport.currentTransactionStatus()得到当前事务,并使用setRollbackOnly回滚.
try{int i =10/0;}catch(ArithmeticException e){TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}
3. @Transactional详解
我们主要学习
@Transactional
注解的三个属性:
- rollbackFor:异常回滚属性,指定能够触发事务回滚的异常类型.可以指定多个异常类型.
- Isolation:事务的隔离级别,默认是Isolation.DEFAULT.
- propagation:事务的传播机制.默认值为propagation.REQUIRED
3.1 rollbackFor
异常回滚的时候,
@Transactional
默认在遇到Error或者运行时异常时才会回滚.
接下来我们来使用代码验证:
@Transactional@RequestMapping("/registry3")publicStringregistry3(String userName,String password)throwsIOException{
userService.registryUser(userName,password);if(true){thrownewIOException();}return"注册成功";}
向服务器提交数据:

我们看到,虽然抛出了异常,但是数据库的数据仍然被修改了.
如果我们要想指定回滚异常的类型,我们需要通过
@Transactional
的rollbackFor属性来完成,给属性传入异常的类对象来实现对回滚异常的指定,
@RequestMapping("/registry3")@Transactional(rollbackFor =Exception.class)publicStringregistry3(String userName,String password)throwsIOException{
userService.registryUser(userName,password);if(true){thrownewIOException();}return"注册成功";}
运行程序:

我们看到,事务并没有进行提交,被回滚了,数据库的数据并没有更行.
3.2 事务隔离级别
3.2.1 回顾MySQL中的事务隔离级别
https://lilesily12385.blog.csdn.net/article/details/137935719
3.3.2 Spring事务隔离级别
Spring中事务的隔离级别有5种:
- Isolation.DEFAULT: 以连接数据库的隔离级别为准.
- Isolation.READ_UNCOMMITTED:读未提交
- Isolating.READ_COMMITTED:读提交.
- Isolation.REPEATABLE_READ:可重复读.
- Isolation.SERIALIZABLE:串行化.
publicenumIsolation{DEFAULT(-1),READ_UNCOMMITTED(1),READ_COMMITTED(2),REPEATABLE_READ(4),SERIALIZABLE(8);privatefinalint value;privateIsolation(int value){this.value = value;}publicintvalue(){returnthis.value;}}
Spring中的隔离级别可以通过
@Transactional
中的
Isolation
属性进行设置.
@RequestMapping("/registry3")@Transactional(isolation =Isolation.DEFAULT)publicStringregistry3(String userName,String password)throwsIOException{
userService.registryUser(userName,password);return"注册成功";}
3.3 Spring事务传播机制
3.3.1 概念
事务的传播机制就是:多个事务方法存在调用关系的时候,事务是如何在这些方法之间进行传播的.事务的传播机制就解决的是一个事务在多个节点上(方法)中的传递.
比如有两个方法A和B,他们都被
@Transactional
修饰,方法A调用了方法B.A方法运行的时候,会开启一个新的事物,当A调用B的时候,B方法本身也有事务,此时B方法运行的时候,是假如A事务,还是创建一个新的事务呢?下面我们就来介绍一下事务的传播机制.
3.3.2 事务的传播机制分类
@Transactional
注解支持事务的传播机制的设置,我们可以通过
propagation
属性来设置传播行为.
- Propagation.REQUIRED:加入事务,默认的事务传播级别.如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务.(加入事务,就是共用一个事务,一个事务发生异常,全部回滚.)
- Propagation.SUPPORTS:如果当前存在事务,则加入该事务.如果当前没有事务,则以非事务的方式继续运行.
- Propagation.MANDATORY:强制性,如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常.
- Propagation.REQUIERS_NEW:新建事务,创建一个新事务如果当前存在事务,则把当前事务挂起.(发生异常,不影响其他事务)也就是不管外部方法是否开启事务,
Propagation.REQUIERS_NEW修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干涉. - Propagation.NOT_SUPPORTED: 以非事务的方式运行,如果当前存在事务则把当前事务挂起.
- Propagation.NEVER: 不支持当前事务,以非事务的方式运行,如果存在事务,则抛出异常.
- Propagation.NESTED: 嵌套事务,如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则该取值等价于
Propagation.REQUIRED. 1,4对应,2,5对应,3,6对应.
publicenumPropagation{REQUIRED(0),SUPPORTS(1),MANDATORY(2),REQUIRES_NEW(3),NOT_SUPPORTED(4),NEVER(5),NESTED(6);privatefinalint value;privatePropagation(int value){this.value = value;}publicintvalue(){returnthis.value;}}
举例说明:一对新人结婚,需要房子
- Propagation.REQUIRES: 如果你有房子,就住你的房子(加入事务),如果你没有房子,我们就一起买房子(创建一个新事务).
- Propagation.SUPPORTS: 如果你有房子,我们就住你的房子(加入事务),如果没有房子,我们就租房子(以非事务的方式运行).
- Propagation.MANDATORY: 要求必须有房子(加入事务),如果没有房子,就不结婚(抛出异常)
- Propagation.REQUIERS_NEW: 必须买新房,不管你有没有房子,必须两个人一起买房(创建一个新事务),即使有房也不住(当前事务挂起).
- Propagation.NOT_SUPPORTED: 不管你有没有房子,我都不住(挂起当前事务),必须租房(以非事务的方式运行).
- Propagation.NEVER:不能有房子(当前存在事务),有房子就不结婚(抛出异常).
- Propagation.NESTED :如果你没房,就⼀起买房.如果你有房,我们就以房子为根据地,做点下生意.(如果如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行.如果当前没有事务,则该取值等价于PROPAGATION_REQUIRED )
3.3.3 Spring事务传播机制代码演示
重点关注两个: REQUIRED,REQUIERS_NEW.
- REQUIRED(加入事务) 用户注册,插入一条数据,并记录操作日志.
@RequestMapping("/r4")@TransactionalpublicStringregistry4(String userName,String password){
userService.registryUser(userName,password);
logService.insertLog(userName,"用户注册");return"注册成功";}@ServicepublicclassUserService{@AutowiredpublicUserInfoMapper userInfoMapper;@Transactional(propagation =Propagation.REQUIRED)publicvoidregistryUser(String name,String password){
userInfoMapper.insert(name, password);}}@ServicepublicclassLogService{@AutowiredprivateLogInfoMapper logInfoMapper;@Transactional(propagation =Propagation.REQUIRED)publicvoidinsertLog(String name,String op){int i =10/0;//记录⽤⼾操作
logInfoMapper.insertLog(name,"⽤户注册");}}
我们在执行之后,发现数据库中并没有插入任何数据,这就是因为
insertLog
方法发生了异常,事务发生回滚,当事务回滚之后,
registry4
方法也发生了回滚,导致了
registryUser
也发生了回滚,导致数据库中没有插入数据.
- REQUIRES_NEW(新建事务) 将上面的UserService和LogService的事务传播机制改为Propagation.REQUIRES_NEW.
@RequestMapping("/r4")@TransactionalpublicStringregistry4(String userName,String password){
userService.registryUser(userName,password);
logService.insertLog(userName,"用户注册");return"注册成功";}@ServicepublicclassLogService{@AutowiredprivateLogInfoMapper logInfoMapper;@Transactional(propagation =Propagation.REQUIRES_NEW)publicvoidinsertLog(String name,String op){int i =10/0;//记录⽤⼾操作
logInfoMapper.insertLog(name,"⽤户注册");}}@ServicepublicclassUserService{@AutowiredpublicUserInfoMapper userInfoMapper;@Transactional(propagation =Propagation.REQUIRES_NEW)publicvoidregistryUser(String name,String password){
userInfoMapper.insert(name, password);}}
在执行之后,我们会发现,日志表中并没有插入新的数据,但是用户表中插入了新的数据,这是由于
UserService
,
LogService
与
registry4
属于不同的事务,
LogService
出现异常回滚之后不会影响
registry4
和
UserService
的执行.
- NEVER(不支持当前事务) 把
REQUIRED代码的UserService中的对应方法的事务传播机制修改为Propagation.NEVER.并去掉制造的异常.
@Transactional(propagation =Propagation.NEVER)publicvoidinsertLog(String name,String op){//记录⽤⼾操作
logInfoMapper.insertLog(name,"用户注册");}
运行之后,程序抛出异常,日志表和用户表均没有数据插入.
- NESTED(嵌套事务) 将上述
REQUIRED的UserService中的对应方法的事务传播机制修改为Propagation.NESTED.
@Transactional(propagation =Propagation.NESTED)publicvoidinsertLog(String name,String op){int i =10/0;//记录用户操作
logInfoMapper.insertLog(name,"用户注册");}
@Transactional(propagation =Propagation.NESTED)`在这里插入代码片`
publicvoidregistryUser(String name,String password){
userInfoMapper.insert(name, password);}
运行程序之后,两张表中都没有插入任何数据,如果我们对出现异常的方法进行手动回滚:
@Transactional(propagation =Propagation.NESTED)publicvoidinsertLog(String name,String op){try{int i =10/0;}catch(Exception e){TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}//记录用户操作
logInfoMapper.insertLog(name,"用户注册");}

我们看到,用户表插入成功了,但是日志表的数据被回滚了.

区分REQUIRED和NESTED
REQUIRED:当其中一个事务出现异常的时候,所有事务都会回滚,如果try-catch语句中对事物进行手动回滚,则子事务和父事务全部会被回滚.
NESTED : 当子事务出现异常的时候,子事务对应的父事务也会回滚,但是如果在有异常的子事务中进行try-catch,catch中对事务进行手动回滚,则只有出现异常的事务被回滚,但是另一个没有出现异常的子事务没有被回滚,与REQUIRED最大的区别就是,NESTED可以做到部分回滚,但是REQUIRED只能做到全部回滚.
版权归原作者 LileSily 所有, 如有侵权,请联系我们删除。
