目录
一、Spring 事务的使用
1.1、编程式事务(了解即可)
Spring 编程式事务的使用主要有 3 个步骤:
- 开启事务(获取事务):通过 Spring Boot 中内置的 DataSourceTransactionManager 的 getTransaction 方法,并搭配内置的 TransactionDefinition 实例作为方法的参数,来获取事务(此操作同时也会开启事务)。
- 提交事务:DataSourceTransactionManager 创建出实例后,使用它的 commit 方法(参数是 getTransaction 方法的返回值,也就是 TransactionStatus,它的本质就是一个事务),就可以完成提交事务。
- 回滚事务:通过 DataSourceTransactionManager 的 rollback 方法(参数就是 事务)进行事务的回滚。
具体的,如果我的业务逻辑是向数据库插入一条数据,如下代码示例:
import com.example.demo.entity.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//事务管理
@Autowired
private DataSourceTransactionManager transactionManager;
//事务属性设置(不设置有默认属性)
@Autowired
private TransactionDefinition transactionDefinition;
@RequestMapping("/add")
public int insertUserInfo(UserInfo userInfo) {
//检验非空
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
// 1.开启事务
TransactionStatus transactionStatus =
transactionManager.getTransaction(transactionDefinition);
// 2.执行业务逻辑
int result = userService.insertUserInfo(userInfo);
System.out.println("添加:" + result + "个用户~");
// // 3.提交事务
// transactionManager.commit(transactionStatus);
// 4.回滚事务(根据情况而定)
transactionManager.rollback(transactionStatus);
return result;
}
}
Ps:一个事务若已经提交,则不可以进行回滚,否则会报错:“不可多次提交或回滚事务”。
1.2、注解实现声明式事务
1.2.1、@Transactional 注解的使用
在 Spring 提供了 @Transactional 注解实现事务,特点如下:
- 可以添加在类上或方法上。
- 添加在方法上(方法必须是 public,否则不生效):在方法执行前自动开启事务,方法执行完(没有任何异常)自动提交事务,但如果方法执行期间出现异常,将会自动回滚事务;添加在类上:对所有的 public 方法生效;
具体的如下代码:
@Transactional //声明式事务
@RequestMapping("/add")
public Integer add(UserInfo userInfo) {
//非空检验
if(userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) ||
!StringUtils.hasLength(userInfo.getPassword())) {
return 0;
}
int result = userService.insertUserInfo(userInfo);
System.out.println("添加:" + result + "个用户~");
return result;
}
1.2.2、参数说明
值得注意的是 isolation 的隔离级别:
读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串⾏化 (Serializable)。
1.2.3、声明式事务对异常的处理
@Transactional 在异常被捕获的情况下,不会进行事务的自动回滚,那么如果需求改变,需要在异常被捕获的情况下也进行回滚,该如何实现呢?
方法1:重新抛出异常(有点脱了裤子放屁的感觉),如下代码:
@Transactional //声明式事务
@RequestMapping("/add")
public Integer add(UserInfo userInfo) {
//插入一条信息到数据库
int result = userService.insertUserInfo(userInfo);
try {
int i = 1 / 0;
} catch(Exception e) {
System.out.println(e.getMessage());
throw e;
}
System.out.println("添加:" + result + "个用户~");
return result;
}
方法2:手动回滚事务,如下代码:
@Transactional //声明式事务
@RequestMapping("/add")
public Integer add(UserInfo userInfo) {
//插入一条信息到数据库
int result = userService.insertUserInfo(userInfo);
try {
int i = 1 / 0;
} catch(Exception e) {
System.out.println(e.getMessage());
//手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
System.out.println("添加:" + result + "个用户~");
return result;
}
Ps:TransactionAspectSupport表示事务的切面建议、currentTransactionStatus表示获取当前事务、setRollbackOnly表示进行回滚。
1.2.3、@Transational 的工作原理
@Transaction 是基于 AOP 实现的,而 AOP 又是使用动态代理实现的。若目标对象实现了接口,默认使用 JDK 的动态代理,若目标对象没有实现接口,则会使用 CGLIB 动态代理。
@Transational 在开始执行业务之前,通过代理先开启事务,在执行成功之后再提交事务。若中途遇到异常,则进行回滚事务。
二、Spring 事务的传播机制
2.1、事务传播机制是什么?
Spring 事务传播机制定义了多个包含了事务的方法,相互调用时,事务是如何在这些方法之间进行传递的。
例如这样一种情况:现在有方法一和方法二,方法一有事务,方法二没有事务,方法一会调用到方法二,那么当方法二中出现了异常,是否要进行回滚呢?事务的传播机制就是用来解决这种类似的问题~
2.2、事务的传播机制有什么作用
事务的传播机制是保证一个事务在多个调用方法间的可控性。
例如这样一种情况:现在有方法一和方法二,方法一有事务,方法二没有事务,方法一会调用到方法二,那么当方法二中出现了异常,是否要进行回滚呢?通过事务的传播机制,就会告诉你该如何进行处理~
2.3、事务的传播机制中有哪些?
从大的方向上看,主要分为以下三种~
2.3.1、支持当前调用链上的事务
- Propagation.REQUIRED(需要有):默认的事务隔离级别,表示如果当前调用链存在事务,则加入该事务;如果没有事务,则创建一个事务。
- Propagation.SUPPORTS(可以有):如果当前调用链存在事务,则加入该事务,如果没有事务,就以非事务方式执行。
- Propagation.MANDATORY(强制有):如果当前调用链存在事务,则加入该事务;如果当前没有事务,则抛出异常。
2.3.2、不支持当前调用链上的事务
- Propagation.REQUIRES_NEW:表示创建一个新的事务,如果当前调用链存在事务,则把当前调用链这个事务挂起,使用刚创建好的事务。也就是说不管调用链上是否开启事务,Propagation.REQUIRES_NEW 修饰的方法都会开启自己的事务,且开启的事务互相独立,互不干扰。
- Propagation.NOT_SUPPORTED:如果当前调用链存在事务,则把当前调用链的事务挂起,也就是说 Propagation.NOT_SUPPORTED 修饰的方法,以非事务的方式运行。
- Propagation.NEVER:以非事务的方式运行,并且若当前调用链存在事务,则抛出异常。
2.3.3、嵌套事务
Propagation.NESTED:表示当前调用链存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前调用链没有事务,则等价于 PROPAGATION_REQUIRED。
2.4、代码示例
这里我将用两个示例来说明(这两个如果你能拿捏住,其他的就没问题~),这里的调用链如下图:
2.4.1、支持当前事务(REQUIRED)示例
首先开始事务,使用 UserService 插入一条用户数据,然后再执行(故意制造错误)报错,最后观察执行结果,如下代码:
UserController如下:
import com.example.demo.entity.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Transactional(propagation = Propagation.REQUIRED)
@RequestMapping("/insert")
public Integer insertUserInfo(UserInfo userInfo) {
//插入用户数据
int result = userService.insertUserInfo(userInfo);
System.out.println("添加:" + result + "个用户~");
return result;
}
}
LogService如下:
import com.example.demo.entity.UserInfo;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public int insertUserInfo(UserInfo userInfo) {
int result = userMapper.insertUserInfo(userInfo);
int e = 1 / 0;
return result;
}
}
输入 url,插入用户 “zhangsan” 运行结果如下,如下图:
可以观察到成功插入数据后引发的算数异常,按照我们设置的事务,是会进行回滚的,接下来观察数据库验证我们的结果:
结果分析:由于我们设置的事务是REQUIRED,它表示调用链上若存在事务,则加入到事务中,若没有就会为这给调用链创建一个事务,因此在 UserService 方法上即使没有事务,也会加入到当前调用链创建好的事务当中,即使引发了异常,也会进行数据的回滚~
2.4.2、嵌套事务示例
首先开始事务,使用 UserService 的 insertUserInfo 插入一条用户数据,然后再执行 insert 方法再插入一条用户数据(这里故意引发异常),插入完后报错,最后观察执行结果,如下代码:
UserController如下:
import com.example.demo.entity.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Transactional(propagation = Propagation.REQUIRED)
@RequestMapping("/insert")
public void insertUserInfo(UserInfo userInfo) {
//插入用户数据
int result1 = userService.insertUserInfo(userInfo);
int result2 = userService.insert();
}
}
UserService如下:
import com.example.demo.entity.UserInfo;
import com.example.demo.mapper.UserMapper;
import org.apache.catalina.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public int insertUserInfo(UserInfo userInfo) {
int result = userMapper.insertUserInfo(userInfo);
return result;
}
@Transactional(propagation = Propagation.NESTED)
public int insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("lisi");
userInfo.setPassword("123");
int result = userMapper.insertUserInfo(userInfo);
//制造异常
int a = 1 / 0;
return result;
}
}
运行结果如下:
数据库结果如下:
结果分析:在 UserController 中开启事务,UserService 中调用 insertUserInfo 插入一条数据,接着调用 insert 方法(使用 NESTED 嵌套上一个调用类的事务)插入一条数据,插入完后引发异常,于是进行回滚当前事务,因此第二条数据添加失败,又因为是嵌套事务,所以第二条数据添加失败进行回滚之后,会继续向上找调用他的方法和事务,再次进行回滚,因此第一条用户信息也添加失败,最终没用向用户表中添加任何数据~、
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/130356.html