概述html
Spring 最成功,最吸引人的地方莫過於輕量級的聲明式事務管理,僅此一點,它就宣告了重量級 EJB 容器的覆滅。Spring 聲明式事務管理將開發者從繁複的事務管理代碼中解脫出來,專一於業務邏輯的開發上,這是一件能夠被拿來頂禮膜拜的事情。可是,世界並未今後消停,開發人員 須要面對的是層出不窮的應用場景,這些場景每每逾越了普通 Spring 技術書籍的理想界定。所以,隨着應用開發的深刻,在使用通過 Spring 層層封裝的聲明式事務時,開發人員愈來愈以爲本身墜入了迷霧,陷入了沼澤,體會不到外界所宣稱的那種暢快淋漓。本系列文章的目標旨在整理並剖析實際應用中 種種讓咱們迷茫的場景,讓陽光照進雲遮霧障的山頭。java
回頁首web
DAO 和事務管理的牽絆spring
不多有使用 Spring 但不使用 Spring 事務管理器的應用,所以經常有人會問:是否用了 Spring,就必定要用 Spring 事務管理器,不然就沒法進行數據的持久化操做呢?事務管理器和 DAO 是什麼關係呢?sql
也許是 DAO 和事務管理形影不離的緣故吧,這個看似簡單的問題實實在在地存在着,從初學者心中涌出,縈繞在開發老手的腦際。答案固然是否認的!咱們都知道:事務管理是 保證數據操做的事務性(即原子性、一致性、隔離性、持久性,也即所謂的 ACID),脫離了事務性,DAO 照樣能夠順利地進行數據的操做。數據庫
下面,咱們來看一段使用 Spring JDBC 進行數據訪問的代碼:express
package user.withouttm; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.apache.commons.dbcp.BasicDataSource; @Service("service1") public class UserJdbcWithoutTransManagerService { @Autowired private JdbcTemplate jdbcTemplate; public void addScore(String userName,int toAdd){ String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?"; jdbcTemplate.update(sql,toAdd,userName); } public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("user/withouttm/jdbcWithoutTransManager.xml"); UserJdbcWithoutTransManagerService service = (UserJdbcWithoutTransManagerService)ctx.getBean("service1"); JdbcTemplate jdbcTemplate = (JdbcTemplate)ctx.getBean("jdbcTemplate"); BasicDataSource basicDataSource = (BasicDataSource)jdbcTemplate.getDataSource(); //①.檢查數據源autoCommit的設置 System.out.println("autoCommit:"+ basicDataSource.getDefaultAutoCommit()); //②.插入一條記錄,初始分數爲10 jdbcTemplate.execute( "INSERT INTO t_user(user_name,password,score) VALUES('tom','123456',10)"); //③.調用工做在無事務環境下的服務類方法,將分數添加20分 service.addScore("tom",20); //④.查看此時用戶的分數 int score = jdbcTemplate.queryForInt( "SELECT score FROM t_user WHERE user_name ='tom'"); System.out.println("score:"+score); jdbcTemplate.execute("DELETE FROM t_user WHERE user_name='tom'"); } } |
jdbcWithoutTransManager.xml 的配置文件以下所示:apache
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:component-scan base-package="user.withouttm"/> <!-- 數據源默認將autoCommit設置爲true --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="oracle.jdbc.driver.OracleDriver" p:url="jdbc:oracle:thin:@localhost:1521:orcl" p:username="test" p:password="test"/> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource"/> </beans> |
運行 UserJdbcWithoutTransManagerService,在控制檯上打出以下的結果:編程
defaultAutoCommit:true score:30 |
在 jdbcWithoutTransManager.xml 中,沒有配置任何事務管理器,可是數據已經成功持久化到數據庫中。在默認狀況下,dataSource 數據源的 autoCommit 被設置爲 true ―― 這也意謂着全部經過 JdbcTemplate 執行的語句立刻提交,沒有事務。若是將 dataSource 的 defaultAutoCommit 設置爲 false,再次運行 UserJdbcWithoutTransManagerService,將拋出錯誤,緣由是新增及更改數據的操做都沒有提交到數據庫,因此 ④ 處的語句因沒法從數據庫中查詢到匹配的記錄而引起異常。緩存
對於強調讀速度的應用,數據庫自己可能就不支持事務,如使用 MyISAM 引擎的 MySQL 數據庫。這時,無須在 Spring 應用中配置事務管理器,由於即便配置了,也是沒有實際用處的。
不過,對於 Hibernate 來講,狀況就有點複雜了。由於 Hibernate 的事務管理擁有其自身的意義,它和 Hibernate 一級緩存有密切的關係:當咱們調用 Session 的 save、update 等方法時,Hibernate 並不直接向數據庫發送 SQL 語句,而是在提交事務(commit)或 flush 一級緩存時才真正向數據庫發送 SQL。因此,即便底層數據庫不支持事務,Hibernate 的事務管理也是有必定好處的,不會對數據操做的效率形成負面影響。因此,若是是使用 Hibernate 數據訪問技術,沒有理由不配置 HibernateTransactionManager 事務管理器。
可是,不使用 Hibernate 事務管理器,在 Spring 中,Hibernate 照樣也能夠工做,來看下面的例子:
package user.withouttm; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.orm.hibernate3.HibernateTemplate; import org.apache.commons.dbcp.BasicDataSource; import user.User; @Service("service2") public class UserHibernateWithoutTransManagerService { @Autowired private HibernateTemplate hibernateTemplate; public void addScore(String userName,int toAdd){ User user = (User)hibernateTemplate.get(User.class,userName); user.setScore(user.getScore()+toAdd); hibernateTemplate.update(user); } public static void main(String[] args) { //參考UserJdbcWithoutTransManagerService相應代碼 … } } |
此時,採用 hiberWithoutTransManager.xml 的配置文件,其配置內容以下:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!--省略掉包掃描,數據源,JdbcTemplate配置部分,參見jdbcWithoutTransManager.xml --> … <bean id="sessionFactory" class= "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" p:dataSource-ref="dataSource"> <property name="annotatedClasses"> <list> <value>user.User</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect"> org.hibernate.dialect.Oracle10gDialect </prop> <prop key="hibernate.show_sql">true</prop> </props> </property> </bean> <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate" p:sessionFactory-ref="sessionFactory"/> </beans> |
運行 UserHibernateWithoutTransManagerService,程序正確執行,並獲得相似於 UserJdbcWithoutTransManagerService 的執行結果,這說明 Hibernate 在 Spring 中,在沒有事務管理器的狀況下,依然能夠正常地進行數據的訪問。
Web、Service 及 DAO 三層劃分就像西方國家的立法、行政、司法三權分立同樣被奉爲金科玉律,甚至有開發人員認爲若是要使用 Spring 的事務管理就必定先要進行三層的劃分。這個看似荒唐的論調在開發人員中很有市場。更有甚者,認爲每層必須先定義一個接口,而後再定義一個實現類。其結果 是:一個很簡單的功能,也至少須要 3 個接口,3 個類,再加上視圖層的 JSP 和 JS 等,打牌均可以轉上兩桌了,這種誤解害人不淺。
對將「面向接口編程」奉爲圭臬,認爲放之四海而皆準的論調,筆者深不覺得然。是的,「面向接口編程」是 Martin Fowler,Rod Johnson 這些大師提倡的行事原則。若是拿這條原則去開發架構,開發產品,怎麼強調都不爲過。可是,對於咱們通常的開發人員來講,作的最多的是普通工程項目,每每最 多的只是一些對數據庫增、刪、查、改的功能。此時,「面向接口編程」除了帶來更多的類文件外,看不到更多其它的好處。
Spring 框架提供的全部附加的好處(AOP、註解加強、註解 MVC 等)惟一的前提就是讓 POJO 的類變成一個受 Spring 容器管理的 Bean,除此之外沒有其它任何的要求。下面的實例用一個 POJO 完成全部的功能,既是 Controller,又是 Service,仍是 DAO:
package user.mixlayer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; //①.將POJO類經過註解變成Spring MVC的Controller @Controller public class MixLayerUserService { //②.自動注入JdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; //③.經過Spring MVC註解映URL請求 @RequestMapping("/logon.do") public String logon(String userName,String password){ if(isRightUser(userName,password)){ String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?"; jdbcTemplate.update(sql,20,userName); return "success"; }else{ return "fail"; } } private boolean isRightUser(String userName,String password){ //do sth... return true; } } |
經過 @Controller 註解將 MixLayerUserService 變成 Web 層的 Controller,同時也是 Service 層的服務類。此外,因爲直接使用 JdbcTemplate 訪問數據,因此 MixLayerUserService 仍是一個 DAO。來看一下對應的 Spring 配置文件:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> <!--掃描Web類包,經過註釋生成Bean--> <context:component-scan base-package="user.mixlayer"/> <!--①.啓動Spring MVC的註解功能,完成請求和註解POJO的映射--> <bean class="org.springframework.web.servlet.mvc.annotation .AnnotationMethodHandlerAdapter"/> <!--模型視圖名稱的解析,即在模型視圖名稱添加先後綴 --> <bean class="org.springframework.web.servlet.view .InternalResourceViewResolver" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/> <!--普通數據源 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="oracle.jdbc.driver.OracleDriver" p:url="jdbc:oracle:thin:@localhost:1521:orcl" p:username="test" p:password="test"/> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource"/> <!--事務管理器 --> <bean id="jdbcManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/> <!--②使用aop和tx命名空間語法爲MixLayerUserService全部公用方法添加事務加強 --> <aop:config proxy-target-class="true"> <aop:pointcut id="serviceJdbcMethod" expression="execution(public * user.mixlayer.MixLayerUserService.*(..))"/> <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/> </aop:config> <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> </beans> |
在 ① 處,咱們定義配置了 AnnotationMethodHandlerAdapter,以便啓用 Spring MVC 的註解驅動功能。而②和③處經過 Spring 的 aop 及 tx 命名空間,以及 Aspject 的切點表達式語法進行事務加強的定義,對 MixLayerUserService 的全部公有方法進行事務加強。要使程序可以運行起來還必須進行 web.xml 的相關配置:
<?xml version="1.0" encoding="GB2312"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:user/mixlayer/applicationContext.xml</param-value> </context-param> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/classes/log4j.properties</param-value> </context-param> <listener> <listener-class> org.springframework.web.util.Log4jConfigListener </listener-class> </listener> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>user</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <!--①經過contextConfigLocation參數指定Spring配置文件的位置 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:user/mixlayer/applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>user</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app> |
這個配置文件很簡單,惟一須要注意的是 DispatcherServlet 的配置。默認狀況下 Spring MVC 根據 Servlet 的名字查找 WEB-INF 下的 <servletName>-servlet.xml 做爲 Spring MVC 的配置文件,在此,咱們經過 contextConfigLocation 參數顯式指定 Spring MVC 配置文件的確切位置。
將 org.springframework.jdbc 及 org.springframework.transaction 的日誌級別設置爲 DEBUG,啓動項目,並訪問 http://localhost:8088/logon.do?userName=tom 應用,MixLayerUserService#logon 方法將做出響應,查看後臺輸出日誌:
13:24:22,625 DEBUG (AbstractPlatformTransactionManager.java:365) - Creating new transaction with name [user.mixlayer.MixLayerUserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT 13:24:22,906 DEBUG (DataSourceTransactionManager.java:205) - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@6e1cbf] for JDBC transaction 13:24:22,921 DEBUG (DataSourceTransactionManager.java:222) - Switching JDBC Connection [org.apache.commons.dbcp.PoolableConnection@6e1cbf] to manual commit 13:24:22,921 DEBUG (JdbcTemplate.java:785) - Executing prepared SQL update 13:24:22,921 DEBUG (JdbcTemplate.java:569) - Executing prepared SQL statement [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?] 13:24:23,140 DEBUG (JdbcTemplate.java:794) - SQL update affected 0 rows 13:24:23,140 DEBUG (AbstractPlatformTransactionManager.java:752) - Initiating transaction commit 13:24:23,140 DEBUG (DataSourceTransactionManager.java:265) - Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@6e1cbf] 13:24:23,140 DEBUG (DataSourceTransactionManager.java:323) - Releasing JDBC Connection [org.apache.commons.dbcp.PoolableConnection@6e1cbf] after transaction 13:24:23,156 DEBUG (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource |
日誌中粗體部分說明了 MixLayerUserService#logon 方法已經正確運行在事務上下文中。
Spring 框架自己不該該是複雜化代碼的理由,使用 Spring 的開發者應該是無拘無束的:從實際應用出發,去除掉那些所謂原則性的接口,去除掉強制分層的束縛,簡單纔是硬道理。
Spring 事務一個被訛傳很廣說法是:一個事務方法不該該調用另外一個事務方法,不然將產生兩個事務。結果形成開發人員在設計事務方法時束手束腳,生怕一不當心就踩到地雷。
其實這種是不認識 Spring 事務傳播機制而形成的誤解,Spring 對事務控制的支持統一在 TransactionDefinition 類中描述,該類有如下幾個重要的接口方法:
很明顯,除了事務的傳播行爲外,事務的其它特性 Spring 是藉助底層資源的功能來完成的,Spring 無非只充當個代理的角色。可是事務的傳播行爲倒是 Spring 憑藉自身的框架提供的功能,是 Spring 提供給開發者最珍貴的禮物,訛傳的說法玷污了 Spring 事務框架最美麗的光環。
所謂事務傳播行爲就是多個事務方法相互調用時,事務如何在這些方法間傳播。Spring 支持 7 種事務傳播行爲:
Spring 默認的事務傳播行爲是 PROPAGATION_REQUIRED,它適合於絕大多數的狀況。假設 ServiveX#methodX() 都工做在事務環境下(即都被 Spring 事務加強了),假設程序中存在以下的調用 鏈:Service1#method1()->Service2#method2()->Service3#method3(),那麼這 3 個服務類的 3 個方法經過 Spring 的事務傳播機制都工做在同一個事務中。
下面,咱們來看一下實例,UserService#logon() 方法內部調用了 UserService#updateLastLogonTime() 和 ScoreService#addScore() 方法,這兩個類都繼承於 BaseService。它們之間的類結構說明以下:
具體的代碼以下所示:
@Service("userService") public class UserService extends BaseService { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private ScoreService scoreService; public void logon(String userName) { updateLastLogonTime(userName); scoreService.addScore(userName, 20); } public void updateLastLogonTime(String userName) { String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?"; jdbcTemplate.update(sql, System.currentTimeMillis(), userName); } } |
UserService 中注入了 ScoreService 的 Bean,ScoreService 的代碼以下所示:
@Service("scoreUserService") public class ScoreService extends BaseService{ @Autowired private JdbcTemplate jdbcTemplate; public void addScore(String userName, int toAdd) { String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?"; jdbcTemplate.update(sql, toAdd, userName); } } |
經過 Spring 的事務配置爲 ScoreService 及 UserService 中全部公有方法都添加事務加強,讓這些方法都工做於事務環境下。下面是關鍵的配置代碼:
<!-- 添加Spring事務加強 --> <aop:config proxy-target-class="true"> <aop:pointcut id="serviceJdbcMethod" <!-- 全部繼承於BaseService類的子孫類的public方法都進行事務加強--> expression="within(user.nestcall.BaseService+)"/> <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/> </aop:config> <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> |
將日誌級別設置爲 DEBUG,啓動 Spring 容器並執行 UserService#logon() 的方法,仔細觀察以下的輸出日誌:
16:25:04,765 DEBUG (AbstractPlatformTransactionManager.java:365) - Creating new transaction with name [user.nestcall.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT ①爲UserService#logon方法啓動一個事務 16:25:04,765 DEBUG (DataSourceTransactionManager.java:205) - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@32bd65] for JDBC transaction logon method... updateLastLogonTime... ②直接執行updateLastLogonTime方法 16:25:04,781 DEBUG (JdbcTemplate.java:785) - Executing prepared SQL update 16:25:04,781 DEBUG (JdbcTemplate.java:569) - Executing prepared SQL statement [UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?] 16:25:04,828 DEBUG (JdbcTemplate.java:794) - SQL update affected 0 rows 16:25:04,828 DEBUG (AbstractPlatformTransactionManager.java:470) - Participating in existing transaction ③ScoreService#addScore方法加入到UserService#logon的事務中 addScore... 16:25:04,828 DEBUG (JdbcTemplate.java:785) - Executing prepared SQL update 16:25:04,828 DEBUG (JdbcTemplate.java:569) - Executing prepared SQL statement [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?] 16:25:04,828 DEBUG (JdbcTemplate.java:794) - SQL update affected 0 rows 16:25:04,828 DEBUG (AbstractPlatformTransactionManager.java:752) - Initiating transaction commit ④提交事務 16:25:04,828 DEBUG (DataSourceTransactionManager.java:265) - Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@32bd65] 16:25:04,828 DEBUG (DataSourceTransactionManager.java:323) - Releasing JDBC Connection [org.apache.commons.dbcp.PoolableConnection@32bd65] after transaction 16:25:04,828 DEBUG (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource |
從上面的輸入日誌中,能夠清楚地看到 Spring 爲 UserService#logon() 方法啓動了一個新的事務,而 UserSerive#updateLastLogonTime() 和 UserService#logon() 是在相同的類中,沒有觀察到有事務傳播行爲的發生,其代碼塊好像「直接合並」到 UserService#logon() 中。接着,當執行到 ScoreService#addScore() 方法時,咱們就觀察到了發生了事務傳播的行爲:Participating in existing transaction,這說明 ScoreService#addScore() 添加到 UserService#logon() 的事務上下文中,二者共享同一個事務。因此最終的結果是 UserService 的 logon(), updateLastLogonTime() 以及 ScoreService 的 addScore 都工做於同一事務中。
因爲 Spring 的事務管理器是經過線程相關的 ThreadLocal 來保存數據訪問基礎設施,再結合 IOC 和 AOP 實現高級聲明式事務的功能,因此 Spring 的事務自然地和線程有着千絲萬縷的聯繫。
咱們知道 Web 容器自己就是多線程的,Web 容器爲一個 Http 請求建立一個獨立的線程,因此由此請求所牽涉到的 Spring 容器中的 Bean 也是運行於多線程的環境下。在絕大多數狀況下,Spring 的 Bean 都是單實例的(singleton),單實例 Bean 的最大的好處是線程無關性,不存在多線程併發訪問的問題,也便是線程安全的。
一個類可以以單實例的方式運行的前提是「無狀態」:即一個類不能擁有狀態化的成員變量。咱們知道,在傳統的編程中,DAO 必須執有一個 Connection,而 Connection 便是狀態化的對象。因此傳統的 DAO 不能作成單實例的,每次要用時都必須 new 一個新的實例。傳統的 Service 因爲將有狀態的 DAO 做爲成員變量,因此傳統的 Service 自己也是有狀態的。
可是在 Spring 中,DAO 和 Service 都以單實例的方式存在。Spring 是經過 ThreadLocal 將有狀態的變量(如 Connection 等)本地線程化,達到另外一個層面上的「線程無關」,從而實現線程安全。Spring 竭盡全力地將狀態化的對象無狀態化,就是要達到單實例化 Bean 的目的。
因爲 Spring 已經經過 ThreadLocal 的設施將 Bean 無狀態化,因此 Spring 中單實例 Bean 對線程安全問題擁有了一種天生的免疫能力。不但單實例的 Service 能夠成功運行於多線程環境中,Service 自己還能夠自由地啓動獨立線程以執行其它的 Service。下面,經過一個實例對此進行描述:
@Service("userService") public class UserService extends BaseService { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private ScoreService scoreService; //① 在logon方法體中啓動一個獨立的線程,在該獨立的線程中執行ScoreService#addScore()方法 public void logon(String userName) { System.out.println("logon method..."); updateLastLogonTime(userName); Thread myThread = new MyThread(this.scoreService,userName,20); myThread.start(); } public void updateLastLogonTime(String userName) { System.out.println("updateLastLogonTime..."); String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?"; jdbcTemplate.update(sql, System.currentTimeMillis(), userName); } //② 封裝ScoreService#addScore()的線程 private class MyThread extends Thread{ private ScoreService scoreService; private String userName; private int toAdd; private MyThread(ScoreService scoreService,String userName,int toAdd) { this.scoreService = scoreService; this.userName = userName; this.toAdd = toAdd; } public void run() { scoreService.addScore(userName,toAdd); } } } |
將日誌級別設置爲 DEBUG,執行 UserService#logon() 方法,觀察如下輸出的日誌:
[main] (AbstractPlatformTransactionManager.java:365) - Creating new transaction with name [user.multithread.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT ① [main] (DataSourceTransactionManager.java:205) - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@1353249] for JDBC transaction logon method... updateLastLogonTime... [main] (JdbcTemplate.java:785) - Executing prepared SQL update [main] (JdbcTemplate.java:569) - Executing prepared SQL statement [UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?] [main] (JdbcTemplate.java:794) - SQL update affected 0 rows [main] (AbstractPlatformTransactionManager.java:752) - Initiating transaction commit [Thread-2](AbstractPlatformTransactionManager.java:365) - Creating new transaction with name [user.multithread.ScoreService.addScore]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT ② [main] (DataSourceTransactionManager.java:265) - Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@1353249] ③ [main] (DataSourceTransactionManager.java:323) - Releasing JDBC Connection [org.apache.commons.dbcp.PoolableConnection@1353249] after transaction [main] (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource [Thread-2] (DataSourceTransactionManager.java:205) - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@10dc656] for JDBC transaction addScore... [main] (JdbcTemplate.java:416) - Executing SQL statement [DELETE FROM t_user WHERE user_name='tom'] [main] (DataSourceUtils.java:112) - Fetching JDBC Connection from DataSource [Thread-2] (JdbcTemplate.java:785) - Executing prepared SQL update [Thread-2] (JdbcTemplate.java:569) - Executing prepared SQL statement [UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?] [main] (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource [Thread-2] (JdbcTemplate.java:794) - SQL update affected 0 rows [Thread-2] (AbstractPlatformTransactionManager.java:752) - Initiating transaction commit [Thread-2] (DataSourceTransactionManager.java:265) - Committing JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@10dc656] ④ [Thread-2] (DataSourceTransactionManager.java:323) - Releasing JDBC Connection [org.apache.commons.dbcp.PoolableConnection@10dc656] after transaction |
在 ① 處,在主線程(main)執行的 UserService#logon() 方法的事務啓動,在 ③ 處,其對應的事務提交,而在子線程(Thread-2)執行的 ScoreService#addScore() 方法的事務在 ② 處啓動,在 ④ 處對應的事務提交。
因此,咱們能夠得出這樣的結論:在 相同線程中進行相互嵌套調用的事務方法工做於相同的事務中。若是這些相互嵌套調用的方法工做在不一樣的線程中,不一樣線程下的事務方法工做在獨立的事務中。
Spring 聲明式事務是 Spring 最核心,最經常使用的功能。因爲 Spring 經過 IOC 和 AOP 的功能很是透明地實現了聲明式事務的功能,通常的開發者基本上無須瞭解 Spring 聲明式事務的內部細節,僅須要懂得如何配置就能夠了。
可是在實際應用開發過程當中,Spring 的這種透明的高階封裝在帶來便利的同時,也給咱們帶來了迷惑。就像經過流言傳播的消息,最終聽衆已經不清楚事情的真相了,而這對於應用開發來講是很危險 的。本系列文章經過剖析實際應用中給開發者形成迷惑的各類難點,經過分析 Spring 事務管理的內部運做機制將真相還原出來。
在本文中,咱們經過剖析瞭解到如下的真相: