javascript
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?{@Autowiredprivate?JdbcTemplate?jdbcTemplate;@Overridepublic?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?{@Testpublic?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?{@Autowiredprivate?JdbcTemplate?jdbcTemplate;@Overridepublic?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才是真正封裝了事務切面邏輯的beanRootBeanDefinition?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的。
總結
以上是生活随笔為你收集整理的Spring 事务用法示例与实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis 面试题 50 问,史上最全
- 下一篇: Java多线程可以分组,还能这样玩!