“简单的事重复做,你就是专家;重复的事用心做,你就是赢家。”
在开始讲解SpringBoot事务之前,我们先来整体回顾下事务的概念及特性,便于我们了解SpringBoot是如何解决事务相关问题的,另外这部分也是面试必考内容。
需要学习交流的可入群,大厂10年+大佬持续分享优质技术内容!带你深入技术腹地,学习硬核技术!
1、事务的概念
从专业技术角度讲,事务管理是对于一系列数据库操作进行管理,一个事务包含一个或多个SQL语句,而所有的SQL就组成了一个事务。那在逻辑层面,这些SQL执行要么全部成功,要么全部失败。比如,购物商城用户下单到付款完成可以看作是一个事务。作为一个事务来讲,这个下单过程要么全部成功,要么全部失败。如果部分失败,你可以想象一下会有什么问题?如果不加事务,又会造成哪些影响呢?
2、关于事务的特性
原子性(A), 主要针对的是事务的整体性,不能对事务进行分割,要么全部成功,要么全部失败。比如部分SQL成功,部分SQL失败这时不允许的;
一致性(C),主要针对的是数据层面,在事务执行前后需要保证数据一致性。比如,A给B转账1000元,那A账户需要扣掉1000元,而B账户需要增加1000元,不能出现A扣减成功而B则没有增加成功;
隔离性(I),主要解决的是多用户并发访问的问题,也就是说,一个用户的事务不能被其他用户的事务所终断,多个事务之间是互相隔离的。比如A账户给B账户赚钱,C账户给D账户赚钱,这两个事务是互不影响的;
持久性(D),主要针对的是事务提交的一种数据状态,一旦一个事务被提交,那么它的数据库中数据的改变就是永久性的,即使数据库发生故障也不会受影响。
以上,我们都是偏概念性的内容,那接下来讲解SpringBoot事务管理源码的部分是事务最核心部分,无论工作还是面试,都属于高频知识点。大家一定要认真学习~~
针对以上事务的特点,那么SpringBoot(或Spring)做了哪些工作呢?(在看下面的代码刨析前,大家可以自己设想一下,如果让你设计一个事务管理器,你会如何设计呢?)
3、SpringBoot事务管理源码分析
3.1 事务管理AOP机制
我们知道Spring有AOP机制,那Spring的事务管理就是基于AOP原理实现的,主要思想就是对业务调用方法前后进行拦截,在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务,如下图所示。
3.2 事务生命周期
事务生命周期6大核心步骤:获取事务(doGetTransaction )、开启事务(doBegin )、暂停事务(doSuspend )、恢复事务(doResume )、提交事务(doCommit )和回滚事务(doRollback )。
以下内容用DataSourceTransactionManager源码来说明这6部分内容的实现(其他几种实现方式类似)。
3.3 事务核心源码分析
在SpringBoot中,最核心类有:TransactionInterceptor(事务拦截器)、TransactionManager(事务管理器标签接口)、PlatformTransactionManager(具体事务管理器实现接口)、 TransactionInfo(事务管理信息对象存储) 和 TransactionStatus(当前事务管理状态记录)这5个核心类。
接下来,我们进行逐一分析。
1) 事务拦截器 TransactionInterceptor(线程安全)
该类的主要作用:拦截事务方法、在事务方法前进行解析事务存储对象、在方法执行后做提交回滚等处理。
我们知道,事务管理是基于AOP实现的,而AOP是需要实现MethodIntercptor(方法拦截器)接口的。那么事务管理对应的就是TransactionInterceptor也如此,在实际方法调用前会调用这个intercepter做额外的前置事务处理,我们可以大概看下TransactionInterceptor源代码。
/**
*
* <p>TransactionInterceptors are thread-safe.(线程安全)
*
*/
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
// 省略...
/**
* AOP核心方法
*/
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
// 这个方法主要完成的内容:
// 1) 设置 TransactionAttribute 事务属性(后面会讲)
// 2) 设置 TransactionManager 事务管理器(后面会讲)
// 3) 设置 TransactionStatus 事务状态管理 (后面会讲)
// 4) 解析包含事务的业务方法的请求参数、返回类型、隔离级别、异常处理机制等
// 5) 设置 TransactionInfo 事务信息管理对象(后面会讲)
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
@Override
public Object getTarget() {
return invocation.getThis();
}
@Override
public Object[] getArguments() {
return invocation.getArguments();
}
});
}
// 序列化相关内容(省略...)
}
简单看下invokeWithinTransaction方法, 这个方法是TransactionInterceptor 对应派生类TransactionAspectSupport中的方法。
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 获取TransactionAttribute和TransactionManager。前面会提前做一些事务判断逻辑:比如是否为一个事务方法。
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr);
// ...
// transactionSupportCache为ConcurrentMap,是线程安全的,主要用于存储当前声明式事务缓存。便于后面流程使用。
ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
Class<?> reactiveType =
(isSuspendingFunction ? (hasSuspendingFlowReturnType ? Flux.class : Mono.class) : method.getReturnType());
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(reactiveType);
if (adapter == null) {
throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
method.getReturnType());
}
return new ReactiveTransactionSupport(adapter);
});
// ...
}
// 事务管理器实现处理
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
// 该部分内容主要针对的是事务执行异常后,进行的一系列异常流程处理。可自行查看
Object result;
final ThrowableHolder throwableHolder = new ThrowableHolder();
//...
}
}
从源码中,我们大致了解到Spring采用的是声明式事务(即通过AOP的形式告诉执行器这个是事务),而且主要拦截在TransactionAspectSupport这个派生类中,这个类与Spring底层事务API进行了集成管理。
2) 事务管理器TransactionManager
该类的主要作用:事务管理标记接口,告诉代码解析器这个是一个事务方法,而不是普通的方法。
我们从TransactionManager源代码可以看到,这个类仅仅是一个空的接口定义,是不是很奇怪?这个属于Spring事务管理器实现类的标记接口, 从注解来看就是要告诉代码解析器这个是一个事务方法,而不是普通的方法。比如我们常见的Serializable 接口为,如果一个类实现了这个接口,说明它可以被序列化。因此,我们实际上通过Serializable这个接口,给该类标记了“可被序列化”的元数据,打上了“可被序列化”的标签。这也是标记/标签接口名字的由来。
关于标记接口,我在这里多解释下这种设计思路。
Marker Interface标记接口有时也叫标签接口(Tag interface),即接口不包含任何方法。
标记接口是计算机科学中的一种设计思路。编程语言本身不支持为类维护元数据。而标记接口则弥补了这个功能上的缺失:一个类实现某个没有任何方法的标记接口,实际上标记接口从某种意义上说就成为了这个类的元数据之一。运行时,通过编程语言的反射机制,我们就可以在代码里拿到这种元数据。
其中,针对这个接口的实现事务管理器如下,
很多人可能会问,之前的HibernateTransactionManager、JpaTransactionManager等管理器怎么没有了呢?这个主要是因为这些比较传统的持久层框架在很多主流开发中慢慢逐渐被淘汰了或者使用不是那么高频,除非是早些年的老项目才会存在这种API。所以对这种不常用的API在Spring5.x中进行了淘汰(如Spring3.0+版本只支持Hibernate 3.2+版本)。
我这里主要使用的是Spring5.x版本,以下是目前Spring5.x中保留的事务管理器。
1)DataSourceTransactionManager
数据源事务管理器,提供对单个javax.sql.DataSource事务管理,用于Spring JDBC抽象框架、iBATIS或MyBatis框架的事务管理;
2)JtaTransactionManager
提供对分布式事务管理的支持,并将事务管理委托给Java EE应用服务器事务管理器;
3)WebSphereUowTransactionManager
Spring提供的对WebSphere 6.0+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持;
4)WebLogicJtaTransactionManager
Spring提供的对WebLogic 8.1+应用服务器事务管理器的适配器,此适配器用于对应用服务器提供的高级事务的支持。
3)PlatformTransactionManager
该类的主要作用:对事务具体实现类的统一管理,包括事务的提交、回滚等操作。
PlatformTransactionManager 实现了TransactionManager,定义了3个基本接口方法。(获取当前事务状态/提交 /回滚). Spring进行了统一的抽象,形成了PlatformTransactionManager事务管理器接口,事务的提交、回滚等操作全部交给它来实现。
public interface PlatformTransactionManager extends TransactionManager {
// 获取事务
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
// 提交事务
void commit(TransactionStatus status) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
而AbstractPlatformTransactionManage 实现了PlatformTransactionManager接口,是一个抽象的基本类,实现了Spring标准的事务管理流程,以DataSourceTransactionManager为例,大致流程如下:
① 确定是否已经有存在的事务
// 是否存在事务
protected boolean isExistingTransaction(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject)transaction;
return txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive();
}
② 运用合适的事务隔离策略
protected void doBegin(Object transaction, TransactionDefinition definition) {
// 省略...
// 设置隔离级别
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
}
③ 暂停或者恢复事务
// 暂停事务
protected Object doSuspend(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject)transaction;
txObject.setConnectionHolder((ConnectionHolder)null);
return TransactionSynchronizationManager.unbindResource(this.obtainDataSource());
}
// 恢复事务
protected void doResume(@Nullable Object transaction, Object suspendedResources) {
TransactionSynchronizationManager.bindResource(this.obtainDataSource(), suspendedResources);
}
④ 提交事务时检查是否有只能回滚标记
// 提交事务
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject)status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
this.logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
} catch (SQLException var5) {
throw this.translateException("JDBC commit", var5);
}
}
// 回滚事务
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject)status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
this.logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
con.rollback();
} catch (SQLException var5) {
throw this.translateException("JDBC rollback", var5);
}
}
以上就是整个事务的执行过程,下面来看事务时如何进行信息管理的。
4) 事务信息 TransactionInfo
该类的主要作用:持有事务管理器,事务配置的属性,连接点,以及事务状态信息等。
TransactionInfo 主要是持有事务的状态,以及上一个TransactionInfo 的一个引用,并与当前线程进行绑定。主要是为了保证当前请求持有的是自己的事务对象,根据自己的事务状态决定事务的提交与否。TransactionInfo 是抽象类TransactionAspectSupport的一个内部类。
protected static final class TransactionInfo {
// 对应事务管理器的实现,比如DataSourceTransactionManager、JdbcTransactionManager等
@Nullable
private final PlatformTransactionManager transactionManager;
// 存储事务所需属性,比如回滚信息、隔离级别、超时设置、是否只读等
@Nullable
private final TransactionAttribute transactionAttribute;
// 记录每次触发与数据库操作相关的位置,方便后续做提交操作
private final String joinpointIdentification;
// 事务状态存储对象,如是否有保存点、是否回滚、是否是新的事务、事务是否完成等
@Nullable
private TransactionStatus transactionStatus;
// 对旧事物的信息存储,比如事务嵌套事务这种场景
@Nullable
private TransactionInfo oldTransactionInfo;
// 省略...
}
说明:如果想要使用事务的回滚相关操作,继承了AbstractTransactionStatus类的子类必须提供实现,否则在使用事务的回滚点相关操作的时候会抛出异常。
该类完成的主要功能包含:
① 获取当前事务 -> getTransaction()
② 是否有激活的事务 -> hasTransaction()
③ 该事务是否是新事务 -> isNewTransaction()
④ 事务是否只读 -> isReadOnly()
⑤ 返回已为此事务挂起的资源的持有者(如果有) -> getSuspendedResources()
5)事务状态TransactionStatus
该类的主要作用:描述当前事务的状态,比如:是否有事务,是否是新事物,事务是否只读和回滚点相关操作等。这些相关的属性在后面会影响事务的提交。
通过源码分析,我们发现TransactionStatus接口的实现类抽象类AbstractTransactionStatus类主要完成以下功能,主要是针对回滚点相关的一些操作。
① 创建回滚点 -> createAndHoldSavepoint()
② 设置回滚点 -> setSavepoint(@Nullable Object savepoint)
③ 获取回滚点 -> getSavepoint()
④ 判断是否有回滚点 -> getSavepoint()
⑤ 释放回滚点 -> releaseHeldSavepoint()
⑥ 回滚到回滚点 -> rollbackToHeldSavepoint()
注意:在AbstractTransactionStatus中创建回滚点是需要子类去实现
getSavepointManager()
方法,默认该方法会抛出异常NestedTransactionNotSupportedException("This transaction does not support savepoints")
4、SpringBoot事务实战演示
在SpringBoot中,使用事务需要做两步操作:
1)在启动类中使用注解 @EnableTransactionManagement 开启事务支持
2)在访问数据库的Service方法上添加注解 @Transactional 即可.(注意,方法必须为public)
下面通过SpringBoot +MyBatis实现对数据库商品表的更新操作,在service层的方法中构建异常,查看事务是否生效;我们依然基于前面的项目: SpringBootCase. 我们接着前面创建的TProductController类和TProductService类进行事务代码演示。
具体步骤如下:
1) 开启事务
package com.xintu.demo;
import com.xintu.demo.config.XinTuConfigInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@EnableTransactionManagement //开启事务
@RestController
@SpringBootApplication
public class SpringbootApplication {
@Autowired
private XinTuConfigInfo configInfo; //测试@ConfigurationProperties
@Value("${test.site}")
private String site;
@Value("${test.user}")
private String user;
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
@GetMapping("/hello")
public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
return String.format("欢迎 %s 来到<a href=\"http://www.35xintu.com\">35新途</a>!", name);
}
@GetMapping("/value")
public String testValue() { //测试 @Value 注解
return String.format("欢迎 %s 来到<a href=\"http://www.35xintu.com\">%s</a>!" , user,site);
}
@GetMapping("/config")
public String config() { //测试 @ConfigurationProperties 注解
return String.format("欢迎 %s 来到<a href=\"http://www.35xintu.com\">%s</a>!" , configInfo.getUser(),configInfo.getSite());
}
}
2)在TProductService类中添加事务方法
package com.xintu.demo.service;
import com.xintu.demo.entity.TProduct;
import com.xintu.demo.entity.TProductExample;
import com.xintu.demo.mapper.TProductMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* @author XinTu
* @classname TProductService
* @description TODO
* @date 2023年04月29日 21:19
*/
@Service
public class TProductService {
@Autowired
private TProductMapper mapper;
/**
* 查询测试
*
* @return
*/
public List<TProduct> queryList() {
TProductExample example = new TProductExample();
TProductExample.Criteria criteria = example.createCriteria();
criteria.andCategoryIdEqualTo(1);
return mapper.selectByExample(example);
}
@Transactional
public Boolean update(TProduct record) {
TProductExample example = new TProductExample();
example.createCriteria().andCategoryIdEqualTo(1);
Boolean flag = mapper.updateByExample(record, example) > 0 ? Boolean.TRUE : Boolean.FALSE;
/** 构造一个除数为0的常见异常,测试事务是否起作用(操作数据库在这个异常之前) **/
int a = 1/0;
return flag;
}
}
这里我们故意构造了一个异常逻辑,来验证在异常情况下事务事务会生效。
3)TProductController实现
package com.xintu.demo.controller;
import com.xintu.demo.entity.TProduct;
import com.xintu.demo.mapper.TProductMapper;
import com.xintu.demo.service.TProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author XinTu
* @classname TProductController
* @description TODO
* @date 2023年04月29日 21:12
*/
@RestController
public class TProductController {
@Autowired
private TProductService service;
/**
* 查询测试
* @return
*/
@GetMapping(value = "/queryList")
public List<TProduct> queryList() {
return service.queryList();
}
@GetMapping(value = "/updateProduct")
public Boolean updateProduct(){
TProduct record = new TProduct();
record.setId(13);
record.setTitle("测试SpringBoot事务");
return service.update(record);
}
}
4) 启动SpringbootApplication,通过浏览器访问进行测试.
① 事务生效场景验证
浏览器执行,http://localhost:8888/springbootcase/updateProduct
控制台
数据库表数据依然没变。
通过以上结果,说明事务起作用了。
② 事务不生效场景验证
注释掉 TProductService 上的@Transactional进行测试。
后台依然报错,但数据库的数据以及被更新了。
以上就是整个SpringBoot事务管理核心内容,最后给大家留一个面试高频题:如果需要做分布式事务,该如何处理呢?
这个问题我会在后面的SpringBoot扩展篇中进行分析,大家可以先提前思考。