關於事務,簡單來講,就是爲了保證數據完整性而存在的一種工具,其主要有四大特性:原子性,一致性,隔離性和持久性。對於Spring事務,其最終仍是在數據庫層面實現的,而Spring只是以一種比較優雅的方式對其進行封裝支持。本文首先會經過一個簡單的示例來說解Spring事務是如何使用的,而後會講解Spring是如何解析xml中的標籤,並對事務進行支持的。java
關於事務最簡單的示例,就是其一致性,好比在整個事務執行過程當中,若是任何一個位置報錯了,那麼都會致使事務回滾,回滾以後數據的狀態將和事務執行以前徹底一致。這裏咱們以用戶數據爲例,在插入用戶數據的時候,若是程序報錯了,那麼插入的動做就會回滾。以下是用戶的實體:mysql
public class User { private long id; private String name; private int age; // getter, setter... }
以下是模擬插入用戶數據的業務代碼:spring
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文件的配置:sql
<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/>
上述數據庫配置用戶按照各自的設置進行配置便可。能夠看到,這裏對於數據庫的配置,主要包括四個方面:數據庫
按照上述配置,咱們的事務功能即配置完成,以下是咱們的驅動類程序:apache
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代碼:app
@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,再次運行上述程序以後咱們發現數據庫中是沒有新增的數據的,這說明咱們的事務在程序出錯後是可以保證數據一致性的。ide
關於事務的實現原理,咱們首先講解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,該文件內容以下:url
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實現事務的根本緣由。
關於Spring事務的實現原理,這裏須要抓住的就是,其是使用Aop實現的,咱們知道,Aop在進行解析的時候,最終會生成一個Adivsor對象,這個Advisor對象中封裝了切面織入所須要的全部信息,其中就包括最重要的兩個部分就是Pointcut
和Adivce
屬性。這裏Pointcut
用於判斷目標bean是否須要織入當前切面邏輯;Advice
則封裝了須要織入的切面邏輯。以下是這三個部分的簡要關係圖:
一樣的,對於Spring事務,其既然是使用Spring Aop實現的,那麼也一樣會有這三個成員。咱們這裏咱們只看到了註冊的Advisor和Advice(即BeanFactoryTransactionAttributeSourceAdvisor和TransactionInterceptor),那麼Pointcut在哪裏呢?這裏咱們查看BeanFactoryTransactionAttributeSourceAdvisor
的源碼能夠發現,其內部聲明瞭一個TransactionAttributeSourcePointcut
類型的屬性,而且直接在內部進行了實現,這就是咱們須要找的Pointcut。這裏這三個對象對應的關係以下:
這樣,用於實現Spring事務的Advisor,Pointcut以及Advice都已經找到了。關於這三個類的具體做用,咱們這裏進行總體的上的講解,後面咱們將會深刻其內部講解其是如何進行bean的過濾以及事務邏輯的織入的。
本文首先經過一個簡單的例子講解了Spring事務是如何使用的,而後講解了Spring事務進行標籤解析的時候作了哪些工做,最後講解了Spring事務是如何與Spring Aop進行一一對應的,而且是如何經過Spring Aop實現將事務邏輯織入目標bean的。