歡迎您光臨本站 註冊首頁

Spring事務管理高級應用難點剖析(4)

←手機掃碼閱讀     火星人 @ 2014-03-09 , reply:0

  多線程的困惑

  由於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.下面,通過一個實例對此進行描述:

  清單13UserService.java在事務方法中啟動獨立線程運行另一個事務方法

  40.@Service("userService")

  41.publicclassUserServiceextendsBaseService{

  42.@Autowired

  43.privateJdbcTemplatejdbcTemplate;

  44.

  45.@Autowired

  46.privateScoreServicescoreService;

  47.//①在logon方法體中啟動一個獨立的線程,在該獨立的線程中執行ScoreService#addScore()方法

  48.publicvoidlogon(StringuserName){

  49.System.out.println("logonmethod...");

  50.updateLastLogonTime(userName);

  51.ThreadmyThread=newMyThread(this.scoreService,userName,20);

  52.myThread.start();

  53.}

  54.

  55.publicvoidupdateLastLogonTime(StringuserName){

  56.System.out.println("updateLastLogonTime...");

  57.Stringsql="UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?";

  58.jdbcTemplate.update(sql,System.currentTimeMillis(),userName);

  59.}

  60.//②封裝ScoreService#addScore()的線程

  61.privateclassMyThreadextendsThread{

  62.privateScoreServicescoreService;

  63.privateStringuserName;

  64.privateinttoAdd;

  65.privateMyThread(ScoreServicescoreService,StringuserName,inttoAdd){

  66.this.scoreService=scoreService;

  67.this.userName=userName;

  68.this.toAdd=toAdd;

  69.}

  70.publicvoidrun(){

  71.scoreService.addScore(userName,toAdd);

  72.}

  73.}

  74.}

  將日誌級別設置為DEBUG,執行UserService#logon()方法,觀察以下輸出的日誌:

  清單14執行日誌

  75.[main](AbstractPlatformTransactionManager.java:365)-Creatingnewtransactionwithname

  76.[user.multithread.UserService.logon]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT①

  77.

  78.[main](DataSourceTransactionManager.java:205)-AcquiredConnection

  79.[org.apache.commons.dbcp.PoolableConnection@1353249]forJDBCtransaction

  80.

  81.logonmethod...

  82.

  83.updateLastLogonTime...

  84.

  85.[main](JdbcTemplate.java:785)-ExecutingpreparedSQLupdate

  86.[main](JdbcTemplate.java:569)-ExecutingpreparedSQLstatement

  87.[UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?]

  88.[main](JdbcTemplate.java:794)-SQLupdateaffected0rows

  89.[main](AbstractPlatformTransactionManager.java:752)-Initiatingtransactioncommit

  90.

  91.[Thread-2](AbstractPlatformTransactionManager.java:365)-

  92.Creatingnewtransactionwithname[user.multithread.ScoreService.addScore]:

  93.PROPAGATION_REQUIRED,ISOLATION_DEFAULT②

  94.[main](DataSourceTransactionManager.java:265)-CommittingJDBCtransaction

  95.onConnection[org.apache.commons.dbcp.PoolableConnection@1353249]③

  96.

  97.[main](DataSourceTransactionManager.java:323)-ReleasingJDBCConnection

  98.[org.apache.commons.dbcp.PoolableConnection@1353249]aftertransaction

  99.[main](DataSourceUtils.java:312)-ReturningJDBCConnectiontoDataSource

  100.

  101.[Thread-2](DataSourceTransactionManager.java:205)-AcquiredConnection

  102.[org.apache.commons.dbcp.PoolableConnection@10dc656]forJDBCtransaction

  103.

  104.addScore...

  105.

  106.[main](JdbcTemplate.java:416)-ExecutingSQLstatement

  107.[DELETEFROMt_userWHEREuser_name='tom']

  108.[main](DataSourceUtils.java:112)-FetchingJDBCConnectionfromDataSource

  109.[Thread-2](JdbcTemplate.java:785)-ExecutingpreparedSQLupdate

  110.[Thread-2](JdbcTemplate.java:569)-ExecutingpreparedSQLstatement

  111.[UPDATEt_useruSETu.score=u.score ?WHEREuser_name=?]

  112.[main](DataSourceUtils.java:312)-ReturningJDBCConnectiontoDataSource

  113.[Thread-2](JdbcTemplate.java:794)-SQLupdateaffected0rows

  114.[Thread-2](AbstractPlatformTransactionManager.java:752)-Initiatingtransactioncommit

  115.[Thread-2](DataSourceTransactionManager.java:265)-CommittingJDBCtransaction

  116.onConnection[org.apache.commons.dbcp.PoolableConnection@10dc656]④

  117.[Thread-2](DataSourceTransactionManager.java:323)-ReleasingJDBCConnection

  118.[org.apache.commons.dbcp.PoolableConnection@10dc656]aftertransaction

  在①處,在主線程(main)執行的UserService#logon()方法的事務啟動,在③處,其對應的事務提交,而在子線程(Thread-2)執行的ScoreService#addScore()方法的事務在②處啟動,在④處對應的事務提交.

  ,我們可以得出這樣的結論:在相同線程中進行相互嵌套調用的事務方法工作於相同的事務中.如果這些相互嵌套調用的方法工作在不同的線程中,不同線程下的事務方法工作在獨立的事務中.

  小結

  Spring聲明式事務是Spring最核心,最常用的功能.由於Spring通過IOC和AOP的功能非常透明地實現了聲明式事務的功能,一般的開發者基本上無須了解Spring聲明式事務的內部細節,僅需要懂得如何配置就可以了.

  但是在實際應用開發過程中,Spring的這種透明的高階封裝在帶來便利的同時,也給我們帶來了迷惑.就像通過流言傳播的消息,最終聽眾已經不清楚事情的真相了,而這對於應用開發來說是很危險的.本系列文章通過剖析實際應用中給開發者造成迷惑的各種難點,通過分析Spring事務管理的內部運作機制將真相還原出來.在本文中,我們通過剖析了解到以下的真相:

  ◆在沒有事務管理的情況下,DAO照樣可以順利進行數據操作;

  ◆將應用分成Web,Service及DAO層只是一種參考的開發模式,並非是事務管理工作的前提條件;

  ◆Spring通過事務傳播機制可以很好地應對事務方法嵌套調用的情況,開發者無須為了事務管理而刻意改變服務方法的設計;

  ◆由於單實例的對象不存在線程安全問題,進行事務管理增強的Bean可以很好地工作在多線程環境下.

  ◆混合使用多種數據訪問技術(如SpringJDBC Hibernate)的事務管理問題;

  ◆在通過Bean的方法通過SpringAOP增強存在哪些特殊的情況.


[火星人 ] Spring事務管理高級應用難點剖析(4)已經有713次圍觀

http://coctec.com/docs/java/show-post-60406.html