目录
3.1.1. PlatformTransactionManager 接口
3.1.2. TransactionDefinition 接口
3.2.3. TransactionTemplate 的实现原理
一. 前言
Spring 事务是指在 Spring 框架中对于数据库操作的一种支持,它通过对一组数据库操作进行整体控制来保证数据的一致性和完整性。Spring 事务可以保证在一组数据库操作执行时,要么所有操作都执行成功,要么所有操作都回滚到之前的状态,从而避免了数据不一致的情况。
二. Spring 中的事务
2.1. 事务的特性和传播性
在讲 Spring 事务之前,我们可以先回顾下事务的一些基本认识,请参见作者前面写的文章:《分布式理论之事务 - 酸(ACID)碱(BASE)理论》中的 ACID 这一部分即可、《MySql 事务隔离级别详解》、《Spring 事务七大传播机制 —— REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED》。
2.2. Spring 事务管理方式
Spring 为事务提供了完整的支持,使用 Spring 来管理事务有以下好处:
- Spring 为不同的编程模型如 JTA、JDBC、Hibernate、JPA 等,提供了统一的事务 API。
- 支持声明式事务,更容易的管理事务。
- 相比于 JTA,Spring 为编程式事务提供更加简单的 API。
Spring 事务管理有两种方式:编程式事务管理、声明式事务管理。
声明式事务和编程式事务对比如下:
声明式事务 | 编程式事务 | |
---|---|---|
使用方法 | @Transactional | TransactionTemplate |
优点 | 使用简单 | 可以控制事务提交的开启和提交时机,能够更小粒度的控制事务的范围,也更加直观 |
缺点 | 使用不当事务可能失效;多个事务性操作可能导致事务无法正常提交,导致长事务 | 需要硬编码来控制事务 |
适用场景 | 当事务操作的数量很少 | 同一个方法中,事务操作比较多 |
在合适的场景使用合适的方式非常重要,在一些场景下,当对事务操作非常频繁,特别是在递归、外部通讯等耗时的场景中使用事务,很有可能就会引发长事务,那么应该考虑将非事务的部分放在前面执行,最后在写入数据环节时再开启事务。
2.3. 声明式事务
声明式事务其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
使用 @Transactional 注解来管理事务非常简单,示例如下:
@Service
public class TransactionDemo {
@Transactional
public void declarativeUpdate() {
updateOperation1();
updateOperation2();
}
}
这样的写法相当于在进入 declarativeUpdate() 方法前,使用 BEGIN 开启了事务,在执行完方法后,使用 COMMIT 提交事务。
也可以将 @Transactional 注解放在类上面,表示类中所有的 public 方法都开启了事务:
@Service
@Transactional
public class TransactionDemo {
public void declarativeUpdate() {
updateOperation1();
updateOperation2();
}
// 其他public方法...
}
2.4. 编程式事务
所谓编程式事务指的是通过编码方式实现事务,允许用户在代码中精确定义事务的边界。
Spring 框架提供了两种编程式事务方式:
- 使用 TransactionTemplate。
- 使用 PlatformTransactionManager。
Spring 团队通常建议使用 TransactionTemplate 进行程序化事务管理。TransactionTemplate 采用与其他 Spring 模板相同的方法,它使用一种回调方法,使应用程序代码可以处理获取和释放事务资源,让开发人员更加专注于业务逻辑。
相比之下,使用编程式事务要略微复杂一些:
@Service
public class TransactionDemo {
@Autowired
private TransactionTemplate transactionTemplate;
public void programmaticUpdate() {
// 这里也可以使用Lambda表达式
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});
}
}
如果你的程序中需要针对某种特定异常有特殊操作,那么可以使用 try...catch,切记此时需要调用 TransactionStatus 的 setRollbackOnly() 方法:
package com.example.transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
@Service
@Slf4j
public class TransactionDemo {
@Autowired
private TransactionTemplate transactionTemplate;
public void programmaticUpdate() {
// 事务执行
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
// 指定异常类型
} catch (Exception ex) {
log.info("exception happen ....");
status.setRollbackOnly();
}
}
});
}
}
如果需要获取事务的执行结果,那么可以使用:
package com.example.transaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class TransactionDemo {
@Autowired
private TransactionTemplate transactionTemplate;
public void programmaticUpdate() {
String result = transactionTemplate.execute(new TransactionCallback<String>() {
@Override
public String doInTransaction(TransactionStatus status) {
updateOperation1();
updateOperation2();
// 返回执行的结果
return "ok";
}
});
}
}
三. Spring 事务的实现原理
了解他们的基本概念和使用方法,接下来我们将一起分析 Spring 事务的实现原理。在正式分析实现过程前,我们首先需要了解一些比较核心 API,这将帮助抽丝剥茧的理解 Spring 事务的实现原理。
3.1. Spring 事务的核心 API
事务操作相关的 API:
- Spring 事务 @Enanle 模块驱动:@EnableTranSactionManagement。
- Spring 事务注解:@Transactional。
- Spring 事务事件监听器:@TransactionalEventListener。
事务抽象相关的 API:
- Spring 平台事务管理器:PlatformTransactionManager。
- Spring 事务定义:TransactionDefinition。
- Spring 事务状态:TransactionStatus。
- Spring 事务代理配置:ProxyTransactionManagementConfiguration。
AOP 相关的 API:
- Spring 事务 PointcutAdvisor 实现:BeanFactoryTransactionAttrubuteSourceAdvisor。
- Spring 事务 MethodInterceptor 实现:TransactionInterceptor。
- Spring 事务属性源:TransactionAttributeSource。
其中,PlatformTransactionManager、TransactionDefinition、TransactionStatus 最为重要,他们之间的关系如下:
3.1.1. PlatformTransactionManager 接口
PlatformTransactionManager 是 Spring 对于事务模型的抽象,它代表事务的整体执行过程。通常事务都应用在关系型数据库中,Spring 对于事务的读写的模型做了更高层次的抽象,使得其可以应用在任何需要数据一致性的场景,比如 JMX 等,Spring 将这些场景统一的抽象为 commit() 和 rollback() 两个核心方法。
PlatformTransactionManager 的核心方法:
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface PlatformTransactionManager {
// 获取事务的执行状态
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
// 提交事务
void commit(TransactionStatus var1) throws TransactionException;
// 回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
这里需要注意的是,如果有 @Transactional 注解的方法,如果他们对应的传播行为不同,那么其对应的 TransactionDefinition 也是不同的,这也就是说 getTransaction() 这个方法获取到的并不是物理事务,而是某个具体方法的逻辑事务,同理,commit() 和 rollback() 也是对应的这个逻辑事务。
3.1.2. TransactionDefinition 接口
TransactionDefinition 是事务的元信息定义,类似于 Spring IoC 中 BeanDefinition。实际上,Spring 中事务的定义参考了 EJB 中对于事务的定义,TransactionDefinition 的核心方法有:
package org.springframework.transaction;
import org.springframework.lang.Nullable;
public interface TransactionDefinition {
// 传播行为的枚举值...
// 返回事务的传播行为,默认值为 REQUIRED。
int getPropagationBehavior();
//返回事务的隔离级别,默认值是 DEFAULT
int getIsolationLevel();
// 返回事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
int getTimeout();
// 返回是否为只读事务,默认值为 false
boolean isReadOnly();
@Nullable
String getName();
}
3.1.3. TransactionStatus 接口
事务分为逻辑事务和物理事务,逻辑事务是指代码中事务的操作;物理事务是通过数据库连接来获取相关的物理的连接以及相关的数据库的事务。TransactionStatus 是用来描述当前逻辑事务的执行情况,其核心方法及含义:
public interface TransactionStatus {
// 当前事务执行是否在新的事务
boolean isNewTransaction();
// 是否有恢复点(小范围的回滚)
boolean hasSavepoint();
// 设置当前事务为只回滚
void setRollbackOnly();
// 当前事务是否为只回滚
boolean isRollbackOnly();
// 当前事务是否已完成
boolean isCompleted;
}
从 Spring5.2 开始,对这个接口进行了拆分,将部分方法放置在了 TransactionExecution 中:
/**
* @since 5.2
*/
public interface TransactionExecution {
boolean isNewTransaction();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
}
3.2. Spring 事务实现过程分析
3.2.1. @Transactional 与 AOP
@Transactional 基于 Spring AOP 实现,其执行流程大致如下:
针对于 @Transactional 的实现,几个关键点是:
- Spring 中是如何定义 Interceptor 的。也就是说,哪些方法会被拦截。
- 拦截后,代理类中 invoke() 方法是如何执行的。
3.2.2. @Transactional 的实现原理
首先我们来看看 @Transactional 的定义:
// 可以作用在类上面,也可以作用在方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
// 事务管理器(PlatformTransactionManager),和transactionManager互为别名
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
// 事务的传播行为
Propagation propagation() default Propagation.REQUIRED;
// 事务的超时时间,默认是-1,表示永远不会超时
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
// 是否只读事务,如果是只读事务,Spring会在执行事务的时候会做相应的优化
boolean readOnly() default false;
// 回滚的异常类型
Class<? extends Throwable>[] rollbackFor() default {};
}
如果需要在 SpringBoot 中要使用 @Transactional,我们需要添加@EnableTransactionManagement 注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 自动装配类
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
// ...
}
进入这个注解对应的自动装配类:
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
@Override
protected String[] selectImports(AdviceMode adviceMode) {
// 支持动态代理和ASPECTJ两种AOP的实现方法,默认是代理的方式
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {determineTransactionAspectClass()};
default:
return null;
}
}
}
进入代理模式的配置类 ProxyTransactionManagementConfiguration,这是一个标准的SpringBoot 的配置类:
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
// 获取AOP的注解
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
TransactionAttributeSource 前面我们介绍过,它是 Spring 事务属性源 ,进入AnnotationTransactionAttributeSource 的构造方法,我们可以看到:
public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
if (jta12Present || ejb3Present) {
this.annotationParsers = new LinkedHashSet<>(4);
this.annotationParsers.add(new SpringTransactionAnnotationParser());
if (jta12Present) {
this.annotationParsers.add(new JtaTransactionAnnotationParser());
}
if (ejb3Present) {
this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
}
}
else {
this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
}
}
这里会将一些注解解析类添加到 AnnotationTransactionAttributeSource 的 annotationParsers中,这里以默认的 SpringTransactionAnnotationParser 为例:
@Override
@Nullable
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
// 获取拥有@Transactional注解的类或方法
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
element, Transactional.class, false, false);
if (attributes != null) {
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
JtaTransactionAnnotationParser 和 Ejb3TransactionAnnotationParser 也是类似的实现,这说明Spring 不仅支持 org.springframework.transaction.annotation.Transactional 还支持javax.transaction.Transactional.class 和 javax.ejb.TransactionAttribute,在 Spring 统一的编程模型下,这三个注解都可以通用。
接下来我们查看执行的核心方法,即 TransactionInterceptor 的 invoke() 方法:
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// ...
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() 方法:
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr);
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 环绕通知
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();
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
try {
Object retVal = invocation.proceedWithInvocation();
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
return retVal;
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// A normal return value: will lead to a commit.
throwableHolder.throwable = ex;
return null;
}
}
finally {
cleanupTransactionInfo(txInfo);
}
});
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
catch (TransactionSystemException ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
ex2.initApplicationException(throwableHolder.throwable);
}
throw ex2;
}
catch (Throwable ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
}
throw ex2;
}
// Check result state: It might indicate a Throwable to rethrow.
if (throwableHolder.throwable != null) {
throw throwableHolder.throwable;
}
return result;
}
}
其中:
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
进入 commit() 方法:
@Override
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus, false);
return;
}
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}
processCommit(defStatus);
}
继续进入 processCommit() 方法:
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
boolean unexpectedRollback = false;
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
status.releaseHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
// 真正提交事务的方法
doCommit(status);
}
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
// can only be caused by doCommit
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException | Error ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
cleanupAfterCompletion(status);
}
}
以 DataSourceTransactionManager 中的 doCommit() 为例:
@Override
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
// 获取数据库连接
Connection con = txObject.getConnectionHolder().getConnection();
try {
// 提交事务
con.commit();
}
catch (SQLException ex) {
throw translateException("JDBC commit", ex);
}
}
可以看到,这里才是物理事务的提交。类似的,回滚也是获取数据库的连接,然后调用回滚的方法:
@Override
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
try {
// 回滚事务
con.rollback();
}
catch (SQLException ex) {
throw translateException("JDBC rollback", ex);
}
}
3.2.3. TransactionTemplate 的实现原理
有了 @Transactional 实现过程的基础,TransactionTemplate 的实现就比较容易理解了,TransactionTemplate 本质上是一种模板方法的设计模式的应用:
@SuppressWarnings("serial")
public class TransactionTemplate extends DefaultTransactionDefinition
implements TransactionOperations, InitializingBean {
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
//...
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
// 模板方法
result = action.doInTransaction(status);
}
catch (RuntimeException | Error ex) {
// 发生异常即回滚
rollbackOnException(status, ex);
throw ex;
}
catch (Throwable ex) {
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
// 没有发生异常就提交事务
this.transactionManager.commit(status);
return result;
}
}
这一点从 TransactionCallbackWithoutResult 这个抽象类中就可以看出:
public abstract class TransactionCallbackWithoutResult implements TransactionCallback<Object> {
@Override
@Nullable
public final Object doInTransaction(TransactionStatus status) {
doInTransactionWithoutResult(status);
return null;
}
// 方法的实现就是我们需要执行的业务代码
protected abstract void doInTransactionWithoutResult(TransactionStatus status);
}
四. Spring 事务失效的情况
4.1. 方法内部调用
在实际开发中,如果碰到长事务,一个自然的想法,就是将其中涉及到事务的部分单独抽出来,只在这个方法上添加 @Transactional 注解。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// @Transactional
public void add(UserModel userModel) {
// do something...
updateStatus(userModel);
}
@Transactional
public void updateStatus(UserModel userModel) {
doSameThing();
}
}
这是一个非常常见的误区,通过前文的分析,我们已经知道 @Transactional 是通过 AOP 来实现的。在调用 add() 方法的时候,add() 方法并没有获取到事务的功能,那么在调用 updateStatus() 的时候,其实就是 this.updateStatus(),这时候的 this 并不是代理对象代理之后的方法,自然也不会再拥有事务的功能了。
解决方法内部调用导致事务失效的方法共有以下三种:
方法一,添加一个 Service 方法:
@Servcie
public class ServiceA {
@Autowired
prvate ServiceB serviceB;
public void save(User user) {
// do something...
serviceB.doSave(user);
}
}
@Servcie
public class ServiceB {
@Transactional(rollbackFor = Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
方法二,如果不想新增一个 Service 类,那么在 Service 类中注入自己也是一种选择:
@Servcie
public class ServiceA {
@Autowired
prvate ServiceA serviceA;
public void save(User user) {
// do something...
serviceA.doSave(user);
}
@Transactional(rollbackFor = Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
方法三,通过 AopContent 类,在该 Service 类中使用 AopContext.currentProxy() 获取代理对象:
@Servcie
public class ServiceA {
public void save(User user) {
// do something...
((ServiceA) AopContext.currentProxy()).doSave(user);
}
@Transactional(rollbackFor = Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
4.2. 访问权限不正确
private 方法将会导致事务失效:
@Service
public class UserService {
@Transactional
private void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
这是因为在 AbstractFallbackTransactionAttributeSource 类的 computeTransactionAttribute 方法中有个判断,如果目标方法不是 public,则 TransactionAttribute 返回 null,即不支持事务。
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// 如果方法不是pulic的,就返回null
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// ...
}
4.3. 方法使用 final 修饰
如果一个事务方法被定义成了 final,也会导致事务不生效,这是因为 cglib 是通过生成子类的方式生成代理类。如果方法被定义为 final,则意味着该方法无法被重写,无法添加事务功能。
@Service
public class UserService {
@Transactional
public final void add(UserModel userModel){
saveData(userModel);
updateData(userModel);
}
}
4.4. 未被 Spring 管理
Spring 在依赖查找的时候,是从 BeanFactory 中取出需要被代理的类,也就是说,事物生效的前提是,对象要被 Spring 管理。我们通过可以使用 @Service、Component、@Repository 等来实现 Bean 的依赖注入。
// @Service
public class UserService {
@Transactional
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
4.5. 多线程调用
事物有一个很重要的特性,就是不能跨线程使用,在如下的例子中,虽然我们使用了@Transactional 注解,但依然无法管理事务:
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
}
@Service
public class RoleService {
@Transactional
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
这是因为 insertUser() 方法和 doOtherThing() 方法在不同的线程中,那么它们获取到的数据库连接也是不一样的,这就导致,这两个方法在不同的事务中。这一点可以在org.springframework.transaction.support.TransactionSynchronizationManager 中得到印证。
4.6. 存储引擎不支持事务
并不是所有的存储引擎都支持事务,例如 MySQL 中的 MyISAM 存储引擎,如果需要支持事务,需要替换为 InnoDB 存储引擎。
4.7. 错误的传播特性
在《Spring 事务七大传播机制 —— REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED》中,对事务的传播特性,做了深入且全面的介绍,如果传播特性设置错误,事务自然也不会生效。
@Service
public class UserService {
@Transactional(propagation = Propagation.NEVER)
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
4.8. 异常处理不正确
对于异常的处理不正确,也会导致事务失效,例如,开发者手动处理了异常,这样被代理的方法将无法捕获到异常,自然也就无法回滚。
@Slf4j
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
另外,由于 @Transactional 默认捕获的异常是 RuntimeException 和 Error,如果抛出的是其他类型异常,则也会导致事务无法回滚。
@Slf4j
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) throws Exception {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
}
}
}
因此,你可能会经常见到这样的代码:
@Slf4j
@Service
public class UserService {
@Transactional(rollbackFor = Exception.class)
public void add(UserModel userModel) throws Exception {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
}
}
}
五. Spring 长事务的解决办法
长事务可能会导致如下问题:
5.1. 少用 @Transactional 注解
虽然 @Transactional 注解使用比较方便,但会导致整个业务方法都在同一个事务中,粒度比较粗,无法精确的控制事务的范围,就可能会导致长事务的产生:
@Transactional(rollbackFor=Exception.class)
public void save(User user) {
// do something...
}
正确的做法是,在有可能产生长事务的地方使用编程式事务:
@Autowired
private TransactionTemplate transactionTemplate;
// ...
public void save(final User user) {
transactionTemplate.execute((status) => {
// do something...
return Boolean.TRUE;
})
}
5.2. 将查询方法放到事务外
如果出现长事务,可以将查询的方法放到事务外,因为一般情况下,这类方法是不需要事务的,例如:
@Transactional(rollbackFor=Exception.class)
public void save(User user) {
queryData1();
queryData2();
addData1();
updateData2();
}
可以将 queryData1() 和 queryData2() 两个查询方法放在事务外执行,将真正需要事务执行的方法 addData1() 和 updateData2() 放在事务中,这样能有效的减少事务的粒度。
@Autowired
private TransactionTemplate transactionTemplate;
// ...
public void save(final User user) {
queryData1();
queryData2();
transactionTemplate.execute((status) => {
addData1();
updateData2();
return Boolean.TRUE;
})
}
5.3. 事务中避免远程调用
在业务代码中,调用其他系统的接口是不可避免的,由于网络不稳定或其他因素,这种远程调用的响应时间无法保证。如果将远程调用的代码放在事务中,就可能导致长事务。
@Transactional(rollbackFor=Exception.class)
public void save(User user) {
callRemoteApi();
addData1();
}
正确的做法是,把这些操作都放在事务外:
@Autowired
private TransactionTemplate transactionTemplate;
// ...
public void save(final User user) {
callRemoteApi();
transactionTemplate.execute((status) => {
addData1();
return Boolean.TRUE;
})
}
不过,这样做就无法保证数据的一致性了,需要建立重试+补偿机制,达到数据的最终一致性。
5.4. 事务中避免一次性处理太多数据
如果一个事务中需要处理大量的数据,也会造成大事务的问题,比如批量更新、批量插入等操作。可以使用分页进行处理,1000条数据,分50页,每次只处理20条数据,这样可以大大减少大事务的出现。
5.5. 非事务执行
在每次使用事务之前,我们都应该思考,是不是所有的数据库操作都需要在事务中执行?
@Autowired
private TransactionTemplate transactionTemplate;
// ...
public void save(final User user) {
transactionTemplate.execute((status) => {
addData();
addLog();
updateCount();
return Boolean.TRUE;
})
}
上面的例子中,增加操作日志的方法 addLog() 和更新统计数量方法 updateCount(),都是可以不在事务中执行,因为操作日志和统计数据这种业务允许少量数据出现不一致的情况。