Spring 事務用法示例與實現原理
關於事務,簡單來說,就是為了保證資料完整性而存在的一種工具,其主要有四大特性:原子性,一致性,隔離性和永續性。對於Spring事務,其最終還是在資料庫層面實現的,而Spring只是以一種比較優雅的方式對其進行封裝支援。本文首先會通過一個簡單的示例來講解Spring事務是如何使用的,然後會講解Spring是如何解析xml中的標籤,並對事務進行支援的。
1. 使用示例
關於事務最簡單的示例,就是其一致性,比如在整個事務執行過程中,如果任何一個位置報錯了,那麼都會導致事務回滾,回滾之後資料的狀態將和事務執行之前完全一致。這裡我們以使用者資料為例,在插入使用者資料的時候,如果程式報錯了,那麼插入的動作就會回滾。如下是使用者的實體:
public class User {
private long id;
private String name;
private int age;
// getter, setter...
}
如下是模擬插入使用者資料的業務程式碼:
public interface UserService { void insert(User user); } @Service @Transactional public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; @Override public void insert(User user) { jdbcTemplate.update("insert into user (name, age) value (?, ?)", user.getName(), user.getAge()); } }
在進行事務支援時,Spring只需要使用者在需要事務支援的bean上使用@Transactional
註解即可,如果需要修改事務的隔離級別和傳播特性的屬性,則使用該註解中的屬性進行指定。這裡預設的隔離級別與各個資料庫一致,比如MySQL是Repeatable Read,而傳播特性預設則為Propagation.REQUIRED
,即只需要當前操作具有事務即可。如下是xml檔案的配置:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost/test?useUnicode=true"/> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="username" value="****"/> <property name="password" value="******"/> </bean> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <context:component-scan base-package="com.transaction"/> <tx:annotation-driven/>
上述資料庫配置使用者按照各自的設定進行配置即可。可以看到,這裡對於資料庫的配置,主要包括四個方面:
-
DataSource配置:設定當前應用所需要連線的資料庫,包括連結,使用者名稱,密碼等;
-
JdbcTemplate宣告:封裝了客戶端呼叫資料庫的方式,使用者可以使用其他的方式,比如JpaRepository,Mybatis等等;
-
TransactionManager配置:指定了事務的管理方式,這裡使用的是DataSourceTransactionManager,對於不同的連結方式,也可以進行不同的配置,比如對於JpaRepository使用JpaTransactionManager,對於Hibernate,使用HibernateTransactionManager;
-
tx:annotation-driven:主要用於事務驅動,其會通過AOP的方式宣告一個為事務支援的Advisor,通過該Advisor和事務的相關配置進行事務相關操作。
按照上述配置,我們的事務功能即配置完成,如下是我們的驅動類程式:
public class TransactionApp {
@Test
public void testTransaction() {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = ac.getBean(UserService.class);
User user = getUser();
userService.insert(user);
}
private User getUser() {
User user = new User();
user.setName("Mary");
user.setAge(27);
return user;
}
}
執行上述程式之後,可以看到資料庫中成功新增了一條資料。這裡如果我們將業務程式碼的插入語句之後手動丟擲一個異常,那麼,理論上插入語句是會回滾的。如下是修改後的service程式碼:
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void insert(User user) {
jdbcTemplate.update("insert into user (name, age) value (?, ?)",
user.getName(), user.getAge());
throw new RuntimeException();
}
}
這裡我們手動丟擲了一個RuntimeException,再次執行上述程式之後我們發現數據庫中是沒有新增的資料的,這說明我們的事務在程式出錯後是能夠保證資料一致性的。
2. 標籤解析
關於事務的實現原理,我們首先講解Spring是如何解析標籤,並且封裝相關bean的,後面我們會深入講解Spring是如何封裝資料庫事務的。
根據上面的示例,我們發現,其主要有三個部分:DataSource,TransactionManager和tx:annotation-driven標籤。這裡前面兩個部分主要是聲明瞭兩個bean,分別用於資料庫連線的管理和事務的管理,而tx:annotation-driven才是Spring事務的驅動。根據本人前面對Spring自定義標籤的講解(Spring自定義標籤解析與實現),可以知道,這裡tx:annotation-driven
是一個自定義標籤,我們根據其名稱空間(www.springframework.org/schema/tx)在全域性範圍內搜尋,可以找到其處理器指定檔案spring.handlers,該檔案內容如下:
http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler
這裡也就是說tx:annotation-driven
標籤的解析在TxNamespaceHandler
中,我們繼續開啟該檔案可以看到起init()
方法如下:
@Override
public void init() {
registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
registerBeanDefinitionParser("annotation-driven",
new AnnotationDrivenBeanDefinitionParser());
registerBeanDefinitionParser("jta-transaction-manager",
new JtaTransactionManagerBeanDefinitionParser());
}
可以看到,這裡的annotation-driven
是在AnnotationDrivenBeanDefinitionParser
中進行處理的,其parse()
方法就是解析標籤,並且註冊相關bean的方法,如下是該方法的實現:
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 註冊事務相關的監聽器,如果某個方法標註了TransactionalEventListener註解,
// 那麼該方法就是一個事務事件觸發方法,即發生某種事務事件後,將會根據該註解的設定,回撥指定
// 型別的方法。常見的事務事件有:事務執行前和事務完成(包括報錯後的完成)後的事件。
registerTransactionalEventListenerFactory(parserContext);
String mode = element.getAttribute("mode");
// 獲取當前事務驅動程式的模式,如果使用了aspectj模式,則會註冊一個AnnotationTransactionAspect
// 型別的bean,使用者可以以aspectj的方式使用該bean對事務進行更多的配置
if ("aspectj".equals(mode)) {
registerTransactionAspect(element, parserContext);
} else {
// 一般使用的是當前這種方式,這種方式將會在Spring中註冊三個bean,分別是
// AnnotationTransactionAttributeSource,TransactionInterceptor
// 和BeanFactoryTransactionAttributeSourceAdvisor,並通過Aop的方式實現事務
AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
}
return null;
}
可以看到,對於事務的驅動,這裡主要做了兩件事:①註冊事務相關的事件觸發器,這些觸發器由使用者自行提供,在事務進行提交或事務完成時會觸發相應的方法;②判斷當前事務驅動的模式,有預設模式和aspectj模式,對於aspectj模式,Spring會註冊一個AnnotationTransactionAspect型別的bean,用於使用者使用更親近於aspectj的方式進行事務處理;對於預設模式,這裡主要是聲明瞭三個bean,最終通過Aop的方式進行事務切入。下面我們看一下Spring是如何註冊這三個bean的,如下是AopAutoProxyConfigurer.configureAutoProxyCreator
的原始碼:
public static void configureAutoProxyCreator(Element element,
ParserContext parserContext) {
// 這個方法主要是在當前BeanFactory中註冊InfrastructureAdvisorAutoProxyCreator這個
// bean,這個bean繼承了AbstractAdvisorAutoProxyCreator,也就是其實現原理與我們前面
// 講解的Spring Aop的實現原理幾乎一致
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
// 這裡的txAdvisorBeanName就是我們最終要註冊的bean,其型別就是下面註冊的
// BeanFactoryTransactionAttributeSourceAdvisor,可以看到,其本質是一個
// Advisor型別的物件,因而Spring Aop會將其作為一個切面織入到指定的bean中
String txAdvisorBeanName = TransactionManagementConfigUtils
.TRANSACTION_ADVISOR_BEAN_NAME;
// 如果當前BeanFactory中已經存在了目標bean,則不進行註冊
if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
Object eleSource = parserContext.extractSource(element);
// 註冊AnnotationTransactionAttributeSource,這個bean的主要作用是封裝
// @Transactional註解中宣告的各個屬性
RootBeanDefinition sourceDef = new RootBeanDefinition(
"org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
sourceDef.setSource(eleSource);
sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String sourceName = parserContext.getReaderContext()
.registerWithGeneratedName(sourceDef);
// 註冊TransactionInterceptor型別的bean,並且將上面的封裝屬性的bean設定為其一個屬性。
// 這個bean本質上是一個Advice(可檢視其繼承結構),Spring Aop使用Advisor封裝實現切面
// 邏輯織入所需的所有屬性,但真正的切面邏輯卻是儲存在其Advice屬性中的,也就是說這裡的
// TransactionInterceptor才是真正封裝了事務切面邏輯的bean
RootBeanDefinition interceptorDef =
new RootBeanDefinition(TransactionInterceptor.class);
interceptorDef.setSource(eleSource);
interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registerTransactionManager(element, interceptorDef);
interceptorDef.getPropertyValues().add("transactionAttributeSource",
new RuntimeBeanReference(sourceName));
String interceptorName = parserContext.getReaderContext()
.registerWithGeneratedName(interceptorDef);
// 註冊BeanFactoryTransactionAttributeSourceAdvisor型別的bean,這個bean實現了
// Advisor介面,實際上就是封裝了當前需要織入的切面的所有所需的屬性
RootBeanDefinition advisorDef =
new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
advisorDef.setSource(eleSource);
advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisorDef.getPropertyValues().add("transactionAttributeSource",
new RuntimeBeanReference(sourceName));
advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
if (element.hasAttribute("order")) {
advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
}
parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);
// 將需要註冊的bean封裝到CompositeComponentDefinition中,並且進行註冊
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);
}
}
如此,Spring事務相關的標籤即解析完成,這裡主要是聲明瞭一個BeanFactoryTransactionAttributeSourceAdvisor
型別的bean到BeanFactory中,其實際為Advisor型別,這也是Spring事務能夠通過Aop實現事務的根本原因。
3. 實現原理
關於Spring事務的實現原理,這裡需要抓住的就是,其是使用Aop實現的,我們知道,Aop在進行解析的時候,最終會生成一個Adivsor物件,這個Advisor物件中封裝了切面織入所需要的所有資訊,其中就包括最重要的兩個部分就是Pointcut
和Adivce
屬性。這裡Pointcut
用於判斷目標bean是否需要織入當前切面邏輯;Advice
則封裝了需要織入的切面邏輯。如下是這三個部分的簡要關係圖:
Advisor
同樣的,對於Spring事務,其既然是使用Spring Aop實現的,那麼也同樣會有這三個成員。我們這裡我們只看到了註冊的Advisor和Advice(即BeanFactoryTransactionAttributeSourceAdvisor和TransactionInterceptor),那麼Pointcut在哪裡呢?這裡我們檢視BeanFactoryTransactionAttributeSourceAdvisor
的原始碼可以發現,其內部聲明瞭一個TransactionAttributeSourcePointcut
型別的屬性,並且直接在內部進行了實現,這就是我們需要找的Pointcut。這裡這三個物件對應的關係如下:
Transaction
這樣,用於實現Spring事務的Advisor,Pointcut以及Advice都已經找到了。關於這三個類的具體作用,我們這裡進行整體的上的講解,後面我們將會深入其內部講解其是如何進行bean的過濾以及事務邏輯的織入的。
-
BeanFactoryTransactionAttributeSourceAdvisor:封裝了實現事務所需的所有屬性,包括Pointcut,Advice,TransactionManager以及一些其他的在Transactional註解中宣告的屬性;
-
TransactionAttributeSourcePointcut:用於判斷哪些bean需要織入當前的事務邏輯。這裡可想而知,其判斷的基本邏輯就是判斷其方法或類宣告上有沒有使用@Transactional註解,如果使用了就是需要織入事務邏輯的bean;
-
TransactionInterceptor:這個bean本質上是一個Advice,其封裝了當前需要織入目標bean的切面邏輯,也就是Spring事務是如果藉助於資料庫事務來實現對目標方法的環繞的。
4. 小結
本文首先通過一個簡單的例子講解了Spring事務是如何使用的,然後講解了Spring事務進行標籤解析的時候做了哪些工作,最後講解了Spring事務是如何與Spring Aop進行一一對應的,並且是如何通過Spring Aop實現將事務邏輯織入目標bean的。