结合源码分析Spring声明式事务失效的一些场景
Spring事务
官方事务文档:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction
编程式事务
声明式事务在我们平时用的可能更少一点,因为它对代码的侵入性太高了,而且没有注解@Transactional
方便快捷。
private final TransactionTemplate transactionTemplate;
ransactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// dosomething
} catch (SomeBusinessException ex) {
// 回滚事务
status.setRollbackOnly();
}
}
});
声明式事务
spring中声明式事务是使用``@Transactional`注解开启的,原理是Aop,这也是我们用的最多的方式。
但是使用不当,可能导致事务失效等一系列问题。
@Transactional
实现原理
// org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
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 PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// 【声明式事务处理】
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 【满足开启事务的条件将创建一个新事务】
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
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) {
// 【回滚或提交事务】
// 如果满足回滚事务的的条件的话,最终将会执行回滚事务的操作:txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
// 如果不满足回滚事务的条件,最终将会执行提交事务的操作:txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
// 【提交事务】。最终将会执行提交事务的操作:txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
commitTransactionAfterReturning(txInfo);
return retVal;
}
// 【编程式事务处理】
else {
final ThrowableHolder throwableHolder = new ThrowableHolder();
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
}
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);
}
});
// Check result state: It might indicate a Throwable to rethrow.
if (throwableHolder.throwable != null) {
throw throwableHolder.throwable;
}
return result;
}
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;
}
}
}
@Transactional
默认回滚的异常
代码位置:org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
可以看到,如果没有指定@Transactional
的回滚异常,默认就只回滚RuntimeException
和Error
。
Spring声明式事务失效的一些场景
1.数据库存储引擎不支持事务
查看MySQL系统当前使用的存储引擎
mysql> show variables like '%storage_engine%';
+----------------------------------+--------+
| Variable_name | Value |
+----------------------------------+--------+
| default_storage_engine | InnoDB |
| default_tmp_storage_engine | InnoDB |
| disabled_storage_engines | |
| internal_tmp_disk_storage_engine | InnoDB |
+----------------------------------+--------+
4 rows in set (0.03 sec)
查看当前MySQL支持的存储引擎
mysql> show engines;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine | Support | Comment | Transactions | XA | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES |
| MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
| MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
| BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO |
| MyISAM | YES | MyISAM storage engine | NO | NO | NO |
| CSV | YES | CSV storage engine | NO | NO | NO |
| ARCHIVE | YES | Archive storage engine | NO | NO | NO |
| PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO |
| FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.03 sec)
在这里,可以很明显的看到我当前MySQL数据库中InnoDB引擎才支持事务。
PS:在MySQL5.5.5之后,InnoDB就是默认存储引擎了。
2.回滚的异常不符合
文章前面我有说到,在没有指定回滚的异常情况下,默认只回滚RuntimeException
和Error
比如:在此处doSomeThing
中会抛出Exception
异常,而此处@Transactional
并没有指定回滚的异常,所以,此处事务将失效。
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public OrderDto create(OrderDto dto) {
// 操作数据库...
doSomeThing()
}
}
正确的方法是手动指定回滚的异常:@Transactional(rollbackFor = Exception.class)
3.没有被Spring管理/不是Bean
比如,此处,把@Service
注解去掉,Spring无法管理,事务将失效。
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional(rollbackFor = Exception.class)
public OrderDto create(OrderDto dto) {
// 操作数据库...
}
}
4.异常被吃了
比如:此处的异常没有抛出来,事务也将失效。
@Service
public class OrderServiceImpl implements OrderService {
@Transactional(rollbackFor = Exception.class)
public OrderDto create(OrderDto dto) {
try {
// 操作数据库...
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
5.加锁处理不当
错误的示例:
@Transactional(rollbackFor = Exception.class)
public synchronized OrderDto create(OrderDto dto) {
// 操作数据库...
}
这样子导致事务失效的原因是:此处synchronized关键字的范围比@Transactional
(Aop)小,它没有涵盖到整个事务过程,恰恰相反的是@Transactional
(Aop)把synchronized
关键字的范围涵盖在内了。
参考解决办法:可以使用编程式事务。
示例(当然还有其它办法):
private static final Object SYNC_ORDER_OBJECT = new Object();
public OrderDto create(OrderDto dto) {
synchronized (SYNC_ORDER_OBJECT) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 操作数据库...
// 提交事务
transactionManager.commit(status);
return orderDto;
} catch (Exception e) {
// 手动回滚事务
transactionManager.rollback(status);
log.error(e.toString(), e);
throw new CustomException(e.getMessage());
}
}
}
6.多线程调用
在文章开头说“@Transactional
实现原理”中的org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
,在声明式事务中,开头有这么一段代码:
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
createTransactionIfNecessary
方法:
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
// If no name specified, apply method identification as transaction name.
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
status = tm.getTransaction(txAttr);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
我们来看一下prepareTransactionInfo
方法:
protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, String joinpointIdentification,
@Nullable TransactionStatus status) {
TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
if (txAttr != null) {
// We need a transaction for this method...
if (logger.isTraceEnabled()) {
logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
// The transaction manager will flag an error if an incompatible tx already exists.
txInfo.newTransactionStatus(status);
}
else {
// The TransactionInfo.hasTransaction() method will return false. We created it only
// to preserve the integrity of the ThreadLocal stack maintained in this class.
if (logger.isTraceEnabled()) {
logger.trace("Don't need to create transaction for [" + joinpointIdentification +
"]: This method isn't transactional.");
}
}
// We always bind the TransactionInfo to the thread, even if we didn't create
// a new transaction here. This guarantees that the TransactionInfo stack
// will be managed correctly even if no transaction was created by this aspect.
txInfo.bindToThread();
return txInfo;
}
最后来看一下bindToThread
方法:
主要作用就是将TransactionInfo
绑定到当前线程中,也就是说一个线程就会持有一个TransactionInfo
对象,所以,这也就不难理解为什么多线程调用会导致事务失效了。
private static final ThreadLocal<TransactionInfo> transactionInfoHolder = new NamedThreadLocal<>("Current aspect-driven transaction");
// ...
private void bindToThread() {
// Expose current TransactionStatus, preserving any existing TransactionStatus
// for restoration after this transaction is complete.
this.oldTransactionInfo = transactionInfoHolder.get();
transactionInfoHolder.set(this);
}
7.错误的事务传播特性
Spring中七种事务传播行为,也可以看代码org.springframework.transaction.annotation.Propagation
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,如果不存在则创建一个新事务。 类似于 EJB 的同名事务属性。 这是事务注释的默认设置。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果不存在则以非事务方式执行。 类似于 EJB 的同名事务属性。 注意:对于具有事务同步的事务管理器,PROPAGATION_SUPPORTS 与根本没有事务略有不同,因为它定义了同步将应用的事务范围。 因此,相同的资源(JDBC 连接、Hibernate 会话等)将在整个指定范围内共享。 请注意,这取决于事务管理器的实际同步配置。 |
PROPAGATION_MANDATORY | 支持当前事务,如果不存在则抛出异常。类似于EJB的同名事务属性。 |
PROPAGATION_REQUIRES_NEW | 创建一个新事务,如果存在,则暂停当前事务。 类似于同名的 EJB 事务属性。 注意:实际的事务挂起不会在所有事务管理器上开箱即用。 这尤其适用于org.springframework.transaction.jta.JtaTransactionManager ,它需要javax.transaction.TransactionManager对其可用(这在标准 Java EE 中是特定于服务器的) |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行,如果存在则暂停当前事务。 类似于 EJB 的同名事务属性。 注意:实际的事务挂起不会在所有事务管理器上开箱即用。 这尤其适用于org.springframework.transaction.jta.JtaTransactionManager ,它需要javax.transaction.TransactionManager对其可用(这在标准 Java EE 中是特定于服务器的)。 |
PROPAGATION_NEVER | 以非事务方式执行,如果存在事务则抛出异常。类似于EJB的同名事务属性。 |
PROPAGATION_NESTED | 如果当前事务存在,则在嵌套事务中执行,其他行为类似于 PROPAGATION_REQUIRED。 EJB 中没有类似的特性。 注意:嵌套事务的实际创建仅适用于特定的事务管理器。 开箱即用,这仅适用于处理 JDBC 3.0 驱动程序时的 JDBC DataSourceTransactionManager。 一些 JTA 提供者也可能支持嵌套事务。 |
@Transactional注解中
可以用propagation指定事务的传播特性,默认是Propagation.REQUIRED
,也就是TransactionDefinition.PROPAGATION_REQUIRED
:
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
8.方法不是public
比如:我这里create方法使用private修饰的话,事务将失效。
@Service
public class OrderServiceImpl implements OrderService {
@Transactional(rollbackFor = Exception.class)
private OrderDto create(OrderDto dto) {
// 操作数据库...
}
}
失效原因看下面代码分析👇🏻
在代码org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
中,限定了allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())
,也就是说被调用的方法如果不是public,将返回null。
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class.
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}
// Second try is the transaction attribute on the target class.
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// Last fallback is the class of the original method.
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
9.方法用final修饰
spring事务基于AOP,底层可以使用jdk动态代理或者cglib代理,它们会帮我们自动生成代理类去实现事务相关功能,但是如果方法被final修饰后就不能被重写,所以,此时事务也将失效。
PS: 如果方法是static,也无法通过动态代理。
@Service
public class OrderServiceImpl implements OrderService {
@Transactional(rollbackFor = Exception.class)
public final OrderDto create(OrderDto dto) {
// 操作数据库...
}
}
10.同一个类中的事务问题
Spring默认的事务传播行为REQUIRED
:如果不存在外层事务,就主动开启事务,否则使用外层事务。
同一个类中无事务方法调用有事务方法问题
错误示例:
@Override
public void doTheThing() {
// ...
actuallyDoTheThing();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void actuallyDoTheThing() {
// ...
}
在doTheThing
方法没有开启事务,但是在调用方actuallyDoTheThing
方法开启了事务,所以,在actuallyDoTheThing
方法中的操作如果符合回滚条件将会回滚,而doTheThing
中除去actuallyDoTheThing
方法之外的其它方法由于没有开启事务,所以不会回滚。
sonar对此操作都有警告:https://rules.sonarsource.com/java/RSPEC-2229
同一个类中事务传播行为虽然满足事务但传播行为不一致问题
错误示例1:doTheThing
方法和actuallyDoTheThing
方法两者事务互不影响。
@Override
@Transactional(rollbackFor = Exception.class)
public void doTheThing() {
// ...
actuallyDoTheThing();
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void actuallyDoTheThing() {
// ...
}
错误示例2:doTheThing
方法有事务,而actuallyDoTheThing
方法没有事务。
@Override
@Transactional(rollbackFor = Exception.class)
public void doTheThing() {
// ...
actuallyDoTheThing();
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
public void actuallyDoTheThing() {
// ...
}
错误示例3:与MANDATORY
的事务传播行为相反,MANDATORY
是当外层方法不存在事务抛出异常,而NEVER
是当外层方法存在事务抛出异常。所以此处会抛出异常,同时也不会进行DB操作。
@Override
@Transactional(rollbackFor = Exception.class)
public void doTheThing() {
// ...
actuallyDoTheThing();
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NEVER)
public void actuallyDoTheThing() {
// ...
}
错误示例4:
@Override
@Transactional(rollbackFor = Exception.class)
public void doTheThing() {
// ...
actuallyDoTheThing();
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public void actuallyDoTheThing() {
// ...
}
正确做法(事务的传播特性):
@Override
@Transactional(rollbackFor = Exception.class)
public void doTheThing() {
// ...
actuallyDoTheThing();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void actuallyDoTheThing() {
// ...
}
参考文章:
https://z.itpub.net/article/detail/18A4D9564A61EC7AF8EAA66FCA251444
https://juejin.cn/post/6844903504792780814
- 本文标签: Java Spring SpringBoot
- 本文链接: http://www.lzhpo.com/article/182
- 版权声明: 本文由lzhpo原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权