1. Spring Tx 編程
整個Spring事務可以通過編程與聲明兩種方式進行操作:
1. AbstractPlatformTransactionManager: 通過其子類來自己控制事務開始, 提交, 回滾等操作
2. TransactionTemplate: 通過傳入一個回調函數來控制程序的執行, 其內部還是通過 AbstractPlatformTransactionManager的子類
3. TransactionProxyFactoryBean: 通過在配置文件中配置 transactionAttributes 來決定對哪些方法進行事務的作用, 此時的 TransactionAttributes獲取器是 NameMatchTransactionAttributeSource <-- 這站方式使用得比較少了
4. 通過 Tx namespace 的方式來聲明事務
5. 通過在方法上標注 @Transactional 注解的方式來實現聲明式事務
2. Spring Tx 編程式事務 AbstractPlatformTransactionManager
AbstractPlatformTransactionManager 是事務操作的基礎類, 它具有 begin(事務開啟), suspend(掛起事務, 比如 PROPAGATION_REQUIRES_NEW 級別), resume (將掛起的事務重用, 往往在上個事務提交會回滾后), commit(事務的提交), rollback(事務的回滾, 默認我們遇到 RuntimeException | Error 時回滾), 當然這些方法都是模版方法, 都是留給對應ORM的子類去實現的, 比如我們最常用的 DataSourceTransactionManager, 下面的 Demo 就是基于 DataSourceTransactionManager :
private static final String CREATE_TABLE_SQL = "create table test" +
"(id int(10) AUTO_INCREMENT PRIMARY KEY, " +
"name varchar(100))";
private static final String DROP_TABLE_SQL = "drop table test";
private static final String INSERT_SQL = "insert into test(name) values(?)";
private static final String COUNT_SQL = "select count(*) from test";
public static void main(String[] args) {
// 定義連接池
DataSource dataSource = getDataSource();
// 先建 PlatformTransactionManager
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
// 定義 TransactionDefinition
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
// 獲取 TransactionStatus
TransactionStatus transactionStatus = transactionManager.getTransaction(definition);
// 從連接池中獲取 <- 這里其實是將 DataSource <--> Connection 放到 ThreadLocal 里面
Connection con = DataSourceUtils.getConnection(dataSource);
try {
// 執行 SQL
con.prepareStatement(CREATE_TABLE_SQL).execute();
PreparedStatement pstmt = con.prepareStatement(INSERT_SQL);
pstmt.setString(1, "test");
pstmt.execute();
ResultSet resultSet = con.prepareStatement(COUNT_SQL).executeQuery();
// 打印查詢結果
while(resultSet.next()){ System.out.println(resultSet.getString(1));}
con.prepareStatement(DROP_TABLE_SQL).execute();
transactionManager.commit(transactionStatus);
} catch (Exception e) { // 遇到異常就直接回滾
transactionManager.rollback(transactionStatus);
}
}
// 設置數據庫連接池
public static DataSource getDataSource(){
org.apache.commons.dbcp.BasicDataSource basicDataSource = new org.apache.commons.dbcp.BasicDataSource();
basicDataSource.setDriverClassName("com.mysql.jdbc.Driver");
basicDataSource.setUrl("jdbc:mysql://localhost:3306/tuomatuo?useUnicode=true&characterEncoding=UTF8");
basicDataSource.setUsername("root");
basicDataSource.setPassword("123456");
return basicDataSource;
}
在上面的代碼中出現了一個新的角色 DataSourceUtils, 這個類主要是通過 TransactionSynchronizationManager 對 ThreadLocal 中存儲的 DataSource 的操作; 以上代碼主要是如下幾步:
1. 新建數據庫連接 DataSource
2. 新建 DataSourceTransactionManager, 將 DataSource 賦值到其中
3. 構建默認的事務配置器 TransactionDefinition
4. 通過 DataSourceTransactionManager 獲取事務狀態器 TransactionStatus (此時已經將 Connection 等事務配置信息存儲到 ThreadLocal 中)
5. 通過 DataSourceUtils.getConnection(dataSource) 獲取存儲在 ThreadLocal 中的 Connection, 接著就是直接對 數據庫的操作
6. 直接成功提交事務, 失敗的話就會直接回滾事務
PS: 在運行 Demo 時數據庫的連接替換成本地環境的參數
3. Spring Tx 編程式事務 TransactionTemplate
TransactionTemplate其實只是在 AbstractPlatformTransactionManager上面做了一個簡單的封裝, 通過傳遞一個回調函數 TransactionCallback 來執行數據庫的操作, 其他部分的操作與直接使用 AbstractPlatformTransactionManager 是一樣的
<!-- 設置目標 -->
<bean id="testBeanTarget" class="org.springframework.tests.sample.beans.TestBean">
<property name="name"><value>dependency</value></property>
</bean>
<!-- 配置 dataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<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="validationQuery">
<value>select 1 from dual</value>
</property>
</bean>
<!-- 配置 DataSourceTransactionManager -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置 TransactionProxyFactoryBean -->
<bean id="transactionProxyFactoryBean" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 代理的目標類 -->
<property name="target"><ref local="testBeanTarget"/></property>
<!-- 配置 transactionManager -->
<property name="transactionManager"><ref local="transactionManager"/></property>
<!-- 設置 transactionAttributes, 這里使用 NameMatchTransactionAttributeSource 來獲取 transactionAttribute 信息 -->
<!-- 在父類 TransactionAspectSupport.invokeWithinTransaction 中會用到 -->
<property name="transactionAttributes">
<props>
<prop key="s*">PROPAGATION_MANDATORY</prop>
<prop key="setAg*"> PROPAGATION_REQUIRED,readOnly</prop>
<prop key="set*">PROPAGATION_SUPPORTS</prop>
</props>
</property>
</bean>
從上面的 Demo 中發現, 其實整個配置信息不復雜, 缺點是 TransactionProxyFactoryBean 是針對單個 Target, 若項目中出現非常多需要事務控制的方法, 則需要配置非常多的 TransactionProxyFactoryBean, 所以出現了基于 Tx NameSpace 與 基于注解 @Transactional 的事務
4. Spring Tx 基于 Tx Namespace 的事務配置
基于 Tx nameSpace 的事務配置, 其實就是在 BeanFactory 中注入 TransactionInterceptor, NameMatchTransactionAttributeSource , 其中 TransactionInterceptor 是個 MethodInterceptor, 通過對目標對象代理, 而攔截器 TransactionInterceptor 中就是完成事務的操作, 先看一個對應的事務配置信息:
<!-- 配置攔截器 -->
<tx:advice id="txAdvice"> <!-- TxAdviceBeanDefinitionParser 解析標簽成 org.springframework.transaction.interceptor.TransactionInterceptor -->
<tx:attributes> <!-- TxAdviceBeanDefinitionParser 解析標簽成 NameMatchTransactionAttributeSource -> String, TransactionAttribute -->
<tx:method name="get*" read-only="true"/>
<tx:method name="set*"/>
<tx:method name="exceptional"/>
</tx:attributes>
</tx:advice>
<!-- 配置 AspectJAwareAdvisorAutoProxyCreator 與 DefaultBeanFactoryPointcutAdvisor -->
<aop:config> <!-- 解析成 DefaultBeanFactoryPointcutAdvisor 與 AspectJExpressionPointcut <- 對應的表達式是 "execution (* com.lami.mhao.service.*.add*(..))" -->
<aop:advisor pointcut="execution (* com.lami.mhao.service.*.add*(..))" advice-ref="txAdvice"/>
</aop:config>
從上面的配置我們可以看到, 原來 事務命名空間這種配置其實就是在 BeanFactory 中注入 MethodInterceptor <--TransactionInterceptor, 在 TransactionInterceptor 中通過 PlatformTransactionManager 來進行事務操作, 在 TransactionInterceptor 中的操作如下:
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
// 這里讀取事務的屬性和設置, 通過 TransactionAttributeSource 對象取得
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
// 獲取 beanFactory 中的 transactionManager
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
// 構造方法唯一標識(類, 方法, 如 service.UserServiceImpl.save)
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
/**
* 這里區分不同類型的 PlatformTransactionManager 因為它們的調用方式不同
* 對 CallbackPreferringPlatformTransactionManager 來說, 需要回調函數來
* 實現事務的創建和提交
* 對于非 CallbackPreferringPlatformTransactionManager 來說, 不需要通過
* 回調函數來實現事務的創建和提交
* 像 DataSourceTransactionManager 就不是 CallbackPreferringPlatformTransactionManager
* 不需要通過回調的方式來使用
*/
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// 這里創建事務, 同時把創建事務過程中得到的信息放到 TransactionInfo 中去 (創建事務的起點)
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
// 如果在事務處理方法調用中出現異常, 事務處理如何進行需要根據具體的情況考慮回滾或者提交
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 這里把與線程綁定的 TransactionInfo 設置為 oldTransactionInfo
cleanupTransactionInfo(txInfo);
}
// 這里通過事務處理器來對事務進行提交
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
其中非常重要的 TransactionAttribute 是從 NameMatchTransactionAttributeSource(這個類也是通過解析 xml 時注入的) 中獲取的, 主流程如下:
// 對調用的方法進行判斷, 判斷它是否是事務方法, 如果是事務方法, 那么取出相應的事務配置屬性
@Override
public TransactionAttribute getTransactionAttribute(Method method, Class<?> targetClass) {
// 判斷當前目標調用的方法與配置的事務方法是否直接匹配
if (!ClassUtils.isUserLevelMethod(method)) {
return null;
}
// Look for direct name match.
String methodName = method.getName();
// 通過方法名從 nameMap 中獲取 attr
TransactionAttribute attr = this.nameMap.get(methodName);
// 如果不能直接匹配, 就通過 PatternMatchUtils 的 simpleMatch 方法來進行匹配判斷
if (attr == null) {
// Look for most specific name match.
String bestNameMatch = null;
for (String mappedName : this.nameMap.keySet()) {
if (isMatch(methodName, mappedName) && // 方法名稱匹配 <- 正則匹配
(bestNameMatch == null || bestNameMatch.length() <= mappedName.length())) {
attr = this.nameMap.get(mappedName); // 匹配成功, 則直接獲取 TransactionAttribute <- 這里的 TransactionAttribute 是在解析 xml 時注入 BeanFactory 的
bestNameMatch = mappedName;
}
}
}
return attr; // 返回通過 methodName 獲取的 attr
}
PS: 對應的 Tx Namespace 解析器是 TxAdviceBeanDefinitionParser <- 代碼量不多
5. Spring Tx 基于注解 @Transactional 的事務
在使用這種形式的事務時, 只需要在配置文件中加上 <tx:annotation-driven />, 接著就可以在方法上加上 @Transactional 就能通過對某個方法內的 SQL 操作進行事務操作, 其中主要的參與者是:
1. AbstractAdvisorAutoProxyCreator: 自動Advisor收集, 自動代理創建器
2. AnnotationTransactionAttributeSource: 通過獲取&解析方法上@Transactional 來獲取 TransactionAttribute 的 TransactionAttributeSource
3. TransactionInterceptor: 一個 MethodInterceptor, 正真的事務操作其實就是在這個類中, 本質上也是通過 PlatformTransactionManager 來操作的
4. BeanFactoryTransactionAttributeSourceAdvisor: 一個基于TransactionAttributeSourcePointcut 的 Advisor, 這種 Pointcut 是通過 AnnotationTransactionAttributeSource, SpringTransactionAnnotationParser 解析方法上的 @Transactional 注解獲取 TransactionAttribute 來確定是否匹配成功的 Pointcut
<tx:annotation-driven /> 的解析器則是: org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser, 主要流程如下:
public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
// 若 BeanFactory 中沒有注入 自動代理創建器, 則將注入 InfrastructureAdvisorAutoProxyCreator
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
// 沒有注入過 internalTransactionAdvisor
if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
Object eleSource = parserContext.extractSource(element);
// Create the TransactionAttributeSource definition.
RootBeanDefinition sourceDef = new RootBeanDefinition( // 注入 AnnotationTransactionAttributeSource (TransactionAttribute 提取器, 其主要是通過注釋在方法上的 @Transactional 獲取 TransactionAttribute)
"org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
sourceDef.setSource(eleSource);
sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); // 這里標志 AnnotationTransactionAttributeSource 是 IOC 的基礎類 <- 針對基礎類不會進行 AOP 等操作
String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
// Create the TransactionInterceptor definition. // 注入 TransactionInterceptor, 正真的事務操作就是在這里攔截器里面 <- 主要還是通過 PlatformTransactionManager
RootBeanDefinition interceptorDef = new RootBeanDefinition(TransactionInterceptor.class);
interceptorDef.setSource(eleSource);
interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); // 標志這是個基礎類 <- 基礎類不會被 AOP 等特性操作
registerTransactionManager(element, interceptorDef); // 設置 transactionManager 的 beanName
interceptorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
// 注冊 TransactionInterceptor, 并使用 Spring 中的定義規則生成 beanName
String interceptorName = parserContext.getReaderContext().registerWithGeneratedName(interceptorDef);
// 注入 BeanFactoryTransactionAttributeSourceAdvisor, 這個類中的 Pointcut 是 TransactionAttributeSourcePointcut, 而
// 只要 TransactionAttributeSource 通 Method, Class 中獲取 TransactionAttribute 就表示這個方法將被事務操作
// Create the TransactionAttributeSourceAdvisor definition.
RootBeanDefinition advisorDef = new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
advisorDef.setSource(eleSource);
advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); // 標志這是個基礎類 <- 基礎類不會被 AOP 等特性操作
// 將 sourceName 的 bean 注入 advisorDef 的 transactionAttributeSource 屬性中
advisorDef.getPropertyValues().add("transactionAttributeSource", new RuntimeBeanReference(sourceName));
advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
// 如果配置了 order 屬性, 則加入到 bean 中
if (element.hasAttribute("order")) {
advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
}
// 注入 BeanFactoryTransactionAttributeSourceAdvisor
parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);
// 創建 CompositeComponentDefinition <- 其中包含上面的需要注入的 BeanDefinition <-- 組合模式
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), eleSource);
compositeDef.addNestedComponent(new BeanComponentDefinition(sourceDef, sourceName));
compositeDef.addNestedComponent(new BeanComponentDefinition(interceptorDef, interceptorName));
compositeDef.addNestedComponent(new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
parserContext.registerComponent(compositeDef);
}
}
6. 總結
在 Spring 中整個事務的操作是圍繞 PlatformTransactionManager 來進行操作的, PlatformTransactionManager 中封裝了 begin(開啟事務), suspend(將先前的事務掛起來 傳播級別可能就是PROPAGATION_REQUIRES_NEW), resume(重用先前的事務屬性), commit(提交事務), 這些方法就是模版方法, 在 AbstractPlatformTransactionManager中有著整個操作的邏輯, 對應的 ORM 只需要實現對應模版方法就可以(比如 我們最常用的 DataSourceTransactionManager) ; 而需要理解我們最常用的, 基于 @Transactional 的事務, 需要先理解以下 Aop 這樣就是非常好理解, 對應的 Pointcut 其實就是通過在方法上獲取 @Transactional 的信息來決定!
7. 參考:
Spring Aop核心源碼分析
Spring技術內幕
Spring 揭秘
Spring 源碼深度分析
開濤 Spring 雜談
傷神 Spring 源碼分析
Spring源碼情操陶冶