目录
前言:
1.为什么需要事务
2.Spring中事务的实现
2.1编程式事务
2.2声明式事务
2.3@Transactional的作用范围
2.4@Transactional参数说明
2.5@Transactional的注意事项
2.6@Transactional工作原理
3.事务隔离级别
3.1事务特性的回顾
3.2Spring中设置事务的隔离级别
3.3MySQL事务隔离级别
3.4Spring的事务隔离级别
4.Spring事务的传播机制
4.1事务传播机制是什么
4.2为什么需要事务传播
结束语:
对于事务我们之前也是有所了解的我们在来回顾一下有关于事务的定义:将一组操作封装成一个执行单元(封装在一起),要么全部执行成功,要么全部执行失败。
就拿转账来说:
如果没有事务,第一步如果成功了第二步失败了,那么A账户的100元就相当于是平白无故的人间蒸发了,不符合现实。所有使用事务就可以解决这个问题了,让这一组操作要么一起成功,要么一起失败。
在Spring中事务的操作分为两类:
接下来我们先来回顾一下在MySQL中是如何使用的。
事务在MySQL中有三个重要的操作:开启事务(start transaction)、提交事务(commit)、回滚事务(rollback)。
Spring手动操作事务和上面MySQL操作事务类似,他也是有是三个重要的操作步骤:
SpringBoot内置了两个对象,DataSourceTransactionManager用来获取事务(开启事务)、提交或回滚事务的,而TransactionDefinition是事务的属性,在获取事务的时候要将TransactionDefinition传递进去从而获得一个事务TransactionStatus,实现代码如下所示:
package com.example.demo.controller;
import com.example.demo.service.UserService;
import org.apache.catalina.User;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class UserController2 {
@Resource
private UserService userService;
//JDBC事务管理器
@Resource
private DataSourceTransactionManager dataSourceTransactionManager;
//定义事务属性
@Resource
private TransactionDefinition transactionDefinition;
@RequestMapping("/save")
public Object save(User user) {
//开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
//插入数据库
int result = userService.save(user);
//提交事务
dataSourceTransactionManager.commit(transactionStatus);
//回滚事务
dataSourceTransactionManager.rollback(transactionStatus);
return result;
}
}
上述的实心方式太过于繁琐,所以接下来我们就使用声明式事务。
声明式事务的实现很简单,只需要在需要的方法上添加@Transactional注解就可以实现了,无需手动开启事务和提交事务,进入方法时开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务,具体代码实现如下所示:
model层代码:
package com.example.demo.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Userinfo {
private int id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
}
dao层代码:
package com.example.demo.dao;
import com.example.demo.model.Userinfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
@Insert("insert into userinfo(username, password) values(#{username}, #{password})")
int add(Userinfo userinfo);
}
service层代码:
package com.example.demo.service;
import com.example.demo.dao.UserMapper;
import com.example.demo.model.Userinfo;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public int add(Userinfo userinfo) {
int result = userMapper.add(userinfo);
System.out.println("add result -> " + result);
return result;
}
}
controller层代码:
package com.example.demo.controller;
import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/add")
@Transactional
public int add() {
//1.非空判断
Userinfo userinfo = new Userinfo();
userinfo.setUsername("二二");
userinfo.setPassword("123");
//2.调用service执行添加
int result = userService.add(userinfo);
System.out.println("result: " + result);
int n = 10 / 0;
//3.将结果给前端
return result;
}
}
结果展示:
当启动服务器的时候访问页面的时候会出现报错异常。
在控制台中可以看到以及将数据添加到数据库中的信息。
但是在访问数据库的时候会发现没有数据,说明触发异常之后数据已经被回滚了。
@Transactional可以用来修饰方法或类:
参数 | 作用 |
value | 当配置了多个事务管理器时,可以使用该属性指定选择那个事务管理器。 |
transactionManager | 当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。 |
propagation | 事务的传播行为,默认值为Propagation.REQUIRED |
isolation | 事务的隔离级别,默认值为Isolation.DEFAULT |
timeout | 事务的隔离级别,默认值为-1,如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
readOnly | 指定事务是否为只读事务,默认值为false,为了忽略那些不需要事务的方法,比如读取事务可以设置read-only为true。 |
rollbackFor | 用于指定能够触发事务回滚的异类型,可以指定多个异常。 |
rollbackForClassName | 用于指定能够触发事务回滚的异类型,可以指定多个异常。 |
noRollbackFor | 抛出指定的异常类型,不会滚事务,也可以指定多个异常类型。 |
noRollbackForClassName | 抛出指定的异常类型,不会滚事务,也可以指定多个异常类型。 |
当程序发生异常,但被try-catch处理之后,就不会发生自动回滚事务了。
如下代码所示:
注意这里只是在上述代码的基础之上改动了controller层的代码。
结果展示:
浏览器不会显示异常。
数据库会面会显示出添加之后的数据。
这里大家就好奇了,为什么明明是发生了异常,为什么事务不回滚了?
原因是因为在Spring中是使用代理来进行管理的,而当异常被捕获之后,外部的代理就没有办法感受到异常信息了,所以就会直接将数据添加到数据库中,不进行回滚事务了。
那么针对于上述的情况当然也会有解决方法,根据它的原因我们就可以找到解决问题的办法了,我们可以让代理感受到异常信息,这样事务就会发生回滚了。下面是被try-catch处理之后,不自动回滚事务的解决方案:
①将异常继续抛出去(代理对象就能感受到异常,也就能自动回滚事务了)
代码展示:
结果展示:
浏览器访问的时候仍然会报错。
控制台显示结果如下所示:
数据库再次查询数据的时候,发现三三这条数据没有被加载到数据库中,已经别回滚了。
②使用代码手动回滚事务。
代码展示:
结果展示:
这次在浏览器中展示的就不是一大堆的报错信息了。
接下来是控制台的信息展示,也是显示已经将数据添加到数据库中了。
在数据库中会发现数据不存在,就说明已经被回滚掉了。
注意:以上两种都可以用来解决被try-catch处理之后,不能自动回顾事务的问题,但是一般我们使用第二种解决方案多,也是推荐的一种写法。
@Transactional是基于AOP实现的,AOP又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK的动态代理,如果目标对象没有实现接口,会使用CGLIB动态代理。
@Transactional在开始执行业务之前,通过代理先开启事务,在执行之后再提交事务。如果中途遇到异常,则回滚事务。
事务有4大特性(ACID),原子性、一致性、持久性、隔离性。具体的概念如下所示:
在上述的四大特性中只有隔离性(隔离级别)是可以设置的。
为什么要设置事务的隔离级别?
设置事务的隔离级别是用来保障多个并发事务执行可控,更符合操作者的预期。
Spring中事务隔离级别可以通过@Transactional中的isolation属性来进行设置,如下所示:
MySQL的事务隔离级别有以下4种:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(READ UNCOMMITTED) | √ | √ | √ |
读已提交(READ COMMITED) | × | √ | √ |
可重复读(REPEATABLE READ) | × | × | √ |
串行化(SERIALIZABLE) | × | × | × |
脏读、幻读、不可重复读导致的原因如下所示:
在数据库中我们可以通过以下SQL语句来查询到当前全局事务隔离级别和当前连接的事务隔离级别:
那么在MySQL中到底有没有解决掉幻读的问题呢?
有但是又没彻底解决,因为 REPEATABLE READ 是通过 + MVCC来进行解决幻读问题的,但是REPEATABLE READ又有两种读,一种是当前读,一种是快照读,快照读的时候是从内存中读取的。所以快照读通过+MVCC是可以解决掉幻读问题的,而当前读是读取当前的数据,可能会发生变化,所以当前读+MVCC是解决不掉幻读问题的,除非加锁。
所以要想彻底解决幻读问题有两种解决办法:
而Spring中事务隔离级别包含以下5种:
从上述介绍中可以看出,相比于MySQL的事务隔离级别,Spring的事务隔离级别只是多一个Isolation.DEFAULT(以数据库的全局事务隔离级别为主)。
我们只需要在Spring事务中设置@Transactional里的属性isolation属性的隔离级别即可,如下代码所示:
Spring事务传播机制定义了多个包含了事务的方法,互相调用,事务是如何在这些方法间进行传递的。他其实就是规定了多个事务在互相调用时,事务的执行行为。
事务的隔离级别是保证多个并发执行的可控性(稳定性的),而事务传播机制是保证一个事务在多个调用方法间的可控性(稳定性)。就像是疫情期间,会存在不同的隔离方式,酒店隔离、居家隔离...,这就是为了保证疫情的可控性。事务的隔离级别解决的是多个事务同时调用一个数据库的问题,如下所示:
而事务的传播机制解决的是一个事务在多个节点(方法)中传递的问题,如下所示:
Spring事务的传播机制包含以下7点:
针对于Propagation.REQUIRED它是表示如果当前存在事务,则加入该事务,没有存在则不加入,以非事务的方式继续运行。接下来我们通过画图和代码来给大家进行具体演示一下。
代码展示:
Controller层代码展示:
package com.example.demo.controller;
import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/add")
@Transactional(propagation = Propagation.REQUIRED)
public int add() {
//1.非空判断
Userinfo userinfo = new Userinfo();
userinfo.setUsername("思思");
userinfo.setPassword("123456");
//2.调用service执行添加
int result = userService.add(userinfo);
System.out.println("result: " + result);
try {
int n = 10 / 0;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
//3.将结果给前端
return result;
}
}
Service层代码展示:
package com.example.demo.service;
import com.example.demo.dao.UserMapper;
import com.example.demo.model.Userinfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int add(Userinfo userinfo) {
int result = userMapper.add(userinfo);
System.out.println("add result -> " + result);
insert(userinfo);
return result;
}
@Transactional(propagation = Propagation.REQUIRED)
public int insert(Userinfo userinfo) {
int result = userMapper.add(userinfo);
System.out.println("add result -> " + result);
int n = 10 / 0;
return result;
}
}
结果展示:
浏览器页面展示,会发现存在算数异常。
控制台会发现有插入的两条数据的记录。
在数据库中发现没有插入任何数据,说明在触发异常之后数据都被回滚了。
解析:当使用Propagation.REQUIRED时说明第一个事务已经开启了事务了,则后续的事务发现前面的事务开启之后就会加入到事务中来。如下图所示:
针对于Propagation.SUPPORTS它是如存在当前事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
代码展示:
controller层代码展示:
package com.example.demo.controller;
import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/add")
@Transactional(propagation = Propagation.SUPPORTS)
public int add() {
//1.非空判断
Userinfo userinfo = new Userinfo();
userinfo.setUsername("思思");
userinfo.setPassword("123456");
//2.调用service执行添加
int result = userService.add(userinfo);
System.out.println("result: " + result);
try {
int n = 10 / 0;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
//3.将结果给前端
return result;
}
}
service层代码展示:
package com.example.demo.service;
import com.example.demo.dao.UserMapper;
import com.example.demo.model.Userinfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Transactional(propagation = Propagation.SUPPORTS)
public int add(Userinfo userinfo) {
int result = userMapper.add(userinfo);
System.out.println("add result -> " + result);
insert(userinfo);
return result;
}
@Transactional(propagation = Propagation.SUPPORTS)
public int insert(Userinfo userinfo) {
int result = userMapper.add(userinfo);
System.out.println("add result -> " + result);
int n = 10 / 0;
return result;
}
}
结果展示:
浏览器依然会报错:
在数据库中国会发现插入了两条数据,说明他是以非事务的方式运行的。
画图解析:
针对于 Propagation.NESTED 它表示当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则该取值等于PROPAGATION_REQUIRED。
代码展示:
Controller层代码展示:
package com.example.demo.controller;
import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@RequestMapping("/add")
@Transactional(propagation = Propagation.NESTED)
public int add() {
//1.非空判断
Userinfo userinfo = new Userinfo();
userinfo.setUsername("五五");
userinfo.setPassword("123456");
//2.调用service执行添加
int result = userService.add(userinfo);
System.out.println("result: " + result);
userService.insert(userinfo);
// try {
// int n = 10 / 0;
// } catch (Exception e) {
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// }
//3.将结果给前端
return result;
}
}
service层代码展示:
package com.example.demo.service;
import com.example.demo.dao.UserMapper;
import com.example.demo.model.Userinfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRED)
public int add(Userinfo userinfo) {
int result = userMapper.add(userinfo);
System.out.println("add result -> " + result);
return result;
}
@Transactional(propagation = Propagation.REQUIRED)
public int insert(Userinfo userinfo) {
int result = userMapper.add(userinfo);
System.out.println("add result -> " + result);
try {
int n = 10 / 0;
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}
}
结果展示:
对于浏览器端仍然出现报错现象,但是不是算数异常错误了。
对于控制台依然会显示插入两条数据。
对于数据库而言它不会插入一条数据,所有的数据都会被回滚掉。
画图解释:
好了这节小编就给大分享到这里啦,希望这节对大家有关于Spring事务和事务的传播机制的基础知识的了解有一定帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)