Spring 數據訪問(DAO層) 總結
統一的異常體系
Spring本質上希望以統一的方式整合底層的持久化技術:以統一的方式進行調用及事務管理,避免讓具體的實現侵入到業務層的代碼中。由於每個持久化實現技術都有各自的異常體系,所以Spring提供了統一的異常體系,使不同異常體系的阻抗得以彌消,方便定義出和具體實現技術無關的DAO介面,以及整合到相同的事務管理體系中。
DAO(Data Access Object)是用於訪問數據的對象,統一的異常體系是整合不同的持久化實現技術的關鍵,Spring面向DAO制定了一個通用的異常體系,這是一套和實現技術無關的、面向於DAO層次語義的異常體系,它屏蔽具體持久化技術的異常,通過轉換器將不同的持久化技術異常轉換成Spring的異常,使業務層和具體的持久化技術達到解耦。
DAO層的抽象:
Spring的DAO異常體系:
Spring在org.springframework.dao包中提供了一套完備優雅的DAO異常體系,這些異常都繼承於 DataAccessException,而DataAccessException本身又繼承於 NestedRuntimeException,NestedRuntimeException異常以嵌套的方式封裝了源異常。因為雖然不同持久化技術的特定異常被轉換到Spring的DAO異常體系中,原始的異常信息並不會丟失,只要你願意,就可以方便地通過getCause()方法獲取原始的異常信息。在JDBC中的SQLException 中,你必須通過異常的getErrorCode()或getSQLState()獲取錯誤代碼,直接根據這些代碼判斷是錯誤的類型,這種過於底層的API 不但帶來了代碼編寫上的難度,而且也使代碼的移植變得困難,因為getErrorCode()是資料庫相關的。 Spring以分類手法建立了異常分類目錄,對於大部分應用來說,這個異常分類目錄對異常類型的劃分具有適當的顆粒度。一方面,使開發者從底層細如針麻的技術細節中脫身出來,另一方面,可以從這個語義豐富的異常體系中選擇感興趣的異常加以處理。
為了進一步細化錯誤的問題域,Spring對一級異常類進行子類的細分,如InvalidDataAccessResourceUsageException就擁有10多個子類。對於InvalidDataAccessResourceUsageException異常,不同的持久化實現技術均有對應的子異常類。如 BadSqlGrammarException對應JDBC實現技術SQL語句語法錯誤的異常,而HibernateQueryExcpetion和 TopLinkQueryException分別對應Hibernate和TopLink實現技術的查詢語法異常。 Spring的這個異常體系具有高度的可擴展性,當Spring需要對一個新的持久化技術提供支持時,只要定義為其定義一個對應的子異常就可以了,這種更改完全滿足設計模式中的開-閉原則。
各ORM持久化技術異常轉換器:
統一的數據訪問模板
JDBC數據訪問操作按以下的流程進行:
1. 準備資源; 2. 啟動事務; 3. 在事務中執行具體數據訪問操作;4. 返回數據;
5. 提交/回滾事務; 6. 關閉資源,處理異常。按照傳統的方式,編寫任何帶事務的數據訪問的程序時,你都需要重複編寫上面的代碼,而其中步驟③的代碼是業務相關的。Spring 將這個相同的數據訪問流程固化到模板類中,並將數據訪問中固定和變化的部分分開,同時保證模板類是線程安全,以便多個數據訪問線程共享同一模板實例。固定的部分在模板類中已經準備好,而變化的部分通過回調介面開放出來,用於定義具體數據訪問和結果返回的操作。
下圖描述了模板類是如何拆分固定和變化部分的邏輯(Spring DAO模板和回調):
Spring為不同持久化技術所提供的模板類 :
如果我們直接使用模板類,一般都需要在DAO中定義一個模板對象並提供數據資源,Spring為每一個持久化技術都提供了支持類,支持類中已經為我們完成這樣的功能。 這些支持類都繼承於dao.support.DaoSupport類,DaoSupport實現了InitializingBean介面,在afterPropertiesSet()介面方法中檢查模板對象和數據源是否被正確設置,否則將拋出異常。 所有的支持類都是abstract的,其目的是希望被繼承使用,而非直接使用。這樣,我們只需要擴展這些支持類就可以直接編寫實際的數據訪問邏輯。
不同持久化技術的支持類:
統一的事務管理
JDBC事務代碼:
事務模板已經幫我們封裝了那些重複的代碼。
統一的事務抽象:
Spring 為事務管理提供了一致的編程模板,在高層次建立了統一的事務抽象。也就是說,不管選擇Spring JDBC、Hibernate 、JPA 還是iBatis,Spring都讓我們可以用統一的編程模型進行事務管理:
- TransactionDefinition用於描述事務的隔離級別、超時時間、是否為只讀事務和事務傳播規則等控制事務具體行為的事務屬性,這些事務屬性可以通過XML配置或註解描述提供,也可以通過手工編程的方式設置。
- PlatformTransactionManager根據TransactionDefinition提供的事務屬性配置信息,創建事務,並用TransactionStatus(繼承於SavepointManager介面)描述這個激活事務的狀態(事務savepoint、事務是否結束等)。
混合數據訪問技術框架所對應的事務管理器:
如果你採用了一個高端ORM技術(Hibernate、JPA、JDO),同時採用一個JDBC技術(Spring JDBC、iBatis),由於前者的會話(Session)是對後者連接(Connection)的封裝,Spring會「足夠智能地」在同一個事務線程讓前者的會話封裝後者的連接。所以,我們只要直接採用前者的事務管理器就可以了。
注意問題:使用Hibernate事務管理器後,可以混合使用Hibernate和SpringJDBC數據訪問技術,它們將工作於同一事務上下文中。但是使用SpringJDBC訪問數據時,Hibernate的一級或二級緩存得不到同步,此外,一級緩存延遲數據同步機制可能會覆蓋SpringJDBC數據更改的結果。 由於混合數據訪問技術方案存在「事務同步而緩存不同步」的情況,所以最好用Hibernate進行讀寫操作,而只用SpringJDBC進行讀操作。如用Spring JDBC進行簡要列表的查詢,而用Hibernate對查詢出的數據進行維護。 如果確實要同時使用Hibernate和Spring JDBC讀寫數據,則必須充分考慮到Hibernate緩存機制引發的問題:必須整體分析數據維護邏輯,根據需要及時調用Hibernate的flush()方法,以免覆蓋Spring JDBC的更改,在Spring JDBC更改資料庫時,維護Hibernate的緩存。由於方法調用順序的不同都可能影響數據的同步性,因此很容易發生問題,這會極大提高數據訪問程序的複雜性。
事務同步管理器:
Spring將JDBC的Connection、Hibernate的Session等訪問資料庫的連接或會話對象統稱為資源(有狀態),這些資源在同一時刻是不能多線程共享的,為了讓Dao、Service類可能做到singleton(在Spring中,DAO和Service都以單實例的方式存在),Spring的事務同步管理器類org.springframework.transaction.support.TransactionSynchronizationManager使用ThreadLocal為不同事務線程提供了獨立的資源副本,將有狀態的變數(如Connection等)本地線程化,在本地線程維護事務配置的屬性和運行狀態信息,將有狀態的對象無狀態化,實現線程安全。
Spring事務使用:
1、編程式事務管理
2、使用XML配置聲明式事務
3、使用註解配置聲明式事務:
基於註解的事務配置是使用AOP代理實現的(詳見Spring AOP原理):Spring的資料庫事務是在動態代理進入到一個invoke方法裡面的,然後判斷是否需要攔截方法,需要的時候才根據註解和配置生成資料庫事務切面上下文。
Spring事務使用AOP代理後的方法調用執行流程:
數據源
Spring 配置DataSource 的三種方式
(1)使用Spring自帶的DriverManagerDataSourceDriverManagerDataSource建立連接
只要有連接就新建一個connection,沒有使用連接池。 這裡的引用屬性是從配置文件jdbc.properties 中讀取的。
說明:由於其沒有使用連接池,故少在項目中用到。
(2)使用數據源
在Spring中,數據連接是通過數據源獲得的。這是一種推薦使用的數據源配置方式,它使用了連接池技術。
Spring在第三方依賴包中包含了兩個數據源的實現類包,其一是Apache的DBCP,其二是 C3P0,可以在Spring配置文件中利用這兩者中任何一個配置數據源。
DBCP數據源:
org.apache.commons.dbcp.BasicDataSource
DBCP是一個依賴 Jakarta commons-pool對象池機制的資料庫連接池,要在Spring中使用DBCP連接池,需要引入spring-framework-2.0-mllobjakarta-commons文件夾中的commons-collections.jar、commons-dbcp.jar和commons-pool.jar。下面是DBCP配置片段:
說明:BasicDataSource提供了close()方法關閉數據源,所以必須設定destroy-method=」close」屬性, 以便Spring容器關閉時,數據源能夠正常關閉。
除以上必須的數據源屬性外,還有一些常用的屬性:
- defaultAutoCommit:設置從數據源中返回的連接是否採用自動提交機制,默認值為 true;
- defaultReadOnly:設置數據源是否僅能執行只讀操作, 默認值為 false;
- maxActive:最大連接資料庫連接數,設置為0時,表示沒有限制;
- maxIdle:最大等待連接中的數量,設置為0時,表示沒有限制;
- maxWait:最大等待秒數,單位為毫秒, 超過時間會報出錯誤信息;
- validationQuery:用於驗證連接是否成功的查詢SQL語句,SQL語句必須至少要返回一行數據, 如你可以簡單地設置為:「select count(*) from user」;
- removeAbandoned:是否自我中斷,默認是 false ;
- removeAbandonedTimeout:幾秒後數據連接會自動斷開,在removeAbandoned為true,提供該值;
- logAbandoned:是否記錄中斷事件, 默認為 false;
C3P0數據源:
com.mchange.v2.c3p0.ComboPooledDataSource
C3P0是一個開放源代碼的JDBC數據源實現項目,它在lib目錄中與Hibernate一起發布,實現了JDBC3和JDBC2擴展規範說明的 Connection 和Statement 池。C3P0類包位於/lib/c3p0/c3p0-0.9.0.4.jar。下面是C3P0配置片段:
說明:ComboPooledDataSource和BasicDataSource一樣提供了一個用於關閉數據源的close()方法,這樣我們就可以保證Spring容器關閉時數據源能夠成功釋放。
C3P0擁有比DBCP更豐富的配置屬性,通過這些屬性,可以對數據源進行各種有效的控制:- acquireIncrement:當連接池中的連接用完時,C3P0一次性創建新連接的數目;
- acquireRetryAttempts:定義在從資料庫獲取新連接失敗後重複嘗試獲取的次數,默認為30;
- acquireRetryDelay:兩次連接中間隔時間,單位毫秒,默認為1000;
- autoCommitOnClose:連接關閉時默認將所有未提交的操作回滾。默認為false;
- automaticTestTable: C3P0將建一張名為Test的空表,並使用其自帶的查詢語句進行測試。如果定義了這個參數,那麼屬性preferredTestQuery將被忽略。你不能在這張Test表上進行任何操作,它將中為C3P0測試所用,默認為null;
- breakAfterAcquireFailure:獲取連接失敗將會引起所有等待獲取連接的線程拋出異常。但是數據源仍有效保留,並在下次調 用getConnection()的時候繼續嘗試獲取連接。如果設為true,那麼在嘗試獲取連接失敗後該數據源將申明已斷開並永久關閉。默認為 false;
- checkoutTimeout:當連接池用完時客戶端調用getConnection()後等待獲取新連接的時間,超時後將拋出SQLException,如設為0則無限期等待。單位毫秒,默認為0;
- connectionTesterClassName: 通過實現ConnectionTester或QueryConnectionTester的類來測試連接,類名需設置為全限定名。默認為 com.mchange.v2.C3P0.impl.DefaultConnectionTester;
- idleConnectionTestPeriod:隔多少秒檢查所有連接池中的空閑連接,默認為0表示不檢查;
- initialPoolSize:初始化時創建的連接數,應在minPoolSize與maxPoolSize之間取值。默認為3;
- maxIdleTime:最大空閑時間,超過空閑時間的連接將被丟棄。為0或負數則永不丟棄。默認為0;
- maxPoolSize:連接池中保留的最大連接數。默認為15;
- maxStatements:JDBC的標準參數,用以控制數據源內載入的PreparedStatement數量。但由於預緩存的Statement屬 於單個Connection而不是整個連接池。所以設置這個參數需要考慮到多方面的因素,如果maxStatements與 maxStatementsPerConnection均為0,則緩存被關閉。默認為0;
- maxStatementsPerConnection:連接池內單個連接所擁有的最大緩存Statement數。默認為0;
- numHelperThreads:C3P0是非同步操作的,緩慢的JDBC操作通過幫助進程完成。擴展這些操作可以有效的提升性能,通過多線程實現多個操作同時被執行。默認為3;
- preferredTestQuery:定義所有連接測試都執行的測試語句。在使用連接測試的情況下這個參數能顯著提高測試速度。測試的表必須在初始數據源的時候就存在。默認為null;
- propertyCycle: 用戶修改系統配置參數執行前最多等待的秒數。默認為300;
- testConnectionOnCheckout:因性能消耗大請只在需要的時候使用它。如果設為true那麼在每個connection提交的時候都 將校驗其有效性。建議使用idleConnectionTestPeriod或automaticTestTable等方法來提升連接測試的性能。默認為false;
- testConnectionOnCheckin:如果設為true那麼在取得連接的同時將校驗連接的有效性。默認為false。
其他數據源
- oracle.jdbc.pool.OracleDataSource:使用Oracle自帶的數據源oracle.jdbc.pool.OracleDataSource,Spring使用完這個數據源提供的數據連接後並不進行關閉操作,需要顯式的關閉連接。
- org.logicalcobwebs.proxool.ProxoolDataSource: Proxool是一種Java資料庫連接池技術。sourceforge下的一個開源項目,這個項目提供一個健壯、易用的連接池,最為關鍵的是這個連接池提供監控的功能,方便易用,便於發現連接泄漏的情況。目前是和DBCP以及C3P0一起,最為常見的三種JDBC連接池技術。
- com.jolbox.bonecp.BoneCPDataSource :bonecp數據連接池, BoneCP最大的特點就是效率,BoneCP號稱是目前市面上最快的Java連接池,從官方的評測來看其效率遠遠超越了其它同類的Java連 接池產品
(3)使用Tomcat提供的JNDI
說明:JndiObjectFactoryBean 能夠通過JNDI獲取DataSource1
這種方式需要在web server中配置數據源,不方便於部署。
總結
3種方式中,第一種沒有使用連接池,故少在項目中用到;第三種方式需要在web server中配置數據源,不方便於部署;通常推薦使用第二種方式進行數據源的配置。
總結
- 異常處理:Spring對不同ORM異常轉換為統一的異常體系;
- 數據模板:Spring對不同ORM框架定製的統一數據訪問模板;
- DAO配置:Spring對不同ORM提供支持類,用於設置數據訪問模板和數據源
- 事務:Spring對不同ORM框架提供統一的事務管理抽象。事務多線程安全:ThreadLocal;註解配置事務:AOP實現
- 數據源:連接池
參考來源:
《Spring 3.x企業應用開發》第3篇 數據訪問http://tech.lede.com/2017/02/06/rd/server/SpringTransactional/
推薦閱讀:
※Spring Boot 整合 Redis 實現緩存操作
※怎麼閱讀Spring源碼?
TAG:Spring |