目前很多公司的架構,從Struts2遷移到了SpringMVC。你有想過為什么不使用Servlet+jsp來構建java web項目,而是采用SpringMVC呢?
既然這樣,我們從源頭說起。Struts2的源頭其實也是Servlet。Servlet的作用是接收瀏覽器傳給服務端的請求(request),并將服務端處理完的響應(response)返回給用戶的瀏覽器,瀏覽器和服務端之間通過http協議進行溝通,其過程是瀏覽器根據用戶的選擇將相關信息按http協議報文的規范組裝請求的http報文,報文通過網絡傳輸到指定的服務器,服務器通過特定的web容器接收這個報文信息,例如:tomcat,jetty,jboss這樣的web容器,web容器會將http報文解析出來,如果是用戶請求,最終解析出來的報文信息會用一個request對象存儲起來,服務端使用這個request做完相應的處理后,服務端程序將結果信息封裝到response對象里,然后將response對象交給web容器,web容器則把這個response對象轉變為http協議的報文,并將報文回傳給瀏覽器,瀏覽器最后解析這個響應報文,將最終結果展示給用戶。
Web容器創造了servlet接口,servlet接口就是開發人員自己實現業務邏輯的地方,程序員開發servlet就好比做填空題,而填空題的語境或者說上下文提示就是由request和response對象,但是javaEE規范里的servlet接口很簡單,就三個方法init,service和destory,但是這個接口太籠統,所以規范里還提供了一個HttpServlet類,這個類根據http請求類型提供了doGet,doPost等方法,servlet接口最大的特點就是根據http協議的特點進行定義,因此做servlet開發時候如果使用者對http協議特點不是特別熟悉,都會碰到或多或少令人迷惑的問題,特別是碰到一些復雜特殊的請求時候:例如文件上傳,返回特殊的文件格式到瀏覽器,這時候使用servlet開發就不是很方便了,servlet開發還有個問題可能大家常常被忽視,就是請求的數據的類型轉化,http協議傳輸都是文本形式,到了web容器解析后也是文本類型,如果碰到貨幣,數字,日期這樣的類型需要我們根據實際情況進行轉化,如果頁面傳送的信息非常多,我們就不得不做大量類型轉化,這種工作沒有什么技術含量,是個體力活而且很容易導致程序錯誤。同時java的企業開發都是圍繞javabean進行,類型轉化好的數據還要封裝到對應的javabean里,這種轉來轉去的事情對于項目開發絕對不是什么好事情,所以古老的struts1為這種問題找到了一種解決方案,就是定義了一個DTO對象(數據傳輸對象),專門負責做這樣的事情,不過到了struts2,整個替代servlet的action本身就是一個javabean。
Java的企業開發一個技術特點就是使用javabean進行的,struts2的特點之一就是它替代servlet的操作類就是一個典型的javabean,首先struts2框架將頁面傳輸的數據進行類型轉化和封裝后將請求信息封裝到了這個javabean的屬性里,這樣我們開發web程序時候就省去了煩心的類型轉化和封裝的問題,前面我講到傳統的servlet是根據http協議進行定義的,它會按你請求方式(post還是get方式)來處理用戶的請求,但是對于一名程序開發人員而言,一個請求,具體到一個url,其實對于服務端而言就是服務端對外提供的一個功能,或者說是服務端對外的一個動作,如果我們使用servlet開發程序我們就得把http的動作轉化為具體的業務動作,這就讓程序開發變得繁瑣,增強了開發的難度,所以struts2替代servlet的javabean就屏蔽了servlet里http的請求方式和具體業務動作轉化的問題,javabean里的每一個方法都可以和每一個url請求一一對應,這必然減輕了開發的難度問題。
Servlet另一個作用就是構造response對象,讓頁面獲得正確的響應,其實現代的瀏覽器是一個多媒體工具,文字,圖片,視屏等等東西都可以在瀏覽器里顯示,資源的不同就會導致http響應報文的差別,如果我們使用servlet開發就要根據資源的不同在java程序里用硬編碼的形式處理,這樣的程序很難復用,而且如果程序員對某種資源的處理理解不到位,就會導致問題的出現,struts2通過配置文件的形式將這樣的邏輯從java程序里剝離出來,使用配置的方式進行統一管理,這個做法和spring的AOP方式類似,這樣就讓結果處理方式更加統一,更加利于管理,同時也提升了程序的健壯性以及降低了開發的難度。
Servlet在MVC開發模式里就是其中C層即控制層,控制層就像俄羅斯的雙頭鷹(一個頭向東看一個頭向西看)一樣,一個頭向M層模型層看,一個頭向V層視圖層看,模型層也是用java編寫的,控制層也屬于服務端語言開發,所以M層和C層的溝通沒有天然的障礙,但是和V層視圖層就不一樣了,這是一個跨語言的溝通,對于瀏覽器,它只懂得html,javascript和CSS,瀏覽器是理解不了java這種語言的東西,但是要讓服務端的東西能被瀏覽器理解接受,我們就必須得把服務端的響應信息放到頁面里,因此就需要一個技術把java的信息轉化到html頁面里,這就是javaEE規范里提供了jsp技術,jsp其實是一種服務端技術而非客戶端技術,不過它看起來似乎更像html技術,最早的jsp開發里都是直接將java代碼寫到頁面里,這種壞處誰都知道,之后javaEE規范提供了自定義標簽技術,使用一種類似html標簽的方式來解析java代碼,struts2框架提供了一整套完整的自定義標簽技術,這似乎聽起來不算啥,但是它的作用非凡,因為自定義標簽之所以叫自定義就是每個人都可以自己來定義,如果沒有一個規范必然產生混亂,而且一套完善的自定義標簽是個系統工程,一套完整的自定義標簽相當于我們在自己定義一套新的開發語言,做程序的人聽到這個一定就會明白開發一套完整的自定義標簽的工作量和開發難度都是難以想象的,而且自定義標簽都是和控制層緊密相連,其難度又會增加一個維度,所以struts2提供的自定義標簽對于業務開發帶來的將是質的飛越。
Servlet里還有兩個重要的技術:監聽器和過濾器,對于監聽器在web開發里使用的場景比較少,都是一些十分特別的情況才會使用,大部分web開發里可以忽略它的使用,我們用的最多的監聽器可能就是對ServletContext創建和銷毀的監聽器,ServletContext是整個web應用的全局對象,它和Web應用的生命周期綁定在一起,因此使用這個監聽器對Web應用的全局信息進行初始化和銷毀操作,例如spring容器的初始化操作。比較有意思的是過濾器,在struts2里有個攔截器,它們的作用相同都是用來攔截請求的,因為攔截器是struts2的特有功能,在struts2里使用攔截器自然比使用過濾器更順手,其實攔截器所用的技術比過濾器更加先進,因為攔截器使用了反射技術,因此攔截器攔截的面更大,控制請求的能力更強,它能完成的任務也會更加的豐富多彩。
在我第一次接觸struts2時候,有人告訴我struts設計的一個目的就是想屏蔽在控制層里操作request和response對象,因為這兩個http協議的兒子會造成web開發里思路的混亂,但是我在實際開發里卻經常不自覺的使用這兩個對象。而且本人做前端開發非常喜歡使用Ajax,使用ajax技術時候我就很討厭struts2的自定義標簽,我更加喜歡在頁面里用Javascript技術處理各種信息,最終struts2在我眼里就是一個servlet的變體,因此曾經有段時間我常常在想是不是可以拋棄struts2,直接用servlet,因為struts2里用到了太多反射機制,特別是使用注解做配置(注解是用反射實現的),在java里反射的執行效率是非常低的,直接使用servlet一定能提升web應用的執行效率。其實這個倒很難做到,因為當時我沒法在servlet里靈活的運用spring技術。
^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^
說完Servlet+jsp技術到Struts2技術的過渡。接下來談談Spring。
spring技術可以說是java企業開發里最重要的技術,不過真的理解spring的作用和意義還真是一件麻煩的事情,很多人對spring理解其實都是停留在使用階段(例如:聲明式事務很好用等等),當今的spring技術生態環境里可謂是蔚為壯觀,spring已經包羅萬象,它的內容之多完全不亞于它的本源java語言了,而spring這么大的框都是建立在ioc和aop技術之上,只有深入理解了這兩個技術我們才能明白為什么spring這個框能裝的下那么多東西了。
首先是ioc,ioc技術第一個解釋叫做控制反轉,它還有個解釋就是依賴注入,這兩個名字很難從字面理解,但是當你理解它的原理后就會發現它們的描述是何等準確。Ioc技術的本質就是構建對象的技術換句話說就是將一個類實例化成對象的技術,在java里實例化類通過new關鍵字進行的,每次new一個類都會產生一個新的實例對象,這么做視乎很浪費,有時這種浪費還挺危險,因為在程序開發時候我們常常只需要某個類永遠只能產生一個的實例對象這個時候就得使用單例模式,此外在設計模式里還可以通過工廠方式產生對象,使用過spring的人看到上面的文字就知道了,spring里bean的定義就和上面的內容一一對應,scope屬性single產生單例對象,prototype產生新對象,bean還可以通過工廠方式產生對象,可以說spring的bean就是制造對象的工具。面向對象編程里對象相當于顯示生活中的一個實體,例如我們有個對象作用是完成打獵的操作,那么打獵這個對象內部包含兩個輔助對象:人和槍,只有人和槍賦予了打獵這個對象,那么打獵對象才能完成打獵的操作,但是構建一個人和槍的對象并不是看起來那么簡單,這里以槍為例,要創造一把槍我們需要金屬,需要機床,需要子彈,而機床和子彈又是兩個新對象,這些對象一個個相互嵌套相互關聯,大伙試想下如果我們在java代碼里構建一個槍的對象那是何其的復雜,假如我們要構造的不是簡單的槍對象而是更加復雜的航空母艦,那么構造這個對象的成本之高是讓人難以想象的,怎么來消除這種對象相互嵌套相互依賴的關系了?spring提供了一種方式,這種方式就是spring提供一個容器,我們在xml文件里定義各個對象的依賴關系,由容器完成對象的構建,當我們java代碼里需要使用某個實例的時候就可以從容器里獲取,那么對象的構建操作就被spring容器接管,所以它被稱為控制反轉,控制反轉的意思就是本來屬于java程序里構建對象的功能交由容器接管,依賴注入就是當程序要使用某個對象時候,容器會把它注入到程序里,這就叫做依賴注入。在java開發里我們想使用某個類提供的功能,有兩種方式,一種就是構造一個新的類,新的類繼承該類,另一種方式則是將某個類定義在新類里,那么兩個類之間就建立一種關聯關系,spring的ioc容器就是實現了這種關聯關系(記住不是繼承關系哦),那么某個類要被賦予到新類有哪些辦法了?一般只有兩種:一種就是通過構造函數,一種就是通過setXXX方式,這也是spring容器使用到了兩種標準的注入方式。
不管是上面說的繼承方式,還是關聯方式其實都是增強目標對象能力的開發手段,在設計模式里有一種代理模式,代理模式將繼承模式和關聯模式結合在一起使用,代理模式就是繼承模式和關聯模式的綜合體,不過這個綜合體的作用倒不是解決對象注入的問題,而是為具體操作對象找到一個保姆或者是秘書,這就和小說里的二號首長一樣,這個二號首長對外代表了具體的實例對象,實例對象的入口和出口都是通過這個二號首長,因為具體的實例對象是一號首長,一號首長是要干大事的,所以一些事務性,重復性的工作例如泡茶,安排車子,這樣的工作是不用勞煩一號首長的大駕,而是二號首長幫忙解決的,這就是aop的思想,aop解決程序開發里事務性,和核心業務無關的問題,但這些問題對于業務場景的實現是很有必要的,在實際開發里aop也是節省代碼的一種方式。
Spring的核心技術的作用本質就是一個溝通機制,spring總是盡全力的讓溝通的雙方信息暢通,同時降低雙方的溝通成本,在現實機構里一個善于溝通的人肯定是該公司的領導,很會溝通的領導能調動起各種資源的積極性,善于溝通的領導就會做到海納百川,讓各種不同人追隨他,所以當今的spring就是一個大框,什么都可以往里裝。Spring很像銀行,它不能直接創造物質財富,但是一切資源都要通過它進行流通,它能控制經濟發展的走向,回到程序的世界,spring的作用是被標榜為程序之間的解耦,spring能降低不同模塊之間的耦合度,原因就是在程序開發里不同模塊之間信息的溝通是通過對象傳遞完成的,而對象能否順利傳遞就是要合理的構建好對象,而管理好對象的構建方式就能管理好對象傳遞,這就是spring給系統架構設計帶來的好處。
^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^ ^_^
說到Spring, Spring的事務你懂嗎?
什么是事務?為什么事務要管理?什么是Spring事務?事務就是對一系列的數據庫操作(比如插入多條數據)進行統一的提交或回滾操作,如果插入成功,那么一起成功,如果中間有一條出現異常,那么回滾之前的所有操作。這樣可以防止出現臟數據,防止數據庫數據出現問題。開發中為了避免這種情況一般都會進行事務管理。在JDBC中,是通過Connection對象進行事務管理的,默認是自動提交事務,可以手工將自動提交關閉,通過commit方法進行提交,rollback方法進行回滾,如果不提交,則數據不會真正的插入到數據庫中。Hibernate中則是通過Transaction進行事務管理,處理方法與JDBC中類似。Spring中也有自己的事務管理機制,一般是使用TransactionMananger進行管理,可以通過Spring的注入完成此功能。
我通俗的理解如下:spring只是控制數據庫的事務提交和回滾,借助于java的反射機制,在事務控制的方法(通常是service層的方法)前后獲取事務開啟session,然后執行你的數據操作,如果你的方法內有異常被拋出,spring會捕獲異常并回滾你在這個方法內所有的數據操作,如果成功則提交所有的數據,最后spring會幫你關閉需要關閉的東西。所以spring想要做的是,要程序員專注于寫邏輯,不需要關心數據庫何時開啟和關閉連接。
再說的通俗點兒:事務,對于一件事,對了就提交,錯了就回滾,什么時候回滾,都是事務要做的事情。具體的操作由spring 配置來管理(同時你也可以脫離框架,自己寫事務管理方法)。
使用Spring事務的優點?
在SSH框假中Spring充當了管理容器的角色。我們都知道Hibernate用來做持久層,因為它將JDBC做了一個良好的封裝,程序員在與數據庫進行交互時可以不用書寫大量的SQL語句。Struts是用來做應用層的,他它負責調用業務邏輯serivce層。所以SSH框架的流程大致是:Jsp頁面----Struts------Service(業務邏輯處理類)---Hibernate(左到右)。struts負責控制Service(業務邏輯處理類),從而控制了Service的生命周期,這樣層與層之間的依賴很強,屬于耦合。這時,使用spring框架就起到了控制Action對象(Strus中的)和Service類的作用,兩者之間的關系就松散了,Spring的Ioc機制(控制反轉和依賴注入)正是用在此處。 Spring的Ioc(控制反轉和依賴注入) 控制反轉:就是由容器控制程序之間的(依賴)關系,而非傳統實現中,由程序代碼直接操控 依賴注入:組件之間的依賴關系由容器在運行期決定 ,由容器動態的將某種依賴關系注入到組件之中。 從上面我們不難看出:從頭到尾Action僅僅是充當了Service的控制工具,這些具體的業務方法是怎樣實現的,他根本就不會管,也不會問,他只要知道這些業務實現類所提供的方法接口就可以了。而在以往單獨使用Struts框架的時候,所有的業務方法類的生命周期,甚至是一些業務流程都是由Action來控制的。層與層之間耦合性太緊密了,既降低了數據訪問的效率又使業務邏輯看起來很復雜,代碼量也很多。Spring容器控制所有Action對象和業務邏輯類的生命周期,由于上層不再控制下層的生命周期,層與層之間實現了完全脫耦,使程序運行起來效率更高,維護起來也方便。 使用Spring的第二個好處(AOP應用): 事務的處理: 在以往的JDBCTemplate中事務提交成功,異常處理都是通過Try/Catch 來完成,而在Spring中。Spring容器集成了TransactionTemplate,她封裝了所有對事務處理的功能,包括異常時事務回滾,操作成功時數據提交等復雜業務功能。這都是由Spring容器來管理,大大減少了程序員的代碼量,也對事務有了很好的管理控制。Hibernate中也有對事務的管理,hibernate中事務管理是通過SessionFactory創建和維護Session來完成。而Spring對SessionFactory配置也進行了整合,不需要在通過hibernate.cfg.xml來對SessionaFactory進行設定。這樣的話就可以很好的利用Sping對事務管理強大功能。避免了每次對數據操作都要現獲得Session實例來啟動事務/提交/回滾事務還有繁瑣的Try/Catch操作。這些也就是Spring中的AOP(面向切面編程)機制很好的應用。一方面使開發業務邏輯更清晰、專業分工更加容易進行。另一方面就是應用Spirng AOP隔離降低了程序的耦合性使我們可以在不同的應用中將各個切面結合起來使用大大提高了代碼重用度。有利于代碼重用,特別是Dao代碼的重用。事務往往和業務規則緊密關聯。當業務邏輯發生改變,意味著dao的大幅度改動。系統規模達到一定程度,修改風險相當大。Spring的好處是不更改現有的dao,僅需對現有的service bean進行配置就達到事務效果了。同時,把事務統一在service層,系統結構更清晰。
為什么說風險風大?Spring對于事務的配置有兩種方式:第一種,使用xml形式,第二種,使用注解的形式。基于XMl方式: 優點:可以在后期維護的時候適當的調整事務管理模式,并且只要遵循一定的命名規范,可以讓程序員不必關心事務。 缺點:系統越龐大,xml配置就越大。基于注解方式:優點:配置比較方便,程序員只要在service層代碼設置即可以實現。不需要知道系統需要多少個bean,交給容器來注入就好了。 缺點:當你要修改或刪除一個bean的時候,你無法確定到底有多少個其他的bean依賴于這個bean。(解決方法:需要有嚴格的開發文檔,在修改實現時盡可能繼續遵守相應的接口避免使其他依賴于此的bean不可用)
在我們用SSH開發項目的時候,我們一般都是將事務設置在Service層 那么當我們調用Service層的一個方法的時候它能夠保證我們的這個方法中執行的所有的對數據庫的更新操作保持在一個事務中,在事務層里面調用的這些方法要么全部成功,要么全部失敗。那么事務的傳播特性也是從這里說起的。如果你在你的Service層的這個方法中,除了調用了Dao層的方法之外,還調用了本類的其他的Service方法,那么在調用其他的 Service方法的時候,這個事務是怎么規定的呢,我必須保證我在我方法里掉用的這個方法與我本身的方法處在同一個事務中,否則如何保證事物的一致性。事務的傳播特性就是解決這個問題的,“事務是會傳播的”在Spring中有針對傳播特性的多種配置我們大多數情況下只用其中的一種:PROPGATION_REQUIRED:這個配置項的意思是說當我調用service層的方法的時候開啟一個事務(具體調用那一層的方法開始創建事務,要看你的aop的配置),那么在調用這個service層里面的其他的方法的時候,如果當前方法產生了事務就用當前方法產生的事務,否則就創建一個新的事務。這個工作是由Spring來幫助我們完成的。以前沒有Spring幫助我們完成事務的時候我們必須自己手動的控制事務,例如當我們項目中僅僅使用hibernate,而沒有集成進 spring的時候,我們在一個service層中調用其他的業務邏輯方法,為了保證事物必須也要把當前的hibernate session傳遞到下一個方法中,或者采用ThreadLocal的方法,將session傳遞給下一個方法,其實都是一個目的。現在這個工作由 spring來幫助我們完成,就可以讓我們更加的專注于我們的業務邏輯。而不用去關心事務的問題。默認情況下當發生RuntimeException的情況下,事務才會回滾,所以要注意一下。如果你在程序發生錯誤的情況下,有自己的異常處理機制定義自己的Exception,必須從RuntimeException類繼承,這樣事務才會回滾!
補充文字來說一下上面的關于Spring的一個問題。基于xml和基于注解,當然了它們都有優缺點。我們通俗的說,是這樣的。先來回顧一下傳統上是如何配置 Bean 并完成 Bean 之間依賴關系的建立。下面是 3 個類,它們分別是 Office、Car 和 Boss,這 3 個類需要在 Spring 容器中配置為 Bean。
// Office.javapublic class Office { private String officeNo =”001”; //省略 get/setter @Override public String toString() { return "officeNo:" + officeNo; }}// Car.javapublic class Car { private String brand; private double price; // 省略 get/setter @Override public String toString() { return "brand:" + brand + "," + "price:" + price; }}// Boss.javapublic class Boss { private Car car; private Office office; // 省略 get/setter @Override public String toString() { return "car:" + car + "/n" + "office:" + office; }}我們在 Spring 容器中將 Office 和 Car 聲明為 Bean,并注入到 Boss Bean 中:下面是使用傳統 XML 完成這個工作的配置文件 beans.xml:
// bean.xml將以上三個類配置成bean。<?xml version="1.0" encoding="UTF-8" ?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="boss" class="com.baobaotao.Boss"> <property name="car" ref="car"/> <property name="office" ref="office" /> </bean> <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="002"/> </bean> <bean id="car" class="com.baobaotao.Car" scope="singleton"> <property name="brand" value=" 紅旗 CA72"/> <property name="price" value="2000"/> </bean></beans>
// 當我們運行這段代碼時,控制臺將正確打印出boss的信息。這說明 Spring 容器已經正確完成了 Bean 創建和裝配的工作。import org.springframework.context.applicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;public class AnnoIoCTest { public static void main(String[] args) { String[] locations = {"beans.xml"}; ApplicationContext ctx = new ClassPathXmlApplicationContext(locations); Boss boss = (Boss) ctx.getBean("boss"); System.out.println(boss); }}
我們知道 Spring 2.5 中引入了 @Autowired 注釋,它可以對類成員變量、方法及構造函數進行標注,完成自動裝配的工作。來看一下使用@Autowired 進行成員變量自動注入的代碼:
// Autowired是自動裝配的意思import org.springframework.beans.factory.annotation.Autowired;public class Boss { @Autowired private Car car; @Autowired private Office office;}// Spring 通過一個 BeanPostProcessor 對 @Autowired 進行解析,所以要讓@Autowired 起作用必須事先在 Spring 容器中聲明 AutowiredAnnotationBeanPostProcessor Bean。// 讓系統認識@Autowired,讓@Autowired注釋工作起來。<?xml version="1.0" encoding="UTF-8" ?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- 該 BeanPostProcessor 將自動起作用,對標注 @Autowired 的 Bean 進行自動注入 --> <bean class="org.springframework.beans.factory.annotation. AutowiredAnnotationBeanPostProcessor"/> <!-- 移除 boss Bean 的屬性注入配置的信息 --> <bean id="boss" class="com.baobaotao.Boss"/> // 這里面沒配置哦 <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="001"/> </bean> <bean id="car" class="com.baobaotao.Car" scope="singleton"> <property name="brand" value=" 紅旗 CA72"/> <property name="price" value="2000"/> </bean></beans>
這樣,當 Spring 容器啟動時,AutowiredAnnotationBeanPostProcessor 將掃描 Spring 容器中所有 Bean,當發現 Bean 中擁有@Autowired 注釋時就找到和其匹配(默認按類型匹配)的 Bean,并注入到對應的地方中去。按照上面的配置,Spring 將直接采用 Java 反射機制對 Boss 中的
car 和 office 這兩個私有成員變量進行自動注入。所以對成員變量使用@Autowired 后,您大可將它們的 setter 方法(setCar() 和 setOffice())從 Boss 中刪除。當然,您也可以通過 @Autowired 對方法或構造函數進行標注,來看下面的代碼:
public class Boss { private Car car; private Office office; @Autowired public void setCar(Car car) { this.car = car; } @Autowired public void setOffice(Office office) { this.office = office; }}這時,@Autowired 將查找被標注的方法的入參類型的 Bean,并調用方法自動注入這些 Bean。而下面的使用方法則對構造函數進行標注: SpringIOC三種注入方式(接口注入、setter注入、構造器注入)public class Boss { private Car car; private Office office; @Autowired public Boss(Car car ,Office office){ this.car = car; this.office = office ; }}// 由于Boss()構造函數有兩個入參,分別是car和office,@Autowired將分別尋找和它們類型匹配的 Bean,將它們作為Boss(Car car ,Office office)的入參來創建BossBean。在默認情況下使用@Autowired注釋進行自動注入時,Spring 容器中匹配的候選 Bean 數目必須有且僅有一個。當找不到一個匹配的 Bean 時,Spring 容器將拋出BeanCreationException異常,并指出必須至少擁有一個匹配的 Bean。我們可以來做一個實驗:
<?xml version="1.0" encoding="UTF-8" ?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd "> <bean class="org.springframework.beans.factory.annotation. AutowiredAnnotationBeanPostProcessor"/> <bean id="boss" class="com.baobaotao.Boss"/> <!-- 將 office Bean 注釋掉 --> <!-- <bean id="office" class="com.baobaotao.Office"> //然后你想,當多個bean之間互相依賴的時候,是不是維護起來很麻煩啊。 <property name="officeNo" value="001"/> </bean>--> <bean id="car" class="com.baobaotao.Car" scope="singleton"> <property name="brand" value=" 紅旗 CA72"/> <property name="price" value="2000"/> </bean></beans>
由于 office Bean 被注釋掉了,所以 Spring 容器中將沒有類型為 Office 的 Bean 了,而 Boss 的office 屬性標注了 @Autowired,當啟動 Spring 容器時,異常就產生了。當不能確定 Spring 容器中一定擁有某個類的 Bean 時,可以在需要自動注入該類 Bean 的地方可以使用 @Autowired(required = false),這等于告訴 Spring:在找不到匹配 Bean 時也不報錯。來看一下具體的例子:
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Required;public class Boss { private Car car; private Office office; @Autowired public void setCar(Car car) { this.car = car; } @Autowired(required = false) public void setOffice(Office office) { this.office = office; }}當然,一般情況下,使用 @Autowired 的地方都是需要注入 Bean 的,使用了自動注入而又允許不注入的情況一般僅會在開發期或測試期碰到(如為了快速啟動 Spring 容器,僅引入一些模塊的 Spring 配置文件),所以@Autowired(required = false) 會很少用到。和找不到一個類型匹配 Bean 相反的一個錯誤是:如果 Spring 容器中擁有多個候選 Bean,Spring 容器在啟動時也會拋出 BeanCreationException 異常。來看下面的例子:
// 在 beans.xml 中配置兩個 Office 類型的 Bean<bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="001"/></bean><bean id="office2" class="com.baobaotao.Office"> <property name="officeNo" value="001"/></bean>
我們在 Spring 容器中配置了兩個類型為 Office 類型的 Bean,當對 Boss 的 office 成員變量進行自動注入時,Spring 容器將無法確定到底要用哪一個 Bean,因此異常發生了。Spring 允許我們通過 @Qualifier 注釋指定注入 Bean 的名稱,這樣歧義就消除了,可以通過下面的方法解決異常:
@Autowiredpublic void setOffice(@Qualifier("office")Office office) { this.office = office;}@Qualifier("office") 中的 office 是 Bean 的名稱,所以 @Autowired 和@Qualifier 結合使用時,自動注入的策略就從 byType 轉變成 byName 了。@Autowired 可以對成員變量、方法以及構造函數進行注釋,而@Qualifier 的標注對象是成員變量、方法入參、構造函數入參。正是由于注釋對象的不同,所以 Spring 不將 @Autowired 和@Qualifier 統一成一個注釋類。下面是對成員變量和構造函數入參進行注釋的代碼:對成員變量進行注釋:
public class Boss { @Autowired private Car car; @Autowired @Qualifier("office") private Office office;}我們面試中提到Spring框架經常被問到的一塊兒是Spring的事務,它優于struts2,優于hibernate。那么我們現在就詳細的說說這個東西。
// 事務的特性:原子性、一致性、隔離性、持久性。常常問的:1. Spring事務的傳播特性; 2. Spring事務的隔離機制。
我們先來探討一下數據庫事務的隔離級別:
事務的(ACID)特性是由關系數據庫管理系統(RDBMS,數據庫系統)來實現的。數據庫管理系統采用日志來保證事務的原子性、一致性和持久性。日志記錄了事務對數據庫所做的更新,如果某個事務在執行過程中發生錯誤,就可以根據日志,撤銷事務對數據庫已做的更新,使數據庫退回到執行事務前的初始狀態。數據庫管理系統采用鎖機制來實現事務的隔離性。當多個事務同時更新數據庫中相同的數據時,只允許持有鎖的事務能更新該數據,其他事務必須等待,直到前一個事務釋放了鎖,其他事務才有機會更新該數據。幻讀 是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,比如這種修改涉及到表中的“全部數據行”。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入“一行新數據”。那么,以后就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣.一般解決幻讀的方法是增加范圍鎖RangeS,鎖定檢鎖范圍為只讀,這樣就避免了幻讀。臟讀 就是指當一個事務正在訪問數據,并且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。因為這個數據是還沒有提交的數據,那么另外一個事務讀到的這個數據是臟數據,依據臟數據所做的操作可能是不正確的。
數據庫系統有四個隔離級別。對數據庫使用何種隔離級別要審慎分析,因為維護一個最高的隔離級別雖然會防止數據的出錯,但是卻導致了并行度的損失,以及導致死鎖出現的可能性增加。然而,降低隔離級別,卻會引起一些難以發現的bug。
1. 序列化 Serializable,添加范圍鎖(比如表鎖,頁鎖等,關于range lock,我也沒有很深入的研究),直到transaction A結束。以此阻止其它trasaction B對此范圍內的insert,update等操作。幻讀,臟讀,不可重復讀等問題都不會發生。
2. 可重復讀 repeatable read,對于讀出的記錄,添加共享鎖直到transaction A結束。其它transaction B對這個記錄的試圖修改會一直等待直到trasaction A結束。InnoDB默認級別。可能發生的問題:當執行一個范圍查詢時,可能會發生幻讀。
3. 提交讀 read commited,在trasaction A中讀取數據時對記錄添加共享鎖,但讀取結束立即釋放。其它transaction B對這個記錄的試圖修改會一直等待直到A中的讀取過程結束,而不需要整個trasaction A的結束。所以,在trasaction A的不同階段對同一記錄的讀取結果可能是不同的。
可能發生的問題:不可重復讀。
4. 未提交讀 read uncommited,不添加共享鎖。所以其它trasaction B可以在trasaction A對記錄的讀取過程中修改同一記錄,可能會導致A讀取的數據是一個被破壞的或者說不完整不正確的數據。另外,在trasaction A中可以讀取到trasaction B(未提交)中修改的數據。比如trasaction B對R記錄修改了,但未提交。此時,在Trasaction A中讀取R記錄,讀出的是被B修改過的數據。可能發生的問題:臟讀。
那么Spring的事務隔離機制和Spring事務的傳播屬性呢,Spring的隔離級別我們知道目的是為了防止幻讀和臟讀。Spring的事務傳播屬性似乎好難理解。。接下來,先解釋下什么叫做事務的傳播屬性:
我們都知道事務的概念,那么事務的傳播特性是什么呢?(先著重介紹傳播特性的概念,關于傳播特性的相關配置稍后再介紹)背景:當我們用SSH開發項目的時候,我們一般都是將事務設置在Service層 那么當我們調用Service層的一個方法的時候它能夠保證我們的這個方法中執行的所有的對數據庫的更新操作保持在一個事務中,在事務層里面調用的這些方法要么全部成功,要么全部失敗。那么事務的傳播特性也是從這里說起的。場景:如果你在你的Service層的這個方法中,除了調用了Dao層的方法之外,還調用了本類的其他的Service方法,那么在調用其他的Service方法的時候,這個事務是怎么規定的呢,我必須保證我在我方法里掉用的這個方法與我本身的方法處在同一個事務中,否則如何保證事物的一致性。事務的傳播特性就是解決這個問題的,“事務是會傳播的”在Spring中有針對傳播特性的多種配置我們大多數情況下只用其中的1種:PROPGATION_REQUIRED:這個配置項的意思是說當我調用service層的方法的時候開啟一個事務(具體調用那一層的方法開始創建事務,要看你的aop的配置),那么在調用這個service層里面的其他的方法的時候,如果當前方法產生了事務就用當前方法產生的事務,否則就創建一個新的事務。這個工作使由Spring來幫助我們完成的。以前沒有Spring幫助我們完成事務的時候我們必須自己手動的控制事務,例如當我們項目中僅僅使用hibernate,而沒有集成進spring的時候,我們在一個service層中調用其他的業務邏輯方法,為了保證事物必須也要把當前的hibernate session傳遞到下一個方法中,或者采用ThreadLocal的方法,將session傳遞給下一個方法,其實都是一個目的。現在這個工作由spring來幫助我們完成,就可以讓我們更加的專注于我們的業務邏輯。而不用去關心事務的問題。默認情況下當發生RuntimeException的情況下,事務才會回滾,所以要注意一下如果你在程序發生錯誤的情況下,有自己的異常處理機制定義自己的Exception,必須從RuntimeException類繼承這樣事務才會回滾!
好了,知道了事務的傳播特性之后,我們看看Spring提供的六種事務傳播Propagation特性:仔細感覺一下,六中Spring事務傳播機制對稱美
1. Propagation required, 如果當前沒有事務,就新建一個事務。2. Propagation supports, 如果當前沒有事務,就以非事務方式執行。3. Propagation mandatory, 如果當前沒有事務,就拋出異常。4. Propagation requires new, 如果當前存在事務,掛起當前事務,新建事務。5. Propagation not supported, 如果當前存在事務,把當前事務掛起,以非事務方式執行。6. Propagation never, 如果當前存在事務,拋出異常。以非事務方式執行。
關于六種事務傳播機制的具體應用場景,參考: Spring事務傳播機制博客
了解了Spring事務的傳播特性,再來看看Spring事務的五種隔離級別isolation level。
// 先再來回顧前面說到的三個概念臟讀: 指當一個事務正在訪問數據,并且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。因為這個數據是還沒有提交的數據, 那么另外一個事務讀到的這個數據是臟數據,依據臟數據所做的操作可能是不正確的。 不可重復讀: 指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那么,在第一個事務中的兩次讀數據之間,由于第二個事務的修改,那么第一個事務兩次讀到的數據可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為是不可重復讀。 幻覺讀: 指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那么,以后就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。
// 再來看Spring事務的隔離級別isolation level: 1. isolation default: 這是一個默認的隔離級別,使用數據庫默認的事務隔離級別.(一般情況下,使用這種)。另外四個與JDBC的隔離級別相對應. 2. isolation read uncommited: 這個是事務最低的隔離級別,它允許令外一個事務可以看到這個事務未提交的數據。這種隔離級別會產生臟讀,不可重復讀和幻像讀。(trasaction B可以在trasaction A對記錄的讀取過程中修改同一記錄) 產生臟讀、產生不重復讀、產生幻讀。 3. isolation read commited: 保證一個事務修改的數據提交后才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數據.(trasaction B提交后trasaction A才能讀取) 避免臟讀、會產生不重復讀、會產生幻讀。 4. isolation repeatable read: 這種事務隔離級別可以防止臟讀,不可重復讀。但是可能出現幻讀。它除了保證一個事務不能讀取另一個事務未提交的數據外,還保證了避免下面的情況產生(不可重復讀)。 避免臟讀、避免不重復讀、會產生幻讀。 5. isolation serializable.這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。 除了防止臟讀,不可重復讀外,還避免了幻讀。 避免臟讀、避免不重復讀、避免幻讀。
換種口吻說,聲明式事務。聲明式事務是Spring提供的對程序事務管理的方式之一。Sping的聲明式事務,就是在配置文件中采用配置的方式對事務進行管理。Spring中的AOP即,是完成事務管理工作的。
總結一下我們出幾道面試題整合一下Spring3的知識。
1. Spring的工作原理:
1.客戶端請求提交到DispatcherServlet2. 由DispatcherServlet控制器查詢一個或多個HandlerMapping,找到處理請求的Controller3. DispatcherServlet將請求提交到Controller4. Controller調用業務邏輯處理后,返回ModelAndView5. DispatcherServlet查詢一個或多個ViewResoler視圖解析器,找到ModelAndView指定的視圖6. 視圖負責將結果顯示到客戶端2. 為什么要用Spring?
1、Spring能很好的與各大框架進行集成2、創建對象時,如果我們不用spring。需要用工廠模式來創建,這個spring相當于工廠模式已經幫我們做了創建對象的功能(IOC、依賴注入)。3、在用Hibernate的時候,如果不用spring每次都要寫事務的提交代碼,有了spring可以通過AOP幫助我們管理事務。4、面向切面編程(AOP)在要記錄日志的時候添加一條記錄后需要在數據里同時添加一條添加成功了或失敗的記錄,那么就可以用Spring的Aop來處理,雖然不用Aop也能做但是不用Spring的Aop就會寫很多重復的代碼。AOP 讓開發人員可以創建非行為性的關注點,稱為橫切關注點,并將它們插入到應用程序代碼中。使用 AOP 后,公共服務 (比 如日志、持久性、事務等)就可以分解成方面并應用到域對象上,同時不會增加域對象的對象模型的復雜性。IOC 允許創建一個可以構造對象的應用環境,然后向這些對象傳遞它們的協作對象。正如單詞 倒置 所表明的,IOC 就像反 過來的 JNDI。沒有使用一堆抽象工廠、服務定位器、單元素(singleton)和直接構造(straight construction),每一個對象都是用 其協作對象構造的。因此是由容器管理協作對象(collaborator)。Spring即使一個AOP框架,也是一IOC容器。 Spring 最好的地方是它有助于您替換對象。有了 Spring,只要用 JavaBean 屬性和配置文件加入依賴性(協作對象)。然后可以很容易地在需要時替換具有類似接口的協作對象。
3. 請你談談SSH的整合?
請你談談SSH整合 SSH:Struts(表示層)+Hibernate(持久層)+Spring(業務層)a、Struts Struts是一個表示層框架,主要作用是界面展示,接收請求,分發請求。b、Hibernate Hibernate是一個持久層框架,它只負責與關系數據庫的操作。c、Spring Spring是一個業務層框架,是一個整合的框架,能夠很好地黏合表示層與持久層。
4. 介紹一下Spring的事務管理?
介紹一下Spring的事務管理 事務就是對一系列的數據庫操作(比如插入多條數據)進行統一的提交或回滾操作,如果插入成功,那么一起成功,如果中間有一條出現異常,那么回滾之前的所有操作。這樣可以防止出現臟數據,防止數據庫數據出現問題。開發中為了避免這種情況一般都會進行事務管理。Spring中也有自己的事務管理機制,一般是使用TransactionMananger進行管理,可以通過Spring的注入來完成此功能。
5. 什么是依賴注入,依賴注入的作用是什么?
什么是依賴注入,依賴注入的作用是什么? IOC是一種思想,它能指導我們如何設計出松耦合、更優良的程序。傳統應用程序都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難于測試. 有了IOC容器后,把創建和查找依賴對象的控制權交給了容器,由容器進行注入組合對象,所以對象與對象之間是松散耦合,這樣也方便測試,利于功能復用,更重要的是使得程序的整個體系結構變得非常靈活。依賴注入的作用:減少類間耦合度,避免用new來創建對象。
6.什么是aop,aop的作用是什么?
什么是AOP,AOP的作用是什么? AOP,面向切面編程,就是把可重用的功能提取出來,然后將這些通用功能在合適的時候織入到應用程序中,比如事務管理、權限控制、日志記錄、性能統計等。AOP的作用AOP并沒有幫助我們解決任何新的問題,它只是提供了一種更好的辦法,能夠用更少的工作量來解決現有的一些問題,使得系統更加健壯,可維護性更好。
7.Spring中的BeanFactory與ApplicationContext的作用有哪些?
1、BeanFactory負責讀取bean的配置文件,管理bean的加載、實例化,維護bean之間的依賴關系,負責bean的生命周期。 2、ApplicationContext除了提供上述BeanFactory所能提供的功能之外,還提供了更完整的框架功能: a. 國際化支持b. 資源訪問c. 事件傳遞
8. Hibernate的工作原理?
Hibernate工作原理及為什么要用? 原理: 1.讀取并解析配置文件 2.讀取并解析映射信息,創建SessionFactory 3.打開Sesssion 4.創建事務Transation 5.持久化操作 6.提交事務 7.關閉Session 8.關閉SesstionFactory
9. 為什么要用hibernate?
// 為什么要用: 1. 對JDBC訪問數據庫的代碼做了封裝,大大簡化了數據訪問層繁瑣的重復性代碼。 2. Hibernate是一個基于JDBC的主流持久化框架,是一個優秀的ORM實現。他很大程度的簡化DAO層的編碼工作 3. hibernate使用Java反射機制,而不是字節碼增強程序來實現透明性。 4. hibernate的性能非常好,因為它是個輕量級框架。映射的靈活性很出色。它支持各種關系數據庫,從一對一到多對多的各種復雜關系。
10. Hibernate如何延遲加載?
Hibernate是如何延遲加載? 1. Hibernate2延遲加載實現:a)實體對象 b)集合(Collection) 2. Hibernate3 提供了屬性的延遲加載功能 當Hibernate在查詢數據的時候,數據并沒有存在與內存中,當程序真正對數據的操作時,對象才存在與內存中,就實現了延遲加載,他節省了服務器的內存開銷,從而提高了服務器的性能。
11. Hibernate怎么樣實現類之間的關系?
Hibernate中怎樣實現類之間的關系?(如:一對多、多對多的關系)類與類之間的關系主要體現在表與表之間的關系進行操作,它們都是對對象進行操作,我們程序中把所有的表與類都映射在一起,它們通過配置文件中的many-to-one、one-to-many、many-to-many、
12. 說下hibernate的緩存機制。
詳細說下hibernate的緩存機制: // 為什么要用hibernate緩存:
Hibernate是一個持久層框架,經常訪問物理數據庫。為了降低應用程序對物理數據源訪問的頻次,從而提高應用程序的運行性能。緩存內的數據是對物理數據源中的數據的復制,應用程序在運行時從緩存讀寫數據,在特定的時刻或事件會同步緩存和物理數據源的數據。
// Hibernate的緩存原理是怎么樣的?
Hibernate一級緩存又稱為“Session的緩存”。Session的緩存是事務范圍的緩存(Session對象的生命周期通常對應一個數據庫事務或者一個應用事務)。
Hibernate二級緩存又稱為“SessionFactory的緩存”。由于SessionFactory對象的生命周期和應用程序的整個過程對應,因此Hibernate二級緩存是進程范圍或者集群范圍的緩存,有可能出現并發問題,因此需要采用適當的并發訪問策略,該策略為被緩存的數據提供了事務隔離級別。第二級緩存是可選的,是一個可配置的插件,默認下SessionFactory不會啟用這個插件。
// 既然二級緩存是進程級別的緩存,那么它適合緩存什么類型的數據呢?
什么樣的數據適合存放到第二級緩存中? 1) 很少被修改的數據 2) 不是很重要的數據,允許出現偶爾并發的數據 3) 不會被并發訪問的數據 4) 常量數據 不適合存放到第二級緩存的數據? 1) 經常被修改的數據 2) 絕對不允許出現并發訪問的數據,如財務數據,絕對不允許出現并發 3) 與其他應用共享的數據。
// Session的延遲加載
Session的延遲加載實現要解決兩個問題:正常關閉連接和確保請求中訪問的是同一個session。Hibernate session就是java.sql.Connection的一層高級封裝,一個session對應了一個Connection。http請求結束后正確的關閉session(過濾器實現了session的正常關閉);延遲加載必須保證是同一個session(session綁定在ThreadLocal)。
// Hibernate查找對象, 如何應用緩存?
當Hibernate根據ID訪問數據對象的時候,首先從Session一級緩存中查;查不到,如果配置了二級緩存,那么從二級緩存中查;如果都查不到,再查詢數據庫,把結果按照ID放入到緩存刪除、更新、增加數據的時候,同時更新緩存。
接下來說說Spring事務的配置:
Spring 如果沒有特殊說明,一般指是跟數據存儲有關的數據操作事務操作;對于數據持久操作的事務配置,一般有三個對象,數據源(dataSouce),事務管理器(transactionManager),以及事務代理機制;Spring 提供了多種的底層數據源實現,以及多種類型的事務管理器;所有的管理器都基于 Platform Transaction Manager 接口實現各自的事務策略;Spring 事務管理采用 AOP 切面代理技術實現,AOP 用于分隔關注點,保證事務的原子性,采用一定的技術 把該關注點 (weaving) 織入到 待完善的關注點上,實現單獨組件無法實現的功能,以解決面向對象編程在某些方式下難于實現的操作,更好的支持面向對象的開關原則(擴展開放,修改關閉)。
對于三部分:dataSource、transactionManager、事務代理機制。無論哪種配置方式,一般變化的都是代理機制部分。DataSource、TransactionManager這兩部分只是會根據數據訪問方式有所變化。比如使用Hibernate進行數據訪問時,DataSource實際為SessionFactory,TransactionManager的實現為HibernateTransactionManager。
那么我們知道了事務有五種配置方式和三個對象,接下來說說它的層面:Spring的事務到底該給Dao配置還是給Service配置呢?Spring的事務為業務邏輯進行事務管理,保證業務邏輯上數據的原子性。事務根據項目性質來細分:事務可以設置到三個層面(dao層、service層和web層),第一:web層事務,這一般是針對那些安全性要求較高的系統來說的。例如電子商務網站。粒度小,一般系統用不著這么細。第二:service層事務,這是一常見的事務劃分, 將事務設置在業務邏輯上,只要業務邏輯出錯或異常就事務回滾。粒度較小,一般推薦這種方式。第三:數據持久層數據務,也就是常說的數據庫事務。這種事務在安全性方面要求低。就是給一個簡單的增刪改之類的操作增加事務操作,粒度大。
Spring聲明式事務讓我們從復雜的事務處理中得到解脫。使得我們再也無需要去處理獲得連接、關閉連接、事務提交和回滾等這些操作。再也無需要我們在與事務相關的方法中處理大量的try…catch…finally代碼。我們在使用Spring聲明式事務時,有一個非常重要的概念就是事務屬性。事務屬性通常由事務的傳播行為,事務的隔離級別,事務的超時值和事務只讀標志組成。我們在進行事務劃分時,需要進行事務定義,也就是配置事務的屬性。給Service層配置事務,因為一個Service層方法操作可以關聯到多個DAO的操作。在Service層執行這些Dao操作,多DAO操作有失敗全部回滾,成功則全部提交。事務分為業務事務和系統事務,業務事務也就是業務邏輯上操作的一致性,系統事務自然就是指真正的數據庫事務,Spring配置事務的是為了什么進行管理,當然是為業務邏輯進行事務管理,保證業務邏輯上數據的原子性;Dao層是什么,數據訪問層,是不應該包含業務邏輯的,這就是和Service層的不同;Service層就是業務邏輯層,事務的管理就是為Service層上的保證。
package com.bluesky.spring.dao; import java.util.List; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import org.springframework.stereotype.Component; import com.bluesky.spring.domain.User; @Transactional // 看代碼(關于事務的配置)@Component("userDao") public class UserDaoImpl extends HibernateDaoSupport implements UserDao { public List<User> listUsers() { return this.getSession().createQuery("from User").list(); } } // 在DAO層上加了注解 @Transactional ,這種申明式事務方式,讓我們從復雜的事務處理中得到解脫,使得我們再也無需要去處理獲得連接、關閉連接、事務提交和回滾等這些操作。再也無需要我們在與事務相關的方法中處理大量的try…catch…finally代碼。接下來,我們看看事務的定義
// TransactionDefinition public interface TransactionDefinition { int getPropagationBehavior(); //方法 返回事務的傳播行為,是否由一個活動事務來決定一個事務的調用。 int getTimeout(); //方法 返回事務必須在多少秒內執行 boolean isReadOnly(); //方法返回事務是否是只讀的 int getIsolationLevel(); // 返回事務的隔離級別
} 說說事務的隔離級別:在TransactionDefinition接口中定義了五個不同的事務隔離級別,ISOLATION_DEFAULT這是一個默認的隔離級別,使用數據庫默認的事務隔離級別.另外四個與JDBC的隔離級別相對應。ISOLATION_READ_UNCOMMITTED這是事務最低的隔離級別,它充許別外一個事務可以看到這個事務未提交的數據。這種隔離級別會產生臟讀,不可重復讀和幻像讀。
例如: Mary的原工資為1000,財務人員將Mary的工資改為了8000,但未提交事務 Java代碼 1. Connection con1 = getConnection(); 2. con.setAutoCommit(false); 3. update employee set salary = 8000 where empId ="Mary"; 與此同時,Mary正在讀取自己的工資 Java代碼 1. Connection con2 = getConnection(); 2. select salary from employee where empId ="Mary"; 3. con2.commit(); Mary發現自己的工資變為了8000,歡天喜地! 而財務發現操作有誤,而回滾了事務,Mary的工資又變為了1000 Java代碼 1. //con1 2. con1.rollback(); 像這樣,Mary記取的工資數8000是一個臟數據。
ISOLATION_READ_COMMITTED保證一個事務修改的數據提交后才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數據。這種事務隔離級別可以避免臟讀出現,但是可能會出現不可重復讀和幻像讀。ISOLATION_REPEATABLE_READ這種事務隔離級別可以防止臟讀,不可重復讀。但是可能出現幻像讀。它除了保證一個事務不能讀取另一個事務未提交的數據外,還保證了避免下面的情況產生(不可重復讀)。
在事務1中,Mary 讀取了自己的工資為1000,操作并沒有完成 Java代碼 1. con1 = getConnection(); 2. select salary from employee empId ="Mary"; 在事務2中,這時財務人員修改了Mary的工資為2000,并提交了事務. Java代碼 1. con2 = getConnection(); 2. update employee set salary = 2000; 3. con2.commit(); 在事務1中,Mary 再次讀取自己的工資時,工資變為了2000 Java代碼 1. //con1 2. select salary from employee empId ="Mary"; 在一個事務中前后兩次讀取的結果并不致,導致了不可重復讀。 使用ISOLATION_REPEATABLE_READ可以避免這種情況發生。
ISOLATION_SERIALIZABLE這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。除了防止臟讀,不可重復讀外,還避免了幻像讀。
@Transactional只能被應用到public方法上,對于其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能。Spring使用聲明式事務處理,默認情況下,如果被注解的數據庫操作方法中發生了unchecked異常,所有的數據庫操作將rollback;如果發生的異常是checked異常,默認情況下數據庫操作還是會提交的。這種默認的行為是可以改變的。事務詳細講解的文章
第一種方式,每個bean都有一個代理類。
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"> <!-- 數據源 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.MySQL.jdbc.Driver" /> <property name="url" value="jdbc:mysql://192.168.0.244:3306/test?useUnicode=true&characterEncoding=UTF-8" /> <property name="username" value="root" /> <property name="passWord" value="root" /> <!-- 連接池啟動時的初始值 --> <property name="initialSize" value="10" /> <!-- 連接池的最大值 --> <property name="maxActive" value="10" /> <!-- 最大空閑值.當經過一個高峰時間后,連接池可以慢慢將已經用不到的連接慢慢釋放一部分,一直減少到maxIdle為止 --> <property name="maxIdle" value="20" /> <!-- 最小空閑值.當空閑的連接數少于閥值時,連接池就會預申請去一些連接,以免洪峰來時來不及申請 --> <property name="minIdle" value="10" /> <property name="defaultAutoCommit" value="true" /> </bean> <!-- 會話工廠 --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mappingLocations"> <list> <value>classpath:/com/nms/entity/**/*.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <props> &l
新聞熱點
疑難解答