前言
本專題大綱:
本文為本專題倒數第二篇文章。
在上篇文章中我們一起學習了Spring中的事務抽象機制以及動手模擬了一下Spring中的事務管理機制,那么本文我們就通過源碼來分析一下Spring中的事務管理到底是如何實現的,本文將選用Spring5.2.x
版本。
從@EnableTransactionManagement開始
Spring事務管理的入口就是@EnableTransactionManagement
注解,所以我們直接從這個注解入手,其源碼如下:
public @interface EnableTransactionManagement {
// 是否使用cglib代理,默認是jdk代理
boolean proxyTargetClass() default false;
// 使用哪種代理模式,Spring AOP還是AspectJ
AdviceMode mode() default AdviceMode.PROXY;
// 為了完成事務管理,會向容器中添加通知
// 這個order屬性代表了通知的執行優先級
// 默認是最低優先級
int order() default Ordered.LOWEST_PRECEDENCE;
}
需要注意的是,@EnableTransactionManagement
的proxyTargetClass
會影響Spring中所有通過自動代理生成的對象。如果將proxyTargetClass
設置為true,那么意味通過@EnableAspectJAutoProxy
所生成的代理對象也會使用cglib進行代理。關於@EnableTransactionManagement
跟@EnableAspectJAutoProxy
混用時的一些問題等我們在對@EnableTransactionManagement
有一定了解后再專門做一個比較,現在我們先來看看這個注解到底在做了什么?
從上圖中可以看出這個注解做的就是向容器中注冊了AutoProxyRegistrar
跟一個ProxyTransactionManagementConfiguration
(這里就不考慮AspectJ了,我們平常都是使用SpringAOP),
AutoProxyRegistrar
用於開啟自動代理,其源碼如下:
AutoProxyRegistrar分析
這個類實現了ImportBeanDefinitionRegistrar
,它的作用是向容器中注冊別的BeanDefinition
,我們直接關注它的registerBeanDefinitions
方法即可
// AnnotationMetadata,代表的是AutoProxyRegistrar的導入類的元信息
// 既包含了類元信息,也包含了注解元信息
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean candidateFound = false;
// 獲取@EnableTransactionManagement所在配置類上的注解元信息
Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
// 遍歷注解
for (String annType : annTypes) {
// 可以理解為將注解中的屬性轉換成一個map
AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
if (candidate == null) {
continue;
}
// 直接從map中獲取對應的屬性
Object mode = candidate.get("mode");
Object proxyTargetClass = candidate.get("proxyTargetClass");
// mode,代理模型,一般都是SpringAOP
// proxyTargetClass,是否使用cglib代理
if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
Boolean.class == proxyTargetClass.getClass()) {
// 注解中存在這兩個屬性,並且屬性類型符合要求,表示找到了合適的注解
candidateFound = true;
// 實際上會往容器中注冊一個InfrastructureAdvisorAutoProxyCreator
if (mode == AdviceMode.PROXY) {
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
if ((Boolean) proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
return;
}
}
}
}
// ......
}
@EnableTransactionManagement跟@EnableAspectJAutoProxy
如果對AOP比較了解的話,那么應該知道@EnableAspectJAutoProxy
注解也向容器中注冊了一個能實現自動代理的bd,那么當@EnableAspectJAutoProxy
跟@EnableTransactionManagement
同時使用會有什么問題嗎?答案大家肯定知道,不會有問題,那么為什么呢?我們查看源碼會發現,@EnableAspectJAutoProxy
最終調用的是
AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary
,其源碼如下
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}
@EnableTransactionManagement
最終調用的是,AopConfigUtils#registerAutoProxyCreatorIfNecessary
,其源碼如下
public static BeanDefinition registerAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}
它們最終都會調用registerOrEscalateApcAsRequired
方法,只不過傳入的參數不一樣而已,一個是AnnotationAwareAspectJAutoProxyCreator
,另一個是InfrastructureAdvisorAutoProxyCreator
。
registerOrEscalateApcAsRequired
源碼如下:
private static BeanDefinition registerOrEscalateApcAsRequired(
Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
// 當前已經注冊到容器中的Bean的優先級
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
// 當前准備注冊到容器中的Bean的優先級
int requiredPriority = findPriorityForClass(cls);
// 誰的優先級大就注冊誰,AnnotationAwareAspectJAutoProxyCreator是最大的
// 所以AnnotationAwareAspectJAutoProxyCreator會覆蓋別的Bean
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
}
// 注冊bd
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
InfrastructureAdvisorAutoProxyCreator
跟AnnotationAwareAspectJAutoProxyCreator
的優先級是如何定義的呢?我們來看看AopConfigUtils
這個類中的一個靜態代碼塊
static {
APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
}
實際上它們的優先級就是在APC_PRIORITY_LIST
這個集合中的下標,下標越大優先級越高,所以AnnotationAwareAspectJAutoProxyCreator
的優先級最高,所以AnnotationAwareAspectJAutoProxyCreator
會覆蓋InfrastructureAdvisorAutoProxyCreator
,那么這種覆蓋會不會造成問題呢?答案肯定是不會的,因為你用了這么久了也沒出過問題嘛~那么再思考一個問題,為什么不會出現問題呢?這是因為InfrastructureAdvisorAutoProxyCreator
只會使用容器內部定義的Advisor
,但是AnnotationAwareAspectJAutoProxyCreator
會使用所有實現了Advisor
接口的通知,也就是說AnnotationAwareAspectJAutoProxyCreator
的作用范圍大於InfrastructureAdvisorAutoProxyCreator
,因此這種覆蓋是沒有問題的。限於篇幅原因這個問題我不做詳細解答了,有興趣的同學可以看下兩個類的源碼。
@EnableTransactionManagement
除了注冊了一個AutoProxyRegistrar
外,還向容器中注冊了一個ProxyTransactionManagementConfiguration
。
那么這個ProxyTransactionManagementConfiguration
有什么作用呢?
如果大家對我文章的風格有一些了解的話就會知道,分析一個類一般有兩個切入點
- 它的繼承關系
- 它提供的API
大家自己在閱讀源碼時也可以參考這種思路,分析一個類的繼承關系可以讓我們了解它從抽象到實現的過程,即使不去細看API也能知道它的大體作用。僅僅知道它的大致作用是不夠的,為了更好了解它的細節我們就需要進一步去探索它的具體實現,也就是它提供的API。這算是我看了這么就源碼的一點心得,正好想到了所以在這里分享下,之后會專門寫一篇源碼心得的文章
ProxyTransactionManagementConfiguration分析
繼承關系
這個類的繼承關系還是很簡單的,只有一個父類AbstractTransactionManagementConfiguration
AbstractTransactionManagementConfiguration
源碼如下:
@Configuration
public abstract class AbstractTransactionManagementConfiguration implements ImportAware {
@Nullable
protected AnnotationAttributes enableTx;
@Nullable
protected TransactionManager txManager;
// 這個方法就是獲取@EnableTransactionManagement的屬性
// importMetadata:就是@EnableTransactionManagement這個注解所在類的元信息
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
// 將EnableTransactionManagement注解中的屬性對存入到map中
// AnnotationAttributes實際上就是個map
this.enableTx = AnnotationAttributes.fromMap( importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false));
// 這里可以看到,限定了導入的注解必須使用@EnableTransactionManagement
if (this.enableTx == null) {
throw new IllegalArgumentException(
"@EnableTransactionManagement is not present on importing class " + importMetadata.getClassName());
}
}
// 我們可以配置TransactionManagementConfigurer
// 通過TransactionManagementConfigurer向容器中注冊一個事務管理器
// 一般不會這么使用,更多的是通過@Bean的方式直接注冊
@Autowired(required = false)
void setConfigurers(Collection<TransactionManagementConfigurer> configurers) {
// .....
TransactionManagementConfigurer configurer = configurers.iterator().next();
this.txManager = configurer.annotationDrivenTransactionManager();
}
// 向容器中注冊一個TransactionalEventListenerFactory
// 這個類用於處理@TransactionalEventListener注解
// 可以實現對事件的監聽,並且在事務的特定階段對事件進行處理
@Bean(name = TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public static TransactionalEventListenerFactory transactionalEventListenerFactory() {
return new TransactionalEventListenerFactory();
}
}
TransactionalEventListenerFactory
上面的代碼中大家可能比較不熟悉的就是TransactionalEventListenerFactory
,這個類主要是用來處理@TransactionalEventListener
注解的,我們來看一個實際使用的例子
@Component
public class DmzListener {
// 添加一個監聽器
// phase = TransactionPhase.AFTER_COMMIT意味着這個方法在事務提交后執行
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void listen(DmzTransactionEvent transactionEvent){
System.out.println("事務已提交");
}
}
// 定義一個事件
public class DmzTransactionEvent extends ApplicationEvent {
public DmzTransactionEvent(Object source) {
super(source);
}
}
@Component
public class DmzService {
@Autowired
ApplicationContext applicationContext;
// 一個需要進行事務管理的方法
@Transactional
public void invokeWithTransaction() {
// 發布一事件
applicationContext.publishEvent(new DmzTransactionEvent(this));
// 以一條sout語句提代sql執行過程
System.out.println("sql invoked");
}
}
// 測試方法
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(Config.class);
DmzService dmzService = ac.getBean(DmzService.class);
dmzService.invokeWithTransaction();
}
}
// 最后程序會按順序輸出
// sql invoked
// 事務已提交
通過上面的例子我們可以看到,雖然我們在invokeWithTransaction
方法中一開始就發布了一個事件,但是監聽事件的方法卻是在invokeWithTransaction
才執行的,正常事件的監聽是同步的,假設我們將上述例子中的@TransactionalEventListener
注解替換成為@EventListener
注解,如下:
@Component
public class DmzListener {
// 添加一個監聽器
@EventListener
public void listen(DmzTransactionEvent transactionEvent){
System.out.println("事務已提交");
}
}
這個時候程序的輸出就會是
// 事務已提交
// sql invoked
那么@TransactionalEventListener
注解是實現這種看似異步(實際上並不是)的監聽方式的呢?
大家按照上面這個調用鏈可以找到這么一段代碼
通過上面的代碼,我們可以發現最終會調用到TransactionalEventListenerFactory
的createApplicationListener
方法,通過這個方法創建一個事件監聽器然后添加到容器中,createApplicationListener
方法源碼如下:
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
return new ApplicationListenerMethodTransactionalAdapter(beanName, type, method);
}
就是創建了一個ApplicationListenerMethodTransactionalAdapter
,這個類本身就是一個事件監聽器(實現了ApplicationListener
接口)。我們直接關注它的事件監聽方法,也就是onApplicationEvent
方法,其源碼如下:
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 激活了同步,並且真實存在事務
if (TransactionSynchronizationManager.isSynchronizationActive() &&
TransactionSynchronizationManager.isActualTransactionActive()) {
// 實際上是依賴事務的同步機制實現的事件監聽
TransactionSynchronization transactionSynchronization = createTransactionSynchronization(event);
TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);
}
// 在沒有開啟事務的情況下是否處理事件
else if (this.annotation.fallbackExecution()) {
// ....
// 如果注解中的fallbackExecution為true,意味着沒有事務開啟的話
// 也會執行監聽邏輯
processEvent(event);
}
else {
// ....
}
}
到這一步邏輯已經清楚了,@TransactionalEventListener
所標注的方法在容器啟動時被解析成了一個ApplicationListenerMethodTransactionalAdapter
,這個類本身就是一個事件監聽器,當容器中的組件發布了一個事件后,如果事件匹配,會進入它的onApplicationEvent
方法,這個方法並沒有直接執行我們所定義的監聽邏輯,而是給當前事務注冊了一個同步的行為,當事務到達某一個階段時,這個行為會被觸發。通過這種方式,實現一種偽異步。實際上注冊到事務的的同步就是TransactionSynchronizationEventAdapter
,這個類的源碼非常簡單,這里就單獨取它一個方法看下
// 這個方法會在事務提交前執行
public void beforeCommit(boolean readOnly) {
// 在執行時會先判斷在@TransactionalEventListener注解中定義的phase是不是BEFORE_COMMIT
// 如果不是的話,什么事情都不做
if (this.phase == TransactionPhase.BEFORE_COMMIT) {
processEvent();
}
}
別看上面這么多內容,到目前為止我們還是只對ProxyTransactionManagementConfiguration
的父類做了介紹,接下來我們就來看看ProxyTransactionManagementConfiguration
自身做了什么事情。
源碼分析
// proxyBeanMethods=false,意味着不對配置類生成代理對象
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
// 注冊了一個BeanFactoryTransactionAttributeSourceAdvisor
// advisor就是一個綁定了切點的通知
// 可以看到通知就是TransactionInterceptor
// 切點會通過TransactionAttributeSource去解析@Transacational注解
// 只會對有這個注解的方法進行攔截
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
// BeanDefinition的角色是一個基礎設施類
@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;
}
// 注冊一個AnnotationTransactionAttributeSource
// 這個類的主要作用是用來解析@Transacational注解
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
// 事務是通過AOP實現的,AOP的核心就是攔截器
// 這里就是注冊了實現事務需要的攔截器
@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;
}
}
實現事務管理的核心就是SpringAOP,而完成SpringAOP的核心就是通知(Advice),通知的核心就是攔截器,關於SpringAOP、切點、通知在之前的文章中已經做過詳細介紹了,所以對這一塊本文就跳過了,我們直接定位到事務管理的核心TransactionInterceptor
。
TransactionInterceptor分析
TransactionInterceptor
實現了MethodInterceptor
,核心方法就是invoke
方法,我們直接定位到這個方法的具體實現邏輯
// invocation:代表了要進行事務管理的方法
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 核心方法就是invokeWithinTransaction
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
invokeWithinTransaction方法分析
這個方法很長,但是主要可以分為三段
- 響應式事務管理
- 標准事務管理
- 通過回調實現事務管理
這里我們只分析標准事務管理
,下面的源碼也只保留標准事務管理相關代碼
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 之前在配置類中注冊了一個AnnotationTransactionAttributeSource
// 這里就是直接返回了之前注冊的那個Bean,通過它去獲取事務屬性
TransactionAttributeSource tas = getTransactionAttributeSource();
// 解析@Transactional注解獲取事務屬性
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
// 獲取對應的事務管理器
final TransactionManager tm = determineTransactionManager(txAttr);
// ...
// 忽略響應式的事務管理
// ...
// 做了個強轉PlatformTransactionManager
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) {
// 方法執行出現異常,在異常情況下完成事務
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 清除線程中的事務信息
cleanupTransactionInfo(txInfo);
}
// ...
// 省略不重要代碼
// ...
// 提交事務
commitTransactionAfterReturning(txInfo);
return retVal;
}
// ....
// 省略回調實現事務管理相關代碼
// ....
return result;
}
}
通過上面這段代碼可以歸納出事務管理的流程如下:
- 獲取事務屬性------->
tas.getTransactionAttribute
- 創建事務------------->
createTransactionIfNecessary
- 執行業務邏輯------->
invocation.proceedWithInvocation
- 異常時完成事務---->
completeTransactionAfterThrowing
- 清除線程中綁定的事務信息----->
cleanupTransactionInfo
- 提交事務------------->
commitTransactionAfterReturning
接下來我們一步步分析
1、獲取事務屬性
// 獲取事務對應的屬性,實際上返回一個AnnotationTransactionAttributeSource
// 之后再調用AnnotationTransactionAttributeSource的getTransactionAttribute
// getTransactionAttribute:先從攔截的方法上找@Transactional注解
// 如果方法上沒有的話,再從方法所在的類上找,如果類上還沒有的話嘗試從接口或者父類上找
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
return null;
}
// 在緩存中查找
Object cacheKey = getCacheKey(method, targetClass);
TransactionAttribute cached = this.attributeCache.get(cacheKey);
if (cached != null) {
if (cached == NULL_TRANSACTION_ATTRIBUTE) {
return null;
}
else {
return cached;
}
}
else {
// 這里真正的去執行解析
TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
// 緩存解析的結果,如果為事務屬性為null,也放入一個標志
// 代表這個方法不需要進行事務管理
if (txAttr == null) {
this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
}
else {
String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
if (txAttr instanceof DefaultTransactionAttribute) {
((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification);
}
this.attributeCache.put(cacheKey, txAttr);
}
return txAttr;
}
}
真正解析注解時調用了computeTransactionAttribute
方法,其代碼如下:
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// 默認情況下allowPublicMethodsOnly為true
// 這意味着@Transactional如果放在非public方法上不會生效
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// method是接口中的方法
// specificMethod是具體實現類的方法
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// 現在目標類方法上找
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
return txAttr;
}
// 再在目標類上找
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
// 降級到接口跟接口中的方法上找這個注解
if (specificMethod != method) {
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
可以看到在computeTransactionAttribute
方法中又進一步調用了findTransactionAttribute
方法,我們一步步跟蹤最終會進入到SpringTransactionAnnotationParser#parseTransactionAnnotation(AnnotationAttributes)
這個方法中
整個調用鏈我這里也畫出來了,感興趣的大家可以跟一下
對於本文,我們直接定位到最后一步,對應源碼如下:
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
// 最終返回的是一個RuleBasedTransactionAttribute
// 在上篇文章分析過了,定義了在出現異常時如何回滾
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
Propagation propagation = attributes.getEnum("propagation");
rbta.setPropagationBehavior(propagation.value());
Isolation isolation = attributes.getEnum("isolation");
rbta.setIsolationLevel(isolation.value());
rbta.setTimeout(attributes.getNumber("timeout").intValue());
rbta.setReadOnly(attributes.getBoolean("readOnly"));
rbta.setQualifier(attributes.getString("value"));
List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
rbta.setRollbackRules(rollbackRules);
return rbta;
}
到這一步很明確了,解析了@Transactional
注解,並將這個注解的屬性封裝到了一個RuleBasedTransactionAttribute
對象中返回。
2、 創建事務
對應代碼如下:
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
// 如果沒有為事務指定名稱,使用切點作為事務名稱
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);
}
// ....省略日志
}
// 將事務相關信息封裝到TransactionInfo對象中
// 並將TransactionInfo綁定到當前線程
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
從上面方法簽名上我們可以看到,創建事務實際上就是創建了一個TransactionInfo
。一個TransactionInfo
對象包含了事務相關的所有信息,例如實現事務使用的事務管理器(PlatformTransactionManager
),事務的屬性(TransactionAttribute
),事務的狀態(transactionStatus
)以及與當前創建的關聯的上一個事務信息(oldTransactionInfo
)。我們可以通過TransactionInfo
對象的hasTransaction
方法判斷是否真正創建了一個事務。
上面的核心代碼只有兩句
tm.getTransaction
,通過事務管理器創建事務prepareTransactionInfo
,封裝TransactionInfo
並綁定到線程上
我們先來看看getTransaction
干了啥,對應代碼如下:
這個代碼應該是整個Spring實現事務管理里面最難的了,因為牽涉到事務的傳播機制,不同傳播級別是如何進行處理的就是下面這段代碼決定的,比較難,希望大家能耐心看完
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 事務的屬性(TransactionAttribute),通過解析@Transacational注解de'dao
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
// 獲取一個數據庫事務對象(DataSourceTransactionObject),
// 這個對象中封裝了一個從當前線程上下文中獲取到的連接
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
// 判斷是否存在事務
// 如果之前獲取到的連接不為空,並且連接上激活了事務,那么就為true
if (isExistingTransaction(transaction)) {
// 如果已經存在了事務,需要根據不同傳播機制進行不同的處理
return handleExistingTransaction(def, transaction, debugEnabled);
}
// 校驗事務的超時設置,默認為-1,代表不進行超時檢查
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
// 檢查隔離級別是否為mandatory(強制性要求必須開啟事務)
// 如果為mandatory,但是沒有事務存在,那么拋出異常
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
// 目前為止沒有事務,並且隔離級別不是mandatory
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
// 當隔離級別為required,required_new,nested時均需要新建事務
// 如果存在同步,將注冊的同步掛起
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;
}
}
else {
// 創建一個空事務,沒有實際的事務提交以及回滾機制
// 會激活同步:將數據庫連接綁定到當前線程上
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
上面這段代碼可以分為兩種情況分析
-
應用程序直接調用了一個被事務管理的方法(直接調用)
-
在一個需要事務管理的方法中調用了另外一個需要事務管理的方法(嵌套調用)
用代碼表示如下:
@Service
public class IndexService {
@Autowired
DmzService dmzService;
// 直接調用
@Transactional
public void directTransaction(){
// ......
}
// 嵌套調用
@Transactional
public void nestedTransaction(){
dmzService.directTransaction();
}
}
ps:我們暫且不考慮自調用的情況,因為自調用可能會出現事務失效,在下篇文章我們專門來聊一聊事務管理中的那些坑
直接調用
我們先來看看直接調用的情況下上述代碼時如何執行的
doGetTransaction
源碼分析
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
// 在創建DataSourceTransactionManager將其設置為了true
// 標志是否允許
txObject.setSavepointAllowed(isNestedTransactionAllowed());
// 從線程上下文中獲取到對應的這個連接池中的連接
// 獲取對應數據源下的這個綁定的連接
// 當我們將數據庫連接綁定到線程上時,實際上綁定到當前線程的是一個map
// 其中key是對應的數據源,value是通過這個數據源獲取的一個連接
ConnectionHolder conHolder =(ConnectionHolder)TransactionSynchronizationManager
.getResource(obtainDataSource());
// 如果當前上下文中已經有這個連接了,那么將newConnectionHolder這個標志設置為false
// 代表復用了之前的連接(不是一個新連接)
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
直接調用的情況下,獲取到的連接肯定為空,所以這里返回的是一個沒有持有數據庫連接的DataSourceTransactionObject
。
isExistingTransaction
源碼分析
protected boolean isExistingTransaction(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// 只有在存在連接並且連接上已經激活了事務才會返回true
return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}
直接調用的情況下,這里肯定是返回fasle
- 從這里可以看出,如果直接調用了一個傳播級別為
MANDATORY
的方法將會拋出異常 - 傳播級別為
required、requires_new、nested
時,會真正開啟事務,對應代碼如下
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
// 如果存在同步,將注冊的同步掛起
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;
}
}
我們先來看看它的掛起操作干了什么,對應代碼如下:
// 直接調用時,傳入的transaction為null
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
// 在之前的代碼中沒有進行任何激活同步的操作,所以不會進入下面這個判斷
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// ...
}
// 傳入的transaction為null,這個判斷也不進
else if (transaction != null) {
// ...
}
else {
return null;
}
}
從上面可以看出,這段代碼其實啥都沒干,OK,減負,直接跳過。
接着,就是真正的開啟事務了,會調用一個startTransaction
方法,對應代碼如下:
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
// 默認情況下,getTransactionSynchronization方法會返回SYNCHRONIZATION_ALWAYS
// 所以這里是true
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// 根據之前的事務定義等相關信息構造一個事務狀態對象
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 真正開啟事務,會從數據源中獲取連接並綁定到線程上
doBegin(transaction, definition);
// 在這里會激活同步
prepareSynchronization(status, definition);
return status;
}
newTransactionStatus
實際上就是調用了DefaultTransactionStatus
的構造函數,我們來看一看每個參數的含義以及實際傳入的是什么。對應代碼如下:
// definition:事務的定義,解析@Transactional注解得到的
// transaction:通過前面的doGetTransaction方法得到的,關聯了一個數據庫連接
// newTransaction:是否是一個新的事務
// debug:是否開啟debug級別日志
// newSynchronization:是否需要一個新的同步
// suspendedResources:代表了執行當前事務時掛起的資源
protected DefaultTransactionStatus newTransactionStatus(
TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
// 是否是一個真實的新的同步
// 除了傳入的標志之外還需要判斷當前線程上的同步是否激活
// 沒有激活才算是一個真正的新的同步
boolean actualNewSynchronization = newSynchronization &&
!TransactionSynchronizationManager.isSynchronizationActive();
// 返回一個事務狀態對象
// 包含了事務的定於、事務使用的連接、事務是否要開啟一個新的同步、事務掛起的資源等
return new DefaultTransactionStatus(
transaction, newTransaction, actualNewSynchronization,
definition.isReadOnly(), debug, suspendedResources);
}
在完成事務狀態對象的構造之后,就是真正的開啟事務了,我們也不難猜出所謂開啟事務其實就是從數據源中獲取一個一個連接並設置autoCommit為false。對應代碼如下:
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
// 判斷txObject中是否存在連接並且連接上已經激活了事務
// txObject是通過之前的doGetTransaction方法得到的
// 直接調用的情況下,這個判斷肯定為true
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
// 從數據源中獲取一個連接
Connection newCon = obtainDataSource().getConnection();
}
// 將連接放入到txObject中
// 第二個參數為true,標志這是一個新連接
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
// 標志連接資源跟事務同步
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
// 應用事務定義中的read only跟隔離級別
// 實際就是調用了Connection的setReadOnly跟setTransactionIsolation方法
// 如果事務定義中的隔離級別跟數據庫默認的隔離級別不一致會返回的是數據庫默認的隔離級別
// 否則返回null
// 主要是為了在事務完成后能將連接狀態恢復
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
// 設置autoCommit為false,顯示開啟事務
if (con.getAutoCommit()) {
// 代表在事務完成后需要將連接重置為autoCommit=true
// 跟之前的previousIsolationLevel作用一樣,都是為了恢復連接
txObject.setMustRestoreAutoCommit(true);
con.setAutoCommit(false);
}
// 是否通過顯示的語句設置read only,默認是不需要的
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
// 設置超時時間
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// 將連接綁定到當前線程上下文中,實際存入到線程上下文的是一個map
// 其中key為數據源,value為從該數據源中獲取的一個連接
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
// 出現異常的話,需要歸還連接
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
歸納起來,doBegin
做了這么幾件事
- 從數據源中獲取一個連接
- 將事務定義應用到這個連接上
- 通過這個連接對象顯示開啟事務
- 將這個連接綁定到線程上下文
在通過doBegin
開啟了事務后,接下來調用了prepareSynchronization
,這個方法的主要目的就是為了准備這個事務需要的同步,對應源碼如下:
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
if (status.isNewSynchronization()) {
// 到這里真正激活了事務
TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
// 隔離級別
// 只有在不是默認隔離級別的情況下才會綁定到線程上,否則綁定一個null
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
definition.getIsolationLevel() : null);
// 是否只讀
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
// 初始化同步行為
TransactionSynchronizationManager.initSynchronization();
}
}
主要對一些信息進行了同步,例如事務真正激活了事務,事務隔離級別,事務名稱,是否只讀。同時初始化了同步事務過程中要執行的一些回調(也就是一些同步的行為)
在前面我們已經介紹了在直接調用的情況下,如果傳播級別為mandatory
會直接拋出異常,傳播級別為required、requires_new、nested
時,會調用startTransaction
真正開啟一個事務,但是除了這幾種傳播級別之外還有supports、not_supported、never
。這幾種傳播級別在直接調用時會做什么呢?前面那張圖我其實已經畫出來了,會開啟一個空事務("empty" transaction
),對應代碼如下:
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// ......
// mandatory拋出異常
// required、requires_new、nested調用startTransaction開啟事務
// supports、not_supported、never會進入下面這個判斷
else {
// 默認同步級別就是always
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
// 直接調用prepareTransactionStatus返回一個事務狀態對象
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
prepareTransactionStatus
代碼如下
// 傳入的參數為:def, null, true, newSynchronization, debugEnabled, null
protected final DefaultTransactionStatus prepareTransactionStatus(
TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
// 調用這個方法時
// definition:傳入的是解析@Transactional注解得到的事務屬性
// transaction:寫死的傳入為null,意味着沒有真正的事務()
// newTransaction:寫死的傳入為true
// newSynchronization:默認同步級別為always,在沒有真正事務的時候也進行同步
// suspendedResources:寫死了,傳入為null,不掛起任何資源
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
prepareSynchronization(status, definition);
return status;
}
可以看到這個方法跟之前介紹的startTransaction
方法相比較下就有幾點不同,最明顯的是少了一個步驟,在prepareTransactionStatus
方法中沒有調用doBegin
方法,這意味這個這個方法不會去獲取數據庫連接,更不會綁定數據庫連接到上下文中,僅僅是做了一個同步的初始化。
其次,startTransaction
方法在調用newTransactionStatus
傳入的第二個參數是從doGetTransaction
方法中獲取的,不可能為null,而調用prepareTransactionStatus
方法時,寫死的傳入為null。這也代表了prepareTransactionStatus
不會真正開啟一個事務。
雖然不會真正開啟一個事務,只是開啟了一個“空事務”,但是當這個空事務完成時仍然會觸發注冊的回調。
嵌套調用
前面已經介紹了在直接調用下七種不同隔離級別在創建事務時的不同表現,代碼看似很多,實際還是比較簡單的,接下來我們要介紹的就是嵌套調用
,也就是已經存在事務的情況下,調用了另外一個被事務管理的方法(並且事務管理是生效的)。我們需要關注的是下面這段代碼
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 事務的屬性(TransactionAttribute)
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
// 從線程上下文中獲取到一個連接,並封裝到一個DataSourceTransactionObject對象中
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
// 判斷之前獲取到的事務上是否有連接,並且連接上激活了事務
if (isExistingTransaction(transaction))
// 嵌套調用處理在這里
return handleExistingTransaction(def, transaction, debugEnabled);
}
// ...
// 下面是直接調用的情況,前文已經分析過了
// 省略直接調用的相關代碼
}
處理嵌套調用的核心代碼其實就是handleExistingTransaction
。但是進入這個方法前首先isExistingTransaction
這個方法得返回true才行,所以我們先來看看isExistingTransaction
這個方法做了什么,代碼如下:
protected boolean isExistingTransaction(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// 首先判斷txObject中是否已經綁定了連接
// 其次判斷這個連接上是否已經激活了事務
return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}
結合我們之前分析的直接調用的代碼邏輯,可以看出,只有外圍的事務的傳播級別為required、requires_new、nested
時,這個判斷才會成立。因為只有在這些傳播級別下才會真正的開啟事務,才會將連接綁定到當前線程,doGetTransaction
方法才能返回一個已經綁定了數據庫連接的事務對象。
在滿足了isExistingTransaction
時,會進入嵌套調用的處理邏輯,也就是handleExistingTransaction
方法,其代碼如下:
代碼很長,但是大家跟着我一步步梳理就可以了,其實邏輯也不算特別復雜
整個代碼邏輯其實就是為了實現事務的傳播,在不同的傳播級別下做不同的事情
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
// 如果嵌套的事務的傳播級別為never,那么直接拋出異常
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
// 如果嵌套的事務的傳播級別為not_soupported,那么掛起外圍事務
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);
}
// 如果嵌套的事務傳播級別為requires_new,那么掛起外圍事務,並且新建一個新的事務
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
return startTransaction(definition, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}
// 如果嵌套事務的傳播級別為nested,會獲取當前線程綁定的數據庫連接
// 並通過數據庫連接創建一個保存點(save point)
// 其實就是調用Connection的setSavepoint方法
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
// 默認是允許的,所以這個判斷不會成立
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
// 默認是true
if (useSavepointForNestedTransaction()) {
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint();
return status;
}
else {
// JTA進行事務管理才會進入這這里,我們不做考慮
return startTransaction(definition, transaction, debugEnabled, null);
}
}
if (debugEnabled) {
logger.debug("Participating in existing transaction");
}
// 嵌套事務傳播級別為supports、required、mandatory時,是否需要校驗嵌套事務的屬性
// 主要校驗的是個隔離級別跟只讀屬性
// 默認是不需要校驗的
// 如果開啟了校驗,那么會判斷如果外圍事務的隔離級別跟嵌套事務的隔離級別是否一致
// 如果不一致,直接拋出異常
if (isValidateExistingTransaction()) {
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
Constants isoConstants = DefaultTransactionDefinition.constants;
// 這里會拋出異常
}
}
// 嵌套事務的只讀為false
if (!definition.isReadOnly()) {
// 但是外圍事務的只讀為true,那么直接拋出異常
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
// 這里會拋出異常
}
}
}
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
觀察上面的代碼我們可以發現,返回值只有兩個情況(不考慮拋出異常)
- 調用了
startTransaction
方法 - 調用了
prepareTransactionStatus
方法
在前面我們已經介紹了這兩個方法的區別,相比較於startTransaction
方法,prepareTransactionStatus
並不會再去數據源中獲取連接。
另外我們還可以發現,只有傳播級別為required_new
的情況才會去調用startTransaction
方法(不考慮JTA),也就是說,只有required_new
才會真正的獲取一個新的數據庫連接,其余的情況如果支持事務的話都是復用了外圍事務獲取到的連接,也就是說它們其實是加入了外圍的事務中,例如supports、required、mandatory、nested
,其中nested
又比較特殊,因為它不僅僅是單純的加入了外圍的事務,而且在加入前設置了一個保存點,如果僅僅是嵌套事務發生了異常,會回滾到之前設置的這個保存點上。另外需要注意的是,因為是直接復用了外部事務的連接,所以supports、required、mandatory、nested
這幾種傳播級別下,嵌套的事務會隨着外部事務的提交而提交,同時也會跟着外部事物的回滾而回滾。
接下來我們開始細節性的分析上邊的代碼,對於傳播級別為never,沒啥好說的,直接拋出異常,因為不支持在事務中運行嘛~
當傳播級別為not_supported
時會進入下面這段代碼
// 如果嵌套的事務的傳播級別為not_soupported,那么掛起外圍事務
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);
}
主要就是兩個動作,首先掛起外圍事務。很多同學可能不是很理解掛起這個詞的含義,掛起實際上做了什么呢?
- 清空外圍事務綁定在線程上的同步
- 掛起是因為將來還要恢復,所以不能單純的只是清空呀,還得將清空的信息保存到當前的事務上,這樣當當前的事務完成后可以恢復到掛起時的狀態,以便於外圍的事務能夠正確的運行下去
其中第一步對應的代碼如下:
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
// 嵌套調用下,同步是已經被激活了的
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 解綁線程上綁定的同步回調,並返回
List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
try {
Object suspendedResources = null;
if (transaction != null) {
// 這里實際上就是解綁線程上綁定的數據庫連接
// 同時返回這個連接
suspendedResources = doSuspend(transaction);
}
// 解綁線程上綁定的事務屬性並返回
String name = TransactionSynchronizationManager.getCurrentTransactionName();
TransactionSynchronizationManager.setCurrentTransactionName(null);
boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
TransactionSynchronizationManager.setActualTransactionActive(false);
// 最后集中封裝為一個SuspendedResourcesHolder返回
return new SuspendedResourcesHolder(
suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
}
catch (RuntimeException | Error ex) {
// 出現異常的話,恢復
doResumeSynchronization(suspendedSynchronizations);
throw ex;
}
}
else if (transaction != null) {
// 如果沒有激活同步,那么也需要將連接掛起
Object suspendedResources = doSuspend(transaction);
return new SuspendedResourcesHolder(suspendedResources);
}
else {
// Neither transaction nor synchronization active.
return null;
}
}
在上面的代碼我們要注意到一個操作,它會清空線程綁定的數據庫連接,同時在后續操作中也不會再去獲取一個數據庫連接重新綁定到當前線程上,所以not_supported
傳播級別下每次執行SQL都可能使用的不是同一個數據庫連接對象(依賴於業務中獲取連接的方式)。這點大家要注意,跟后面的幾種有很大的區別。
獲取到需要掛起的資源后,調用了prepareTransactionStatus
,這個方法我們之前分析過了,但是在這里傳入的參數是不同的
// definition:非null,解析事務注解得來的
// transaction:null
// newTransaction:false
// newSynchronization:true
// suspendedResources:代表掛起的資源,包括連接以及同步
protected final DefaultTransactionStatus prepareTransactionStatus(
TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
prepareSynchronization(status, definition);
return status;
}
最終會返回一個這樣的事務狀態對象,其中的transaction
為null、newTransaction
為false,這二者代表了不存在一個真正的事務。在后續的事務提交跟回滾時會根據事務狀態對象中的這兩個屬性來判斷是否需要真正執行回滾,如果不存在真正的事務,那么也就沒有必要去回滾(當然,這只是針對內部的空事務而言,如果拋出的異常同時中斷了外部事務,那么外部事務還是會回滾的)。除了這兩個屬性外,還有newSynchronization
,因為在掛起同步時已經將之前的同步清空了,所以newSynchronization
仍然為true,這個屬性會影響后續的一些同步回調,只有為true的時候才會執行回調操作。最后就是suspendedResources
,后續需要根據這個屬性來恢復外部事務的狀態。
當傳播級別為requires_new
時,會進入下面這段代碼
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
// 第一步,也是掛起
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
// 第二步,開啟一個新事務,會綁定一個新的連接到當前線程上
return startTransaction(definition, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error beginEx) {
// 出現異常,恢復外部事務狀態
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}
很簡單吧,掛起事務,然后新開一個事務,並且新事務的連接跟外圍事務的不一樣。也就是說這兩個事務互不影響。它也會返回一個事務狀態對象,但是不同的是,transaction
不為null、newTransaction
為true。也就是說它有自己的提交跟回滾機制,也不難理解,畢竟是兩個不同的數據庫連接嘛~
當傳播級別為nested
進入下面這段代碼
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (useSavepointForNestedTransaction()) {
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint();
return status;
}
else {
// JTA相關,不考慮
return startTransaction(definition, transaction, debugEnabled, null);
}
}
前面其實已經介紹過了,也很簡單。還是把重點放在返回的事務狀態對象中的那幾個關鍵屬性上,transaction
不為null,但是newTransaction
為false,也就是說它也不是一個新事務,另外需要注意的是,它沒有掛起任何事務相關的資源,僅僅是創建了一個保存點而已。這個事務在回滾時,只會回滾到指定的保存點。同時因為它跟外圍事務共用一個連接,所以它會跟隨外圍事務的提交而提交,回滾而回滾。
剩下的supports、required、mandatory
這幾種傳播級別都會進入下面這段代碼
// 省略了校驗相關代碼,前面已經介紹過了,默認是關閉校驗的
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
我們會發現相比較於nested
只是少了一個創建保存點的動作。最終返回的事務狀態對象中的屬性,transaction
不為null,但是newTransaction
為false,也就是說它也不是一個新事務。同時因為沒有掛起外圍事務的同步,所以它也不是一個新的同步(newSynchronization為false
)。
對於每個隔離級別下返回的事務狀態對象中的屬性希望大家有一定了解,因為后續的回滾、提交等操作都依賴於這個事務狀態對象。
到目前為止,我們就介紹完了事務的創建,緊接着就是真正的執行業務代碼了,要保證業務代碼能被事務管理,最重要的一點是保證在業務代碼中執行SQL時仍然是使用我們在開啟事務時綁定到線程上的數據庫連接。那么是如何保證的呢?我們分為兩種情況討論
- 使用
JdbcTemplate
訪問數據庫 - 使用
Mybatis
訪問數據庫
對於第二種,可能需要你對Mybatis
稍微有些了解
3、執行業務代碼
JdbcTemplate
我們要討論的只是
JdbcTemplate
是如何獲取到之前綁定在線程上的連接這個問題,不要想的太復雜
在事務專題的第一篇我就對JdbcTemplate
做了一個簡單的源碼分析,它底層獲取數據庫連接實際上就是調用了DataSourceUtils#doGetConnection
方法,代碼如下:
關鍵代碼我已經標出來了,實際上獲取連接時也是從線程上下文中獲取
Mybatis
Mybatis相對來說比較復雜,大家目前做為了解即可。當Spring整合Mybatis時,事務是交由Spring來管理的,那么Spring是如何接管Mybatis的事務的呢?核心代碼位於SqlSessionFactoryBean#buildSqlSessionFactory
方法中。其中有這么一段代碼
在這里替換掉了Mybatis的事務工廠(Mybatis
依賴事務工廠創建的事務對象來獲取連接),使用了Spring自己實現的一個事務工廠SpringManagedTransactionFactory
。通過它可以獲取一個事務對象SpringManagedTransaction
。我們會發現這個事務對象在獲取連接時調用的也是DataSourceUtils
的方法
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
}
所以它也能保證使用的是開啟事務時綁定在線程上的連接,從而保證事務的正確性。
4、執行出現異常
出現異常時其實分為兩種情況,並不是一定會回滾
對應代碼如下:
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
// transactionAttribute是從@Transactional注解中解析得來的
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
// 回滾
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
// 省略異常處理
}
else {
try {
// 即使出現異常仍然提交事務
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
// 省略異常處理
}
}
}
可以看到,只有在滿足txInfo.transactionAttribute.rollbackOn(ex)
這個條件時才會真正執行回滾,否則即使出現了異常也會先提交事務。這個條件取決於@Transactiona
注解中的rollbackFor
屬性是如何配置的,如果不進行配置的話,默認只會對RuntimeException
或者Error
進行回滾。
在進行回滾時會調用事務管理器的rollback
方法,對應代碼如下:
public final void rollback(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;
// 這里真正處理回滾
processRollback(defStatus, false);
}
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
// 傳入時寫死的為false
boolean unexpectedRollback = unexpected;
try {
// 觸發之前注冊的同步回調
triggerBeforeCompletion(status);
// 存在保存點,根據我們之前的分析,說明這是一個嵌套調用的事務
// 並且內部事務的傳播級別為nested
if (status.hasSavepoint()) {
// 這里會回滾到定義的保存點
status.rollbackToHeldSavepoint();
}
// 根據我們之前的分析有兩種情況會滿足下面這個判斷
// 1.直接調用,傳播級別為nested、required、requires_new
// 2.嵌套調用,並且內部事務的傳播級別為requires_new
else if (status.isNewTransaction()) {
// 直接獲取當前線程上綁定的數據庫連接並調用其rollback方法
doRollback(status);
}
else {
// 到這里說明存在事務,但是不是一個新事務並且沒有保存點
// 也就是嵌套調用並且內部事務的傳播級別為supports、required、mandatory
if (status.hasTransaction()) {
// status.isLocalRollbackOnly,代表事務的結果只能為回滾
// 默認是false的,在整個流程中沒有看到修改這個屬性
// isGlobalRollbackOnParticipationFailure
// 這個屬性的含義是在加入的事務失敗時是否回滾整個事務,默認為true
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
// 從這里可以看出,但內部的事務發生異常時會將整個大事務標記成回滾
doSetRollbackOnly(status);
}
else {
// 進入這個判斷說明修改了全局配置isGlobalRollbackOnParticipationFailure
// 內部事務異常並不影響外部事務
}
}
else {
// 不存在事務,回滾不做任何操作
logger.debug("Should roll back transaction but cannot - no transaction available");
}
// isFailEarlyOnGlobalRollbackOnly這個參數默認為false
// unexpectedRollback的值一開始就被賦值成了false
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
// 觸發同步回調
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// unexpectedRollback是false
// 這個值如果為true,說明
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
// 在事務完成后需要執行一些清理動作
cleanupAfterCompletion(status);
}
}
上面的代碼結合注釋看起來應該都非常簡單,我們最后關注一下cleanupAfterCompletion
這個方法,對應代碼如下
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
// 將事務狀態修改為已完成
status.setCompleted();
// 是否是新的同步
// 清理掉線程綁定的所有同步信息
// 直接調用時,在任意傳播級別下這個條件都是滿足的
// 嵌套調用時,只有傳播級別為not_supported、requires_new才會滿足
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
// 是否是一個新的事務
// 直接調用下,required、requires_new、nested都是新開的一個事務
// 嵌套調用下,只有requires_new會新起一個事務
if (status.isNewTransaction()) {
// 真正執行清理
doCleanupAfterCompletion(status.getTransaction());
}
// 如果存在掛起的資源,將掛起的資源恢復
// 恢復的操作跟掛起的操作正好相反
// 就是將之前從線程解綁的資源(數據庫連接等)已經同步回調重新綁定到線程上
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
logger.debug("Resuming suspended transaction after completion of inner transaction");
}
Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
}
}
再來看看doCleanupAfterCompletion
到底做了什么,源碼如下:
protected void doCleanupAfterCompletion(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// 先判斷是否是一個新連接
// 直接調用,如果真正開啟了一個事務必定是個連接
// 但是嵌套調用時,只有requires_new會新起一個連接,其余的都是復用外部事務的連接
// 這種情況下不能將連接從線程上下文中清除,因為外部事務還需要使用
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
// 恢復連接的狀態
// 1.重新將連接設置為自動提交
// 2.恢復隔離級別
// 3.將read only重新設置為false
Connection con = txObject.getConnectionHolder().getConnection();
try {
if (txObject.isMustRestoreAutoCommit()) {
con.setAutoCommit(true);
}
DataSourceUtils.resetConnectionAfterTransaction(
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
}
catch (Throwable ex) {
logger.debug("Could not reset JDBC Connection after transaction", ex);
}
// 最后,因為事務已經完成了所以歸還連接
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, this.dataSource);
}
// 將事務對象中綁定的連接相關信息也清空掉
txObject.getConnectionHolder().clear();
}
5、提交事務
提交事務有兩種情況
- 正常執行完成,提交事務
- 出現異常,但是不滿足回滾條件,仍然提交事務
但是不管哪種清空最終都會調用
AbstractPlatformTransactionManager#commit
方法,所以我們就直接分析這個方法
代碼如下:
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");
}
// 這里會檢查事務的狀態是否被設置成了只能回滾
// 這里檢查的是事務狀態對象中的rollbackOnly屬性
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
processRollback(defStatus, false);
return;
}
// 這里會檢查事務對象本身是否被設置成了rollbackOnly
// 之前我們在分析回滾的代碼時知道,當內部的事務發生回滾時(supports、required)
// 默認情況下會將整個事務對象標記為回滾,實際上在外部事務提交時就會進入這個判斷
// shouldCommitOnGlobalRollbackOnly:在全局被標記成回滾時是否還要提交,默認為false
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
processRollback(defStatus, true);
return;
}
// 真正處理提交
processCommit(defStatus);
}
可以看到,即使進入了提交事務的邏輯還是可能回滾。
- 事務狀態對象(TransactionStatus)中的狀態被設置成了
rollbackOnly
- 事務對象本身(DataSourceTransactionObject)被設置成了
rollbackOnly
二者在回滾時都是調用了processRollback
方法,但是稍有區別,通過事務狀態對象造成的回滾最終在回滾后並不會拋出異常,但是事務對象本身會拋出異常給調用者。
真正處理提交的邏輯在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();
}
// 直接調用,傳播級別為required、nested、requires_new
// 嵌套調用,且傳播級別為requires_new
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
// 雖然前面已經檢查過rollbackOnly了,在shouldCommitOnGlobalRollbackOnly為true時
// 仍然需要提交事務,將unexpectedRollback設置為true,意味着提交事務后仍要拋出異常
unexpectedRollback = status.isGlobalRollbackOnly();
// 提交的邏輯很簡單,調用了connection的commit方法
doCommit(status);
}
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// unexpectedRollback為true時,進入這個catch塊
// 觸發同步回調
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
// connection的doCommit方法拋出SqlException時進入這里
if (isRollbackOnCommitFailure()) {
// 在doCommit方法失敗后是否進行回滾,默認為false
doRollbackOnCommitException(status, ex);
}
else {
// 觸發同步回調
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException | Error ex) {
// 剩余其它異常進入這個catch塊
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
// 觸發同步回調
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
// 跟回滾時一樣,做一些清理動作
cleanupAfterCompletion(status);
}
}
整個邏輯還是比較簡單的,最核心的步驟就是調用了數據庫連接對象(Connection)的commit方法。
總結
本文主要分析了Spring中事務的實現機制,從事務實現的入口---->EnableTransactionManagement
注解,到事務實現的核心TransactionInterceptor
都做了詳細的分析。其中最復雜的一塊在於事務的創建,也就是createTransactionIfNecessary
,既要考慮直接調用的情況,也要考慮嵌套調用的情況,並且還需要針對不同的傳播級別做不同的處理。對於整個事務管理,我們需要分清楚下面這幾個對象的含義
- TransactionDefinition
- TransactionStatus
- DataSourceTransactionObject
- TransactionInfo
TransactionDefinition
代表的是我們通過@Transactional
注解指定的事務的屬性,包括是否只讀、超時、回滾等。
TransactionStatus
代表的是事務的狀態,這個狀態分為很多方面,比如事務的運行狀態(是否完成、是否被標記為rollbakOnly),事務的同步狀態(這個事務是否開啟了一個新的同步),還有事務是否設置了保存點。更准確的來說,TransactionStatus
代表的是被@Transactional
注解修飾的方法的狀態,只要被@Transactional
注解修飾了,在執行通知邏輯時就會生成一個TransactionStatus
對象,即使這個方法沒有真正的開啟一個數據庫事務
DataSourceTransactionObject
代表的是一個數據庫事務對象,實際上保存的就是一個數據庫連接以及這個連接的狀態。
TransactionInfo
代表的是事務相關的所有信息,組合了事務管理器,事務狀態,事務定義並持有一個舊的TransactionInfo
引用,這個對象在事務管理的流程中其實沒有實際的作用,主要的目的是為了讓我們在事務的運行過程中獲取到事務的相關信息,我們可以直接調用TransactionAspectSupport
的currentTransactionInfo
方法獲取到當前線程綁定的TransactionInfo
對象。
另外,看完本文希望大家對於事務的傳播機制能有更深一點的認知,而不是停留在概念上,要講清楚事務的傳播機制我們先做以下定。
-
凡是在執行代理方法的過程中從數據源重新獲取了連接並調用了其
setAtuoCommit(false)
方法,而且還將這個連接綁定到了線程上下文中,我們就認為它新建了一個事務。注意了,有三個要素-
連接是從數據源中獲取到的
-
調用了連接的
setAtuoCommit(false)
方法 -
這個連接被綁定到了線程上下文中
-
-
凡是在執行代理方法的過程中,掛起了外部事務,並且沒有新建事務,那么我們認為這個這個方法是以非事務的方式運行的。
-
凡是在執行代理方法的過程中,沒有掛起外部事務,但是也沒有新建事務,那么我們認為這個方法加入了外部的事務
掛起事務的表現在於清空了線程上下文中綁定的連接!
同時我們分為兩種情況討論來分析傳播機制
- 直接調用
- 間接調用
直接調用
不存在加入外部事務這么一說,要么就是新建事務,要么就是以非事務的方式運行,當然,也可能拋出異常。
傳播級別 | 運行方式 |
---|---|
requires_new | 新建事務 |
nested | 新建事務 |
required | 新建事務 |
supports | 非事務的方式運行 |
not_supported | 非事務的方式運行 |
never | 非事務的方式運行 |
mandatory | 拋出異常 |
間接調用
時我們要分兩種情況討論
- 外部的方法新建了事務
- 外部的方法本身就是非事務的方式運行的
在外部方法新建事務的情況下時(說明外部事務的傳播級別為requires_new
,nested
或者required
),當前方法的運行方式如下表所示
傳播級別 | 運行方式 |
---|---|
requires_new | 新建事務 |
nested | |
required | 直接加入到外部事務 |
supports | 直接加入到外部事務 |
not_supported | 掛起外部事務,以非事務的方式運行 |
never | 拋出異常 |
mandatory | 直接加入到外部事務 |
當外部方法沒有新建事務時,其實它的運行方式跟直接調用是一樣的,這里就不贅述了。
本文到這里就結束了,文章很長,希望你可以耐心看完哈~
碼到手酸,求個三連啊!啊!啊!
如果本文對你由幫助的話,記得點個贊吧!也歡迎關注我的公眾號,微信搜索:程序員DMZ,或者掃描下方二維碼,跟着我一起認認真真學Java,踏踏實實做一個coder。
我叫DMZ,一個在學習路上匍匐前行的小菜鳥!