一、Spring事務管理的特點
Spring框架為事務管理提供一套統一的抽象,帶來的好處有:
1. 跨不同事務API的統一的編程模型,無論你使用的是jdbc、jta、jpa、hibernate。
2. 支持聲明式事務
3. 簡單的事務管理API
4. 能與Spring的數據訪問抽象層完美集成
說明:Spring的事物管理是用AOP實現的
二、事務概念學習
1. Isolation隔離級別
此事務與其他事務的工作隔離的程度。例如,該事務能否看到來自其他事務的未提交的寫操作
READ_UNCOMMITTED讀未提交
READ_COMMITTED讀提交
REPEATABLE_READ可重復讀
SERIALIZABLE序列化(串行)
2. Read/Write讀寫
該事務操作是讀、還是寫、還是有讀有寫
3. Timeout超時
對事務執行的時長設置一個閥值,如果超過閥值還未完成則回滾。
4. Propagation傳播行為
當一個方法開啟事務后,在方法中調用了其他的方法,其他方法可能也需要事務管理,此時就涉及事務該如何傳播了。
4.1. TransactionDefinition.PROPAGATION_REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。
4.2. TransactionDefinition.PROPAGATION_REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起。
4.3. TransactionDefinition.PROPAGATION_SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。
4.4. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起。
4.5. TransactionDefinition.PROPAGATION_NEVER:以非事務方式運行,如果當前存在事務,則拋出異常。
4.6. TransactionDefinition.PROPAGATION_MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。
4.7. TransactionDefinition.PROPAGATION_NESTED:如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價於TransactionDefinition.PROPAGATION_REQUIRED。
幾種事物傳播行為的情況:
5. SavePoint保存點
事務中可以設置一些保存點(階段標識),回滾時可以指定回滾到前面的哪個保存點。
6. Commit/Rollback提交/回滾
提交、回滾事務
三、Spring事務使用學習
1. 配置事務管理器
<!-- 配置事務管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
2. 注解方式
2.1 開啟注解支持
在xml中開啟注解方式支持
<!-- 開啟注解方式的事務配置支持--> <tx:annotation-driven transaction-manager="txManager"/>
或在java代碼中以注解的方式(@EnableTransactionManagement)開啟注解方式支持
package com.study.leesmall.spring.sample.tx; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import org.springframework.transaction.annotation.EnableTransactionManagement; import com.study.leesmall.spring.sample.tx.entity.User; import com.study.leesmall.spring.sample.tx.service.UserService; @Configuration @ComponentScan("com.study.leesmall.spring.sample.tx") @ImportResource("classpath:com/study/leesmall/spring/sample/tx/application.xml") @EnableTransactionManagement public class TxMain { public static void main(String[] args) { try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TxMain.class);) { User user = new User(); user.setId("1234564"); user.setUserName("leesmall-666666666"); UserService userService = context.getBean(UserService.class); userService.insertUser(user); } } }
2.2 在要加事務管理的類或方法上加@Transactional注解
package com.study.leesmall.spring.sample.jta.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.study.leesmall.spring.sample.jta.dao.LogDao; import com.study.leesmall.spring.sample.jta.entity.Log; @Service public class LogService { @Autowired private LogDao logDao; @Transactional public void insertLog(Log log) { this.logDao.insert(log); } }
2.3 掌握@Transactional的屬性配置
說明:rollbackFor默認情況下是對RuntimeException進行回滾。
3. 聲明式事務配置
Spring提供基於AOP的聲明式事務管理,讓我們的事務管理變得簡單、易用!
<!-- 數據源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 配置初始化大小、最小、最大連接數 --> <property name="initialSize" value="1" /> <property name="minIdle" value="1" /> <property name="maxActive" value="10" /> </bean> <!-- 配置事務管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- ********************** 聲明式事務配置 begin ************** --> <!-- 配置事務增強的advice --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- all methods starting with 'get' are read-only --> <tx:method name="get*" read-only="true" /> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*" /> </tx:attributes> </tx:advice> <!-- 配置事務的AOP切面 --> <aop:config> <aop:pointcut id="allService" expression="execution(* com.study.leesmall.spring.sample.tx.service.*Service.*(..)))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="allService"/> </aop:config>
<!-- ********************** 聲明式事務配置 end ************** -->
3.1 掌握<tx:method>的各項屬性的配置
4. 編程式事務管理
@Autowired
private PlatformTransactionManager txManager;
public User insertUser(User u) { // 1、創建事務定義 TransactionDefinition definition = new DefaultTransactionDefinition(); // 2、根據定義開啟事務 TransactionStatus status = txManager.getTransaction(definition); try { this.userDao.insert(u); // 3、提交事務 txManager.commit(status); return this.userDao.find(u.getId()); } catch (Exception e) { // 4、異常了,回滾事務 txManager.rollback(status); throw e; } }
4.1 TransactionTemplate中的代碼示例:
@Override @Nullable public <T> T execute(TransactionCallback<T> action) throws TransactionException { Assert.state(this.transactionManager != null, "No PlatformTransactionManager set"); if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) { return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action); } else { TransactionStatus status = this.transactionManager.getTransaction(this); T result; try { result = action.doInTransaction(status); } catch (RuntimeException | Error ex) { // Transactional code threw application exception -> rollback rollbackOnException(status, ex); throw ex; } catch (Throwable ex) { // Transactional code threw unexpected exception -> rollback rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); } this.transactionManager.commit(status); return result; } }
四、Spring事務管理API學習
Spring為事務管理提供了統一的抽象建模,這樣我們使用Spring來進行事務管理時,就只需要學會這套API即可,無論底下使用的是何種事務管理方式,jdbc也好,jpa也好,hibernate也好,jta也好。我們的業務代碼中都是面向Spring的事務API。大大降低了我們的學習、使用成本
1.TransactionDefinition
TransactionDefinition:事務定義。Spring事務管理框架將為我們管理事務,但不清楚該如何替我們管理,我們就通過事務定義來定義我們需要的事務管理信息,把這些信給事務管理器,它就知道我們的意圖了。
1.1 來看看它都定義了哪些信息項:
1.2 來看看它有什么實現類可用
1.3 看下TransactionAttribute
1.4 請看下DefaultTransactionDefinition、TransactinoTemplate、DefaultTransactionAttribute的源碼
1.5 請把這個繼承體系的類圖畫出來
2. PlatformTransactionManager
PlatformTransactionManager平台級的事務管理器,它抽象定義了事務管理行為,不同的事務管理實現實現該接口。我們編程面向該接口。
2.1 看下PlatformTransactionManager中定義的事務管理行為:
請仔細看源碼中每個方法的的注釋。
2.2 看下PlatformTransactionManager的子類有哪些:
3. TransactionStatus
TransactionStatus 事務狀態,持有事務的狀態信息。事務管理代碼可通過它獲取事務狀態、以及顯式地設置回滾(代替異常的方式)。它繼承了SavePoint接口。在它的實現中會持有事務的很多對象:如事務對象、被掛起的事務資源等等。
從TransactionManager中獲取事務得到它,提交/回滾事務時要給入它:
看 TransactionTemplate 中的使用示例:
3.1 看下它定義的方法
3.2 看它有哪些實現類
3.3 看下AbstractTransactionStatus、DefaultTransactionStatus中定義了哪些屬性
五、Spring事務源碼學習
1. AbstractPlatformTransactionManager
AbstractPlatformTransactionManager是PlatformTransactionManager的實現類
PlatformTransactionManager對應的源代碼:
PlatformTransactionManager的子類:
下面來看一下AbstractPlatformTransactionManager中的三個方法的實現邏輯
getTransaction()
commit()
rollback()
1.1 看一下獲取事物的方法org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(TransactionDefinition)
1.1.1 看一下事物已存在時的處理邏輯
1.1.2 看一下事物掛起時的處理邏輯
1.2 看一下提交事物的方法org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(TransactionStatus)
1.3 看一下事物回滾的方法
org.springframework.transaction.support.AbstractPlatformTransactionManager.rollback(TransactionStatus)
org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(DefaultTransactionStatus, boolean)
總結這里的設計模式、設計原則:
面向接口編程
抽象類:固定不變的實現,提供模板方法,具體子類實現模板方法
一句話:接口、抽象類、模板方法、具體子類。Spring中很多地方用了。
2. DataSourceTransactionManager
DataSourceTransactionManager是基於jdbc connection的本地事務管理實現。多個方法調用參與到同一個事務,是通過共用connection來完成的
方法一:UserService.insertUser調用了方法二:logService.insertLog(log),兩個都加事務定義,驗證以下幾種傳播:
方法一required---方法二required
方法一required---方法二requires_new
方法一required---方法二nested
2.1 准備
Xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 加載配置參數 --> <context:property-placeholder location="classpath:com/study/leesmall/spring/sample/tx/application.properties"/> <!-- 數據源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> <!-- 配置初始化大小、最小、最大連接數 --> <property name="initialSize" value="1" /> <property name="minIdle" value="1" /> <property name="maxActive" value="10" /> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" scope="prototype"> <property name="dataSource" ref="dataSource" /> </bean> <!-- ******************* 事務管理配置 begin ********************************** --> <!-- 配置事務管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- ******************* 事務管理配置 end ********************************** --> </beans>
UserService:
@Service public class UserService { @Autowired private UserDao userDao; @Autowired private LogService logService; @Transactional public User insertUser(User u) { this.userDao.insert(u); Log log = new Log(System.currentTimeMillis() + "", System.currentTimeMillis() + "-" + u.getUserName()); this.logService.insertLog(log); return this.userDao.find(u.getId()); } }
LogService:
@Service public class LogService { @Autowired private LogDao logDao; @Transactional // @Transactional(propagation = Propagation.REQUIRES_NEW) // @Transactional(propagation = Propagation.NESTED) public void insertLog(Log log) { this.logDao.insert(log); } }
TxMain:
package com.study.leesmall.spring.sample.tx; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import org.springframework.transaction.annotation.EnableTransactionManagement; import com.study.leesmall.spring.sample.tx.entity.User; import com.study.leesmall.spring.sample.tx.service.UserService; @Configuration @ComponentScan("com.study.leesmall.spring.sample.tx") @ImportResource("classpath:com/study/leesmall/spring/sample/tx/application.xml") @EnableTransactionManagement public class TxMain { public static void main(String[] args) { try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TxMain.class);) { User user = new User(); user.setId("1234564"); user.setUserName("leesmall-666666666"); UserService userService = context.getBean(UserService.class); userService.insertUser(user); } } }
在AbstractPlatformTransactionManager.getTransaction方法里面打斷點拿到調用棧去分析
接下來跟代碼,重點是要找到 Connection 綁定到線程,綁定到了哪里
調用棧如下:
看完getTransaction即可。現在已有connection了,接下來業務代碼中使用connection地方
找到JdbcTemplate的update操作的獲取connection的代碼,加斷點,然后F8,讓程序執行到這里,看下調用棧。
F5進入DataSourceUtils.getConnection(obtainDataSource()),看它如何獲取Connection
調用棧如下:
第二次進到 getTransaction()
3. 聲明式事務過程源碼學習
先看一下聲明式事務配置的示例:
<!-- ********************** 聲明式事務配置 ************** --> <!-- 配置事務增強的advice --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- all methods starting with 'get' are read-only --> <tx:method name="get*" read-only="true" /> <!-- other methods use the default transaction settings (see below) --> <tx:method name="*" /> </tx:attributes> </tx:advice> <!-- 配置事務的AOP切面 --> <aop:config> <aop:pointcut id="allService" expression="execution(* com.study.leesmall.spring.sample.tx.service.*Service.*(..)))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="allService"/> </aop:config>
3.1 標簽解析
1)入口
E:\lib\spring-tx-5.1.3.RELEASE.jar
/META-INF/spring.handlers
http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler
3.2、<tx:advice>標簽的解析器TxAdviceBeanDefinitionParser
a)思考:<tx:advice>advice注冊的會是個什么advice?
b)瀏覽TxAdviceBeanDefinitionParser的代碼,了解<tx:advice><tx:attributes>
<tx:method>的解析過程,產出了什么。
c)瀏覽TransactionInterceptor的代碼,重點:invoke(MethodInvocationinvocation)方法
invokeWithinTransaction方法
d)瀏覽TransactionInterceptor的繼承體系,事務處理的邏輯都在TransactionAspectSupport中
e)瀏覽TransactionAspectSupport
它里面有如下屬性:
它的重點方法是invokeWithinTransaction
f)瀏覽TransactionAttributeSource接口定義
g)瀏覽TransactionAttributeSource的繼承體系
h)再來看下TransactionAttribute,可能已不記得它是什么了
1)解析<tx:advice>標簽
org.springframework.transaction.config.TxAdviceBeanDefinitionParser.doParse(Element, ParserContext, BeanDefinitionBuilder)
org.springframework.transaction.config.TxAdviceBeanDefinitionParser.parseAttributeSource(Element, ParserContext)
解析<tx:advice id="txAdvice" />標簽的子標簽<tx:attributes>
2)分析TransactionInterceptor
TransactionInterceptor的繼承體系:
父類:
子類沒有:
org.springframework.transaction.interceptor.TransactionInterceptor.invoke(MethodInvocation)
事務處理的邏輯都在TransactionAspectSupport中
瀏覽TransactionAspectSupport
它里面有如下屬性:
它的重點方法是invokeWithinTransaction
org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(Method, Class<?>, InvocationCallback)
org.springframework.transaction.interceptor.TransactionAspectSupport.getTransactionAttributeSource()
org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(PlatformTransactionManager, TransactionAttribute, String)
org.springframework.transaction.interceptor.TransactionAspectSupport.completeTransactionAfterThrowing(TransactionInfo, Throwable)
3)瀏覽TransactionAttributeSource接口定義
TransactionAttributeSource的繼承體系:
3. 事務處理的監聽
事務處理結果的監聽:Spring里面可以對事物處理的結果進行監聽
https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#transaction-event
4. 注解方式事務過程學習
開啟注解支持:
<tx:annotation-driven transaction-manager="txManager"/>
1)入口
E:\lib\spring-tx-5.1.3.RELEASE.jar
/META-INF/spring.handlers
http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler
org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser
org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser.parse(Element, ParserContext)
org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser.AopAutoProxyConfigurer.configureAutoProxyCreator(Element, ParserContext)
org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor
org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut
org.springframework.aop.support.StaticMethodMatcherPointcut
org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
什么時候創建代理?
創建代理:getBean()創建Bean實例的時候
判斷是否要創建代理?
怎么判斷?Advisor的Pointcut是否匹配。事務是不是一個advisor
怎么使用調用它的方法?
切面增強、事務
2)開啟注解支持的另外一種方式:
@EnableTransactionManagement
2.1)來看@EnableTransactionManagement這個注解的定義
@EnableTransactionManagement起作用靠:
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
@Import等價於在TxMain上面導入了一個包
2.2)看TransactionManagementConfigurationSelector
和<tx:annotation-driven transaction-manager="txManager"/>的實現是一樣的
/** * Returns {@link ProxyTransactionManagementConfiguration} or * {@code AspectJ(Jta)TransactionManagementConfiguration} for {@code PROXY} * and {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()}, * respectively. */ @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; } }
2.3)看org.springframework.context.annotation.AutoProxyRegistrar實現的接口方法:
2.4)看org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration類
2.5)@import在哪里被解析的
ContextNamespaceHandler
AnnotationConfigBeanDefinitionParser
ConfigurationClassPostProcessor
ConfigurationClassParser
org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass()
org.springframework.context.annotation.ConfigurationClassParser
啟動的時候,解析@EnableTransactionManagement,完成事務管理相關的AOP bean注冊
剩下的事情都交給AOP
Xml:
啟動的時候,完成事務管理相關的AOP bean注冊
完整代碼獲取地址:https://github.com/leeSmall/FrameSourceCodeStudy/tree/master/spring-source-study
官網學習鏈接:
https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#transaction