SpringBoot 事务
事务是一组操作的集合, 是一个不可分割的操作.会把所有的操作作为一个整体, 一起向数据库提交或者是撤销操作请求. 所以这组操作要么同时成功, 要么同时失败.
为什么需要事务?
我们在进行程序开发时, 也会有事务的需求.
比如转账操作:
第一步:A 账户 -100 元.
第二步:B 账户 +100 元.
如果没有事务,第一步执行成功了, 第二步执行失败了, 那么A 账户的100 元就平白无故消失了. 如果使用事务就可以解决这个问题, 让这一组操作要么一起成功, 要么一起失败.
比如秒杀系统,
第一步: 下单成功
第二步: 扣减库存
下单成功后, 库存也需要同步减少. 如果下单成功, 库存扣减失败, 那么就会造成下单超出的情况. 所以就需要把这两步操作放在同一个事务中. 要么一起成功, 要么一起失败.
理解事务概念为主, 实际企业开发时, 并不是简单的通过事务来处理.
事务的操作
事务的操作主要有三步:
- 开启事start transaction/ begin (一组操作前开启事务)
- 提交事务: commit (这组操作全部成功, 提交事务)
- 回滚事务: rollback (这组操作中间任何一个操作出现异常, 回滚事务)
-- 开启事务
start transaction;
-- 提交事务
commit;
-- 回滚事务
rollback;
Spring中事务的实现
Spring 中的事务操作分为两类:
- 编程式事务(手动写代码操作事务).
- 声明式事务(利用注解自动开启和提交事务).
-- 创建数据库
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;
-- 用户表
DROP TABLE IF EXISTS 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 IF EXISTS 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';
配置文件
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/trans_test?characterEncoding=utf8&useSSL=false
username: root
password: 123
driver-class-name: com.mysql.cj.jdbc.Driver
# 设置动态代理的方式 true jdk代理, false cglib代理
aop:
proxy-target-class: true
mybatis:
configuration:
map-underscore-to-camel-case: true #配置驼峰自动转换
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句
mapper-locations: classpath:mapper/**Mapper.xml
# 设置日志文件的文件名
logging:
file:
name: logger/spring-book.log
实体类
@Data
public class LogInfo {
private Integer id;
private String userName;
private String op;
private Date createTime;
private Date updateTime;
}
@Data
public class UserInfo {
private Integer id;
private String userName;
private String password;
private Date createTime;
private Date updateTime;
}
mapper
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info(`user_name`,`password`)values(#{name},#
{password})")
Integer insert(String name,String password);
}
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface LogInfoMapper {
@Insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")
Integer insertLog(String name,String op);
}
service
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public void registryUser(String name,String password){
//插入用户信息
userInfoMapper.insert(name,password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
public void insertLog(String name,String op){
//记录用户操作
logInfoMapper.insertLog(name,"用户注册");
}
}
controller
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name,String password){
//用户注册
userService.registryUser(name,password);
return "注册成功";
}
}
Spring编程式事务
Spring 手动操作事务和上面 MySQL 操作事务类似, 有 3 个重要操作步骤:
• 开启事务(获取事务)
• 提交事务
• 回滚事务
SpringBoot 内置了两个对象:
- DataSourceTransactionManager 事务管理器. 用来获取事务(开启事务), 提交或回滚事务的
- TransactionDefinition 是事务的属性, 在获取事务的时候需要将TransactionDefinition 传递进去从而获得一个事务 TransactionStatus
事务交提
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name,String password){
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
//用户注册
userService.registryUser(name,password);
// 提交事务
dataSourceTransactionManager.commit(transaction);
//回滚事务
//dataSourceTransactionManager.rollback(transactionStatus);
return "注册成功";
}
}
http://localhost:8080/user/registry?name=user1&password=123123
事务回滚
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name,String password){
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
//用户注册
userService.registryUser(name,password);
//回滚事务
dataSourceTransactionManager.rollback(transactionStatus);
return "注册成功";
}
}
http://localhost:8080/user/registry?name=user2&password=123123
虽然返回结果时注册成功,但是数据库中并没有插入数据。
Spring声明式事务@Transactional
声明式事务的实现很简单, 只需要在需要事务的方法上添加 @Transactional 注解就可以实现了.无需手动开启事务和提交事务, 进入方法时自动开启事务, 方法执行完会自动提交事务, 如果中途发生了没有处理的异常会自动回滚事务.
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
userService.registryUser(name,password);
return "注册成功";
}
}
http://localhost:8080/user/registry?name=user2&password=123123
数据库中插入了数据。
使程序出现异常
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
userService.registryUser(name,password);
int a = 10 / 0;
return "注册成功";
}
}
事务会进行回滚,所以数据库中没有新插入的数据。
我们一般会在业务逻辑层当中来控制事务, 因为在业务逻辑层当中, 一个业务功能可能会包含多个数据访问的操作. 在业务逻辑层来控制事务, 我们就可以将多个数据访问操作控制在一个事务范围内.
Transactional作用
@Transactional 可以用来修饰方法或类:
• 修饰方法时: 只有修饰public 方法时才生效(修饰其他方法时不会报错, 也不生效)[推荐]
• 修饰类时: 对@Transactional 修饰的类中所有的 public 方法都生效.
方法/类被 @Transactional 注解修饰时, 在目标方法执行开始之前, 会自动开启事务, 方法执行结之后, 自动提交事务.如果在方法执行过程中, 出现异常, 且异常未被捕获, 就进行事务回滚操作.如果异常被程序捕获, 方法就被认为是成功执行, 依然会提交事务.
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
//用户注册
userService.registryUser(name,password);
log.info("用户数据插入成功");
//对异常进行捕获
try {
//强制程序抛出异常
int a = 10/0;
}catch (Exception e){
e.printStackTrace();
}
return "注册成功";
}
运行程序, 发现虽然程序出错了, 但是由于异常被捕获了, 所以事务依然得到了提交.
如果需要事务进行回滚, 有以下两种方式:
- 重新抛出异常
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
//用户注册
userService.registryUser(name,password);
log.info("用户数据插入成功");
//对异常进行捕获
try {
//强制程序抛出异常
int a = 10/0;
}catch (Exception e){
//将异常重新抛出去
throw e;
}
return "注册成功";
}
- 手动回滚事务
// 使用 TransactionAspectSupport.currentTransactionStatus() 得到当前的事务, 并
// 使用 setRollbackOnly 设置setRollbackOnly
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
//用户注册
userService.registryUser(name,password);
log.info("用户数据插入成功");
//对异常进行捕获
try {
//强制程序抛出异常
int a = 10/0;
}catch (Exception e){
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return "注册成功";
}
Transactional详解
通过上面的代码, 我们学习了@Transactional 的基本使用. 接下来我们学习@Transactional注解的使用细节.
我们主要学习@Transactional 注解当中的三个常见属性:
- rollbackFor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型
- Isolation: 事务的隔离级别. 默认值为Isolation.DEFAULT
- propagation: 事务的传播机制. 默认值为 Propagation.REQUIRED
rollbackFor
@Transactional 默认只在遇到运行时异常和Error时才会回滚, 非运行时异常不回滚. 即Exception的子类中, 除了RuntimeException及其子类.
修改一下代码
@Transactional
@RequestMapping("/r2")
public String r2(String name,String password) throws IOException {
//用户注册
userService.registryUser(name,password);
log.info("用户数据插入成功");
if (true){
throw new IOException();
}
return "r2";
}
发现虽然程序抛出了异常, 但是事务依然进行了提交.
如果我们需要所有异常都回滚, 需要来配置@Transactional 注解当中的rollbackFor 属性, 通过rollbackFor 这个属性指定出现何种异常类型时事务进行回滚.
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/r2")
public String r2(String name,String password) throws IOException {
//用户注册
userService.registryUser(name,password);
log.info("用户数据插入成功");
if (true){
throw new IOException();
}
return "r2";
}
数据回滚了。
在Spring的事务管理中,默认只在遇到运行时异常RuntimeException和Error时才会回滚.
如果需要回滚指定类型的异常, 可以通过rollbackFor属性来指定.
事务隔离级别
MySQL事务隔离级别
- 读未提交(READ UNCOMMITTED): 读未提交, 也叫未提交读. 该隔离级别的事务可以看到其他事务中未提交的数据.(因为其他事务未提交的数据可能会发生回滚, 但是该隔离级别却可以读到, 我们把该级别读到的数
据称之为脏数据, 这个问题称之为脏读.) - 读提交(READ COMMITTED): 读已提交, 也叫提交读. 该隔离级别的事务能读取到已经提交事务的数据,(该隔离级别不会有脏读的问题.但由于在事务的执行中可以读取到其他事务提交的结果, 所以在不同时间的相同 SQL 查询可能会得到不同的结果, 这种现象叫做不可重复读)
- 可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也就可以确保同一事务多次查询的结果一致, 但是其他事务新插入的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别.(比如此级别的事务正在执行时, 另一个事务成功的插入了某条数据, 但因为它每次查询的结果都是一样的, 所以会导致查询不到这条数据, 自己重复插入时又失败(因为唯一约束的原因). 明明在事务中查询不到这条信息,但自己就是插入不进去, 这个现象叫幻读.)
- 串行化(SERIALIZABLE): 序列化, 事务最高隔离级别. 它会强制事务排序, 使之不会发生冲突, 从而解决了脏读, 不可重复读和幻读问题, 但因为执行效率低, 所以真正使用的场景并不多.
Spring事务隔离级别
Spring 中事务隔离级别有5 种:
- Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主.
- Isolation.READ_UNCOMMITTED : 读未提交, 对应SQL标准中 READ UNCOMMITTED
- Isolation.READ_COMMITTED : 读已提交,对应SQL标准中 READ COMMITTED
- Isolation.REPEATABLE_READ : 可重复读, 对应SQL标准中 REPEATABLE READ
- Isolation.SERIALIZABLE : 串行化, 对应SQL标准中 SERIALIZABLE
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;
}
}
Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进行设置
@Transactional(isolation = Isolation.READ_COMMITTED)
@RequestMapping("/r3")
public String r3(String name,String password) throws IOException {
//... 代码省略
return "r3";
}
Spring事务传播机制
事务传播机制就是: 多个事务方法存在调用关系时, 事务是如何在这些方法间进行传播的.
比如有两个方法A, B都被@Transactional 修饰, A方法调用B方法,A方法运行时, 会开启一个事务. 当A调用B时, B方法本身也有事务, 此时B方法运行时, 是加入A的事务, 还是创建一个新的事务呢?
这个就涉及到了事务的传播机制.
事务隔离级别解决的是多个事务同时调用一个数据库的问题
而事务传播机制解决的是一个事务在多个节点(方法)中传递的问题
事务传播机制有哪些
@Transactional 注解支持事务传播机制的设置, 通过 propagation 属性来指定传播行为.
Spring 事务传播机制有以下 7 种:
- Propagation.REQUIRED : 默认的事务传播级别. 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则创建一个新的事务.
- Propagation.SUPPORTS : 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则以非事务的方式继续运行.
- Propagation.MANDATORY :强制性. 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则抛出异常.
- Propagation.REQUIRES_NEW : 创建一个新的事务. 如果当前存在事务, 则把当前事务挂起. 也就是说不管外部方法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部方法都会新开启自己的事务, 且开启的事务相互独立, 互不干扰.
- Propagation.NOT_SUPPORTED : 以非事务方式运行, 如果当前存在事务, 则把当前事务挂起(不用).
- Propagation.NEVER : 以非事务方式运行, 如果当前存在事务, 则抛出异常.
- Propagation.NESTED : 如果当前存在事务, 则创建一个事务作为当前事务的嵌套事务来运行.如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED .
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;
}
}
- Spring中使用事务, 有两种方式: 编程式事务(手动操作)和声明式事务. 其中声明式事务使用较多,在方法上添加 @Transactional 就可以实现了
- 通过 @Transactional(isolation = Isolation.SERIALIZABLE) 设置事务的隔离级别. Spring 中的事务隔离级别有 5 种
- 通过 @Transactional(propagation = Propagation.REQUIRED) 设置事务的传播机制, Spring 中的事务传播级别有 7 种, 重点关注 REQUIRED (默认值) 和 REQUIRES_NEW
原文地址:https://blog.csdn.net/weixin_73888239/article/details/144727995
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!