? ? ? 最近看了比較多Spring的東西,腦中概念知識比較雜亂,借助周六周天的閑暇時間,寫一些內容,梳理一下自己腦中的概念,也以此作為記錄,希望自己以后能夠方便查閱,也希望能夠對各位看官有所幫助。
? ? 第一個Spring相關主題是Spring的事務管理,借助官方文檔的一句話,Spring提供了一致的事物管理抽象模型,能夠讓大家在不同的事務API(JTA、JDBC、Hibernate、JPA、JDO等)之間有一致的編程體驗,Spring提供了聲明式事務管理和編程事務管理兩種事務管理方式,前者借鑒了EJB CMT中事務管理方式,后者則簡化了JTA的事務異常編程模型,從這個角度來看,Spring事務提供了全面簡潔的一致性事務管理方案。
熟悉Java EE編程的人應該知道,事務分為全局(global)和本地(local)兩種事務;全局事務提供了在多個事務源之間操作保證原子性的能力,典型的事務源是數據庫,在一些企業集成領域還包括MQ等中間件,在Java EE體系中通過JTA來對全局事務提供支持,事務管理功能由應用服務器提供支持,在實際操作時需要通過JNDI進行事務對象的引用,這就存在兩種耦合綁定,特定查找協議和特定應用容器綁定,這是不是不良的組織形式要看應用的需求,但在大多數時候,大多數應用為了獲取全局事務的能力而付出這種耦合的代價都是比較不合算的;本地事務是資源特定的,例如使用JDBC connection進行的事務操作,本地事務中的事物管理功能是應用程序進行控制的,非容器托管,因此其能擺脫容器綁定的副作用,但其局限性也很明顯,因為是資源特定的,其不能支持跨事務源的操作,所幸我們大部分應用都沒有跨事務源的需求,本地事務在這種情況下比較適用;關于全局事務這里多補充一點,因為分布式事務本身的復雜性,其比較重量級,考慮的事務模型可能也比較復雜,除非必須使用,否則還是敬而遠之為好,即使因為水平擴展、高可用、負載均衡等需求而需要添置額外的事務源,也應該盡量避免,例如使用Oracle RAC,將分布式事務的復雜性封裝在數據庫產品層面,簡化應用級別的事務操作;實際上,全局事務操作不一定非需要綁定應用容器,借助獨立的事務管理器如Atomikos Transactions 和?JOTM也能提供JTA全局事務的功能,當然,還是如上所述,并不推薦使用。
? ? Spring提供了一致的事務抽象,而這抽象的核心就是PlatformTransactionManager接口,接口定義如下
該接口有三個方法,第一個方法根據TransationDefinition對象獲取事務(TransactionStatus),TransationDefinition也是一個接口,定義如下
通過這個定義,我們知道TransationDefinition提供了事務傳播策略以及事務隔離級別的常量定義,同時提供了默認隔離和默認事務超時的常量定義,方法上提供了獲取事務隔離級別、事務傳播策略、事務超時時間、是否只讀事務以及事務名稱信息,基本上事務的基本概念都包含在了這個接口定義中;TransactionStatus提供了簡單的控制事務以及查詢事務狀態的方法,定義如下
因為接口定義比較明了,這里不再展開贅述;PlatformTransactionManager接口是Spring事務管理的核心,其類級結構如下
通過上面的結構圖,我們看到Spring為每一種常用的具體的事務操作API提供了具體的實現,例如我們常用的基于純JDBC的DataSourceTransactionManager和常用ORM hibernate的HibernateTransactionManager,JTA API特供了通用的實現以及針對Weblogic、WebSphere應用服務器提供的特定實現;PlatformTransactionManager定義了基本的事務操作模型,而AbstractPlatformTransactionManager抽象類實現了該接口,該抽象類定義了一些所有具體實現類共有的屬性和方法,同時定義了統一的事務處理流程,這是設計模式模板模式非常經典的應用,因為該類方法定義較多,這里我們就不在貼出該類的定義,我們來看看該類如何定義了統一的事務處理流程,該類實現了PlatformTransactionManager接口的commit方法,同時將該方法設為final,使其子類不能夠重寫,commit方法如下:
該方法定義了基本的提交處理,我們看到實際處理是processXXX等方法,processCommit部分代碼如下
我們看到這個方法負責了具體事務提交的相關操作,最后實際提交操作是doCommit,而doCommit為一個抽象方法,這就是該類給子類實現留下的“鉤子”,具體的提交操作由子類來去實現,這是一個典型的模板模式應用,接下來我們找一個具體子類來看看doCommit的實際操作,我們看看比較熟悉的HibernateTransactionManager,doCommit如下:
很明顯,看到了Hibernate事務操作典型的用法,其他實現類似,這里我們不在贅述,大家可以自己看看。
? ? ? ? 關于具體的Spring事務配置,雖然比較基礎,但我還是寫在這里吧,在事務配置過程中,我穿插著描述一些原理性的東西,因為頭腦比較混亂,條理不清,還望大家海涵。
事務管理配置的核心是各種具體PlatformTransactionManager的配置,其次就是各種事務管理器如何作用在具體操作方法上的切面配置,因為事務管理器到具體方法的切面配置都是一致的,這里我們給出各種PlatformTransactionManager的配置,最后在來一個具體的事務切面配置,這里我們提供DataSourceTransactionManager、JtaTransactionManager以及HibernateTransactionManager的配置,關于用XML配置還是注解配置,這里我們先提供XML的配置,然后在后面給出一個完全注解的配置實例。
PlatformTransactionManager配置
1. DataSourceTransactionManager
首先需要配置JDBC的DataSource,畢竟這些本地事務是資源特定的嘛,DataSource定義如下
接著就是事務管理器的配置,DataSourceTransactionManager定義如下
這里事務管理器就配置完了,非常簡單。
2.?JtaTransactionManager
JtaTransactionManager配置更為簡單,因為其是全局事務,為容器托管事務,其不需要知道特定的資源(DataSource),配置如下
但是注意JtaTransactionManager不需要知道DataSource,并不意味著不需要配置,Spring Data模塊對資源的操作還是需要DataSource的,在這種場景下DataSource由應用容器托管,所以使用JNDI進行查找引用,如下
3.HibernateTransactionManager
Hibernate的配置比較多一些,首先是配置DataSource,和1中一致,這里不再贅述,接著就是SessionFactory的配置,配置如下
注意這里的配置比較老,如果使用基于注解的實體配置,請將mappingResources替換為
接著就是HibernateTransactionManager的配置了,配置如下
注意實際的持久化技術例如JDBC、JPA、JDO、Hibernate等和實際使用的事務管理器之間的區別,其沒有必要一一對應,在使用JDBC時可以使用JTA全局事務管理器,同樣,使用Hibernate也可以,只需將最后一步的事務管理器替換為JtaTransactionManager即可。
事務管理器到特定方法配置
? ? 接下來就是具體事務管理器如何作用在具體方法上的配置了,不過在這之前我們描述一下這個使用背景,如前所述,Spring提供了兩種事務管理方式,一種是聲明式事務管理(Declarative transaction management),另一種則是編程式事務管理(Programmatic transaction management),這兩種方式顯然聲明式比較簡單,而在事務管理需求比較少的時候,想比較聲明式比較繁瑣的配置,編程式則比較簡單,這里大家根據自己的需求進行選擇,Spring官方推薦使用聲明式事務管理,后面的論述也會分別給出聲明式事務和編程式事務相關主題。
聲明式配置
? ? 聲明式事務管理的基本原理是利用在Spring中應用比較廣泛的面向切面編程(AOP),通過將事務處理放在切面對象中來進行一致性的管理,減少冗余代碼,提升簡潔性,原理圖大致如下
其中的AOP proxy,Spring定義了TransactionInterceptor類,該類結構如下
通過這個結構,我們看到實際的方法都在TransactionInterceptor的父類TransactionAspectSupport中,TransactionInterceptor的關鍵方法是invoke方法,而這個方法內部調用了父類的invokeWithinTransaction方法,這是這里事務處理的核心,invokeWithinTransaction方法片段如下
這看起來是一個典型的切面代理方法,我們按照順序講下主要流程,首先270行獲取該方法的事務屬性,這些屬性包括事務隔離級別、事務傳播策略、只讀、回滾等屬性信息,然后271行獲取平臺事務管理器,因為Spring支持多事務管理器特性,這里需要配合實際配置來決定具體的事務管理器,注意276行,這一行獲取了事務信息,同時又在必要情況下開啟了事務,后面281行是具體方法執行,291行提交事務。
? ? 上面說了些原理,這里給出聲明式事務的一些具體應用配置,在Spring中聲明式事務大體有兩種用法,一種是純粹的切面配置,一種是基于@Transactional的注解配置,這里都會給出具體應用實例。
? 1.基于切面的配置
? ? 基于界面的配置比較簡單,核心是Transaction 的Advice的配置,然后將這Advice配置在具體pointcut上就可以了,我們先看看Advice的配置,配置實例如下:
如上,這里的配置比較清晰明了,引用具體的事務管理器(如果事務管理器名稱為transactionManager,那么可以不需要明確引用事務管理器),然后通配符匹配特定方法,配置特定事務屬性;配置完Advice,接下來是將Advice關聯到pointcut的配置,如下
這里的配置比較簡單,不在贅述
關于事務屬性的配置,可配置屬性如下
除方法名之外,其他屬性都是可選的,根據具體的應用場景進行定制,事務傳播策略定義了不同事務的存在關系,可選屬性列表如下(注意Spring事務概念中邏輯事務和物理事務的區別,物理事務就是指數據庫層面的事務,而邏輯事務是應用層面的,可以具有更豐富的行為特性,Spirng中的事務就是特指邏輯事務,要注意)
事務隔離級別這個屬性是數據庫層面的,可選屬性列表如下
具體這些問題以及每個隔離級別的具體特征有機會在進行敘述吧,不然太多了,這里注意不同的數據庫對不同隔離級別支持是不同的,例如Oracle只能支持讀提交和序列化,不可重復讀的問題通過額外的樂觀鎖實現,其中存在只讀事務,隔離級別實際是序列化,在配置事務只讀時要注意這里的特性;Mysql支持比較豐富,但其可重復讀能夠解決部分幻讀問題,這是與其實現有關的,等有機會把它展開敘述,一般應用場景下設置讀提交就能滿足要求,讀未提交隔離過低,而其他兩個隔離級別又太重量級,使用的話會嚴重降低應用性能,對于一些并發問題的容忍性,第一更新丟失、臟讀、不可重復讀(特殊場景為第二更新丟失)是不能容忍的,這里可以使用讀提交隔離級別+樂觀鎖來屏蔽第二更新丟失的問題,這是一個權衡場景需要和性能情況下做出的綜合性方案。
事務超時設置可以根據需要設置,一般情況下,為了防止數據庫鎖阻塞,所有方法都應該有事務超時時間。
只讀事務設置注意其性能優化的價值,一般情況下,只讀操作可能都不需要事務,這時候降低事務隔離級別或者干脆不要事務都能夠提升應用性能。
事務異?;貪L,關于受檢異常和非受檢異常的爭論這里就不在論述了,Spring事務采用了JTA的慣例,默認情況下只用運行時異常及其子類會導致事務回滾,受檢異常不會導致事務回滾,后面兩個選項就是關于回滾與不回滾異常設置的,這里可以根據需要進行設置。
2.基于@Transactional的事務管理
說完了切面設置的事務管理,這里說一下基于@Transactional的事務管理,我們看下該注解的一些定義,如下
通過上面的定義,我們知道這個注解可以放在類上和特定方法(只能為public方法,protected、private、package-visible都不可以)上,放在類上的話,該類所有的public方法都有了事務,放在特定方法上可以定制這個方法執行的事務屬性,為了啟用這種注解式的事務管理方式,需要在xml中添加如下信息
這時候就可以把之前事務配置中切面的部分刪掉了,關于該注解上的屬性,因為和上面切面形式中類似,這里就不在贅述了
上面的配置還是基于XML的,這里給出一個完全基于注解的實例
注解配置類如下
這里開啟bean掃描,開啟基于@Transactional的事務管理
其中的一些Bean定義,DataSource
SessionFactory
編程式事務管理
? ? 說完了聲明式的事務管理,接下來講講編程式事務管理,Spring提供了兩種編程方式,一種是使用TransactionTemplate,一種是直接使用具體PlatformTransactionManager,官方推薦第一種方式,接下來我們分別進行敘述。
1. 使用TransactionTemplate
TransactionTemplate采用一種回調的方式將要執行方法包含在事務中,其初始化需要指定特定的事務管理器,實際使用代碼片段如下
2.使用PlatformTransactionManager
這個比較簡單,關鍵是定義TransactionDefinition,代碼如下
代碼很清晰明了,這里就不在贅述了。
寫不下去了,算是做了一次梳理吧,有時間在進行詳細敘述。