引语:Spring对事务的嵌套调用有专门的传播机制,有时只从字面上理解往往会误入歧途,下面对传播机制做了详细讲解。
坑1 —— 新建子事务,导致当前事务挂起
坑2 —— 事务嵌套,新建子事务引起死锁
Lock wait timeout exceeded; try restarting transaction
坑3 —— 事务嵌套,想回滚事务至某个savepoint报错
Transaction rolled back because it has been marked as rollback-only
有时想对一段业务逻辑分段加事务,比如修改订单信息orderService.updateOrder()
(A)之后想记录日志logService.log()
(B),但是不想保存日志异常影响到正常的逻辑回滚,往往我们的第一想法就是将记录日志的代码try catch
,想法是对的,但往往做法是错的,往往我们会在orderService.updateOrder()
里调用logService.log()
时加try catch
,那么问题就来了。
坑4 —— 配置文件跟注解同时使用
Transaction rolled back because it has been marked as rollback-only
同样的错误,还以为是Spring传播机制不起作用的问题,故此深挖坑3,将事务仔细研究了一下,最后发现Spring还是很靠谱的,不会坑蒙拐骗,那么就是项目中的问题了。项目中spring-tx如下配置:
<tx:advice id="ossTxAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<!-- ... -->
<tx:method name="save*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception" />
<tx:method name="add*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception" />
<tx:method name="update*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception" />
<tx:method name="delete*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception" />
</tx:attributes>
</tx:advice>
a. 以为是rollback-for配置的问题,看了代码,代码中是先判断有无rollbackFor,如果有,那么只判断rollbackFor是否是NoRollbackRuleAttribute
的实例,如果不是,则会去回滚,也就是问题不是出在这。
b. 详细看日志,发现加了注解的方法,其实事务传播机制是REQUIRED
,并不是注解中的REQUIRES_NEW
,那么问题就出在这,xml文件中的将注解的配置覆盖了!
Transaction rolled back because it has been marked as rollback-only —— 解决方案
配置嵌套子事务的传播机制为REQUIRES_NEW
优:解决了抛异常的情况
缺:会造成当前事务挂起,会有脏读情况,无法避免
配置嵌套子事务的传播机制为NESTED
优:完美解决抛异常情况,并且不会挂起当前事务,异常抛出时仅仅是将事务回滚到子事务入口处的savepoint;经过测试真的就完美解决了,好鸡冻
缺:数据库驱动得支持JDBC3.0,不过目前的驱动只要不是太旧,都是支持的!
Spring会在进入orderService.updateOrder()
方法时,为其加AOP代理CglibAopProxy
,并设置拦截器TransactionAspectSupport->TransactionInterceptor
,执行方法A前获取连接,建立新事务,做如下操作
主事务信息TransactionInfo txInfo
代理:TransactionInterceptor@b890db3
joinpointIdentification:com.hongkun.oss.test.trans.impl.TransParentImpl.updateOrder
oldTransaction:null
transactionStatus: DefaultTransactionStatus@5457558
|-transaction: DataSourceTransactionObject@11739298
|—-connectionHold: ConnectionHolder@19c53683
transactionManager: DataSourceTransactionManager@51dbc859,事务管理器,包含属性globalRollbackOnParticipationFailure,表示子事务失败,主事务如何处理,很早之前的做法,目前改为PROPAGATION_NESTED来处理
子事务信息TransactionInfo txInfo
代理:TransactionInterceptor@b890db3
joinpointIdentification:com.hongkun.oss.test.trans.impl.TransChildImpl.updateOrderStatus
oldTransaction:父事务TransactionInfo txInfo
transactionStatus: DefaultTransactionStatus@144444
|-transaction: DataSourceTransactionObject@111c19a3
|—-connectionHold: ConnectionHolder@19c53683
transactionManager: DataSourceTransactionManager@51dbc859
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
// ...
// 主方法进入,调用invokeWithinTransaction方法
public Object invoke(final 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...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
}
/**
* General delegate for around-advice-based subclasses, delegating to several other template
* methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager}
* as well as regular {@link PlatformTransactionManager} implementations.
* @param method the Method being invoked
* @param targetClass the target class that we're invoking the method on
* @param invocation the callback to use for proceeding with the target invocation
* @return the return value of the method, if any
* @throws Throwable propagated from the target invocation
*/
// 总共步 1. createTransactionIfNecessary;2. completeTransactionAfterThrowing;3. cleanupTransactionInfo;4. commitTransactionAfterReturning
// 代码中的日志记录了抛出异常Participating transaction failed - marking existing transaction as rollback-only的过程
protected Object invokeWithinTransaction(Method method, Class targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
// 全局的事务处理类,子事务、主事务共用一个,里边有globalRollbackOnParticipationFailure标识
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// 如果需要的话,就创建事务,事务放入TransactionStatus
// 如果有事务,从当前事务中初始化子事务TransactionStatus,跟当前事务用一个ConnectionHolder,里边有rollbackOnly标识
// ------------- create new transaction start ----------------
// Creating new transaction with name[*impl#**]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
// Acquired Connection [A] for JDBC transaction
// Switching JDBC Connection [A] to manual commit
// --- 实际业务方法中用到数据库连接,并操作数据库
// --- Creating a new SqlSession
// --- Registering transaction synchronization for SqlSession [27375710]
// --- JDBC Connection [A] will be managed by Spring
// --- Using Connection [A]
// --- Preparing: some sql
// --- Releasing transactional SqlSession [27375710]
// ------------- create child transaction start (主事务嵌套调用其他服务的子事务,子事务非新建)----------------
// Participating in existing transaction
// --- 实际业务方法中用到数据库连接,并操作数据库
// --- Fetched SqlSession [ 27375710] from current transaction
// --- Using Connection [A]
// --- Preparing: some sql
// --- Releasing transactional SqlSession [27375710]
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) {
// target invocation exception
// 判断是否设置了rollbackExcepiton,如果没有设置,则回滚 ‘ex instanceof RuntimeException || ex instanceof Error’ 异常
// 如果回滚异常,调用AbstractPlatformTransactionManager.processRollback
// 有异常,根据子
// Participating transaction failed - marking existing transaction as rollback-only
// Setting JDBC transaction [A] rollback-only
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 如果子事务嵌入到主事务,还原事务上下文到主事务
cleanupTransactionInfo(txInfo);
}
// Global transaction is marked as rollback-only but transactional code requested commit
// Initiating transaction rollback
// Rolling back JDBC transaction on Connection [A]
// Transaction synchronization rolling back SqlSession [27375710]
// Transaction synchronization closing SqlSession [27375710]
// Releasing JDBC Connection [A] after transaction
// Returning JDBC Connection to DataSource
// ** throw UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only")
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus 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.
return new ThrowableHolder(ex);
}
}
finally {
cleanupTransactionInfo(txInfo);
}
}
});
// Check result: It might indicate a Throwable to rethrow.
if (result instanceof ThrowableHolder) {
throw ((ThrowableHolder) result).getThrowable();
}
else {
return result;
}
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
}
}
// ...
}
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
// ...
// 事务拦截器主入口代码,调用父类的invokeWithinTransaction,
public Object invoke(final 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...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
}
// ...
}
@SuppressWarnings("serial")
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
/**
* Set whether to globally mark an existing transaction as rollback-only
* after a participating transaction failed.
* <p>Default is "true": If a participating transaction (e.g. with
* PROPAGATION_REQUIRES or PROPAGATION_SUPPORTS encountering an existing
* transaction) fails, the transaction will be globally marked as rollback-only.
* The only possible outcome of such a transaction is a rollback: The
* transaction originator <i>cannot</i> make the transaction commit anymore.
* <p>Switch this to "false" to let the transaction originator make the rollback
* decision. If a participating transaction fails with an exception, the caller
* can still decide to continue with a different path within the transaction.
* However, note that this will only work as long as all participating resources
* are capable of continuing towards a transaction commit even after a data access
* failure: This is generally not the case for a Hibernate Session, for example;
* neither is it for a sequence of JDBC insert/update/delete operations.
* <p><b>Note:</b>This flag only applies to an explicit rollback attempt for a
* subtransaction, typically caused by an exception thrown by a data access operation
* (where TransactionInterceptor will trigger a {@code PlatformTransactionManager.rollback()}
* call according to a rollback rule). If the flag is off, the caller can handle the exception
* and decide on a rollback, independent of the rollback rules of the subtransaction.
* This flag does, however, <i>not</i> apply to explicit {@code setRollbackOnly}
* calls on a {@code TransactionStatus}, which will always cause an eventual
* global rollback (as it might not throw an exception after the rollback-only call).
* <p>The recommended solution for handling failure of a subtransaction
* is a "nested transaction", where the global transaction can be rolled
* back to a savepoint taken at the beginning of the subtransaction.
* PROPAGATION_NESTED provides exactly those semantics; however, it will
* only work when nested transaction support is available. This is the case
* with DataSourceTransactionManager, but not with JtaTransactionManager.
* @see #setNestedTransactionAllowed
* @see org.springframework.transaction.jta.JtaTransactionManager
*/
// 注释已经说的很明确了,要改变该值,请用PROPAGATION_NESTED,不过必须得支持JDBC3.0
public final void setGlobalRollbackOnParticipationFailure(boolean globalRollbackOnParticipationFailure) {
this.globalRollbackOnParticipationFailure = globalRollbackOnParticipationFailure;
}
/**
* This implementation of commit handles participating in existing
* transactions and programmatic rollback requests.
* Delegates to {@code isRollbackOnly}, {@code doCommit}
* and {@code rollback}.
* @see org.springframework.transaction.TransactionStatus#isRollbackOnly()
* @see #doCommit
* @see #rollback
*/
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;
// 如果子事务异常,全局回滚标识会被设置为rollbackOnly,回滚主事务
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus);
return;
}
// 如果子事务异常,TransactionManager的globalRollbackOnParticipationFailure为true,且事务jdbc连接的connectionHold为rollbackOnly,等主事务回滚成功后再抛出异常!
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus);
// Throw UnexpectedRollbackException only at outermost transaction boundary
// or if explicitly asked to.
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
return;
}
processCommit(defStatus);
}
/**
* Process an actual rollback.
* The completed flag has already been checked.
* @param status object representing the transaction
* @throws TransactionException in case of rollback failure
*/
private void processRollback(DefaultTransactionStatus status) {
try {
try {
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
}
else if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
// Setting JDBC transaction [connetion name] rollback-only,标记连接拥有者rollbackOnly=true(ResourceHolderSupport.rollbackOnly)
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
}
catch (RuntimeException ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
catch (Error err) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw err;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
}
finally {
cleanupAfterCompletion(status);
}
}
}
以上解释在网上和书本中一大堆,但是这么简单的解释,得知道里边的内涵,不然就会有无限的大坑等着你!
其中 REQUIRES_NEW 和 NESTED 的使用,REQUIRED 和 NESTED 的使用,最易混淆,以后会就这两种气情况做测试,并撰文说明。1. 主事务调用子事务,子事务抛异常? 2. 主事务调用子事务,子事务结束后主事务回滚?