spring jdbc抽象架構所帶來的價值将在以下幾個方面得以展現:(注:使用了spring jdbc抽象架構之後,應用開發人員隻需要完成斜體字部分的編碼工作。)
定義資料庫連接配接參數
打開資料庫連接配接
聲明sql語句
預編譯并執行sql語句
周遊查詢結果(如果需要的話)
處理每一次周遊操作
處理抛出的任何異常
處理事務
關閉資料庫連接配接
spring将替我們完成所有使用jdbc api進行開發的單調乏味的、底層細節處理工作。
使用spring進行基本的jdbc通路資料庫有多種選擇。spring至少提供了三種不同的工作模式:jdbctemplate, 一個在spring2.5中新提供的simplejdbc類能夠更好的處理資料庫中繼資料; 還有一種稱之為rdbms object的風格的面向對象封裝方式, 有點類似于jdo的查詢設計。 我們在這裡簡要列舉你采取某一種工作方式的主要理由. 不過請注意, 即使你選擇了其中的一種工作模式, 你依然可以在你的代碼中混用其他任何一種模式以擷取其帶來的好處和優勢。 所有的工作模式都必須要求jdbc 2.0以上的資料庫驅動的支援, 其中一些進階的功能可能需要jdbc 3.0以上的資料庫驅動支援。
jdbctemplate - 這是經典的也是最常用的spring對于jdbc通路的方案。這也是最低級别的封裝, 其他的工作模式事實上在底層使用了jdbctemplate作為其底層的實作基礎。jdbctemplate在jdk 1.4以上的環境上工作得很好。
namedparameterjdbctemplate - 對jdbctemplate做了封裝,提供了更加便捷的基于命名參數的使用方式而不是傳統的jdbc所使用的“?”作為參數的占位符。這種方式在你需要為某個sql指定許多個參數時,顯得更加直覺而易用。該特性必須工作在jdk 1.4以上。
simplejdbctemplate - 這個類結合了jdbctemplate和namedparameterjdbctemplate的最常用的功能,同時它也利用了一些java 5的特性所帶來的優勢,例如泛型、varargs和autoboxing等,進而提供了更加簡便的api通路方式。需要工作在java 5以上的環境中。
simplejdbcinsert 和 simplejdbccall - 這兩個類可以充分利用資料庫中繼資料的特性來簡化配置。通過使用這兩個類進行程式設計,你可以僅僅提供資料庫表名或者存儲過程的名稱以及一個map作為參數。其中map的key需要與資料庫表中的字段保持一緻。這兩個類通常和simplejdbctemplate配合使用。這兩個類需要工作在jdk 5以上,同時資料庫需要提供足夠的中繼資料資訊。
rdbms 對象包括mappingsqlquery, sqlupdate and storedprocedure - 這種方式允許你在初始化你的資料通路層時建立可重用并且線程安全的對象。該對象在你定義了你的查詢語句,聲明查詢參數并編譯相應的query之後被模型化。一旦模型化完成,任何執行函數就可以傳入不同的參數對之進行多次調用。這種方式需要工作在jdk 1.4以上。
spring framework的jdbc抽象架構由四個包構成:core、 datasource、object以及support。
org.springframework.jdbc.core包由jdbctemplate類以及相關的回調接口(callback interface)和類組成。 org.springframework.jdbc.core.simple 子包則包含了 simplejdbctemplate 類以及相關的simplejdbcinsert 類和simplejdbccall 類。 org.springframework.jdbc.core.namedparam 子包則包含了namedparameterjdbctemplate 類以及其相關的支援類。
org.springframework.jdbc.datasource包提供了一些工具類來簡化對datasource的通路。同時提供了多種簡單的datasource實作。這些實作可以脫離j2ee容器進行獨立測試和運作。 這些工具類提供了一些靜态方法,允許你通過jndi來擷取資料庫連接配接和關閉連接配接。同時支援綁定到目前線程的資料庫連接配接。例如使用datasourcetransactionmanager。
接着org.springframework.jdbc.object包包含了一些類,用于将rdbms查詢、更新以及存儲過程表述為一些可重用的、線程安全的對象。這種方式通過jdo進行模型化,不過這些通過查詢傳回的對象是與資料庫脫離的對象。 這種對于jdbc的高層次的封裝是基于org.springframework.jdbc.core包對jdbc的低層次封裝之上的。
最後,org.springframework.jdbc.support包定義了sqlexception轉化類以及一些其他的工具類。
在jdbc調用過程中所抛出的異常都會被轉化為在org.springframework.dao包中定義的異常。也就是說,凡是使用spring的jdbc封裝層的代碼無需實作任何jdbc或者rdbms相關的異常處理。所有的這些被轉化的異常都是unchecked異常,因而也給了你一種額外的選擇,你可以抓住這些異常,進而轉化成其他類型的異常被允許調用者傳播。
jdbctemplate是core包的核心類。它替我們完成了資源的建立以及釋放工作,進而簡化了我們對jdbc的使用。 它還可以幫助我們避免一些常見的錯誤,比如忘記關閉資料庫連接配接。 jdbctemplate将完成jdbc核心處理流程,比如sql語句的建立、執行,而把sql語句的生成以及查詢結果的提取工作留給我們的應用代碼。 它可以完成sql查詢、更新以及調用存儲過程,可以對resultset進行周遊并加以提取。 它還可以捕獲jdbc異常并将其轉換成org.springframework.dao包中定義的,通用的,資訊更豐富的異常。
使用jdbctemplate進行編碼隻需要根據明确定義的一組契約來實作回調接口。 preparedstatementcreator回調接口通過給定的connection建立一個preparedstatement,包含sql和任何相關的參數。 callablestatementcreateor實作同樣的處理,隻不過它建立的是callablestatement。 rowcallbackhandler接口則從資料集的每一行中提取值。
我們可以在dao實作類中通過傳遞一個datasource引用來完成jdbctemplate的執行個體化,也可以在spring的ioc容器中配置一個jdbctemplate的bean并賦予dao實作類作為一個執行個體。 需要注意的是datasource在spring的ioc容器中總是配制成一個bean,第一種情況下,datasource bean将傳遞給service,第二種情況下datasource bean傳遞給jdbctemplate bean。
最後,jdbctemplate中使用的所有sql将會以“debug”級别記入日志(一般情況下日志的category是jdbctemplate相應的全限定類名,不過如果需要對jdbctemplate進行定制的話,可能是它的子類名)。
下面是一些使用jdbctemplate類的示例。(這些示例并不是完整展示所有的jdbctemplate所暴露出來的功能。請檢視與之相關的javadoc)。
一個簡單的例子用于展示如何擷取一個表中的所有行數。
一個簡單的例子展示如何進行參數綁定。
查詢一個string。
查詢并将結果記錄為一個簡單的資料模型。
查詢并組裝多個資料模型。
如果最後2個示例中的代碼出現在同一段程式中,我們有必要去掉這些重複的rowmapper匿名類代碼,将這些代碼抽取到一個單獨的類中(通常是一個靜态的内部類)。 這樣,這個内部類就可以在dao的方法中被共享。因而,最後2個示例寫成如下的形式将更加好:
execute(..)方法可以被用作執行任何類型的sql,甚至是ddl語句。 這個方法的實作需要傳入一個回調接口、需要綁定的參數數組等作為參數。
調用一個簡單的存儲過程(更多複雜的存儲過程支援請參見存儲過程支援)。
jdbctemplate類的執行個體是線程安全的執行個體。這一點非常重要,正因為如此,你可以配置一個簡單的jdbctemplate執行個體,并将這個“共享的”、“安全的”執行個體注入到不同的dao類中去。 另外, jdbctemplate 是有狀态的,因為他所維護的datasource 執行個體是有狀态的,但是這種狀态是無法變化的。
使用jdbctemplate的一個常見的最佳實踐(同時也是simplejdbctemplate和namedparameterjdbctemplate 類的最佳實踐)就是在spring配置檔案中配置一個datasource執行個體,然後将這個共享的datasource執行個體助于到你的dao中去。 而jdbctemplate的執行個體将在datasource的setter方法中被建立。這樣的話,dao可能看上去像這樣:
相關的配置看上去就像這樣。
如果你使用spring提供的jdbcdaosupport類,并且你的那些基于jdbc的dao都繼承自這個類,那麼你會自動地從jdbcdaosupport類中繼承了setdatasource(..)方法。 是否将你的dao類繼承自這些類完全取決于你自己的決定,事實上這并不是必須的,如果你看一下jdbcdaosupport類你會發現,這裡隻是提供了一個簡便的方式而已。
無論你是否使用上述這種初始化方式,都無需在執行某些sql操作時多次建立一個jdbctemplate執行個體。記住,一旦jdbctemplate被建立,他是一個線程安全的對象。 一個你需要建立多次jdbctemplate執行個體的理由可能在于,你的應用需要通路多個不同的資料庫,進而需要不同的datasources來建立不同的jdbctemplates執行個體。
namedparameterjdbctemplate類為jdbc操作增加了命名參數的特性支援,而不是傳統的使用('?')作為參數的占位符。namedparameterjdbctemplate類對jdbctemplate類進行了封裝, 在底層,jdbctemplate完成了多數的工作。這一個章節将主要描述namedparameterjdbctemplate類與jdbctemplate類的一些差別,也就是使用命名參數進行jdbc操作。
注意這裡在'sql'中使用了命名參數作為變量,而這個名稱所對應的值被定義在傳入的'namedparameters' 中作為參數(也可以傳入到mapsqlparametersource中作為參數)。
你也可以傳入許多命名參數以及他們所對應的值,以map的方式,作為鍵值對傳入到namedparameterjdbctemplate中。 (其餘的被namedparameterjdbcoperations所暴露的接口以及namedparameterjdbctemplate實作類遵循了類似的方式,此處不包含相關内容)。
namedparameterjdbctemplate類所具備的另外一個比較好的特性就是可以接收sqlparametersource作為傳入參數 (這個類位于相同的包定義中)。 你已經在先前的一個例子中看到了這個接口的一個具體實作類。( mapsqlparametersource類)。而sqlparametersource 這個接口對于namedparameterjdbctemplate類的操作而言是一個傳入的參數。mapsqlparametersource隻是一個非常簡單的實作,使用了java.util.map作為轉接器, 其中,map中的key表示參數名稱,而map中的value表示參數值。
另外一個sqlparametersource 的實作類是beanpropertysqlparametersource。這個類對傳統的java進行了封裝(也就是那些符合javabean标準的類), 并且使用了javabean的屬性作為參數的名稱和值。
注意,namedparameterjdbctemplate類隻是封裝了jdbctemplate模闆; 因而如果你需要通路相應被封裝的jdbctemplate類,并通路一些隻有在jdbctemplate中擁有的功能,你需要使用getjdbcoperations()方法來進行通路。
請參照section 11.2.1.2, “jdbctemplate 的最佳實踐”來擷取一些使用namedparameterjdbctemplate的最佳實踐。
note
simplejdbctemplate所提供的一些特性必須工作在java 5及以上版本。
simplejdbctemplate類是對jdbctemplate類進行的封裝,進而可以充分利用java 5所帶來的varargs和autoboxing等特性。 simplejdbctemplate類完全利用了java 5文法所帶來的蜜糖效應。凡是使用過java 5的程式員們如果要從java 5遷移回之前的jdk版本,無疑會發現這些特性所帶來的蜜糖效應。
“before and after”示例可以成為simplejdbctemplate類所帶來的蜜糖效應的最佳诠釋。 下面的代碼示例首先展示了使用傳統的jdbctemplate進行jdbc通路的代碼,接着是使用simplejdbctemplate類做同樣的事情。
下面是同樣的邏輯,使用了simplejdbctemplate;可以看到代碼“幹淨”多了:
請同樣參照section 11.2.1.2, “jdbctemplate 的最佳實踐”來擷取一些simplejdbctemplate的最佳實踐
simplejdbctemplate隻是提供了jdbctemplate所提供的功能的子類。 如果你需要使用jdbctemplate的方法,而這些方法又沒有在simplejdbctemplate中定義,你需要調用getjdbcoperations()方法 擷取相應的方法調用。jdbcoperations接口中定義的方法需要在這邊做強制轉化才能使用。
為了從資料庫中取得資料,我們首先需要擷取一個資料庫連接配接。spring通過datasource對象來完成這個工作。 datasource是jdbc規範的一部分,它被視為一個通用的資料庫連接配接工廠。通過使用datasource, container或framework可以将連接配接池以及事務管理的細節從應用代碼中分離出來。 作為一個開發人員,在開發和測試産品的過程中,你可能需要知道連接配接資料庫的細節。但在産品實施時,你不需要知道這些細節。通常資料庫管理者會幫你設定好資料源。
在使用spring jdbc時,你既可以通過jndi獲得資料源,也可以自行配置資料源(使用spring提供的datasource實作類)。使用後者可以更友善的脫離web容器來進行單元測試。 這裡我們将使用drivermanagerdatasource,不過datasource有多種實作, 後面我們會講到。使用drivermanagerdatasource和你以前擷取一個jdbc連接配接 的做法沒什麼兩樣。你首先必須指定jdbc驅動程式的全限定名,這樣drivermanager 才能加載jdbc驅動類,接着你必須提供一個url(因jdbc驅動而異,為了保證設定正确請參考相關jdbc驅動的文檔), 最後你必須提供一個使用者連接配接資料庫的使用者名和密碼。下面我們将通過一個例子來說明如何配置一個drivermanagerdatasource:
sqlexceptiontranslator是一個接口,如果你需要在 sqlexception和org.springframework.dao.dataaccessexception之間作轉換,那麼必須實作該接口。 轉換器類的實作可以采用一般通用的做法(比如使用jdbc的sqlstate code),如果為了使轉換更準确,也可以進行定制(比如使用oracle的error code)。
sqlerrorcodesqlexceptiontranslator是sqlexceptiontranslator的預設實作。 該實作使用指定資料庫廠商的error code,比采用sqlstate更精确。轉換過程基于一個javabean(類型為sqlerrorcodes)中的error code。 這個javabean由sqlerrorcodesfactory工廠類建立,其中的内容來自于 “sql-error-codes.xml”配置檔案。該檔案中的資料庫廠商代碼基于 database metadata 資訊中的databaseproductname,進而配合目前資料庫的使用。
sqlerrorcodesqlexceptiontranslator使用以下的比對規則:
首先檢查是否存在完成定制轉換的子類實作。通常sqlerrorcodesqlexceptiontranslator 這個類可以作為一個具體類使用,不需要進行定制,那麼這個規則将不适用。
接着将sqlexception的error code與錯誤代碼集中的error code進行比對。 預設情況下錯誤代碼集将從sqlerrorcodesfactory取得。 錯誤代碼集來自classpath下的sql-error-codes.xml檔案,它們将與資料庫metadata資訊中的database name進行映射。
使用fallback翻譯器。sqlstatesqlexceptiontranslator類是預設的fallback翻譯器。
sqlerrorcodesqlexceptiontranslator可以采用下面的方式進行擴充:
在上面的這個例子中,error code為'-12345'的sqlexception将采用該轉換器進行轉換,而其他的error code将由預設的轉換器進行轉換。 為了使用該轉換器,必須将其作為參數傳遞給jdbctemplate類的setexceptiontranslator方法,并在需要使用這個轉換器器的資料 存取操作中使用該jdbctemplate。下面的例子示範了如何使用該定制轉換器:
在上面的定制轉換器中,我們給它注入了一個資料源,因為我們仍然需要 使用預設的轉換器從sql-error-codes.xml中擷取錯誤代碼集。
我們僅需要非常少的代碼就可以達到執行sql語句的目的,一旦獲得一個 datasource和一個jdbctemplate, 我們就可以使用jdbctemplate提供的豐富功能實作我們的操作。下面的例子使用了極少的代碼完成建立一張表的工作。
除了execute方法之外,jdbctemplate還提供了大量的查詢方法。 在這些查詢方法中,有很大一部分是用來查詢單值的。比如傳回一個彙總(count)結果 或者從傳回行結果中取得指定列的值。這時我們可以使用queryforint(..)、 queryforlong(..)或者queryforobject(..)方法。 queryforobject方法用來将傳回的jdbc類型對象轉換成指定的java對象,如果類型轉換失敗将抛出 invaliddataaccessapiusageexception異常。 下面的例子示範了兩個查詢的用法,一個傳回int值,另一個傳回string。
除了傳回單值的查詢方法,jdbctemplate還提供了一組傳回list結果 的方法。list中的每一項對應查詢傳回結果中的一行。其中最簡單的是queryforlist方法, 該方法将傳回一個list,該list中的每一條 記錄是一個map對象,對應應資料庫中某一行;而該map 中的每一項對應該資料庫行中的某一列值。下面的代碼片斷接着上面的例子示範了如何用該方法傳回表中所有記錄:
傳回的結果集類似下面這種形式:
jdbctemplate還提供了一些更新資料庫的方法。 在下面的例子中,我們根據給定的主鍵值對指定的列進行更新。 例子中的sql語句中使用了“?”占位符來接受參數(這種做法在更新和查詢sql語句中很常見)。 傳遞的參數值位于一個對象數組中(基本類型需要被包裝成其對應的對象類型)。
jdbctemplate中有一個update方法,可以友善地從資料庫中擷取資料庫自動建立的主鍵。(這是jdbc 3.0的标準 - 可以參見13.6節擷取詳細資訊)。 這個方法使用了preparedstatementcreator接口作為第一個參數, 可以通過這個接口的實作類來定義相應的insert語句。另外一個參數是keyholder, 一旦update方法成功,這個參數将包含生成的主鍵。這裡對于建立合适的preparedstatement并沒有一個統一的标準。(這也解釋了函數簽名如此定義的原因)。下面是一個在oracle上運作良好的示例,它可能在其他平台上無法工作:
datasourceutils作為一個幫助類提供易用且強大的資料庫通路能力, 我們可以使用該類提供的靜态方法從jndi擷取資料庫連接配接以及在必要的時候關閉之。 它提供支援線程綁定的資料庫連接配接(比如使用datasourcetransactionmanager的時候,将把資料庫連接配接綁定到目前的線程上)。
smartdatasource是datasource 接口的一個擴充,用來提供資料庫連接配接。使用該接口的類在指定的操作之後可以檢查是否需要關閉連接配接。該接口在某些情況下非常有用,比如有些情況需要重用資料庫連接配接。
abstractdatasource是一個實作了datasource 接口的abstract基類。它實作了datasource接口的 一些無關痛癢的方法,如果你需要實作自己的datasource,那麼可以繼承該類。
singleconnectiondatasource是smartdatasource接口 的一個實作,其内部包裝了一個單連接配接。該連接配接在使用之後将不會關閉,很顯然它不能在多線程的環境下使用。
當用戶端代碼調用close方法的時候,如果它總是假設資料庫連接配接來自連接配接池(就像使用持久化工具時一樣), 你應該将suppressclose設定為true。這樣,通過該類擷取的将是代理連接配接(禁止關閉)而不是原有的實體連接配接。 需要注意的是,我們不能把使用該類擷取的資料庫連接配接造型(cast)為oracle connection之類的本地資料庫連接配接。
singleconnectiondatasource主要在測試的時候使用。它使得測試代碼很容易脫離應用伺服器而在一個簡單的jndi環境下運作。 與drivermanagerdatasource不同的是,它始終隻會使用同一個資料庫連接配接,進而避免每次建立實體連接配接的開銷。
drivermanagerdatasource類實作了 smartdatasource接口。可以使用bean properties來設定jdbc driver屬性,該類每次傳回的都是一個新的連接配接。
該類主要在測試以及脫離j2ee容器的獨立環境中使用。它既可以用來在application context中作為一個datasource bean,也可以在簡單的jndi環境下使用。 由于connection.close()僅僅隻是簡單的關閉資料庫連接配接,是以任何能夠擷取datasource的持久化代碼都能很好的工作。不過使用javabean風格的連接配接池 (比如commons-dbcp)也并非難事。即使是在測試環境下,使用連接配接池也是一種比使用drivermanagerdatasource更好的做法。
transactionawaredatasourceproxy作為目标datasource的一個代理, 在對目标datasource包裝的同時,還增加了spring的事務管理能力, 在這一點上,這個類的功能非常像j2ee伺服器所提供的事務化的jndi datasource。
該類幾乎很少被用到,除非現有代碼在被調用的時候需要一個标準的 jdbc datasource接口實作作為參數。 這種情況下,這個類可以使現有代碼參與spring的事務管理。通常最好的做法是使用更高層的抽象 來對資料源進行管理,比如jdbctemplate和datasourceutils等等。
如果需要更詳細的資料,請參考 transactionawaredatasourceproxy javadocs。
datasourcetransactionmanager類是 platformtransactionmanager接口的一個實作,用于處理單jdbc資料源。 它将從指定datasource取得的jdbc連接配接綁定到目前線程,是以它也支援了每個資料源對應到一個線程。
我們推薦在應用代碼中使用datasourceutils.getconnection(datasource)來擷取 jdbc連接配接,而不是使用j2ee标準的datasource.getconnection。因為前者将抛出 unchecked的org.springframework.dao異常,而不是checked的 sqlexception異常。spring framework中所有的類(比如 jdbctemplate)都采用這種做法。如果不需要和這個 datasourcetransactionmanager類一起使用,datasourceutils 提供的功能跟一般的資料庫連接配接政策沒有什麼兩樣,是以它可以在任何場景下使用。
datasourcetransactionmanager類支援定制隔離級别,以及對sql語句查詢逾時的設定。 為了支援後者,應用代碼必須使用jdbctemplate或者在每次建立sql語句時調用datasourceutils.applytransactiontimeout(..)方法。
在使用單個資料源的情形下,你可以用datasourcetransactionmanager來替代jtatransactionmanager, 因為datasourcetransactionmanager不需要容器支援jta。如果你使用datasourceutils.getconnection(datasource)來擷取 jdbc連接配接,二者之間的切換隻需要更改一些配置。最後需要注意的一點就是jtatransactionmanager不支援隔離級别的定制!
有時我們需要執行特殊的,由特定廠商提供的與标準jdbc的api不同的jdbc方法。此時,當我們在某個應用伺服器上運作包裝了這些廠商各自實作的connection, statement和resultset對象的datasource 時,可能會遇到一些問題。如果你要通路這些對象,你可以配置一個包含nativejdbcextractor的jdbctemplate或者oraclelobhandler。
nativejdbcextractor根據執行環境的不同,會有不同的風格的實作:
simplenativejdbcextractor
c3p0nativejdbcextractor
commonsdbcpnativejdbcextractor
jbossnativejdbcextractor
weblogicnativejdbcextractor
webspherenativejdbcextractor
xapoolnativejdbcextractor
通常來說simplenativejdbcextractor類對于絕大多數環境,已經足以屏蔽 connection 對象。可以參見java docs擷取詳細資訊。
絕大多數jdbc驅動針對批量調用相同的prepared statement對象提供了性能提升。通過将這些更新操作封裝到一個批量操作中,可以大量減少與資料庫的操作頻繁度。 本章節将較長的描述使用jdbctemplate或者simplejdbctemplate進行批量操作的流程。
jdbctemplate的批量操作特性需要實作特定的接口batchpreparedstatementsetter來進行的, 通過實作這個接口,并将其傳入batchupdate方法進行調用。 這個接口有兩個方法需要實作。一個叫做getbatchsize來提供目前需要批量操作的數量。另外一個方法是setvalues 允許你為prepared statement設定參數。這個方法将在整個過程中被調用的次數,則取決于你在getbatchsize中所指定的大小。 下面的示例展示了根據傳入的list參數更新actor表,而傳入的list同時作為批量操作的參數。
如果你是通過讀取檔案進行批量操作,那麼你可能需要一個特定的批量操作的數量,不過最後一次的批量操作,你可能沒有那麼多數量的記錄。 在這種情況下,你可以實作interruptiblebatchpreparedstatementsetter接口,進而允許你在某些情況中斷批量操作,isbatchexhausted 方法允許你指定一個終止批量操作的信号量。
simplejdbctemplate類提供了另外一種批量操作的方式。無需實作一個特定的接口,你隻需要提供所有在調用過程中要用到的參數,架構會周遊這些參數值,并使用内置的prepared statement類進行批量操作。api将根據你是否使用命名參數而有所不同。對于使用命名參數的情況,你需要提供一個sqlparametersource的數組, 其中的每個元素将将作為批量操作的參數。 你可以使用sqlparametersource.createbatch方法,通過傳入一個javabean的數組或者一個包含了參數鍵值對的map數組來建立這個數組。
下面的示例展示了使用命名參數進行批量更新的方法:
對于使用傳統的“?”作為參數占位符的情況,你可以傳入一個list,包含了所有需要進行批量更新的對象。這樣的對象數組必須與每個sql statement的占位符以及他們在sql statement中出現的位置一一對應。
下面是同樣的例子,使用的傳統的“?”作為參數占位符:
所有的批量更新的方法都會傳回一組int的數組,表示在整個操作過程中有多少記錄被批量更新。 這個數量是由jdbc驅動所傳回的,有時這個傳回并不可靠,尤其對于某些jdbc驅動隻是簡單的傳回-2作為傳回值。
simplejdbcinsert類和simplejdbccall類主要利用了jdbc驅動所提供的資料庫中繼資料的一些特性來簡化資料庫操作配置。 這意味着你可以盡可能的簡化你的資料庫操作配置。當然,你可以可以将中繼資料處理的特性關閉,進而在你的代碼中詳細指定這些特性。
讓我們從simplejdbcinsert類開始。我們将盡可能使用最少量的配置。simplejdbcinsert類必須在資料通路層的初始化方法中被初始化。 在這個例子中,初始化方法為setdatasource方法。你無需繼承自simplejdbcinsert 類,隻需要建立一個新的執行個體,并通過withtablename方法設定table名稱。 這個類使用了“fluid”模式傳回simplejdbcinsert,進而你可以通路到所有的可以進行配置的方法。在這個例子中,我們隻使用了一個方法,稍後我們會看到更多的配置方法。
這個方法通過接收 java.utils.map 作為它唯一的參數。 在這裡需要重點注意的是,在map中的所有的key,必須和資料庫中定義的列名完全比對。這是因為我們是通過讀取中繼資料來構造實際的insert語句的。
接下來,我們對于同樣的插入語句,我們并不傳入id,而是通過資料庫自動擷取主鍵的方式來建立新的actor對象并插入資料庫。 當我們建立simplejdbcinsert執行個體時, 我們不僅需要指定表名,同時我們通過usinggeneratedkeycolumns方法指定需要資料庫自動生成主鍵的列名。
這樣我們可以看到與之前執行的insert操作的不同之處在于,我們無需添加id到參數的map中去,隻需要調用executereturningkey方法。 這個方法将傳回一個java.lang.number對象,我們可以使用這個對象來建立一個字元類型的執行個體作為我們的域模型的屬性。 有一點很重要的地方需要注意,我們無法依賴所有的資料庫來傳回我們指定的java類型,因為我們隻知道這些對象的基類是java.lang.number。 如果你有聯合主鍵或者那些非數字類型的主鍵需要生成,那麼你可以使用executereturningkeyholder方法傳回的keyholder對象。
通過指定所使用的字段名稱,可以使simplejdbcinsert僅使用這些字段作為insert語句所使用的字段。這可以通過usingcolumns方法進行配置。
執行這樣的insert語句所使用的字段,與之前我們所依賴的資料庫中繼資料是一緻的。
使用map來指定參數值有時候工作得非常好,但是這并不是最簡單的使用方式。spring提供了一些其他的sqlparametersource實作類來指定參數值。 我們首先可以看看beanpropertysqlparametersource類,這是一個非常簡便的指定參數的實作類,隻要你有一個符合javabean規範的類就行了。它将使用其中的getter方法來擷取參數值。 下面是一個例子:
另外一個實作類:mapsqlparametersource也使用map來指定參數,但是他另外提供了一個非常簡便的addvalue方法,可以被連續調用,來增加參數。
正如你看到的,配置是一樣的,差別隻是切換了不同的提供參數的實作方式來執行調用。
接下來我們把我們的關注點放在使用 simplejdbccall 來進行存儲過程的調用上。 設計這個類的目的在于使得調用存儲過程盡可能簡單。它同樣利用的資料庫中繼資料的特性來查找傳入的參數和傳回值。 這意味着你無需明确聲明那些參數。當然,如果你喜歡,可以依然聲明這些參數,尤其對于某些參數,你無法直接将他們映射到java類上,例如array類型和struct類型的參數。 在我們的第一個示例中,我們可以看到一個簡單的存儲過程調用,它僅僅傳回varchar和date類型。 這裡,我特地為actor類增加了一個birthdate的屬性,進而可以使得傳回值擁有不同的資料類型。 這個存儲過程讀取actor的主鍵,并傳回first_name,last_name,和birth_date字段作為傳回值。 下面是這個存儲過程的源碼,它可以工作在mysql資料庫上:
正如你看到的,這裡有四個參數,其中一個是傳入的參數“in_id”,表示了actor的主鍵,剩下的參數是作為讀取資料庫表中的資料所傳回的傳回值。
simplejdbccall的聲明與simplejdbcinsert類似,你無需繼承這個類,而隻需要在初始化方法中進行初始化。 在這裡例子中,我們隻需要指定存儲過程的名稱。
通過simplejdbccall執行存儲過程需要建立一個sqlparametersource的實作類來指定傳入的參數。 需要注意的是,傳入參數的名稱與存儲過程中定義的名稱必須保持一緻。這裡,我們無需保持一緻,因為我們使用資料庫的中繼資料資訊來決定我們需要什麼樣的資料庫對象。 當然,你在源代碼中所指定的名稱可能和資料庫中完全不同,有的資料庫會把這些名稱全部轉化成大寫,而有些資料庫會把這些名稱轉化為小寫。
execute方法接收傳入的參數,并傳回一個map作為傳回值,這個map包含所有在存儲過程中指定的參數名稱作為key。 在這個例子中,他們分别是out_first_name,out_last_name和 out_birth_date。
execute方法的最後部分是使用存儲過程所傳回的值建立一個新的actor執行個體。 同樣的,這裡我們将名稱與存儲過程中定義的名稱保持一緻非常重要。在這個例子中,在傳回的map中所定義的key值和資料庫的存儲過程中定義的值一緻。 你可能需要在這裡指定spring使用jakarta commons提供的caseinsensitivemap。這樣做,你需要在建立你自己的jdbctemplate類時,設定setresultsmapcaseinsensitive屬性為true。 然後,你将這個自定義的jdbctemplate傳入simplejdbccall的構造函數。當然,你需要把commons-collections.jar加入到classpath中去。 下面是配置示例:
通過這樣的配置,你就可以無需擔心傳回參數值的大小寫問題。
你已經看到如何通過中繼資料來簡化參數配置,但是你也可以明确地指定這些參數。可以在建立simplejdbccall時,通過使用declareparameters方法來聲明參數。 這個方法接收一組sqlparameter對象作為參數。我們可以參照下一個章節,如何建立sqlparameter。
我們可以有選擇性的顯示聲明一個、多個、甚至所有的參數。參數中繼資料在這裡會被同時使用。 通過調用withoutprocedurecolumnmetadataaccess方法,我們可以指定資料庫忽略所有的中繼資料處理并使用顯示聲明的參數。 另外一種情況是,其中的某些參數值具有預設的傳回值,我們需要在傳回值中指定這些傳回值。為了實作這個特性,我們可以使用useinparameternames來指定一組需要被包含的參數名稱。
這是一個完整的聲明存儲過程調用的例子:
執行和最終的傳回處理是相同的,我們在這裡隻是明确聲明了參數類型,而不是依賴資料庫中繼資料特性。 這一點很重要,尤其對于那些資料庫并不支援中繼資料的情況。目前,我們支援中繼資料的特性的資料包含:apache derby、db2、mysql、 microsoft sql server、oracle和sybase。我們同時對某些資料庫内置函數支援中繼資料特性:mysql、microsoft sql server和oracle。
為simplejdbc類或者後續章節提到的rdbms操作指定參數,你需要使用sqlparameter或者他的子類。 你可以通過指定參數名稱以及對應的sql類型并傳入構造函數作為參數來指定sqlparameter,其中,sql類型是java.sql.types中所定義的常量。 我們已經看到過類似的聲明:
第一行的sqlparameter定義了一個傳入參數。傳入參數可以在所有的存儲過程中使用,也可以在稍後章節中提到的sqlquery類及其子類中使用。
第二行sqloutparameter定義了一個傳回值。它可以被存儲過程調用所使用。當然,還有一個sqlinoutparameter類,可以用于輸入輸出參數。 也就是說,它既是一個傳入參數,也是一個傳回值。
除了參數名稱和sql類型,你還可以聲明一些其他額外的選項。對于傳入參數,你可以為numeric資料類型指定精度,或者對于特定的資料庫指定特殊類型。 對于傳回值,你可以提供一個rowmapper接口來處理所有從ref cursor傳回的列。另外一個選項是指定一個sqlreturntype類,進而可以定制傳回值的處理方式。
内置函數的調用幾乎和存儲過程的調用是一樣的。唯一的不同在于,你需要聲明的是一個函數的名稱而不是存儲過程的名稱。 這可以通過withfunctionname方法來完成。使用這個方法,表明你的調用是一個函數。你所指定的這個函數名稱将被作為調用對象。 同時有一個叫做executefunction的方法,将獲得特定的java類型的函數調用的傳回值。 此時,你無需通過傳回的map來擷取傳回值。另外有一個類似的便捷方法executeobject用于存儲過程,但是他隻能處理單個傳回值的情況。 下面的示例展示了一個叫做get_actor_name 的函數調用,傳回actor的完整的名稱。 這個函數将工作在mysql資料庫上。
調用這個函數,我們依然在初始化方法中建立simplejdbccall
被調用的函數傳回一個string類型。
期望通過調用存儲過程或者函數來傳回resultset一直是一個問題。一些資料庫在jdbc結果進行中傳回結果集,而另外一些資料庫則需要明确指定傳回值的類型。 無論哪種方法,都需要在循環周遊結果集時,做出一些額外的工作,進而處理每一條記錄。 通過simplejdbccall,你可以使用returningresultset方法,并定義一個rowmapper的實作類來處理特定的傳回值。 當結果集在傳回結果處理過程中沒有被定義名稱時,傳回的結果集必須與定義的rowmapper的實作類指定的順序保持一緻。 而指定的名字也會被用作傳回結果集中的名稱。
在這個例子中,我們将使用一個存儲過程,它并不接收任何參數,傳回t_actor表中的所有的行,下面是mysql資料庫中的存儲過程源碼:
要調用這個存儲過程,我們需要定義一個rowmapper的實作類。我們所使用的類遵循javabean的規範,是以我們可以使用parameterizedbeanpropertyrowmapper作為實作類。 通過将相應的class類作為參數傳入到newinstance方法中,我們可以建立這個實作類。
這個函數調用傳入一個空的map進入,因為這裡不需要任何的參數傳入。而函數調用所傳回的結果集将傳回的是actors清單。
org.springframework.jdbc.object包下的類允許使用者以更加 面向對象的方式去通路資料庫。比如說,使用者可以執行查詢并傳回一個list, 該list作為一個結果集将把從資料庫中取出的列資料映射到業務對象的屬性上。 使用者也可以執行存儲過程,以及運作更新、删除以及插入sql語句。
在許多spring開發人員中間存在有一種觀點,那就是下面将要提到的各種rdbms操作類 (storedprocedure類除外) 通常也可以直接使用jdbctemplate相關的方法來替換。 相對于把一個查詢操作封裝成一個類而言,直接調用jdbctemplate方法将更簡單而且更容易了解。
必須強調的一點是,這僅僅隻是一種觀點而已, 如果你認為你可以從直接使用rdbms操作類中擷取一些額外的好處,你不妨根據自己的需要和喜好進行不同的選擇。
sqlquery是一個可重用、線程安全的類,它封裝了一個sql查詢。 其子類必須實作newresultreader()方法,該方法用來在周遊 resultset的時候能使用一個類來儲存結果。 我們很少需要直接使用sqlquery,因為其子類 mappingsqlquery作為一個更加易用的實作能夠将結果集中的行映射為java對象。 sqlquery還有另外兩個擴充分别是 mappingsqlquerywithparameters和updatablesqlquery。
mappingsqlquery是一個可重用的查詢抽象類,其具體類必須實作 maprow(resultset, int)抽象方法來将結果集中的每一行轉換成java對象。 下面這個例子示範了一個定制查詢,它将從客戶表中取得的資料映射到一個customer類執行個體。
在上面的例子中,我們為使用者查詢提供了一個構造函數并為構造函數傳遞了一個 datasource參數。在構造函數裡面我們把 datasource和一個用來傳回查詢結果的sql語句作為參數 調用父類的構造函數。sql語句将被用于生成一個preparedstatement對象, 是以它可以包含占位符來傳遞參數。而每一個sql語句的參數必須通過調用 declareparameter方法來進行聲明,該方法需要一個 sqlparameter(封裝了一個字段名字和一個 java.sql.types中定義的jdbc類型)對象作為參數。 所有參數定義完之後,我們調用compile()方法來對sql語句進行預編譯。
在上面的例子中,getcustomer方法通過傳遞惟一參數id來傳回一個客戶對象。 該方法内部在建立customermappingquery執行個體之後, 我們建立了一個對象數組用來包含要傳遞的查詢參數。這裡我們隻有唯一的一個 integer參數。執行customermappingquery的 execute方法之後,我們得到了一個list,該list中包含一個 customer對象,如果有對象滿足查詢條件的話。
sqlupdate類封裝了一個可重複使用的sql更新操作。 跟所有rdbmsoperation類一樣,sqlupdate可以在sql中定義參數。 該類提供了一系列update()方法,就像sqlquery提供的一系列execute()方法一樣。 sqlupdate是一個具體的類。通過在sql語句中定義參數,這個類可以支援不同的更新方法,我們一般不需要通過繼承來實作定制。
storedprocedure類是一個抽象基類,它是對rdbms存儲過程的一種抽象。 該類提供了多種execute(..)方法,不過這些方法的通路類型都是protected的。
從父類繼承的sql屬性用來指定rdbms存儲過程的名字。 盡管該類提供了許多必須在jdbc3.0下使用的功能,但是我們更關注的是jdbc 3.0中引入的命名參數特性。
下面的程式示範了如何調用oracle中的sysdate()函數。 這裡我們建立了一個繼承storedprocedure的子類,雖然它沒有輸入參數, 但是我必須通過使用sqloutparameter來聲明一個日期類型的輸出參數。 execute()方法将傳回一個map,map中的每個entry是一個用參數名作key,以輸出參數為value的名值對。
下面是storedprocedure的另一個例子,它使用了兩個oracle遊标類型的輸出參數。
值得注意的是titlesandgenresstoredprocedure構造函數中 declareparameter(..)的sqloutparameter參數, 該參數使用了rowmapper接口的實作。這是一種非常友善而強大的重用方式。 下面我們來看一下rowmapper的兩個具體實作。
首先是titlemapper類,它簡單的把resultset中的每一行映射為一個titledomain object。
另一個是genremapper類,也是非常簡單的将resultset中的每一行映射為一個genredomain object。
如果你需要給存儲過程傳輸入參數(這些輸入參數是在rdbms存儲過程中定義好了的), 則需要提供一個指定類型的execute(..)方法, 該方法将調用基類的protected execute(map parameters)方法。例如:
sqlfunction rdbms操作類封裝了一個sql“函數”包裝器(wrapper), 該包裝器适用于查詢并傳回一個單行結果集。預設傳回的是一個int值, 不過我們可以采用類似jdbctemplate中的queryforxxx 做法自己實作來傳回其它類型。sqlfunction優勢在于我們不必建立 jdbctemplate,這些它都在内部替我們做了。
該類的主要用途是調用sql函數來傳回一個單值的結果集,比如類似“select user()”、 “select sysdate from dual”的查詢。如果需要調用更複雜的存儲函數, (可以為這種類型的處理使用storedprocedure或sqlcall)。
sqlfunction是一個具體類,通常我們不需要它的子類。 其用法是建立該類的執行個體,然後聲明sql語句以及參數就可以調用相關的run方法去多次執行函數。 下面的例子用來傳回指定表的記錄行數:
在spring的jdbc架構的所有工作模式中貫徹了一些與參數和資料處理相關的基本原則。
多數情況下,spring會根據傳入的參數值來設定相應的sql類型。有時,我們有必要明确指定傳入參數所代表的sql類型,這一點對于正确設定null值的時候可能比較有用。
另外還有一些其他的不同方面的作用:
多數jdbctemplate的update或者query方法會接收一個額外的int數組構成的參數。 這個數組需要提供的是使用java.sql.types中所定義的sql類型。而這個數組中定義的類型需要與每個傳入的參數所對應。
你可以使用sqlparametervalue對參數進行額外的封裝進而包裝更多的參數資訊。通過傳入參數值和對應的sql類型作為構造函數的參數,你可以建立這個類的一個執行個體。 你也可以為numeric的值提供一些額外的精度要求。
對于那些使用命名參數的情況,你可以使用sqlparametersource、beanpropertysqlparametersource或者mapsqlparametersource類。 他們都具備了為命名參數注冊sql類型的功能。
你可以在資料庫中存儲圖像、二進制對象或者大文本等對象。這些較大的二進制對象被稱之為blob類型,而對應的大文本對象被稱之為clob對象。 spring允許你使用jdbctemplate、更高層次封裝的rdbms對象和simplejdbc類對這些大對象進行操作。 所有的這些操作方式都實作了lobhandler接口來處理lob類型的資料。 lobhandler接口提供了通路lobcreator的方法,通過調用getlobcreator,你可以建立一個新的lob類型的資料。
lobcreator/lobhandler接口針對lob類型的資料操作提供了下列支援:
blob
byte[] – getblobasbytes and setblobasbytes
byte[] – getblobasbytes 和 setblobasbytes
inputstream – getblobasbinarystream and setblobasbinarystream
inputstream – getblobasbinarystream和setblobasbinarystream
clob
string – getclobasstring and setclobasstring
string – getclobasstring和setclobasstring
inputstream – getclobasasciistream and setclobasasciistream
inputstream – getclobasasciistream和setclobasasciistream
reader – getclobascharacterstream and setclobascharacterstream
reader – getclobascharacterstream和setclobascharacterstream
現在我們通過一個示例來展示如何建立一個blob資料并插入資料庫。稍後的例子,我們将展示如何從資料庫中将blob資料讀取出來。
這個例子使用jdbctemplate和一個abstractlobcreatingpreparedstatementcallback的實作類。 這裡唯一需要實作的方法就是"setvalues"。在這個方法中,将提供一個lobcreator接口,被用作在你的插入語句中設定lob字段的值。
我們假設有一個變量叫做“lobhandler”已經被設定到defaultlobhandler的執行個體中。當然,這是由注入完成的。
我們在這裡使用的lobhandler實作類是一個普通的defaultlobhandler
使用setclobascharacterstream,我們傳入clob的内容
使用setblobasbinartstream,我們傳入blob的内容
現在我們來示範從資料庫中讀取lob資料。我們這裡再次使用jdbctempate并使用相同的defaultlobhandler執行個體。
使用getclobasstring 擷取clob内容
使用getblobasbytes擷取blob内容
sql标準允許基于一個帶參數清單的表達式進行查詢。一個典型的例子可能像這樣:"select * from t_actor where id in (1, 2, 3)"。 不過這種參數清單的方式并不能直接被jdbc标準所支援 - 因為并不存在這種聲明一個清單參數作為占位符的方式。 你不得不為此寫多個占位符來表示多個參數,或者當你知道占位符的數量時,你可以動态建構sql字元串。 namedparameterjdbctemplate和simplejdbctemplate中所提供的命名參數的特性,采用的是後面一種做法。 當你傳入參數時,你需要傳入一個java.util.list類型,支援基本類型。而這個list将會在sql執行時替換占位符并傳入參數。
在使用in語句時,當你傳入大批量的值時要小心,jdbc标準并不確定超過100個元素在in語句中。 有不少資料庫可以超出這個值的限制,但是不同的資料庫會有不同的數量限制,比如oracle的限制數量是1000個。
除了基本類型之外,你還可以建立一個java.util.list的對象數組,這可以讓你支援在in表達式中編寫多重表達式,例如"select * from t_actor where (id, last_name) in ((1, 'johnson'), (2, 'harrop'))". 當然,這樣做的前提是資料庫底層的文法支援。
當調用存儲過程時,有時需要使用資料庫特定的複雜類型。為了适應這些類型,spring提供了sqlreturntype類來處理存儲過程的傳回值,而使用sqltypevalue來處理傳入的參數。
下面這個例子,将oracle的struct對象作為傳回值,這是一個由使用者自定義的“item_type”。 sqlreturntype接口有唯一的方法“gettypevalue”需要被實作。而這個接口的實作将被用作sqloutparameter聲明的一部分。
通過java代碼調用存儲過程使用sqltypevalue來傳入一個testitem作為參數。 sqltypevalue接口有一個方法"createtypevalue"需要被實作。 一個活動的資料庫連接配接也同時被傳入,它将被用作建立資料庫特定的對象,類似structdescriptor和arraydescriptor
這裡的sqltypevalue 現在可以被加入到作為參數的map中去,進而可以執行相應的存儲過程。