主要內容
- 定義Spring的數據訪問支持
- 配置數據庫資源
- 使用Spring提供的JDBC模板
寫在前面:通過上一篇文章的學習,咱們掌握瞭如何寫web應用的控制器層,不過因爲只定義了SpitterRepository和SpittleRepository接口,在本地啓動該web服務的時候會遇到控制器沒法注入對應的bean的錯誤,所以我決定跳過6~9章,先搞定數據庫訪問者一章。css
在企業級應用開發中不可避省得會涉及到數據持久化層,在數據持久化層的開發過程當中,可能遇到不少陷阱。你須要初始化數據庫訪問框架、打開數據庫鏈接、處理各類異常,最後還要記得關閉鏈接。若是在這些步驟中你有一步作錯了,那就又丟失公司數據的風險。穩當得處理這些並不容易,Spring提供了一套完整的數據庫訪問框架,用於簡化各類數據庫訪問技術的使用。html
在開發Spttr應用的持久層時,你須要在JDBC、Hibernate、Java Perssitence或者其餘ORM框架等技術中進行選擇。Spring扮演的角色是儘可能消除你在使用這些技術時須要寫的重複代碼,以便開發人員專一於業務邏輯。java
Spring框架的目標之一就是讓開發者面向接口編程,Spring的數據訪問支持也不例外。web
和不少其餘應用同樣,Spittr應用也須要從數據庫中讀取信息或者寫入信息到數據庫。爲了不持久化相關的代碼遍及應用的各個地方,通常咱們會將這些任務整合到一個模塊中完成,這類模塊一般被稱之爲數據訪問對象(DAOs)或者repositories。算法
爲了不業務層模塊強依賴於某種類型的數據庫(關係型orNoSQL),數據庫訪問層應以接口形式對外提供服務。下圖展現了這個思路:spring
如你所見,service對象經過接口訪問repository對象,這有不少好處:(1)由於service對象並不限制於某個特定的數據訪問實現,這使得service對象便於測試;(2)你能夠建立這些數據庫訪問接口的mock實現,這樣即便沒有創建數據庫鏈接你也能夠測試service對象;(3)能夠顯著加速單元測試的執行速度;(4)能夠避免某個測試用例因數據不一致而失敗。sql
數據訪問層經過repository接口中的幾個方法與service層溝通,這使得應用設計很是靈活,即便未來要更換數據庫持久層框架,對應用的其餘部分的影響也很是小。若是數據訪問層的實現細節散步到應用的其餘部分,則整個應用跟數據訪問層緊密耦合在一塊兒。數據庫
INTERFACES AND SPRING 若是你讀完上面兩段話以後可以感受到我有很強的意願將持久化層隱藏在接口以後,那說明我正確得表達了本身的想法。我相信接口是書寫鬆耦合的代碼的關鍵,不只是數據庫訪問層,應該在應用的全部模塊之間使用接口進行交互。雖然Spring並無強制要求面向接口編程,可是Spring的設計思想鼓勵面向接口編程——最好經過接口將一個bean裝配到另外一個bean的屬性中。apache
Spring提供了方便的異常體系,也能夠幫助開發者隔離數據庫訪問層與應用的其餘模塊。編程
在使用原始的JDBC接口時,若是你不捕獲SQLException,就不能作任何事情。SQLException的意思是在嘗試訪問數據庫過程當中發生了某些錯誤,可是並無提供足夠的信息告訴開發人員具體的錯誤緣由以及如何修正錯誤。
下列這些狀況均可能引起SQLException:
關於SQLException最大的問題在於:當捕獲它的時候應該如何處理。調查顯示,不少引發SQLException的故障不能在catch代碼塊中修復。大部分被拋出的SQLException表示應用發生了致命故障。若是應用不能鏈接數據庫,一般意味着應用不能繼續執行;一樣地,若是在查詢語句中有錯誤,在運行時能作的工做也不多。
既然咱們並不能作些操做來恢復SQLException,爲何必須捕獲它?
即便你計劃處理一些SQLException,你也必須捕獲SQLException對象而後查看它的屬性才能發掘出問題的本質。這是由於SQLException是一個代之全部數據庫訪問相關問題的異常,而不是針對每一個可能的問題定義一個異常類型。
一些持久化框架提供了豐富的異常體系。例如,Hibernate提供了幾乎兩打不通的異常,每種表明一個特定的數據庫訪問問題。這令使用Hibernate的開發者能夠爲本身想處理的異常書寫catch塊。
即便這樣,Hibernate的異常也只對Hibernate框架有用,若是你使用Hibernate本身的異常體系,就可能使程序的剩餘部分強依賴於Hibernate,未來若是想升級爲其餘的持久化框架會很是麻煩。在這節開頭的時候說過,咱們但願隔離數據訪問層和持久化機制的特性。若是在數據訪問層處理Hibernate框架拋出的專屬異常,則會影響到應用中的其他模塊;若是不這麼作,你必須捕獲該持久化的專屬異常,而後從新拋出一個平臺無關的異常。
一方面,JDBC提供的異常體系過於廣泛——根本沒有異常體系可言;另外一方面,Hibernate的異常體系是針對這個框架本身的,所以咱們須要一套數據庫訪問的異常體系,既具有足夠強的描述能力,又不要跟具體的持久化框架直接關聯。
Spring JDBC提供的異常體系同時知足上述兩個條件。不一樣於傳統的JDBC,Spring JDBC針對某些具體的問題定義了對應的數據庫訪問異常。下表展現了Spring 數據訪問異常和JDBC的異常之間的對應關係。
如你所見,Spring爲在讀取或者寫入數據庫時可能出錯的緣由設置了對應的異常類型,Spring 實際提供的數據庫訪問異常要遠多於表10.1所列出的那些。
Spring在提供如此豐富的異常前提下,還保證這些異常類型跟具體的持久化機制隔離。這意味着不管你使用什麼持久化框架,你均可以使用同一套異常定義——持久化機制的選擇與數據訪問層實現解耦合。
表10.1中沒有說明的是:全部這些異常的根對象是DataAccessException,這是一個unchecked exception。換句話說,Spring不會強制你捕獲這些數據庫訪問異常。
Spring經過提供unchecked exception,讓開發者決定是否須要捕獲並處理某個異常。爲了充分發揮Spring的數據訪問異常,你最好使用Spring提供的數據訪問模板。
若是你以前經過飛機出行過,你必定明白在行程過程當中最重要的事情是將行李從A地託運到B地。要穩當得完成這個事情須要不少步驟:當你到達機場時,你首先須要檢查行李;而後須要經過機場的安全掃描,以避免不當心將可能危害飛行安全的東西帶上飛機;而後行李須要經過長長的傳送帶被運上飛機。若是你須要轉乘航線,行李也須要跟着你一塊兒運輸。當你到達最終目的地時,行李會被運下飛機而後放置在傳送帶上,最後,你須要去目的地機場的指定地點領取本身的行李。
雖然在這個過程當中有須要步驟,可是你僅僅須要參與其中的一部分。在這個例子中,整個過程就是將行李從出發城市運輸到目的城市,這個過程是固定的不會改變。在運輸過程能夠分紅明確的幾步:檢查行李、裝載行李、卸載行李等。在這其中一些步驟也是固定的,每次都同樣:當飛機到達目的地以後,全部行李都須要卸載並放在機場的指定地點。
在指定的節點,總程序會將一部分工做委託給一個子程序,用於完成更加細節的任務,這就是總程序中的變量部分。例如,行李的託運開始於乘客本身檢查行李,由於每一個乘客的動做都不相同——各自檢查本身的行李,所以總程序中的這個步驟如何執行具體取決於每一個乘客。用軟件開發中的術語描述,上述過程就是模板模式:模板方法規定整個算法的執行過程,將每一個步驟的具體細節經過接口委託給子類完成。
Spring提供的數據訪問支持也使用了模板模式。不管你選擇使用什麼技術,數據訪問的步驟就是固定的幾步(例如,在開始時,你必定須要獲取一個數據庫鏈接;在操做完成後,你必定須要釋放以前獲取的資源),可是每一步具體怎麼實現有所不一樣。你用不一樣的方法查詢或者更新不一樣的數據,這些屬於數據庫訪問過程當中的變量。
Spring將數據訪問過程當中的固定步驟和變量部分分爲兩類:模板(templates)和回調函數(callbacks)。模板負責管理數據訪問過程當中的固定步驟,而由你定製的業務邏輯則寫在回調函數中。下圖顯示了這兩類對象的責任和角色:
如你所見,Spring的模板類處理數據訪問的固定步驟——事務管理、資源管理和異常處理;與此同時,跟應用相關的數據訪問任務——建立語句、綁定參數和處理結果集等,則須要在回調函數中完成。這種框架十分優雅,做爲開發人員你只須要關注具體的數據訪問邏輯。
Spring提供了集中不一樣的模板,開發者根據項目中使用的持久化框架選擇對應的模板工具類。若是你使用原始的JDBC方式,則可使用JdbcTemplate;若是你更傾向於使用ORM框架,則可使用HibernateTemplate和JpaTemplate。表10.2列出了Spring提供的數據訪問模板。
Spring爲不一樣的持久化技術提供了對應的數據訪問模板,在這一章中並不能一一講述。所以咱們將選擇最有效和你最可能使用的進行講解。
這一章首先介紹JDBC技術,由於它最簡單;在後面還會介紹Hibernate和JPA——兩種最流行的基於POJO的ORM框架。PS:除了《Spring in Action》中的這幾種持久化技術,如今更加流行的是Mybatis框架,後續我會專門寫對應的總結和學習筆記。
可是,全部這些持久化框架都須要依賴於具體的數據源,所以在開始學習templates和repositories以前,須要學習在Spring中如何配置數據源——用於鏈接數據庫。
Spring提供了幾種配置數據源的方式,列舉以下:
對於生產級別的應用,我建議使用從數據庫鏈接池中獲取的數據源;若是有可能,也能夠經過JNDI從應用服務器中獲取數據源;接下來首先看下如何配置Spring應用從JNDI獲取數據源。
Spring應用通常部署在某個J2EE容器中,例如WebSphere、JBoss或者Tomcat。開發者能夠在這些服務器中配置數據源,一遍Spring應用經過JNDI獲取。按照這種方式配置數據源的好處在於:數據源配置在應用外部,容許應用在啓動完成時再請求數據源進行數據訪問;並且,數據源配置在應用服務器中有助於提升性能,且系統管理員能夠進行熱切換。
首先,須要在tomcat中配置數據源,方法參見stackoverflowHow to use JNDI DataSource provided by Tomcat in Spring?
在SpringXML配置文件中使用<jee:jndi-lookup>元素定義數據源對應的Spring bean。Spring應用根據jndi-name從Tomcat容器中查找數據源;若是應用是運行Java應用服務器中,則須要設置resource-ref爲true,這樣在查詢的時候會在jndi-name指定的名字前面加上java:comp/env/。
<jee:jndi-lookup id="dataSource"
jndi-name="/jdbc/SpitterDS"
resource-ref="true" />
若是你使用JavaConfig,則可使用JndiObjectFactoryBean從JNDI中獲取DataSource:
@Beanpublic JndiObjectFactoryBean dataSource() {
JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();
jndiObjectFB.setJndiName("/jdbc/SpittrDS");
jndiObjectFB.setResourceRef(true);
jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);
return jndiObjectFB;
}
顯然,在這裏Java配置文件須要寫更多代碼,通常而言JavaConfig要比XML配置文件更簡單,這是個例外。
儘管Spring自身不提供數據鏈接池,但能夠和不少第三方庫集成使用,例如:
最經常使用的是DBCP,首先須要在pom文件中添加對應的依賴,代碼以下:
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-dbcp2</artifactId><version>2.0</version></dependency>
關於commons-dbcp版本的區別:commons-dbcp如今分紅了2個大版本,不一樣的版本要求的JDK不一樣:
- DBCP 2.X compiles and runs under Java 7 only (JDBC 4.1)
- DBCP 1.4 compiles and runs under Java 6 only (JDBC 4)
若是在XML文件中使用,則可使用下列代碼配置DBCP的BasicDataSource:
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
p:driverClassName="org.h2.Driver"
p:url="jdbc:h2:tcp://localhost/~/spitter"
p:username="sa"
p:password=""
p:initialSize="5" />
若是你使用Java配置文件,則可使用下列代碼配置DataSourcebean。
@Beanpublic BasicDataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:tcp://localhost/~/spitter");
ds.setUsername("sa");
ds.setPassword("");
ds.setInitialSize(5);
return ds;
}
前四個屬性屬於配置BasicDataSource的必備屬性,driverClassName指定JDBC驅動類的全稱,這裏咱們配置了H2數據庫的驅動;url屬性用於設置完整的數據庫地址;username和password分別指定用戶名和密碼。BasicDataSource中還有其餘的屬性,能夠設置數據鏈接池的屬性,例如,initialSize屬性用於指定鏈接池初始化時創建幾個數據庫鏈接。對於dbcp1.4系列,BasicDataSource的屬性可列舉以下表10.3所示:
對於dbcp2.x系列,若是你但願瞭解更多BasicDataSource的屬性,可參照官方文檔:dbcp2配置。
在Spring中最簡單的數據源就是經過JDBC驅動配置的數據源。Spring提供了三個相關的類供開發者選擇(都在org.springframework.jdbc.datasource包中):
配置這些數據源跟以前配置DBCP的BasicDataSource相似,例如,能夠用下列代碼配置DriverManagerDataSource
@Beanpublic DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:tcp://localhost/~/spitter");
ds.setUsername("sa");
ds.setPassword("");
return ds;
}
上述配置代碼的XML形式以下:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="org.h2.Driver"
p:url="jdbc:h2:tcp://localhost/~/spitter"
p:username="sa"
p:password="" />
因爲上述這三個數據源對象對多線程應用的支持都很差,所以強烈建議直接使用數據庫鏈接池。
嵌入式數據源做爲應用的一部分運行,很是適合在開發和測試環境中使用,可是不適合用於生產環境。由於在使用嵌入式數據源的狀況下,你能夠在每次應用啓動或者每次運行單元測試以前初始化測試數據。
使用Spring的jdbc名字空間配置嵌入式數據源很是簡單,下列代碼顯示瞭如何使用jdbc名字空間配置嵌入式的H2數據庫,並配置須要初始化的數據。
<jdbc:embedded-database id="dataSource" type="H2"><jdbc:script location="classpath*:schema.sql" /><jdbc:script location="classpath*:test-data.sql" /></jdbc:embedded-database>
<jdbc:embedded-database>的type屬性設置爲H2代表嵌入式數據庫的類型是H2數據庫(確保引入了H2的依賴庫)。在<jdbc:embedded-database>配置中,能夠配置多個<jdbc:script>元素,用於設置和初始化數據庫:在這個例子中,schema.sql文件中包含用於建立數據表的關係;test-data.sql文件中用於插入測試數據。
若是你使用JavaConfig,則可使用EmbeddedDatabaseBuilder構建嵌入式數據源:
@Bean public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath*:schema.sql")
.addScript("classpath*:test-data.sql")
.build();
}
能夠看出,setType()方法的做用等同於<jdbc:embedded-database>元素的type屬性,addScript()方法的做用等同於<jdbc:script>元素。
通常須要在不一樣的環境(平常環境、性能測試環境、預發環境和生產環境等等)中配置不一樣的數據源,例如,在開發時很是適合使用嵌入式數據源、在QA環境中比較適合使用DBCP的BasicDataSource、在生產環境中則適合使用<jee:jndi-lookup>元素,即便用JNDI查詢數據源。
在Spring實戰3:裝配bean的進階知識一文中咱們探討過Spring的bean-profiles特性,這裏就須要給不一樣的數據源配置不一樣的profiles,Java配置文件的內容以下所示:
package org.test.spittr.config;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;
import javax.sql.DataSource;
@Configurationpublic class DataSourceConfiguration {
@Profile("development")
@Beanpublic DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath*:schema.sql")
.addScript("classpath*:test-data.sql")
.build();
}
@Profile("qa")
@Beanpublic BasicDataSource basicDataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("org.h2.Driver");
ds.setUrl("jdbc:h2:tcp://localhost/~/spitter");
ds.setUsername("sa");
ds.setPassword("");
ds.setInitialSize(5); //初始大小
ds.setMaxTotal(10); //數據庫鏈接池大小return ds;
}
@Profile("production")
@Beanpublic DataSource dataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("/jdbc/SpittrDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource)jndiObjectFactoryBean.getObject();
}
}
利用@Profile註解,Spring應用能夠運行時再根據激活的profile選擇指定的數據源。在上述代碼中,當development對應的profile被激活時,應用會使用嵌入式數據源;當qa對應的profile被激活時,應用會使用DBCP的BasicDataSource;當production對應的profile被激活時,應用會使用從JNDI中獲取的數據源。
上述代碼對應的XML形式的配置代碼以下所示:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xmlns:jee="http://www.springframework.org/schema/jee" xmlns:jdbc="http://www.springframework.org/schema/jdbc"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"><beans profile="qa"><bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"p:driverClassName="org.h2.Driver"p:url="jdbc:h2:tcp://localhost/~/spitter"p:username="sa"p:password=""p:initialSize="5" /></beans><beans profile="production"><jee:jndi-lookup id="dataSource"jndi-name="/jdbc/SpittrDS"resource-ref="true"/></beans><beans profile="development"><jdbc:embedded-database id="dataSource" type="H2"><jdbc:script location="classpath*:schema.sql" /><jdbc:script location="classpath*:test-data.sql" /></jdbc:embedded-database></beans></beans>
創建好數據庫鏈接後,就能夠執行訪問數據庫的任務了。正如以前提到的,Spring對不少持久化技術提供了支持,包括JDBC、Hibernate和Java Persistence API(API)。在下一小節中,咱們首先介紹如何在Spring應用中使用JDBC書寫持久層。
在實際開發過程當中有不少持久化技術可供選擇:Hibernate、iBATIS和JPA等。儘管如此,仍是有不少應用使用古老的方法即JDBC技術,來訪問數據庫。
使用JDBC技術不須要開發人員學習新的框架,由於它就是基於SQL語言運行的。JDBC技術更加靈活,開發人員能夠調整的餘地很大,JDBC技術容許開發人員充分利用數據庫的本地特性,而在其餘ORM框架中可能作不到如此靈活和可定製。
除了上述提到的靈活性、可定製能力,JDBC技術也有一些缺點。
開發者使用JDBC技術提供的API能夠很是底層得操做數據庫,同時也意味着,開發者須要負責處理數據訪問過程當中的各個具體步驟:管理數據庫資源和處理數據庫訪問異常。若是你使用JDBC插入數據庫,在這個例子中,假設須要插入一條spitter數據,則可使用以下代碼:
@Componentpublic class SpitterDao {
private static final String SQL_INSERT_SPITTER =
"insert into spitter (username, password, firstName, lastName) values (?, ?, ?, ?)";
@Autowiredprivate DataSource dataSource;
public void addSpitter(Spitter spitter) {
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(SQL_INSERT_SPITTER);
stmt.setString(1, spitter.getUsername());
stmt.setString(2, spitter.getPassword());
stmt.setString(3, spitter.getFirstName());
stmt.setString(4, spitter.getLastName());
stmt.execute();
} catch (SQLException e) {
//do something...not sure what, though
} finally {
try {
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
//I'm even less sure about what to do here
}
}
}
}
addSpitter函數一共有28行,可是隻有6行是真正的業務邏輯。爲何如此簡單的操做也須要這麼多代碼?JDBC須要開發者本身管理數據庫鏈接、本身管理SQL語句,以及本身處理可能拋出的異常。
對於SQLException,開發者並不清楚具體該如何處理該異常(該異常並未指明具體的錯誤緣由),卻被迫須要捕獲該異常。若是在執行插入語句時發生錯誤,你須要捕獲該異常;若是在關閉statement和connection資源時發生錯誤,你也須要捕獲該異常,可是捕獲後你並不能作實際的有意義的操做。
一樣,若是你須要更新一條spitter記錄,則可以使用下列代碼:
private static final String SQL_UPDATE_SPITTER =
"update spitter set username = ?, password = ?, firstName = ?, lastName=? where id = ?";
public void saveSpitter(Spitter spitter) {
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(SQL_UPDATE_SPITTER);
stmt.setString(1, spitter.getUsername());
stmt.setString(2, spitter.getPassword());
stmt.setString(3, spitter.getFirstName());
stmt.setString(4, spitter.getLastName());
stmt.setLong(5, spitter.getId());
stmt.execute();
} catch (SQLException e) {
// Still not sure what I'm supposed to do here
} finally {
try {
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
// or here
}
}
}
這一次,saveSpitter函數用於更新數據庫中的一行記錄,能夠看出,有不少重複代碼。理想狀況應該是:你只須要寫特定功能相關的代碼。
爲了補足JDBC體驗之旅,咱們再看看如何使用JDBC從數據庫中查詢一條記錄,例子代碼以下:
private static final String SQL_SELECT_SPITTER =
"select id, username, firstName, lastName from spitter where id = ?";
public Spitter findOne(long id) {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(SQL_SELECT_SPITTER);
stmt.setLong(1, id);
rs = stmt.executeQuery();
Spitter spitter = null;
if (rs.next()) {
spitter = new Spitter();
spitter.setId(rs.getLong("id"));
spitter.setUsername(rs.getString("username"));
spitter.setPassword(rs.getString("password"));
spitter.setFirstName(rs.getString("firstName"));
spitter.setLastName(rs.getString("lastName"));
}
return spitter;
} catch (SQLException e) {
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) { }
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) { }
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) { }
}
}
return null;
}
這個函數跟以前的insert和update例子同樣囉嗦冗長:幾乎只有20%的代碼是真正有用的業務邏輯,而80%的代碼則是模板樣式代碼。
能夠看出,使用JDBC持久化技術,就須要編寫大量的模板樣式代碼,用於建立鏈接、建立statements和處理異常。另外,上述提到的模板樣式代碼在數據庫訪問過程當中又很是重要:釋放資源和處理異常等,這都能提升數據訪問的穩定性。若是沒有這些操做,應用就沒法及時處理錯誤、資源始終被佔用,會致使內存泄露。所以,開發者須要一個數據庫訪問框架,用於處理這些模板樣式代碼。
Spring提供的JDBC框架負責管理資源和異常處理,從而能夠簡化開發者的JDBC代碼。開發者只須要編寫寫入和讀取數據庫相關的代碼便可。
正如在以前的小節中論述過的,Spring將數據庫訪問過程當中的模板樣式代碼封裝到各個模板類中了,對於JDBC,Spring提供了下列三個模板類:
從Spring 3.1開始已經將SimpleJdbcTemplate廢棄,它所擁有的Java 5那些特性被添加到原來的JdbcTemplate中了,所以你能夠直接使用JdbcTemplate;當你但願在查詢中使用命名參數時,則能夠選擇使用NamedParameterJdbcTemplate。
要使用JdbcTemplate對象,須要爲之傳遞DataSource對象。若是使用Java Config配置JdbcTemplatebean,則對應代碼以下:
@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
這裏經過構造函數將DataSource對象注入,而dataSourcebean則來自DataSourceConfiguration文件中定義的javax.sql.DataSource實例。
而後就能夠在本身的repository實現中注入jdbcTemplatebean,例如,假設Spitter的repository使用jdbcTemplatebean,代碼可列舉以下:
package org.test.spittr.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.stereotype.Repository;
import org.test.spittr.data.Spitter;
@Repository public class JdbcSpitterRepository implements SpitterRepository {
@Autowired private JdbcOperations jdbcOperations;
.....
}
這裏JdbcSpitterRepository被@Repository註解修飾,component-scanning掃描機制起做用時會自動建立對應的bean。按照「面向接口編程」的原則,咱們定義JdbcOperations接口對應的實例,而JdbcTemplate實現了這個接口,從而使得JdbcSpitterRepository與JdbcTemplate解耦合。
使用JdbcTemplate實現的addSpitter()方法很是簡單,代碼以下:
public void addSpitter(Spitter spitter) {
jdbcOperations.update(SQL_INSERT_SPITTER,
spitter.getUsername(),
spitter.getPassword(),
spitter.getFirstName(),
spitter.getLastName());
}
能夠看出,這個版本的addSpitter十分簡單,不強制開發者寫任何管理資源和處理異常的代碼,只有插入語句和對應的參數。
當調用update()方法時,JdbcTemplate獲取一個鏈接、建立一個statement,並執行插入語句。
JdbcTemplate內部捕獲了可能拋出的SQLException異常,而後轉爲更具體的數據庫訪問異常,並從新拋出。因爲Spring的數據庫訪問異常都是運行時異常,開發者能夠本身決定是否捕獲這些異常。
使用JdbcTemplate工具從數據庫中讀取數據也很是簡單,下列代碼展現了改造事後的findOne()函數:調用JdbctTemplate的queryForObject函數,用於經過ID查詢Spitter對象。
public Spitter findOne(long id) {
return jdbcOperations.queryForObject(
SQL_SELECT_SPITTER,
new SpitterRowMapper(),
id);
}
private static final class SpitterRowMapper implements RowMapper<Spitter> {
public Spitter mapRow(ResultSet resultSet, int i) throws SQLException {
return new Spitter(
resultSet.getLong("id"),
resultSet.getString("firstName"),
resultSet.getString("lastName"),
resultSet.getString("username"),
resultSet.getString("password"));
}
}
findOne()函數使用JdbcTemplate的queryForObject()方法從數據庫中查詢Spitter記錄。queryForObject()方法包括三個參數:
這裏須要注意SpitterRowMapper類,它實現了RowMapper接口,對於查詢結果,JdbcTemplate調用mapRow()方法——一個ResultSet參數和一個row number參數。mapRow()方法的主要做用是:從結果集中取出對應屬性的值,並構造一個Spitter對象。
和addSpitter()方法相同,findOne()方法也沒有那些JDBC模板樣式代碼,只有純粹的用於查詢Spitter數據的代碼。
數據就像應用的血液,在某些以數據爲中心的業務中,數據自己就是應用。在企業級應用開發中,編寫穩定、簡單、性能良好的數據訪問層很是重要。
JDBC是Java處理關係型數據的基本技術。原生的JDBC技術並不完美,開發者不得不寫不少模板樣式代碼,用於管理資源和處理異常。Spring提供了對應的模板工具類,用於消除這些模板樣式代碼。