JDBC 抽象和數(shù)據(jù)存儲(chǔ)異常層次
數(shù)據(jù)訪問(wèn)是Spring 的另一個(gè)閃光點(diǎn)。
JDBC 提供了還算不錯(cuò)的數(shù)據(jù)庫(kù)抽象,但是需要用痛苦的API。這些問(wèn)題包括:
Spring用兩種方法解決這些問(wèn)題:
Spring提供兩層JDBC API。第一個(gè)時(shí),在org.springframework.jdbc.core包中,使用回調(diào)機(jī)制移動(dòng)控制權(quán)——并且因而把錯(cuò)誤處理和連接獲取和釋放——從程序的代碼移到了框架之中。這是一種不同的Inversion of Control,但是和用于配置管理的幾乎有同等重要的意義。
Spring使用類似的回調(diào)機(jī)制關(guān)注其他包含特殊獲取和清理資源步驟的API,例如JDO(獲取和釋放是由PersistenceManager完成的),事務(wù)管理(使用JTA)和JNDI。Spring中完成這些回調(diào)的類被稱作template。
例如,Spring的JdbcTemplate對(duì)象能夠用于執(zhí)行SQL查詢并且在如下的列表中保存結(jié)果:
注意回調(diào)中的程序代碼是能夠自由拋出SQLException的:Spring將會(huì)捕捉到這些異常并且用自己的類層次重新拋出。程序的開(kāi)發(fā)者可以選擇哪個(gè)異常,如果有的話,被捕捉然后處理。
JdbcTemplate提供許多支持不同情景包括prepared statements和批量更新的方法。Spring的JDBC抽象有比起標(biāo)準(zhǔn)JDBC來(lái)說(shuō)性能損失非常小,甚至在當(dāng)應(yīng)用中需要的結(jié)果集數(shù)量很大的時(shí)候。
在org.springframework.jdbc.object包中是對(duì)JDBC的更高層次的抽象。這是建立在核心的JDBC回調(diào)功能基礎(chǔ)紙上的,但是提供了一個(gè)能夠?qū)DBMS操作——無(wú)論是查詢,更新或者是存儲(chǔ)過(guò)程——使用Java對(duì)象來(lái)建模的API。這個(gè)API部分是受到JDO查詢API的影響,我發(fā)現(xiàn)它直觀而且非常有用。
一個(gè)用于返回User對(duì)象的查詢對(duì)象可能是這樣的:
這個(gè)類可以在下面用上:
這樣的對(duì)象經(jīng)常可以用作DAO的inner class。它們是線程安全的,除非子類作了一些超出常規(guī)的事情。
在org.springframework.jdbc.object包中另一個(gè)重要的類是StoredProcedure類。Spring讓存儲(chǔ)過(guò)程通過(guò)帶有一個(gè)業(yè)務(wù)方法的Java類進(jìn)行代理。如果你喜歡的話,你可以定義一個(gè)存儲(chǔ)過(guò)程實(shí)現(xiàn)的接口,意味著你能夠把你的程序代碼從對(duì)存儲(chǔ)過(guò)程的依賴中完全解脫出來(lái)。
Spring數(shù)據(jù)訪問(wèn)異常層次是基于unchecked(運(yùn)行時(shí))exception的。在幾個(gè)工程中使用了Spring之后,我越來(lái)越確信這個(gè)決定是正確的。
數(shù)據(jù)訪問(wèn)異常一般是不可恢復(fù)的。例如,如果我們不能鏈接到數(shù)據(jù)庫(kù),某個(gè)業(yè)務(wù)對(duì)象很有可能就不能完成要解決的問(wèn)題了。一個(gè)可能的異常是optimistic locking violation,但是不是所有的程序使用optimistic locking。強(qiáng)制編寫(xiě)捕捉其無(wú)法有效處理的致命的異常通常是不好的。讓它們傳播到上層的handler,比如servlet或者EJB 容器通常更加合適。所有的Spring對(duì)象訪問(wèn)異常都是DataAccessException的子類,因而如果我們確實(shí)選擇了捕捉所有的Spring數(shù)據(jù)訪問(wèn)異常,我們可以很容易做到這點(diǎn)。
注意如果我們確實(shí)需要從unchecked數(shù)據(jù)訪問(wèn)異常中恢復(fù),我們?nèi)匀豢梢赃@么做。我們可以編寫(xiě)代碼僅僅處理可恢復(fù)的情況。例如,如果我們認(rèn)為只有optimistic locking violation是可恢復(fù)的,我們可以在Spring的DAO中如下這么寫(xiě):
如果Spring的數(shù)據(jù)訪問(wèn)異常是checked的,我們需要編寫(xiě)如下的代碼。注意我們還是可以選擇這么寫(xiě):
第一個(gè)例子的潛在缺陷是——編譯器不能強(qiáng)制處理可能的可恢復(fù)的異常——這對(duì)于第二個(gè)也是如此。因?yàn)槲覀儽粡?qiáng)制捕捉base exception(DataAccessException),編譯器不會(huì)強(qiáng)制對(duì)子類(OptimisticLockingFailureException)的檢查。因而編譯器可能強(qiáng)制我們編寫(xiě)處理不可恢復(fù)問(wèn)題的代碼,但是對(duì)于強(qiáng)制我們處理可恢復(fù)的問(wèn)題并未有任何幫助。
Spring對(duì)于數(shù)據(jù)訪問(wèn)異常的unchecked使用和許多——可能是大多數(shù)——成功的持久化框架是一致的。(確實(shí),它部分是受到JDO的影響。)JDBC是少數(shù)幾個(gè)使用checked exception的數(shù)據(jù)訪問(wèn)API之一。例如TopLink和JDO大量使用unchecked exception。Gavin King現(xiàn)在相信Hibernate也應(yīng)該選擇使用unchecked exception。
Spring的JDBC能夠用以下辦法幫助你:
在實(shí)踐中,我們發(fā)現(xiàn)所有這些都確實(shí)有助于生產(chǎn)力的提高和更少的bug。我過(guò)去常常厭惡編寫(xiě)JDBC代碼;現(xiàn)在我發(fā)現(xiàn)我能夠集中精力于我要執(zhí)行的SQL,而不是煩雜的JDBC資源管理。
如果需要的話Spring的JDBC抽象可以獨(dú)立使用——不強(qiáng)迫你把它們用作Spring的一部分。
數(shù)據(jù)訪問(wèn)是Spring 的另一個(gè)閃光點(diǎn)。
JDBC 提供了還算不錯(cuò)的數(shù)據(jù)庫(kù)抽象,但是需要用痛苦的API。這些問(wèn)題包括:
- 需要冗長(zhǎng)的錯(cuò)誤處理代碼來(lái)確保ResultSets,Statements以及(最重要的)Connections在使用后關(guān)閉。這意味著對(duì)JDBC的正確使用可以快速地導(dǎo)致大量的代碼量。它還是一個(gè)常見(jiàn)的錯(cuò)誤來(lái)源。Connection leak可以在有負(fù)載的情況下快速宕掉應(yīng)用程序。
SQLException相對(duì)來(lái)說(shuō)不能說(shuō)明任何問(wèn)題。JDBC不提供異常的層次,而是用拋出SQLException來(lái)響應(yīng)所有的錯(cuò)誤。找出到底哪里出錯(cuò)了——例如,問(wèn)題是死鎖還是無(wú)效的SQL?——要去檢查SQLState或錯(cuò)誤代碼。這意味著這些值在數(shù)據(jù)庫(kù)之間是變化的。
Spring用兩種方法解決這些問(wèn)題:
- 提供API,把冗長(zhǎng)乏味和容易出錯(cuò)的異常處理從程序代碼移到框架之中。框架處理所有的異常處理;程序代碼能夠集中精力于編寫(xiě)恰當(dāng)?shù)腟QL和提取結(jié)果上。
為你本要處理SQLException程序代碼提供有意義的異常層次。當(dāng)Spring第一次從數(shù)據(jù)源取得一個(gè)連接時(shí),它檢查元數(shù)據(jù)以確定數(shù)據(jù)庫(kù)。它使用這些信息把SQLException映射為自己從org.springframework.dao.DataAccessException派生下來(lái)的類層次中正確的異常。因而你的代碼可以與有意義的異常打交道,并且不需要為私有的SQLState或者錯(cuò)誤碼擔(dān)心。Spring的數(shù)據(jù)訪問(wèn)異常不是JDBC特有的,因而你的DAO并不一定會(huì)因?yàn)樗鼈兛赡軖伋龅漠惓6壦涝贘DBC上。
Spring提供兩層JDBC API。第一個(gè)時(shí),在org.springframework.jdbc.core包中,使用回調(diào)機(jī)制移動(dòng)控制權(quán)——并且因而把錯(cuò)誤處理和連接獲取和釋放——從程序的代碼移到了框架之中。這是一種不同的Inversion of Control,但是和用于配置管理的幾乎有同等重要的意義。
Spring使用類似的回調(diào)機(jī)制關(guān)注其他包含特殊獲取和清理資源步驟的API,例如JDO(獲取和釋放是由PersistenceManager完成的),事務(wù)管理(使用JTA)和JNDI。Spring中完成這些回調(diào)的類被稱作template。
例如,Spring的JdbcTemplate對(duì)象能夠用于執(zhí)行SQL查詢并且在如下的列表中保存結(jié)果:
代碼: |
JdbcTemplate template = new JdbcTemplate(dataSource); final List names = new LinkedList(); template.query("SELECT USER.NAME FROM USER", new RowCallbackHandler() { public void processRow(ResultSet rs) throws SQLException { names.add(rs.getString(1)); } }); |
注意回調(diào)中的程序代碼是能夠自由拋出SQLException的:Spring將會(huì)捕捉到這些異常并且用自己的類層次重新拋出。程序的開(kāi)發(fā)者可以選擇哪個(gè)異常,如果有的話,被捕捉然后處理。
JdbcTemplate提供許多支持不同情景包括prepared statements和批量更新的方法。Spring的JDBC抽象有比起標(biāo)準(zhǔn)JDBC來(lái)說(shuō)性能損失非常小,甚至在當(dāng)應(yīng)用中需要的結(jié)果集數(shù)量很大的時(shí)候。
在org.springframework.jdbc.object包中是對(duì)JDBC的更高層次的抽象。這是建立在核心的JDBC回調(diào)功能基礎(chǔ)紙上的,但是提供了一個(gè)能夠?qū)DBMS操作——無(wú)論是查詢,更新或者是存儲(chǔ)過(guò)程——使用Java對(duì)象來(lái)建模的API。這個(gè)API部分是受到JDO查詢API的影響,我發(fā)現(xiàn)它直觀而且非常有用。
一個(gè)用于返回User對(duì)象的查詢對(duì)象可能是這樣的:
代碼: |
class UserQuery extends MappingSqlQuery { public UserQuery(DataSource datasource) { super(datasource, "SELECT * FROM PUB_USER_ADDRESS WHERE USER_ID = ?"); declareParameter(new SqlParameter(Types.NUMERIC)); compile(); } // Map a result set row to a Java object protected Object mapRow(ResultSet rs, int rownum) throws SQLException { User user = new User(); user.setId(rs.getLong("USER_ID")); user.setForename(rs.getString("FORENAME")); return user; } public User findUser(long id) { // Use superclass convenience method to provide strong typing return (User) findObject(id); } } |
這個(gè)類可以在下面用上:
代碼: |
User user = userQuery.findUser(25); |
這樣的對(duì)象經(jīng)常可以用作DAO的inner class。它們是線程安全的,除非子類作了一些超出常規(guī)的事情。
在org.springframework.jdbc.object包中另一個(gè)重要的類是StoredProcedure類。Spring讓存儲(chǔ)過(guò)程通過(guò)帶有一個(gè)業(yè)務(wù)方法的Java類進(jìn)行代理。如果你喜歡的話,你可以定義一個(gè)存儲(chǔ)過(guò)程實(shí)現(xiàn)的接口,意味著你能夠把你的程序代碼從對(duì)存儲(chǔ)過(guò)程的依賴中完全解脫出來(lái)。
Spring數(shù)據(jù)訪問(wèn)異常層次是基于unchecked(運(yùn)行時(shí))exception的。在幾個(gè)工程中使用了Spring之后,我越來(lái)越確信這個(gè)決定是正確的。
數(shù)據(jù)訪問(wèn)異常一般是不可恢復(fù)的。例如,如果我們不能鏈接到數(shù)據(jù)庫(kù),某個(gè)業(yè)務(wù)對(duì)象很有可能就不能完成要解決的問(wèn)題了。一個(gè)可能的異常是optimistic locking violation,但是不是所有的程序使用optimistic locking。強(qiáng)制編寫(xiě)捕捉其無(wú)法有效處理的致命的異常通常是不好的。讓它們傳播到上層的handler,比如servlet或者EJB 容器通常更加合適。所有的Spring對(duì)象訪問(wèn)異常都是DataAccessException的子類,因而如果我們確實(shí)選擇了捕捉所有的Spring數(shù)據(jù)訪問(wèn)異常,我們可以很容易做到這點(diǎn)。
注意如果我們確實(shí)需要從unchecked數(shù)據(jù)訪問(wèn)異常中恢復(fù),我們?nèi)匀豢梢赃@么做。我們可以編寫(xiě)代碼僅僅處理可恢復(fù)的情況。例如,如果我們認(rèn)為只有optimistic locking violation是可恢復(fù)的,我們可以在Spring的DAO中如下這么寫(xiě):
代碼: |
try { // do work } catch (OptimisticLockingFailureException ex) { // I'm interested in this } |
如果Spring的數(shù)據(jù)訪問(wèn)異常是checked的,我們需要編寫(xiě)如下的代碼。注意我們還是可以選擇這么寫(xiě):
代碼: |
try { // do work } catch (OptimisticLockingFailureException ex) { // I'm interested in this } catch (DataAccessException ex) { // Fatal; just rethrow it } |
第一個(gè)例子的潛在缺陷是——編譯器不能強(qiáng)制處理可能的可恢復(fù)的異常——這對(duì)于第二個(gè)也是如此。因?yàn)槲覀儽粡?qiáng)制捕捉base exception(DataAccessException),編譯器不會(huì)強(qiáng)制對(duì)子類(OptimisticLockingFailureException)的檢查。因而編譯器可能強(qiáng)制我們編寫(xiě)處理不可恢復(fù)問(wèn)題的代碼,但是對(duì)于強(qiáng)制我們處理可恢復(fù)的問(wèn)題并未有任何幫助。
Spring對(duì)于數(shù)據(jù)訪問(wèn)異常的unchecked使用和許多——可能是大多數(shù)——成功的持久化框架是一致的。(確實(shí),它部分是受到JDO的影響。)JDBC是少數(shù)幾個(gè)使用checked exception的數(shù)據(jù)訪問(wèn)API之一。例如TopLink和JDO大量使用unchecked exception。Gavin King現(xiàn)在相信Hibernate也應(yīng)該選擇使用unchecked exception。
Spring的JDBC能夠用以下辦法幫助你:
你決不需要在使用JDBC時(shí)再編寫(xiě)finally block。
總的來(lái)說(shuō)你需要編寫(xiě)的代碼更少了
你再也不需要挖掘你的RDBMS的文檔以找出它為錯(cuò)誤的列名稱返回的某個(gè)罕見(jiàn)的錯(cuò)誤代碼。你的程序不再依賴于RDBMS特有的錯(cuò)誤處理代碼。
無(wú)論使用的是什么持久化技術(shù),你都會(huì)發(fā)現(xiàn)容易實(shí)現(xiàn)DAO模式,讓業(yè)務(wù)代碼無(wú)需依賴于任何特定的數(shù)據(jù)訪問(wèn)API。
在實(shí)踐中,我們發(fā)現(xiàn)所有這些都確實(shí)有助于生產(chǎn)力的提高和更少的bug。我過(guò)去常常厭惡編寫(xiě)JDBC代碼;現(xiàn)在我發(fā)現(xiàn)我能夠集中精力于我要執(zhí)行的SQL,而不是煩雜的JDBC資源管理。
如果需要的話Spring的JDBC抽象可以獨(dú)立使用——不強(qiáng)迫你把它們用作Spring的一部分。