目录
1.3 Spring 声明式事务@Transactional 注解(重要)
回顾 MySQL 之前学过的事务:
事务:将⼀组操作封装成⼀个执行单元(封装到⼀起),要么全部成功,要么全部失败
为什么要用事务?
比如转账分为两个操作:
- 第⼀步操作:A 账户 -100 元
-
第⼆步操作:B 账户 +100 元
如果没有事务,第⼀步执行成功了,第⼆步执行失败了,那么 A 账户的 100 元就“⼈间蒸发”了。⽽如果使⽤事务就可以解决这个问题,让这⼀组操作要么⼀起成功,要么⼀起失败
1.Spring 中的事务
Spring 中的事务分为两类:
-
编程式事务(手动写代码操作事务)
-
声明式事务(利用注解自动开启和提交事务)
在这里,我们要使用的就是声明式事务,一个注解即可搞定,编程式事务会存在代码写错误的时候,因此主要学习声明式事务,而编程式事务只需要了解即可
1.1 回顾 MySQL 中的事务
1️⃣开启事务
start transaction;
2️⃣提交事务
commit;
3️⃣回滚事务
rollback;
1.2 Spring 编程式事务(了解)
编程式事务只需要了解即可
1️⃣开启事务2️⃣提交事务3️⃣回滚事务
SpringBoot 内置了两个对象,DataSourceTransactionManager 用来获取事务(开启事务)、提交或回滚事务的,而 TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得⼀个事务 TransactionStatus
@RestController
public class UserController {
@Resource
private UserService userService;
// JDBC 事务管理器
@Resource //自动注入
private DataSourceTransactionManager dataSourceTransactionManager;
// 定义事务属性
@Resource
private TransactionDefinition transactionDefinition;
@RequestMapping("/sava")
public Object save(User user) {
// 开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(transactionDefinition);
// 插⼊数据库
int result = userService.save(user);
// 提交事务
dataSourceTransactionManager.commit(transactionStatus);
// 回滚事务
// dataSourceTransactionManager.rollback(transactionStatus);
return result;
}
}
1.3 Spring 声明式事务@Transactional 注解(重要)
声明式事务需要添加 @Transactional 注解,无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务
设置常用配置 application.properties:
#设置数据库的相关连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mycnblog2023?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 设置 MyBatis XML 存放路径和命名格式
mybatis.mapper-locations=classpath:mybatis/*Mapper.xml
# 将数据库中的下划线换成驼峰,比如 user_name -> username
mybatis-plus.configuration.map-underscore-to-camel-case=true
# 配置打印 MyBatis 执行的 SQL
mybatis.configuration.map-underscore-to-camel-case=true
# 配置打印 MyBatis 执行的 SQL
logging.level.com.example.demo=debug
创建实体类 Userinfo :
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;
}
添加 Mapper(UserMapper 类):
package com.example.demo.mapper;
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);
}
创建 UserService 类:
package com.example.demo.service;
import com.example.demo.mapper.UserMapper;
import com.example.demo.model.Userinfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
//注入 Mapper
@Autowired
private UserMapper userMapper;
public int add(Userinfo userinfo) {
return userMapper.add(userinfo);
}
}
创建 UserController 类:
package com.example.demo.controller;
import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/add")
@Transactional
public int add() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("张三");
userinfo.setPassword("123");
//1.调用 service 执行添加
int result = userService.add(userinfo);
System.out.println("result:" + result);
int num = 10/0;//异常
//2.将结果给前端
return 0;
}
}
访问 localhost:8080/user/add
- 我们可以看到已经把 张三和123 添加到数据库中,但是发生了异常
- 这个时候我们看数据库中是否发生回滚操作:如果回滚则和预期一样;如果不回滚则 @Transactional 注解有问题
此时数据库中没有张三,则说明发生回滚操作
1.3.1 @Transactional 作用范围
@Transactional 可以用来修饰方法或类:
- 修饰方法时:需要注意只能应用到 public 方法上,否则不生效。推荐此种用法
- 修饰类时:表明该注解对该类中所有的 public 方法都生效
1.3.2 异常捕获事务不回滚
期望只要发生异常,就进行回滚操作,接下来我们捕获异常
package com.example.demo.controller;
import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/add")
@Transactional
public int add() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("张三");
userinfo.setPassword("123");
//1.调用 service 执行添加
int result = userService.add(userinfo);
System.out.println("result:" + result);
try {
int num = 10 / 0;
}catch (Exception e) {
}
//2.将结果给前端
return result;
}
}
这个时候我们看到没有发生回滚操作:@Transactional 在异常被捕获的情况下,不会进行事务自动回滚
- @Transactional 是一个 AOP,AOP执行是环绕通知:在执行方法之前开启事务,如果没有异常就提交事务;出现异常进行事务回滚
- 异常被捕获,代理对象检测不出异常,不会进行事务回滚
1.3.3 解决异常捕获事务不回滚
有两种解决方案:
1️⃣将异常继续抛出去(代理对象就能够感知到异常,也就能自动的回滚事务)
package com.example.demo.controller;
import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/add")
@Transactional
public int add() {
Userinfo userinfo = new Userinfo();
userinfo.setUsername("小温");
userinfo.setPassword("123");
//1.调用 service 执行添加
int result = userService.add(userinfo);
System.out.println("result:" + result);
try {
int num = 10 / 0;
}catch (Exception e) {
throw e;
}
//2.将结果给前端
return result;
}
}
这个时候我们看到没有 小温 这个用户,则发生了回滚操作
2️⃣使用代码手动回滚事务(使用 TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法 setRollbackOnly 就可以实现回滚了)
try {
int num = 10 / 0;
}catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
1.4 @Transactional 工作原理
@Transactional 是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会使用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。
@Transactional 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。如果中途遇到的异常,则回滚事务。
具体细节:
bean进行实例化的时候, 在方法中是否有 @Transactional ,如果有会在这个类中生成一个代理类,代理类去访问方法(@Transactional)会开启事务,并且执行目标方法而后提交事务;如果这个类的方法中没有 @Transactional,代理类会直接执行目标方法不会开启事务
1.5 Spring 事务隔离级别
- Isolation.DEFAULT:以连接的数据库的事务隔离级别为主
- Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读
- Isolation.READ_COMMITTED读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读
- Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)
- lsolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低
2.Spring 事务传播机制
Spring 事务传播机制:规定多个事务在相互调用时,事务的执行行为
换句话说:事务隔离级别是保证多个并发事务执行的可控性的(稳定性的),而事务传播机制是保证⼀个事务在多个调用间的可控性的(稳定性的)
对于事务的隔离界别解决的是多个事务同时调用数据库的问题
事务传播机制解决的是⼀个事务在多个节点(方法)中传递的问题
2.1 传播机制
Spring 事务传播机制包含以下 7 种:
1️⃣Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建⼀个新的事务。(支持当前事务)
2️⃣Propagation.SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。(支持当前事务)
3️⃣Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常(支持当前事务)
4️⃣Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰(不支持当前事务)
5️⃣Propagation.NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起(不支持当前事务)
6️⃣Propagation.NEVER:以非事务方式运行,如果当前存在事务,则抛出异常(不支持当前事务)
7️⃣Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED(嵌套事务)