@Transaction注解相信读者们肯定都用过,spring通过这种注解式声明事务,使我们开发者无需关注与数据库的事务编写,更多的关注到业务逻辑的代码编写上,使编码更加简单,但是@Transaction是如何工作的,以及面试中常问到的事务的传播行为是怎么一回事,我们还是有必要了解一下,本文将带你从源码层面上理解这这些东西,如有错误欢迎各位指正并讨论。
当我们需要使用@Transaction注解声明事务时,必须先要使用@EnableTransactionManagement开启事务注解的支持,现在大多数都在用springboot框架进行编码,我们几乎没有写过@EnableTransactionManagement,这是因为springboot中采用自动装配TransactionAutoConfiguration这个自动配置类已经将@EnableTransactionManagement注解写入该类了,所以无需我们编写。那我们看一下这个类干了些什么:
一般spring的尿性都是都是通过@Enablexxx注解导入一个Import相关的类.,这个也不列外
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
//是否要创建基于子类的目标代理
boolean proxyTargetClass() default false;
//使用那种模式进行代理
AdviceMode mode() default AdviceMode.PROXY;
//所有切面的排序
int order() default Ordered.LOWEST_PRECEDENCE;
}
TransactionManagementConfigurationSelector这个类我们主要是看它父类#AdviceModeImportSelector.selectImports()方法
//importingClassMetadata:代表bean的注解信息
public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
Class> annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
Assert.state(annType != null, "Unresolvable type argument for AdviceModeImportSelector");
//获取注解的属性值
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
if (attributes == null) {
throw new IllegalArgumentException(String.format(
"@%s is not present on importing class '%s' as expected",
annType.getSimpleName(), importingClassMetadata.getClassName()));
}
//默认情况下模式未PROXY
AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());
//调用子类的selectImports()重载方法
String[] imports = selectImports(adviceMode);
if (imports == null) {
throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
}
return imports;
}
接下来我们看子类的#TransactionManagementConfigurationSelector.selectImports()方法
@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {determineTransactionAspectClass()};
default:
return null;
}
}
因为默认是PROXY模式,这里的意思就是往spring容器中注入一个
ProxyTransactionManagementConfiguration类型的bean
接下来我们看ProxyTransactionManagementConfiguration这个bean干了些什么事
//这个bean不开启CGLIB代理
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class
ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
/**
* 定义事务切面
* @param transactionAttributeSource
* @param transactionInterceptor
* @return
*/
@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);
//enableTx 设置排序 也是从导入类的AnnotationMetada中获取
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
/**
* 设置事务切面的MethodInterceprtor
* @param transactionAttributeSource
* @return
*/
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
/**
* 注入transactionAttributeSource进行方法的match
*/
interceptor.setTransactionAttributeSource(transactionAttributeSource);
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
可以看到这个配置bean又往容器注入了3个类型的bean
首先来分析第一个bean,这里就会涉及到一些spring-aop相关的知识了
面向切面编程中有一个核心的概念就是切面,spring中的advisor就可以理解为一个切面,一个切面又主要由切入点pointCut和增强器advice组成。
事务这个advisor的切入点point就是TransactionAttributeSourcePointcut,增强器advisor就是TransactionInterceptor。
TransactionAttributeSourcePointcut主要做两件事
第一件事是做类匹配,判断这个这个类是否需要进行代理主要是通过TransactionAttributeSourceClassFilter的matches方法
第二件事就是方法匹配,若方法上需要进行代理那么这个类肯定进行代理比如方法上加了@Transaction注解,方法匹配的逻辑就是TransactionAttributeSourcePointcut的matches方法
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
/**
* 类匹配基本不做什么,可以过滤掉
*/
protected TransactionAttributeSourcePointcut() {
setClassFilter(new TransactionAttributeSourceClassFilter());
}
/**
* 方法匹配
* @param method
* @param targetClass
* @return
*/
@Override
public boolean matches(Method method, Class> targetClass) {
//获取注入的事务属性解析器
TransactionAttributeSource tas = getTransactionAttributeSource();
//判断方法或者类上是否有@Transaction注解,并将@Transaction的属性值封装成TransactionAttribute放入缓存中
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
主要就是看tas.getTransactionAttribute(method, targetClass)方法调用,这个方法主要就是收集@Transaction注解的一些属性比如事务的隔离级别 传播行为,收集后在放入缓存中
从这里 可以看到几个关于@Transaction注解不生效的原因
1.若方法所属的类为Object类型,不进行处理
if (method.getDeclaringClass() == Object.class) { return null; }
2.被修饰的方法只能是一个pubic类型的
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; }
截至到这里 做的主要就是判断这个类是否需要进行事务相关的代理,并收集@Transaction注解相关的属性放入缓存中,若需要则把对象升级成代理对象放入容器中,待到真正调用方法时,根据aop的调用逻辑会调到TransactionInterceptor这个类的invoke()方法,那么我们接下来就来仔细研究一下#TransactionInterceptor.invoke()方法
@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对象
Class> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
由于invokeWithinTransaction()的方法逻辑比较长 我们分成几段来看
1.首先看第一段逻辑
//获取事务属性管理器,里面有TransactionAttribute的缓存
TransactionAttributeSource tas = getTransactionAttributeSource();
//获取缓存中的TransactionAttribute
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
这里主要就是获取@Transaction注解的属性信息,信息在前面执行match的时候就被封装成了一个TransactionAttribute对象
2.获取事务管理器
final TransactionManager tm = determineTransactionManager(txAttr);
protected TransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
// Do not attempt to lookup tx manager if no tx attributes are set
if (txAttr == null || this.beanFactory == null) {
return getTransactionManager();
}
String qualifier = txAttr.getQualifier();
if (StringUtils.hasText(qualifier)) {
return determineQualifiedTransactionManager(this.beanFactory, qualifier);
}
else if (StringUtils.hasText(this.transactionManagerBeanName)) {
return determineQualifiedTransactionManager(this.beanFactory, this.transactionManagerBeanName);
}
else {
TransactionManager defaultTransactionManager = getTransactionManager();
if (defaultTransactionManager == null) {
defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
if (defaultTransactionManager == null) {
//直接从容器中获取
defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class);
//获取到后放入缓存
this.transactionManagerCache.putIfAbsent(
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
}
}
return defaultTransactionManager;
}
}
这个事务管理器99%的情况都是需要我们手动注入到容器中才能获取到,一般情况下会往容器中注入一个DataSourceTransactionManager类型的bean
3.获取连接点的名字,其实就是方法名
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
4.创建事务
/*
* ptm:事务管理器 管理了数据源
* txAttr:事务属性 @Transaction注解的属性
* joinpointIdentification 连接点 方法名
*/
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
那么我们就仔细看一下这个事务是如何创建的
4.1首先根据事务管理器创建status
获取事务
Object transaction = doGetTransaction();
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
//允许设置回滚点和事务传播行为有关
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder)TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
这里创建了一个DataSourceTransactionObject对象,
TransactionSynchronizationManager.getResource(obtainDataSource())这行代码致关重要,主要就是threadLocal中存储了一个map,map的key就是datasource数据源,vaule就是Connection连接对象,这里将它封装成spring的ConnectionHolder对象,这里第一次进来都没有设置肯定是空的。
这里的if判断主要就是ConnectionHolder是否为空,所以不会进,里面的逻辑就是处理事务的传播行为的
if (isExistingTransaction(transaction)) { // Existing transaction found -> check propagation behavior to find out how to behave. return handleExistingTransaction(def, transaction, debugEnabled); } 紧接着看着行代码
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
TransactionDefinition.PROPAGATION_MANDATORY属于传播行为的一种,就是如果当前线程不存在事务,就直接报错,表示强制要使用别人创建好的事务 不需要我自己创建
当执行到这里时,这里就是我们需要关注的传播行为
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
//挂起当前事务,这里一般为null
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
当传播行为属于PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,
PROPAGATION_NESTED其中一种时就会创建一个新的事务,新的事务其实就是从连接池中获取一个连接对象,并关闭自动提交,那我们就来看一下startTransaction()方法,记住这个方法,不管那种传播行为,只要调用这个方法就一定会创建一个新的事务。
看doBegin(transaction, definition);这个方法
首先从连接池中获取一个连接对象,并填充DataSourceTransactionObject中connectionHolder的值,因为之前从线程上下文获取为空,这里不为空,这里还有一个属性为true,表示它是一个新的connectionHolder对象
Connection newCon = obtainDataSource().getConnection(); txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
后面就是设置连接的属性 比如隔离级别,是否只读等,最后还要关闭自动提交con.setAutoCommit(false);
当这些都设置好后,将事务放入线程上下文中此时具有事务功能的连接对象就创建好了
if (txObject.isNewConnectionHolder()) {
//如果是一个新的ConnectionHolder,将ConnectionHolder放入ThradLocal中
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
开启事务后 ,还要开启一些spring的功能
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
//判断是否为新的newSynchronization
if (status.isNewSynchronization()) {
//设置是否有事务,即DataSourceTransactionObject不为null
TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
//设置事务的隔离级别
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
definition.getIsolationLevel() : null);
//设置当前事务的只读属性
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
//设置当前事务的名字,即TransactionAttribute的name 方法名
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
//初始换synchronizations,可在synchronizations容器中注册TransactionSynchronization用于一些业务处理如afterCommit,beforeCommit
TransactionSynchronizationManager.initSynchronization();
}
}
事务开启完毕后,继续执行aop的调用链 invocation.proceedWithInvocation();最终执行业务逻辑
在catch里面就是执行回滚逻辑
completeTransactionAfterThrowing(txInfo, ex);
若不报错 则执行事务的提交
commitTransactionAfterReturning(txInfo);
再看回滚和提交逻辑前我们先看一下事务的传播行为是如何进行的
我们在进入到这个方法
if (isExistingTransaction(transaction)) { // Existing transaction found -> check propagation behavior to find out how to behave. /** * 如果当前线程存在事务 即从ThradLocal中能获取到ConnectionHolder * 且ConnectionHolder为活跃状态 * def:TransactionAttribute 事务属性 * transaction:DataSourceTransactionObject 数据源事务对象 */ return handleExistingTransaction(def, transaction, debugEnabled); }
若当前存在事务则进行handleExistingTransaction()逻辑
//PROPAGATION_NEVER 当前环境有事务 直接报错
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
// PROPAGATION_NOT_SUPPORTED 若当前环境有事务 将事务挂起
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
if (debugEnabled) {
logger.debug("Suspending current transaction");
}
//挂起
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
我们来看我们比较熟悉的当事务的传播行为PROPAGATION_REQUIRES_NEW时,事务是如何进行操作的
首先将原来的事务挂起
SuspendedResourcesHolder suspendedResources = suspend(transaction);主要的就是将原来的connectionHolder对象从线程上下文中移除,并将当前事务封装成一个SuspendedResourcesHolder对象,
接着创建一个新的事务,我们之前说过,碰到startTransaction()就是创建一个新的事务startTransaction(definition, transaction, debugEnabled, suspendedResources);
这个方法就不细说了,这里就是多了suspendedResources关于旧事务的封装,把它和当前事务绑定了,主要就是用于当时事务提交后,恢复旧事务用的。
因为REQUIRED_NEW会创建一个新的连接所以这里有可能会发生死锁,关于这块可以看另一篇文章
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)也会导致死锁吗?_1996_ZK的博客-CSDN博客线上的服务,突然就卡死了,整个服务不可用了,必须要重启才能解决,但重启过后,过一段时间就又出现了,后来通过jstack命令排查到是获取数据库连接对象时,tomcat的线程阻塞在那里导致线程被耗尽(Connection newCon = obtainDataSource().getConnection();),最终造成服务不可用。但究竟是什么原因造成获取连接一直阻塞呢?后来通过压测发现只要并发数超过了连接池的最大连接数,这个问题就必现,下面的代码是模拟生产的代码写的demo操作表A...https://blog.csdn.net/weixin_43716742/article/details/124433561接着我们看另外一种传播行为PROPAGATION_NESTED,这个和PROPAGATION_REQUIRES_NEW不同 ,不会创建一个新的事务只是设置一个回滚点。
回滚点的意思就是只会回滚到创建回滚点这之间的业务逻辑,之前的并不会回滚,具体可以看jdbc的相关说明。这里就做了一件事,设置回滚点
getConnection().setSavepoint(SAVEPOINT_NAME_PREFIX + this.savepointCounter);
如果播行为PROPAGATION_REQUIRED,那么就会执行
prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
接着我们回过头来看回滚逻辑
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
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 {
// Participating in larger transaction
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
/**
* 若检测到异常则设置 ConnectionHolder的rollbackonly属性为true
*/
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");
}
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
//执行afterCompletion操作
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// Raise UnexpectedRollbackException if we had a global rollback-only marker
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
//清空ThreadLocal中的属性
cleanupAfterCompletion(status);
}
}
这里就是执行正常的回滚逻辑,只是这里需要注意一个属性,当传播行为PROPAGATION_REQUIRED时会设置doSetRollbackOnly(status);这个属性和提交的时候有关,在提交的时候有这段逻辑判断
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;
}
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
如果这个值被设置成了true即使外部事务未捕获到异常,事务也不会正常提交。
后面还会写一篇spring整合mybatis时事务是如何执行的