自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Spring事務(wù)管理高級(jí)應(yīng)用難點(diǎn)剖析

開(kāi)發(fā) 后端
Spring的事務(wù)管理是被使用得最多的功能之一,雖然Spring事務(wù)管理已經(jīng)幫助程序員將要做的事情減到了最小。但在實(shí)際開(kāi)發(fā)中,如果使用不當(dāng),依然會(huì)造成數(shù)據(jù)連接泄漏等問(wèn)題。

Spring最成功,最吸引人的地方莫過(guò)于輕量級(jí)的聲明式事務(wù)管理,僅此一點(diǎn),它就宣告了重量級(jí)EJB容器的覆滅。Spring聲明式事務(wù)管理將開(kāi)發(fā)者從繁復(fù)的事務(wù)管理代碼中解脫出來(lái),專(zhuān)注于業(yè)務(wù)邏輯的開(kāi)發(fā)上,這是一件可以被拿來(lái)頂禮膜拜的事情。

但是,世界并未從此消停,開(kāi)發(fā)人員需要面對(duì)的是層出不窮的應(yīng)用場(chǎng)景,這些場(chǎng)景往往逾越了普通Spring技術(shù)書(shū)籍的理想界定。因此,隨著應(yīng)用開(kāi)發(fā)的深入,在使用經(jīng)過(guò)Spring層層封裝的聲明式事務(wù)時(shí),開(kāi)發(fā)人員越來(lái)越覺(jué)得自己墜入了迷霧,陷入了沼澤,體會(huì)不到外界所宣稱(chēng)的那種暢快淋漓。本系列文章的目標(biāo)旨在整理并剖析實(shí)際應(yīng)用中種種讓我們迷茫的場(chǎng)景,讓陽(yáng)光照進(jìn)云遮霧障的山頭。

很少有使用Spring但不使用Spring事務(wù)管理器的應(yīng)用,因此常常有人會(huì)問(wèn):是否用了Spring,就一定要用Spring事務(wù)管理器,否則就無(wú)法進(jìn)行數(shù)據(jù)的持久化操作呢?事務(wù)管理器和DAO是什么關(guān)系呢?

也許是DAO和事務(wù)管理如影隨行的緣故吧,這個(gè)看似簡(jiǎn)單的問(wèn)題實(shí)實(shí)在在地存在著,從初學(xué)者心中涌出,縈繞在開(kāi)發(fā)老手的腦際。答案當(dāng)然是否定的!我們都知道:Spring事務(wù)管理是保證數(shù)據(jù)操作的事務(wù)性(即原子性、一致性、隔離性、持久性,也即所謂的ACID),脫離了事務(wù)性,DAO照樣可以順利地進(jìn)行數(shù)據(jù)的操作。下面,我們來(lái)看一段使用SpringJDBC進(jìn)行數(shù)據(jù)訪問(wèn)的代碼:

清單1.UserJdbcWithoutTransManagerService.java

  1. packageuser.withouttm;  
  2.  
  3. importorg.springframework.beans.factory.annotation.Autowired;  
  4. importorg.springframework.jdbc.core.JdbcTemplate;  
  5. importorg.springframework.stereotype.Service;  
  6. importorg.springframework.context.ApplicationContext;  
  7. importorg.springframework.context.support.ClassPathXmlApplicationContext;  
  8. importorg.apache.commons.dbcp.BasicDataSource;  
  9.  
  10. @Service("service1")  
  11. publicclassUserJdbcWithoutTransManagerService{  
  12. @Autowired  
  13. privateJdbcTemplatejdbcTemplate;  
  14.  
  15. publicvoidaddScore(StringuserName,inttoAdd){  
  16. Stringsql="UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?";  
  17. jdbcTemplate.update(sql,toAdd,userName);  
  18. }  
  19.  
  20. publicstaticvoidmain(String[]args){  
  21. ApplicationContextctx=  
  22. newClassPathXmlApplicationContext("user/withouttm/jdbcWithoutTransManager.xml");  
  23. UserJdbcWithoutTransManagerServiceservice=  
  24. (UserJdbcWithoutTransManagerService)ctx.getBean("service1");  
  25. JdbcTemplatejdbcTemplate=(JdbcTemplate)ctx.getBean("jdbcTemplate");  
  26. BasicDataSourcebasicDataSource=(BasicDataSource)jdbcTemplate.getDataSource();  
  27.  
  28. //①.檢查數(shù)據(jù)源autoCommit的設(shè)置  
  29. System.out.println("autoCommit:"+basicDataSource.getDefaultAutoCommit());  
  30.  
  31. //②.插入一條記錄,初始分?jǐn)?shù)為10  
  32. jdbcTemplate.execute(  
  33. "INSERTINTOt_user(user_name,password,score)VALUES('tom','123456',10)");  
  34.  
  35. //③.調(diào)用工作在無(wú)事務(wù)環(huán)境下的服務(wù)類(lèi)方法,將分?jǐn)?shù)添加20分  
  36. service.addScore("tom",20);  
  37.  
  38. //④.查看此時(shí)用戶的分?jǐn)?shù)  
  39. intscore=jdbcTemplate.queryForInt(  
  40. "SELECTscoreFROMt_userWHEREuser_name='tom'");  
  41. System.out.println("score:"+score);  
  42. jdbcTemplate.execute("DELETEFROMt_userWHEREuser_name='tom'");  
  43. }  

jdbcWithoutTransManager.xml的配置文件如下所示:

清單2.jdbcWithoutTransManager.xml

  1. xmlversionxmlversion="1.0"encoding="UTF-8"?> 
  2. <beansxmlnsbeansxmlns="http://www.springframework.org/schema/beans" 
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4. xmlns:context="http://www.springframework.org/schema/context" 
  5. xmlns:p="http://www.springframework.org/schema/p" 
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans  
  7. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  8. http://www.springframework.org/schema/context  
  9. http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 
  10. <context:component-scanbase-packagecontext:component-scanbase-package="user.withouttm"/> 
  11.  
  12. <beanidbeanid="dataSource" 
  13. class="org.apache.commons.dbcp.BasicDataSource" 
  14. destroy-method="close" 
  15. p:driverClassName="oracle.jdbc.driver.OracleDriver" 
  16. p:url="jdbc:oracle:thin:@localhost:1521:orcl" 
  17. p:username="test" 
  18. p:password="test"/> 
  19. <beanidbeanid="jdbcTemplate" 
  20. class="org.springframework.jdbc.core.JdbcTemplate" 
  21. p:dataSource-ref="dataSource"/> 
  22. beans> 

運(yùn)行UserJdbcWithoutTransManagerService,在控制臺(tái)上打出如下的結(jié)果:

  1. defaultAutoCommit:true  
  2. score:30 

在jdbcWithoutTransManager.xml中,沒(méi)有配置任何事務(wù)管理器,但是數(shù)據(jù)已經(jīng)成功持久化到數(shù)據(jù)庫(kù)中。在默認(rèn)情況下,dataSource數(shù)據(jù)源的autoCommit被設(shè)置為true――這也意謂著所有通過(guò)JdbcTemplate執(zhí)行的語(yǔ)句馬上提交,沒(méi)有事務(wù)。如果將dataSource的defaultAutoCommit設(shè)置為false,再次運(yùn)行UserJdbcWithoutTransManagerService,將拋出錯(cuò)誤,原因是新增及更改數(shù)據(jù)的操作都沒(méi)有提交到數(shù)據(jù)庫(kù),所以④處的語(yǔ)句因無(wú)法從數(shù)據(jù)庫(kù)中查詢(xún)到匹配的記錄而引發(fā)異常。

對(duì)于強(qiáng)調(diào)讀速度的應(yīng)用,數(shù)據(jù)庫(kù)本身可能就不支持事務(wù),如使用MyISAM引擎的MySQL數(shù)據(jù)庫(kù)。這時(shí),無(wú)須在Spring應(yīng)用中配置事務(wù)管理器,因?yàn)榧词古渲昧?,也是沒(méi)有實(shí)際用處的。

不過(guò),對(duì)于Hibernate來(lái)說(shuō),情況就有點(diǎn)復(fù)雜了。因?yàn)镠ibernate的事務(wù)管理?yè)碛衅渥陨淼囊饬x,它和Hibernate一級(jí)緩存有密切的關(guān)系:當(dāng)我們調(diào)用Session的save、update等方法時(shí),Hibernate并不直接向數(shù)據(jù)庫(kù)發(fā)送SQL語(yǔ)句,而是在提交事務(wù)(commit)或flush一級(jí)緩存時(shí)才真正向數(shù)據(jù)庫(kù)發(fā)送SQL。所以,即使底層數(shù)據(jù)庫(kù)不支持事務(wù),Hibernate的事務(wù)管理也是有一定好處的,不會(huì)對(duì)數(shù)據(jù)操作的效率造成負(fù)面影響。所以,如果是使用Hibernate數(shù)據(jù)訪問(wèn)技術(shù),沒(méi)有理由不配置HibernateTransactionManager事務(wù)管理器。但是,不使用Hibernate事務(wù)管理器,在Spring中,Hibernate照樣也可以工作,來(lái)看下面的例子: #p#

清單3.UserHibernateWithoutTransManagerService.java

  1. packageuser.withouttm;  
  2.  
  3. importorg.springframework.beans.factory.annotation.Autowired;  
  4. importorg.springframework.jdbc.core.JdbcTemplate;  
  5. importorg.springframework.stereotype.Service;  
  6. importorg.springframework.context.ApplicationContext;  
  7. importorg.springframework.context.support.ClassPathXmlApplicationContext;  
  8. importorg.springframework.orm.hibernate3.HibernateTemplate;  
  9. importorg.apache.commons.dbcp.BasicDataSource;  
  10. importuser.User;  
  11.  
  12. @Service("service2")  
  13. publicclassUserHibernateWithoutTransManagerService{  
  14. @Autowired  
  15. privateHibernateTemplatehibernateTemplate;  
  16.  
  17. publicvoidaddScore(StringuserName,inttoAdd){  
  18. Useruser=(User)hibernateTemplate.get(User.class,userName);  
  19. user.setScore(user.getScore()+toAdd);  
  20. hibernateTemplate.update(user);  
  21. }  
  22.  
  23. publicstaticvoidmain(String[]args){  
  24. //參考UserJdbcWithoutTransManagerService相應(yīng)代碼  
  25. …  
  26. }  

此時(shí),采用hiberWithoutTransManager.xml的配置文件,其配置內(nèi)容如下:

清單4.hiberWithoutTransManager.xml

  1. xmlversionxmlversion="1.0"encoding="UTF-8"?> 
  2. <beansxmlnsbeansxmlns="http://www.springframework.org/schema/beans" 
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4. xmlns:context="http://www.springframework.org/schema/context" 
  5. xmlns:p="http://www.springframework.org/schema/p" 
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans  
  7. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  8. http://www.springframework.org/schema/context  
  9. http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 
  10.  
  11. …  
  12. <beanidbeanid="sessionFactory" 
  13. class=  
  14. "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" 
  15. p:dataSource-ref="dataSource"> 
  16. <propertynamepropertyname="annotatedClasses"> 
  17. <list> 
  18. <value>user.Uservalue> 
  19. list> 
  20. property> 
  21. <propertynamepropertyname="hibernateProperties"> 
  22. <props> 
  23. <propkeypropkey="hibernate.dialect"> 
  24. org.hibernate.dialect.Oracle10gDialect  
  25. prop> 
  26. <propkeypropkey="hibernate.show_sql">trueprop> 
  27. props> 
  28. property> 
  29. bean> 
  30.  
  31. <beanidbeanid="hibernateTemplate" 
  32. class="org.springframework.orm.hibernate3.HibernateTemplate" 
  33. p:sessionFactory-ref="sessionFactory"/> 
  34. beans> 

運(yùn)行UserHibernateWithoutTransManagerService,程序正確執(zhí)行,并得到類(lèi)似于UserJdbcWithoutTransManagerService的執(zhí)行結(jié)果,這說(shuō)明Hibernate在Spring中,在沒(méi)有事務(wù)管理器的情況下,依然可以正常地進(jìn)行數(shù)據(jù)的訪問(wèn)。

應(yīng)用分層的迷惑

Web、Service及DAO三層劃分就像西方國(guó)家的立法、行政、司法三權(quán)分立一樣被奉為金科玉律,甚至有開(kāi)發(fā)人員認(rèn)為如果要使用Spring事務(wù)管理就一定先要進(jìn)行三層的劃分。這個(gè)看似荒唐的論調(diào)在開(kāi)發(fā)人員中頗有市場(chǎng)。更有甚者,認(rèn)為每層必須先定義一個(gè)接口,然后再定義一個(gè)實(shí)現(xiàn)類(lèi)。其結(jié)果是:一個(gè)很簡(jiǎn)單的功能,也至少需要3個(gè)接口,3個(gè)類(lèi),再加上視圖層的JSP和JS等,打牌都可以轉(zhuǎn)上兩桌了,這種誤解貽害不淺。

對(duì)將“面向接口編程”奉為圭臬,認(rèn)為放之四海而皆準(zhǔn)的論調(diào),筆者深不以為然。是的,“面向接口編程”是MartinFowler,RodJohnson這些大師提倡的行事原則。如果拿這條原則去開(kāi)發(fā)架構(gòu),開(kāi)發(fā)產(chǎn)品,怎么強(qiáng)調(diào)都不為過(guò)。但是,對(duì)于我們一般的開(kāi)發(fā)人員來(lái)說(shuō),做的最多的是普通工程項(xiàng)目,往往最多的只是一些對(duì)數(shù)據(jù)庫(kù)增、刪、查、改的功能。此時(shí),“面向接口編程”除了帶來(lái)更多的類(lèi)文件外,看不到更多其它的好處。

Spring框架提供的所有附加的好處(AOP、注解增強(qiáng)、注解MVC等)唯一的前提就是讓POJO的類(lèi)變成一個(gè)受Spring容器管理的Bean,除此以外沒(méi)有其它任何的要求。下面的實(shí)例用一個(gè)POJO完成所有的功能,既是Controller,又是Service,還是DAO:

清單5.MixLayerUserService.java

  1. packageuser.mixlayer;  
  2. importorg.springframework.beans.factory.annotation.Autowired;  
  3. importorg.springframework.jdbc.core.JdbcTemplate;  
  4. importorg.springframework.stereotype.Controller;  
  5. importorg.springframework.web.bind.annotation.RequestMapping;  
  6. //①.將POJO類(lèi)通過(guò)注解變成SpringMVC的Controller  
  7. @Controller  
  8. publicclassMixLayerUserService{  
  9.  
  10. //②.自動(dòng)注入JdbcTemplate  
  11. @Autowired  
  12. privateJdbcTemplatejdbcTemplate;  
  13.  
  14. //③.通過(guò)SpringMVC注解映URL請(qǐng)求  
  15. @RequestMapping("/logon.do")  
  16. publicStringlogon(StringuserName,Stringpassword){  
  17. if(isRightUser(userName,password)){  
  18. Stringsql="UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?";  
  19. jdbcTemplate.update(sql,20,userName);  
  20. return"success";  
  21. }else{  
  22. return"fail";  
  23. }  
  24. }  
  25. privatebooleanisRightUser(StringuserName,Stringpassword){  
  26. //dosth...  
  27. returntrue;  
  28. }  

通過(guò)@Controller注解將MixLayerUserService變成Web層的Controller,同時(shí)也是Service層的服務(wù)類(lèi)。此外,由于直接使用JdbcTemplate訪問(wèn)數(shù)據(jù),所以MixLayerUserService還是一個(gè)DAO。來(lái)看一下對(duì)應(yīng)的Spring配置文件:

清單6.applicationContext.xml

  1. xmlversionxmlversion="1.0"encoding="UTF-8"?> 
  2. <beansxmlnsbeansxmlns="http://www.springframework.org/schema/beans" 
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4. xmlns:context="http://www.springframework.org/schema/context" 
  5. xmlns:p="http://www.springframework.org/schema/p" 
  6. xmlns:aop="http://www.springframework.org/schema/aop" 
  7. xmlns:tx="http://www.springframework.org/schema/tx" 
  8. xsi:schemaLocation="http://www.springframework.org/schema/beans  
  9. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  10. http://www.springframework.org/schema/context  
  11.  http://www.springframework.org/schema/context/spring-context-3.0.xsd  
  12.  http://www.springframework.org/schema/aop  
  13.  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
  14.  http://www.springframework.org/schema/tx  
  15. http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> 
  16.  
  17. <context:component-scanbase-packagecontext:component-scanbase-package="user.mixlayer"/> 
  18.  
  19. <beanclassbeanclass="org.springframework.web.servlet.mvc.annotation  
  20.  .AnnotationMethodHandlerAdapter"/> 
  21.  
  22.  
  23. <beanclassbeanclass="org.springframework.web.servlet.view  
  24.  .InternalResourceViewResolver"  
  25. pp:prefix="/WEB-INF/jsp/"p:suffix=".jsp"/> 
  26.  
  27.  
  28. <beanidbeanid="dataSource" 
  29. class="org.apache.commons.dbcp.BasicDataSource" 
  30. destroy-method="close" 
  31. p:driverClassName="oracle.jdbc.driver.OracleDriver" 
  32. p:url="jdbc:oracle:thin:@localhost:1521:orcl" 
  33. p:username="test" 
  34. p:password="test"/> 
  35.  
  36. <beanidbeanid="jdbcTemplate" 
  37. class="org.springframework.jdbc.core.JdbcTemplate" 
  38. p:dataSource-ref="dataSource"/> 
  39.  
  40.  
  41. <beanidbeanid="jdbcManager" 
  42. class="org.springframework.jdbc.datasource.DataSourceTransactionManager" 
  43. p:dataSource-ref="dataSource"/> 
  44.  
  45.  
  46. <aop:configproxy-target-classaop:configproxy-target-class="true"> 
  47. <aop:pointcutidaop:pointcutid="serviceJdbcMethod" 
  48. expression="execution(public*user.mixlayer.MixLayerUserService.*(..))"/> 
  49. <aop:advisorpointcut-refaop:advisorpointcut-ref="serviceJdbcMethod" 
  50. advice-ref="jdbcAdvice"order="0"/> 
  51. aop:config> 
  52. <tx:adviceidtx:adviceid="jdbcAdvice"transaction-manager="jdbcManager"> 
  53. <tx:attributes> 
  54. <tx:methodnametx:methodname="*"/> 
  55. tx:attributes> 
  56. tx:advice> 
  57. beans> 

在①處,我們定義配置了AnnotationMethodHandlerAdapter,以便啟用SpringMVC的注解驅(qū)動(dòng)功能。而②和③處通過(guò)Spring的aop及tx命名空間,以及Aspject的切點(diǎn)表達(dá)式語(yǔ)法進(jìn)行事務(wù)增強(qiáng)的定義,對(duì)MixLayerUserService的所有公有方法進(jìn)行事務(wù)增強(qiáng)。要使程序能夠運(yùn)行起來(lái)還必須進(jìn)行web.xml的相關(guān)配置:#p#

清單7.web.xml

  1. xmlversionxmlversion="1.0"encoding="GB2312"?> 
  2. <web-appversionweb-appversion="2.4"xmlns="http://java.sun.com/xml/ns/j2ee"  
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4. xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee  
  5. http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> 
  6. <context-param> 
  7. <param-name>contextConfigLocationparam-name> 
  8. <param-value>classpath*:user/mixlayer/applicationContext.xmlparam-value> 
  9. context-param> 
  10. <context-param> 
  11. <param-name>log4jConfigLocationparam-name> 
  12. <param-value>/WEB-INF/classes/log4j.propertiesparam-value> 
  13. context-param> 
  14.  
  15. <listener> 
  16. <listener-class> 
  17. org.springframework.web.util.Log4jConfigListener  
  18. listener-class> 
  19. listener> 
  20. <listener> 
  21. <listener-class> 
  22. org.springframework.web.context.ContextLoaderListener  
  23. listener-class> 
  24. listener> 
  25.  
  26. <servlet> 
  27. <servlet-name>userservlet-name> 
  28. <servlet-class> 
  29. org.springframework.web.servlet.DispatcherServlet  
  30. servlet-class> 
  31.  
  32. <init-param> 
  33. <param-name>contextConfigLocationparam-name> 
  34. <param-value>classpath:user/mixlayer/applicationContext.xmlparam-value> 
  35. init-param> 
  36. <load-on-startup>1load-on-startup> 
  37. servlet> 
  38. <servlet-mapping> 
  39. <servlet-name>userservlet-name> 
  40. <url-pattern>*.dourl-pattern> 
  41. servlet-mapping> 
  42. web-app> 

這個(gè)配置文件很簡(jiǎn)單,唯一需要注意的是DispatcherServlet的配置。默認(rèn)情況下SpringMVC根據(jù)Servlet的名字查找WEB-INF下的-servlet.xml作為SpringMVC的配置文件,在此,我們通過(guò)contextConfigLocation參數(shù)顯式指定SpringMVC配置文件的確切位置。

將org.springframework.jdbc及org.springframework.transaction的日志級(jí)別設(shè)置為DEBUG,啟動(dòng)項(xiàng)目,并訪問(wèn)http://localhost:8088/logon.do?userName=tom應(yīng)用,MixLayerUserService#logon方法將作出響應(yīng),查看后臺(tái)輸出日志:

清單8執(zhí)行日志

  1. 13:24:22,625DEBUG(AbstractPlatformTransactionManager.java:365)-  
  2. Creatingnewtransactionwithname  
  3.  [user.mixlayer.MixLayerUserService.logon]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT  
  4. 13:24:22,906DEBUG(DataSourceTransactionManager.java:205)-  
  5. AcquiredConnection[org.apache.commons.dbcp.PoolableConnection@6e1cbf]  
  6.  forJDBCtransaction  
  7. 13:24:22,921DEBUG(DataSourceTransactionManager.java:222)-  
  8. SwitchingJDBCConnection  
  9.  [org.apache.commons.dbcp.PoolableConnection@6e1cbf]tomanualcommit  
  10. 13:24:22,921DEBUG(JdbcTemplate.java:785)-  
  11. ExecutingpreparedSQLupdate  
  12. 13:24:22,921DEBUG(JdbcTemplate.java:569)-  
  13. ExecutingpreparedSQLstatement  
  14.  [UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?]  
  15. 13:24:23,140DEBUG(JdbcTemplate.java:794)-  
  16. SQLupdateaffected0rows  
  17. 13:24:23,140DEBUG(AbstractPlatformTransactionManager.java:752)-  
  18. Initiatingtransactioncommit  
  19. 13:24:23,140DEBUG(DataSourceTransactionManager.java:265)-  
  20. CommittingJDBCtransactiononConnection  
  21.  [org.apache.commons.dbcp.PoolableConnection@6e1cbf]  
  22. 13:24:23,140DEBUG(DataSourceTransactionManager.java:323)-  
  23. ReleasingJDBCConnection[org.apache.commons.dbcp.PoolableConnection@6e1cbf]  
  24.  aftertransaction  
  25. 13:24:23,156DEBUG(DataSourceUtils.java:312)-  
  26. ReturningJDBCConnectiontoDataSource 

日志中粗體部分說(shuō)明了MixLayerUserService#logon方法已經(jīng)正確運(yùn)行在事務(wù)上下文中。Spring框架本身不應(yīng)該是復(fù)雜化代碼的理由,使用Spring的開(kāi)發(fā)者應(yīng)該是無(wú)拘無(wú)束的:從實(shí)際應(yīng)用出發(fā),去除掉那些所謂原則性的接口,去除掉強(qiáng)制分層的束縛,簡(jiǎn)單才是硬道理。

事務(wù)方法嵌套調(diào)用的迷茫

Spring事務(wù)一個(gè)被訛傳很廣說(shuō)法是:一個(gè)事務(wù)方法不應(yīng)該調(diào)用另一個(gè)事務(wù)方法,否則將產(chǎn)生兩個(gè)事務(wù)。結(jié)果造成開(kāi)發(fā)人員在設(shè)計(jì)事務(wù)方法時(shí)束手束腳,生怕一不小心就踩到地雷。其實(shí)這種是不認(rèn)識(shí)Spring事務(wù)傳播機(jī)制而造成的誤解,Spring對(duì)事務(wù)控制的支持統(tǒng)一在TransactionDefinition類(lèi)中描述,該類(lèi)有以下幾個(gè)重要的接口方法:

◆intgetPropagationBehavior():事務(wù)的傳播行為;
◆intgetIsolationLevel():事務(wù)的隔離級(jí)別;
◆intgetTimeout():事務(wù)的過(guò)期時(shí)間;
◆booleanisReadOnly():事務(wù)的讀寫(xiě)特性。

很明顯,除了事務(wù)的傳播行為外,事務(wù)的其它特性Spring是借助底層資源的功能來(lái)完成的,Spring無(wú)非只充當(dāng)個(gè)代理的角色。但是事務(wù)的傳播行為卻是Spring憑借自身的框架提供的功能,是Spring提供給開(kāi)發(fā)者最珍貴的禮物,訛傳的說(shuō)法玷污了Spring事務(wù)框架最美麗的光環(huán)。所謂事務(wù)傳播行為就是多個(gè)事務(wù)方法相互調(diào)用時(shí),事務(wù)如何在這些方法間傳播。Spring支持7種事務(wù)傳播行為:

◆PROPAGATION_REQUIRED如果當(dāng)前沒(méi)有事務(wù),就新建一個(gè)事務(wù),如果已經(jīng)存在一個(gè)事務(wù)中,加入到這個(gè)事務(wù)中。這是最常見(jiàn)的選擇。

◆PROPAGATION_SUPPORTS支持當(dāng)前事務(wù),如果當(dāng)前沒(méi)有事務(wù),就以非事務(wù)方式執(zhí)行。

◆PROPAGATION_MANDATORY使用當(dāng)前的事務(wù),如果當(dāng)前沒(méi)有事務(wù),就拋出異常。

◆PROPAGATION_REQUIRES_NEW新建事務(wù),如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起。

◆PROPAGATION_NOT_SUPPORTED以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起。

◆PROPAGATION_NEVER以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。

◆PROPAGATION_NESTED如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果當(dāng)前沒(méi)有事務(wù),則執(zhí)行與PROPAGATION_REQUIRED類(lèi)似的操作。

Spring默認(rèn)的事務(wù)傳播行為是PROPAGATION_REQUIRED,它適合于絕大多數(shù)的情況。假設(shè)ServiveX#methodX()都工作在事務(wù)環(huán)境下(即都被Spring事務(wù)增強(qiáng)了),假設(shè)程序中存在如下的調(diào)用鏈:Service1#method1()->Service2#method2()->Service3#method3(),那么這3個(gè)服務(wù)類(lèi)的3個(gè)方法通過(guò)Spring的事務(wù)傳播機(jī)制都工作在同一個(gè)事務(wù)中。

下面,我們來(lái)看一下實(shí)例,UserService#logon()方法內(nèi)部調(diào)用了UserService#updateLastLogonTime()和ScoreService#addScore()方法,這兩個(gè)類(lèi)都繼承于BaseService。它們之間的類(lèi)結(jié)構(gòu)說(shuō)明如下:

UserService和ScoreService 
圖1.UserService和ScoreService

具體的代碼如下所示:

清單9UserService.java

  1. @Service("userService")  
  2. publicclassUserServiceextendsBaseService{  
  3. @Autowired  
  4. privateJdbcTemplatejdbcTemplate;  
  5. @Autowired  
  6. privateScoreServicescoreService;  
  7.  
  8. publicvoidlogon(StringuserName){  
  9. updateLastLogonTime(userName);  
  10. scoreService.addScore(userName,20);  
  11. }  
  12.  
  13. publicvoidupdateLastLogonTime(StringuserName){  
  14. Stringsql="UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?";  
  15. jdbcTemplate.update(sql,System.currentTimeMillis(),userName);  
  16. }  

UserService中注入了ScoreService的Bean,ScoreService的代碼如下所示:

清單10ScoreService.java

  1. @Service("scoreUserService")  
  2. publicclassScoreServiceextendsBaseService{  
  3. @Autowired  
  4. privateJdbcTemplatejdbcTemplate;  
  5. publicvoidaddScore(StringuserName,inttoAdd){  
  6. Stringsql="UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?";  
  7. jdbcTemplate.update(sql,toAdd,userName);  
  8. }  

通過(guò)Spring的事務(wù)配置為ScoreService及UserService中所有公有方法都添加事務(wù)增強(qiáng),讓這些方法都工作于事務(wù)環(huán)境下。下面是關(guān)鍵的配置代碼:#p#

清單11事務(wù)增強(qiáng)配置

  1.  
  2. <aop:configproxy-target-classaop:configproxy-target-class="true"> 
  3. <aop:pointcutidaop:pointcutid="serviceJdbcMethod" 
  4.  
  5. expression="within(user.nestcall.BaseService+)"/> 
  6. <aop:advisorpointcut-refaop:advisorpointcut-ref="serviceJdbcMethod" 
  7. advice-ref="jdbcAdvice"order="0"/> 
  8. aop:config> 
  9. <tx:adviceidtx:adviceid="jdbcAdvice"transaction-manager="jdbcManager"> 
  10. <tx:attributes> 
  11. <tx:methodnametx:methodname="*"/> 
  12. tx:attributes> 
  13. tx:advice> 

將日志級(jí)別設(shè)置為DEBUG,啟動(dòng)Spring容器并執(zhí)行UserService#logon()的方法,仔細(xì)觀察如下的輸出日志:

清單12執(zhí)行日志

  1. 16:25:04,765DEBUG(AbstractPlatformTransactionManager.java:365)-  
  2. Creatingnewtransactionwithname[user.nestcall.UserService.logon]:  
  3. PROPAGATION_REQUIRED,ISOLATION_DEFAULT①為UserService#logon方法啟動(dòng)一個(gè)事務(wù)  
  4. 16:25:04,765DEBUG(DataSourceTransactionManager.java:205)-  
  5. AcquiredConnection[org.apache.commons.dbcp.PoolableConnection@32bd65]  
  6. forJDBCtransaction  
  7. logonmethod...  
  8. updateLastLogonTime...②直接執(zhí)行updateLastLogonTime方法  
  9. 16:25:04,781DEBUG(JdbcTemplate.java:785)-ExecutingpreparedSQLupdate  
  10. 16:25:04,781DEBUG(JdbcTemplate.java:569)-ExecutingpreparedSQLstatement  
  11. [UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?]  
  12. 16:25:04,828DEBUG(JdbcTemplate.java:794)-SQLupdateaffected0rows  
  13. 16:25:04,828DEBUG(AbstractPlatformTransactionManager.java:470)-Participating  
  14. inexistingtransaction③ScoreService#addScore方法加入到UserService#logon的事務(wù)中  
  15. addScore...  
  16. 16:25:04,828DEBUG(JdbcTemplate.java:785)-ExecutingpreparedSQLupdate  
  17. 16:25:04,828DEBUG(JdbcTemplate.java:569)-ExecutingpreparedSQLstatement  
  18. [UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?]  
  19. 16:25:04,828DEBUG(JdbcTemplate.java:794)-SQLupdateaffected0rows  
  20. 16:25:04,828DEBUG(AbstractPlatformTransactionManager.java:752)-  
  21. Initiatingtransactioncommit  
  22. 16:25:04,828DEBUG(DataSourceTransactionManager.java:265)-CommittingJDBCtransaction  
  23. onConnection[org.apache.commons.dbcp.PoolableConnection@32bd65]  
  24. 16:25:04,828DEBUG(DataSourceTransactionManager.java:323)-ReleasingJDBCConnection  
  25. [org.apache.commons.dbcp.PoolableConnection@32bd65]aftertransaction  
  26. 16:25:04,828DEBUG(DataSourceUtils.java:312)-ReturningJDBCConnectiontoDataSource  

從上面的輸入日志中,可以清楚地看到Spring為UserService#logon()方法啟動(dòng)了一個(gè)新的事務(wù),而UserSerive#updateLastLogonTime()和UserService#logon()是在相同的類(lèi)中,沒(méi)有觀察到有事務(wù)傳播行為的發(fā)生,其代碼塊好像“直接合并”到UserService#logon()中。接著,當(dāng)執(zhí)行到ScoreService#addScore()方法時(shí),我們就觀察到了發(fā)生了事務(wù)傳播的行為:Participatinginexistingtransaction,這說(shuō)明ScoreService#addScore()添加到UserService#logon()的事務(wù)上下文中,兩者共享同一個(gè)事務(wù)。所以最終的結(jié)果是UserService的logon(),updateLastLogonTime()以及ScoreService的addScore都工作于同一事務(wù)中。

多線程的困惑

由于Spring事務(wù)管理器是通過(guò)線程相關(guān)的ThreadLocal來(lái)保存數(shù)據(jù)訪問(wèn)基礎(chǔ)設(shè)施,再結(jié)合IOC和AOP實(shí)現(xiàn)高級(jí)聲明式事務(wù)的功能,所以Spring的事務(wù)天然地和線程有著千絲萬(wàn)縷的聯(lián)系。

我們知道Web容器本身就是多線程的,Web容器為一個(gè)Http請(qǐng)求創(chuàng)建一個(gè)獨(dú)立的線程,所以由此請(qǐng)求所牽涉到的Spring容器中的Bean也是運(yùn)行于多線程的環(huán)境下。在絕大多數(shù)情況下,Spring的Bean都是單實(shí)例的(singleton),單實(shí)例Bean的最大的好處是線程無(wú)關(guān)性,不存在多線程并發(fā)訪問(wèn)的問(wèn)題,也即是線程安全的。一個(gè)類(lèi)能夠以單實(shí)例的方式運(yùn)行的前提是“無(wú)狀態(tài)”:即一個(gè)類(lèi)不能擁有狀態(tài)化的成員變量。我們知道,在傳統(tǒng)的編程中,DAO必須執(zhí)有一個(gè)Connection,而Connection即是狀態(tài)化的對(duì)象。所以傳統(tǒng)的DAO不能做成單實(shí)例的,每次要用時(shí)都必須new一個(gè)新的實(shí)例。傳統(tǒng)的Service由于將有狀態(tài)的DAO作為成員變量,所以傳統(tǒng)的Service本身也是有狀態(tài)的。

但是在Spring中,DAO和Service都以單實(shí)例的方式存在。Spring是通過(guò)ThreadLocal將有狀態(tài)的變量(如Connection等)本地線程化,達(dá)到另一個(gè)層面上的“線程無(wú)關(guān)”,從而實(shí)現(xiàn)線程安全。Spring不遺余力地將狀態(tài)化的對(duì)象無(wú)狀態(tài)化,就是要達(dá)到單實(shí)例化Bean的目的。由于Spring已經(jīng)通過(guò)ThreadLocal的設(shè)施將Bean無(wú)狀態(tài)化,所以Spring中單實(shí)例Bean對(duì)線程安全問(wèn)題擁有了一種天生的免疫能力。不但單實(shí)例的Service可以成功運(yùn)行于多線程環(huán)境中,Service本身還可以自由地啟動(dòng)獨(dú)立線程以執(zhí)行其它的Service。下面,通過(guò)一個(gè)實(shí)例對(duì)此進(jìn)行描述:

清單13UserService.java在事務(wù)方法中啟動(dòng)獨(dú)立線程運(yùn)行另一個(gè)事務(wù)方法

  1. @Service("userService")  
  2. publicclassUserServiceextendsBaseService{  
  3. @Autowired  
  4. privateJdbcTemplatejdbcTemplate;  
  5.  
  6. @Autowired  
  7. privateScoreServicescoreService;  
  8. //①在logon方法體中啟動(dòng)一個(gè)獨(dú)立的線程,在該獨(dú)立的線程中執(zhí)行ScoreService#addScore()方法  
  9. publicvoidlogon(StringuserName){  
  10. System.out.println("logonmethod...");  
  11. updateLastLogonTime(userName);  
  12. ThreadmyThread=newMyThread(this.scoreService,userName,20);  
  13. myThread.start();  
  14. }  
  15.  
  16. publicvoidupdateLastLogonTime(StringuserName){  
  17. System.out.println("updateLastLogonTime...");  
  18. Stringsql="UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?";  
  19. jdbcTemplate.update(sql,System.currentTimeMillis(),userName);  
  20. }  
  21. //②封裝ScoreService#addScore()的線程  
  22. privateclassMyThreadextendsThread{  
  23. privateScoreServicescoreService;  
  24. privateStringuserName;  
  25. privateinttoAdd;  
  26. privateMyThread(ScoreServicescoreService,StringuserName,inttoAdd){  
  27. this.scoreService=scoreService;  
  28. this.userName=userName;  
  29. this.toAdd=toAdd;  
  30. }  
  31. publicvoidrun(){  
  32. scoreService.addScore(userName,toAdd);  
  33. }  
  34. }  

將日志級(jí)別設(shè)置為DEBUG,執(zhí)行UserService#logon()方法,觀察以下輸出的日志:

清單14執(zhí)行日志

  1. [main](AbstractPlatformTransactionManager.java:365)-Creatingnewtransactionwithname  
  2. [user.multithread.UserService.logon]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT①  
  3.  
  4. [main](DataSourceTransactionManager.java:205)-AcquiredConnection  
  5. [org.apache.commons.dbcp.PoolableConnection@1353249]forJDBCtransaction  
  6.  
  7. logonmethod...  
  8.  
  9. updateLastLogonTime...  
  10.  
  11. [main](JdbcTemplate.java:785)-ExecutingpreparedSQLupdate  
  12. [main](JdbcTemplate.java:569)-ExecutingpreparedSQLstatement  
  13. [UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?]  
  14. [main](JdbcTemplate.java:794)-SQLupdateaffected0rows  
  15. [main](AbstractPlatformTransactionManager.java:752)-Initiatingtransactioncommit  
  16.  
  17. [Thread-2](AbstractPlatformTransactionManager.java:365)-  
  18. Creatingnewtransactionwithname[user.multithread.ScoreService.addScore]:  
  19. PROPAGATION_REQUIRED,ISOLATION_DEFAULT②  
  20. [main](DataSourceTransactionManager.java:265)-CommittingJDBCtransaction  
  21. onConnection[org.apache.commons.dbcp.PoolableConnection@1353249]③  
  22.  
  23. [main](DataSourceTransactionManager.java:323)-ReleasingJDBCConnection  
  24. [org.apache.commons.dbcp.PoolableConnection@1353249]aftertransaction  
  25. [main](DataSourceUtils.java:312)-ReturningJDBCConnectiontoDataSource  
  26.  
  27. [Thread-2](DataSourceTransactionManager.java:205)-AcquiredConnection  
  28. [org.apache.commons.dbcp.PoolableConnection@10dc656]forJDBCtransaction  
  29.  
  30. addScore...  
  31.  
  32. [main](JdbcTemplate.java:416)-ExecutingSQLstatement  
  33. [DELETEFROMt_userWHEREuser_name='tom']  
  34. [main](DataSourceUtils.java:112)-FetchingJDBCConnectionfromDataSource  
  35. [Thread-2](JdbcTemplate.java:785)-ExecutingpreparedSQLupdate  
  36. [Thread-2](JdbcTemplate.java:569)-ExecutingpreparedSQLstatement  
  37. [UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?]  
  38. [main](DataSourceUtils.java:312)-ReturningJDBCConnectiontoDataSource  
  39. [Thread-2](JdbcTemplate.java:794)-SQLupdateaffected0rows  
  40. [Thread-2](AbstractPlatformTransactionManager.java:752)-Initiatingtransactioncommit  
  41. [Thread-2](DataSourceTransactionManager.java:265)-CommittingJDBCtransaction  
  42. onConnection[org.apache.commons.dbcp.PoolableConnection@10dc656]④  
  43. [Thread-2](DataSourceTransactionManager.java:323)-ReleasingJDBCConnection  
  44. [org.apache.commons.dbcp.PoolableConnection@10dc656]aftertransaction 

在①處,在主線程(main)執(zhí)行的UserService#logon()方法的事務(wù)啟動(dòng),在③處,其對(duì)應(yīng)的事務(wù)提交,而在子線程(Thread-2)執(zhí)行的ScoreService#addScore()方法的事務(wù)在②處啟動(dòng),在④處對(duì)應(yīng)的事務(wù)提交。

所以,我們可以得出這樣的結(jié)論:在相同線程中進(jìn)行相互嵌套調(diào)用的事務(wù)方法工作于相同的事務(wù)中。如果這些相互嵌套調(diào)用的方法工作在不同的線程中,不同線程下的事務(wù)方法工作在獨(dú)立的事務(wù)中。

小結(jié)

Spring聲明式事務(wù)是Spring最核心,最常用的功能。由于Spring通過(guò)IOC和AOP的功能非常透明地實(shí)現(xiàn)了聲明式事務(wù)的功能,一般的開(kāi)發(fā)者基本上無(wú)須了解Spring聲明式事務(wù)的內(nèi)部細(xì)節(jié),僅需要懂得如何配置就可以了。

但是在實(shí)際應(yīng)用開(kāi)發(fā)過(guò)程中,Spring的這種透明的高階封裝在帶來(lái)便利的同時(shí),也給我們帶來(lái)了迷惑。就像通過(guò)流言傳播的消息,最終聽(tīng)眾已經(jīng)不清楚事情的真相了,而這對(duì)于應(yīng)用開(kāi)發(fā)來(lái)說(shuō)是很危險(xiǎn)的。本系列文章通過(guò)剖析實(shí)際應(yīng)用中給開(kāi)發(fā)者造成迷惑的各種難點(diǎn),通過(guò)分析Spring事務(wù)管理的內(nèi)部運(yùn)作機(jī)制將真相還原出來(lái)。在本文中,我們通過(guò)剖析了解到以下的真相:

◆在沒(méi)有事務(wù)管理的情況下,DAO照樣可以順利進(jìn)行數(shù)據(jù)操作;

◆將應(yīng)用分成Web,Service及DAO層只是一種參考的開(kāi)發(fā)模式,并非是事務(wù)管理工作的前提條件;

◆Spring通過(guò)事務(wù)傳播機(jī)制可以很好地應(yīng)對(duì)事務(wù)方法嵌套調(diào)用的情況,開(kāi)發(fā)者無(wú)須為了事務(wù)管理而刻意改變服務(wù)方法的設(shè)計(jì);

◆由于單實(shí)例的對(duì)象不存在線程安全問(wèn)題,所以進(jìn)行事務(wù)管理增強(qiáng)的Bean可以很好地工作在多線程環(huán)境下。

在下一篇文章中,筆者將繼續(xù)分析Spring事務(wù)管理的以下難點(diǎn):

◆混合使用多種數(shù)據(jù)訪問(wèn)技術(shù)(如SpringJDBC+Hibernate)的事務(wù)管理問(wèn)題;

◆在通過(guò)Bean的方法通過(guò)SpringAOP增強(qiáng)存在哪些特殊的情況。

【編輯推薦】

  1. 簡(jiǎn)單介紹Spring事務(wù)管理
  2. Spring的Hibernate事務(wù)管理機(jī)制
  3. Spring聲明式事務(wù)管理源碼解讀之事務(wù)提交
  4. 實(shí)例詳解Spring JDBC事務(wù)管理
  5. Hibernate事務(wù)管理機(jī)制剖析
責(zé)任編輯:王曉東 來(lái)源: IBM
相關(guān)推薦

2010-03-29 13:34:15

ibmdwSpring

2014-08-25 09:12:47

Spring事務(wù)管理

2009-09-23 17:48:00

Hibernate事務(wù)

2009-06-17 14:43:47

Spring框架Spring事務(wù)管理

2023-10-08 08:28:10

Spring事務(wù)管理

2009-06-17 14:57:11

Spring事務(wù)管理

2009-06-30 16:57:42

Spring事務(wù)管理

2023-03-27 10:40:09

2009-06-08 17:56:00

SpringJDBC事務(wù)

2009-09-25 12:59:53

Hibernate事務(wù)

2009-02-11 13:08:29

事務(wù)提交事務(wù)管理Spring

2009-02-11 11:14:31

事務(wù)管理事務(wù)開(kāi)始Spring

2025-02-08 10:56:18

2025-02-18 13:00:00

SpringBoot事務(wù)管理代碼

2009-06-03 10:20:11

Hibernate事務(wù)管理配置

2009-09-29 09:44:52

Hibernate事務(wù)

2025-02-21 08:00:00

事務(wù)管理SpringBootJava

2023-05-06 07:29:49

Spring事務(wù)傳播

2022-08-04 08:46:16

單體架構(gòu)微服務(wù)事務(wù)管理

2009-07-17 14:03:34

ibatis DAO事務(wù)管理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)