Spring交易管理進階應用程式痛點剖析(1)
Spring最成功,最迷人的地方莫過於輕量級的聲明式交易管理,僅此一點,它就宣告了重量級EJB容器的覆滅。Spring聲明式交易管理將開發人員從繁複的交易管理代碼中解脫出來,專註於商務邏輯的開發上,這是一件可以被拿來頂禮膜拜的事情。
但是,世界並未從此消停,開發人員需要面對的是層出不窮的應用情境,這些情境往往逾越了普通Spring技術書籍的理想界定。因此,隨著應用開發的深入,在使用經過Spring層層封裝的聲明式事務時,開發人員越來越覺得自己墜入了迷霧,陷入了沼澤,體會不到外界所宣稱的那種暢快淋漓。本系列文章的目標旨在整理並剖析實際應用中種種讓我們迷茫的情境,讓陽光照進雲遮霧障的山頭。
很少有使用Spring但不使用Spring交易管理員的應用,因此常常有人會問:是否用了Spring,就一定要用Spring交易管理員,否則就無法進行資料的持久化操作呢?交易管理員和DAO是什麼關係呢?
也許是DAO和交易管理如影隨行的緣故吧,這個看似簡單的問題實實在在地存在著,從初學者心中湧出,縈繞在開發老手的腦際。答案當然是否定的!我們都知道:Spring交易管理是保證資料操作的事務性(即原子性、一致性、隔離性、持久性,也即所謂的ACID),脫離了事務性,DAO照樣可以順利地進行資料的操作。
在預設情況下,dataSource資料來源的autoCommit被設定為true――這也意謂著所有通過Jdbc執行的語句馬上提交,沒有事務。如果將dataSource的defaultAutoCommit設定為false,新增及更改資料的操作都沒有提交到資料庫。對於強調讀速度的應用,資料庫本身可能就不支援事務,如使用MyISAM引擎的MySQL資料庫。這時,無須在Spring應用中配置交易管理員,因為即使配置了,也是沒有實際用處的。也就是說使用MySql資料庫時jdbc設定autoCommit是沒有實際用處的。對於Hibernate來說,情況就有點複雜了。因為Hibernate的交易管理擁有其自身的意義,它和Hibernate一級緩衝有密切的關係:當我們調用Session的save、update等方法時,Hibernate並不直接向資料庫發送SQL語句,而是在提交事務(commit)或flush一級緩衝時才真正向資料庫發送SQL。所以,即使底層資料庫不支援事務,Hibernate的交易管理也是有一定好處的,不會對資料操作的效率造成負面影響。所以,如果是使用Hibernate資料訪問技術,沒有理由不配置HibernateTransactionManager交易管理員。
Spring交易管理進階應用程式痛點剖析(2)
應用分層的迷惑
Web、Service及DAO三層劃分就像西方國家的立法、行政、司法三權分立一樣被奉為金科玉律,甚至有開發人員認為如果要使用Spring交易管理就一定先要進行三層的劃分。這個看似荒唐的論調在開發人員中頗有市場。更有甚者,認為每層必須先定義一個介面,然後再定義一個實作類別。其結果是:一個很簡單的功能,也至少需要3個介面,3個類,再加上視圖層的JSP和JS等,打牌都可以轉上兩桌了,這種誤解貽害不淺。
對將“面向介面編程”奉為圭臬,認為放之四海而皆準的論調,筆者深不以為然。是的,“面向介面編程”是MartinFowler,RodJohnson這些大師提倡的行事原則。如果拿這條原則去開發架構,開發產品,怎麼強調都不為過。但是,對於我們一般的開發人員來說,做的最多的是普通工程項目,往往最多的只是一些對資料庫增、刪、查、改的功能。此時,“面向介面編程”除了帶來更多的類檔案外,看不到更多其它的好處。
Spring架構提供的所有附加的好處(AOP、註解增強、註解MVC等)唯一的前提就是讓POJO的類變成一個受Spring容器管理的Bean,除此以外沒有其它任何的要求。
Spring交易管理進階應用程式痛點剖析(3)
事務方法嵌套調用的迷茫
Spring事務一個被訛傳很廣說法是:一個事務方法不應該調用另一個事務方法,否則將產生兩個事務。結果造成開發人員在設計事務方法時束手束腳,生怕一不小心就踩到地雷。其實這種是不認識Spring事務傳播機制而造成的誤解,Spring對事務控制的支援統一在TransactionDefinition類中描述,該類有以下幾個重要的介面方法:
◆intgetPropagationBehavior():事務的傳播行為;
◆intgetIsolationLevel():事務的隔離等級;
◆intgetTimeout():事務的到期時間;
◆booleanisReadOnly():事務的讀寫特性。
很明顯,除了事務的傳播行為外,事務的其它特性Spring是藉助底層資源的功能來完成的,Spring無非只充當個代理的角色。但是事務的傳播行為卻是Spring憑藉自身的架構提供的功能,是Spring提供給開發人員最珍貴的禮物,訛傳的說法玷汙了Spring事務架構最美麗的光環。所謂事務傳播行為就是多個事務方法相互調用時,事務如何在這些方法間傳播。Spring支援7種事務傳播行為:
◆PROPAGATION_REQUIRED如果當前沒有事務,就建立一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。
◆PROPAGATION_SUPPORTS支援當前事務,如果當前沒有事務,就以非事務方式執行。
◆PROPAGATION_MANDATORY使用當前的事務,如果當前沒有事務,就拋出異常。
◆PROPAGATION_REQUIRES_NEW建立事務,如果當前存在事務,把當前事務掛起。
◆PROPAGATION_NOT_SUPPORTED以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
◆PROPAGATION_NEVER以非事務方式執行,如果當前存在事務,則拋出異常。
◆PROPAGATION_NESTED如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。
Spring預設的事務傳播行為是PROPAGATION_REQUIRED,它適合於絕大多數的情況。假設ServiveX#methodX()都工作在事務環境下(即都被Spring事務增強了),假設程式中存在如下的調用鏈:Service1#method1()->Service2#method2()->Service3#method3(),那麼這3個服務類的3個方法通過Spring的事務傳播機制都工作在同一個事務中。
Spring交易管理進階應用程式痛點剖析(4)
多線程的困惑
由於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。
小結
Spring聲明式事務是Spring最核心,最常用的功能。由於Spring通過IOC和AOP的功能非常透明地實現了聲明式事務的功能,一般的開發人員基本上無須瞭解Spring聲明式事務的內部細節,僅需要懂得如何配置就可以了。
但是在實際應用開發過程中,Spring的這種透明的高階封裝在帶來便利的同時,也給我們帶來了迷惑。就像通過流言傳播的訊息,最終聽眾已經不清楚事情的真相了,而這對於應用開發來說是很危險的。本系列文章通過剖析實際應用中給開發人員造成迷惑的各種痛點,通過分析Spring交易管理的內部運作機制將真相還原出來。在本文中,我們通過剖析瞭解到以下的真相:
◆在沒有交易管理的情況下,DAO照樣可以順利進行資料操作;
◆將應用分成Web,Service及DAO層只是一種參考的開發模式,並非是交易管理工作的前提條件;
◆Spring通過事務傳播機制可以很好地應對事務方法嵌套調用的情況,開發人員無須為了交易管理而刻意改變服務方法的設計;
◆由於單一實例的對象不存線上程安全問題,所以進行交易管理增強Bean可以很好地工作在多線程環境下。
◆混合使用多種資料訪問技術(如SpringJDBC+Hibernate)的交易管理問題;
◆在通過Bean的方法通過SpringAOP增強存在哪些特殊的情況。