javascript
Spring实战6-利用Spring和JDBC访问数据库
主要內容
- 定義Spring的數據訪問支持
- 配置數據庫資源
- 使用Spring提供的JDBC模板
寫在前面:經過上一篇文章的學習,我們掌握了如何寫web應用的控制器層,不過由于只定義了SpitterRepository和SpittleRepository接口,在本地啟動該web服務的時候會遇到控制器無法注入對應的bean的錯誤,因此我決定跳過6~9章,先搞定數據庫訪問者一章。
在企業級應用開發中不可避免得會涉及到數據持久化層,在數據持久化層的開發過程中,可能遇到很多陷阱。你需要初始化數據庫訪問框架、打開數據庫連接、處理各種異常,最后還要記得關閉連接。如果在這些步驟中你有一步做錯了,那就又丟失公司數據的風險。妥當得處理這些并不容易,Spring提供了一套完整的數據庫訪問框架,用于簡化各種數據庫訪問技術的使用。
在開發Spttr應用的持久層時,你需要在JDBC、Hibernate、Java Perssitence或者其他ORM框架等技術中進行選擇。Spring扮演的角色是盡量消除你在使用這些技術時需要寫的重復代碼,以便開發人員專注于業務邏輯。
10.1 學習Spring的數據庫訪問哲學
Spring框架的目標之一就是讓開發者面向接口編程,Spring的數據訪問支持也不例外。
和很多其他應用一樣,Spittr應用也需要從數據庫中讀取信息或者寫入信息到數據庫。為了避免持久化相關的代碼遍布應用的各個地方,一般我們會將這些任務整合到一個模塊中完成,這類模塊通常被稱之為數據訪問對象(DAOs)或者repositories。
為了避免業務層模塊強依賴于某種類型的數據庫(關系型orNoSQL),數據庫訪問層應以接口形式對外提供服務。下圖展示了這個思路:
service層不自己處理數據訪問,將這個任務委托給repositories;repository的接口使得service對象與具體的數據庫訪問策略松耦合
如你所見,service對象通過接口訪問repository對象,這有很多好處:(1)因為service對象并不限制于某個特定的數據訪問實現,這使得service對象便于測試;(2)你可以創建這些數據庫訪問接口的mock實現,這樣即使沒有建立數據庫連接你也可以測試service對象;(3)可以顯著加速單元測試的執行速度;(4)可以避免某個測試用例因數據不一致而失敗。
數據訪問層通過repository接口中的幾個方法與service層溝通,這使得應用設計非常靈活,即使將來要更換數據庫持久層框架,對應用的其他部分的影響也非常小。如果數據訪問層的實現細節散步到應用的其他部分,則整個應用跟數據訪問層緊密耦合在一起。
INTERFACES AND SPRING 如果你讀完上面兩段話之后能夠感覺到我有很強的意愿將持久化層隱藏在接口之后,那說明我正確得表達了自己的想法。我相信接口是書寫松耦合的代碼的關鍵,不僅是數據庫訪問層,應該在應用的所有模塊之間使用接口進行交互。雖然Spring并沒有強制要求面向接口編程,但是Spring的設計思想鼓勵面向接口編程——最好通過接口將一個bean裝配到另一個bean的屬性中。
Spring提供了方便的異常體系,也可以幫助開發者隔離數據庫訪問層與應用的其他模塊。
10.1.1 了解Spring的數據訪問的異常體系
在使用原始的JDBC接口時,如果你不捕獲SQLException,就不能做任何事情。SQLException的意思是在嘗試訪問數據庫過程中發生了某些錯誤,但是并沒有提供足夠的信息告訴開發人員具體的錯誤原因以及如何修正錯誤。
下列這些情況都可能引發SQLException:
- 連接數據庫失敗;
- 查詢語句中存在語法錯誤;
- 查詢中提到的表或者列不存在;
- 插入或者更新操作違背了數據庫一致性;
關于SQLException最大的問題在于:當捕獲它的時候應該如何處理。調查顯示,很多引起SQLException的故障不能在catch代碼塊中修復。大部分被拋出的SQLException表示應用發生了致命故障。如果應用不能連接數據庫,通常意味著應用不能繼續執行;同樣地,如果在查詢語句中有錯誤,在運行時能做的工作也很少。
既然我們并不能做些操作來恢復SQLException,為什么必須捕獲它?
即使你計劃處理一些SQLException,你也必須捕獲SQLException對象然后查看它的屬性才能發掘出問題的本質。這是因為SQLException是一個代之所有數據庫訪問相關問題的異常,而不是針對每個可能的問題定義一個異常類型。
一些持久化框架提供了豐富的異常體系。例如,Hibernate提供了幾乎兩打不通的異常,每種代表一個特定的數據庫訪問問題。這令使用Hibernate的開發者可以為自己想處理的異常書寫catch塊。
即使這樣,Hibernate的異常也只對Hibernate框架有用,如果你使用Hibernate自己的異常體系,就可能使程序的剩余部分強依賴于Hibernate,將來如果想升級為其他的持久化框架會非常麻煩。在這節開頭的時候說過,我們希望隔離數據訪問層和持久化機制的特性。如果在數據訪問層處理Hibernate框架拋出的專屬異常,則會影響到應用中的其余模塊;如果不這么做,你必須捕獲該持久化的專屬異常,然后重新拋出一個平臺無關的異常。
SPRING'S PERSISTENCE PLATFORM-AGNOSTIC EXCEPTION
一方面,JDBC提供的異常體系過于普遍——根本沒有異常體系可言;另一方面,Hibernate的異常體系是針對這個框架自己的,因此我們需要一套數據庫訪問的異常體系,既具備足夠強的描述能力,又不要跟具體的持久化框架直接關聯。
Spring JDBC提供的異常體系同時滿足上述兩個條件。不同于傳統的JDBC,Spring JDBC針對某些具體的問題定義了對應的數據庫訪問異常。下表展示了Spring 數據訪問異常和JDBC的異常之間的對應關系。
JDBC的異常 VS Spring 的數據庫訪問異常
如你所見,Spring為在讀取或者寫入數據庫時可能出錯的原因設置了對應的異常類型,Spring 實際提供的數據庫訪問異常要遠多于表10.1所列出的那些。
Spring在提供如此豐富的異常前提下,還保證這些異常類型跟具體的持久化機制隔離。這意味著無論你使用什么持久化框架,你都可以使用同一套異常定義——持久化機制的選擇與數據訪問層實現解耦合。
LOOK, MA! NO CATCH BLOCKS!
表10.1中沒有說明的是:所有這些異常的根對象是DataAccessException,這是一個unchecked exception。換句話說,Spring不會強制你捕獲這些數據庫訪問異常。
Spring通過提供unchecked exception,讓開發者決定是否需要捕獲并處理某個異常。為了充分發揮Spring的數據訪問異常,你最好使用Spring提供的數據訪問模板。
10.1.2 模式化數據訪問
如果你之前通過飛機出行過,你一定明白在行程過程中最重要的事情是將行李從A地托運到B地。要妥當得完成這個事情需要很多步驟:當你到達機場時,你首先需要檢查行李;然后需要通過機場的安全掃描,以免不小心將可能危害飛行安全的東西帶上飛機;然后行李需要通過長長的傳送帶被運上飛機。如果你需要轉乘航線,行李也需要跟著你一起運輸。當你到達最終目的地時,行李會被運下飛機然后放置在傳送帶上,最后,你需要去目的地機場的指定地點領取自己的行李。
雖然在這個過程中有需要步驟,但是你僅僅需要參與其中的一部分。在這個例子中,整個過程就是將行李從出發城市運輸到目的城市,這個過程是固定的不會改變。在運輸過程可以分成明確的幾步:檢查行李、裝載行李、卸載行李等。在這其中一些步驟也是固定的,每次都一樣:當飛機到達目的地之后,所有行李都需要卸載并放在機場的指定地點。
在指定的節點,總程序會將一部分工作委托給一個子程序,用于完成更加細節的任務,這就是總程序中的變量部分。例如,行李的托運開始于乘客自己檢查行李,因為每個乘客的動作都不相同——各自檢查自己的行李,因此總程序中的這個步驟如何執行具體取決于每個乘客。用軟件開發中的術語描述,上述過程就是模板模式:模板方法規定整個算法的執行過程,將每個步驟的具體細節通過接口委托給子類完成。
Spring提供的數據訪問支持也使用了模板模式。無論你選擇使用什么技術,數據訪問的步驟就是固定的幾步(例如,在開始時,你一定需要獲取一個數據庫連接;在操作完成后,你一定需要釋放之前獲取的資源),但是每一步具體怎么實現有所不同。你用不同的方法查詢或者更新不同的數據,這些屬于數據庫訪問過程中的變量。
Spring將數據訪問過程中的固定步驟和變量部分分為兩類:模板(templates)和回調函數(callbacks)。模板負責管理數據訪問過程中的固定步驟,而由你定制的業務邏輯則寫在回調函數中。下圖顯示了這兩類對象的責任和角色:
Spring的數據訪問模板類負責數據訪問過程中的通用操作;與業務邏輯相關的任務則通過回調函數由開發者定制
如你所見,Spring的模板類處理數據訪問的固定步驟——事務管理、資源管理和異常處理;與此同時,跟應用相關的數據訪問任務——創建語句、綁定參數和處理結果集等,則需要在回調函數中完成。這種框架十分優雅,作為開發人員你只需要關注具體的數據訪問邏輯。
Spring提供了集中不同的模板,開發者根據項目中使用的持久化框架選擇對應的模板工具類。如果你使用原始的JDBC方式,則可以使用JdbcTemplate;如果你更傾向于使用ORM框架,則可以使用HibernateTemplate和JpaTemplate。表10.2列出了Spring提供的數據訪問模板。
Spring為不同持久化技術提供了對應的數據訪問模板
Spring為不同的持久化技術提供了對應的數據訪問模板,在這一章中并不能一一講述。因此我們將選擇最有效和你最可能使用的進行講解。
這一章首先介紹JDBC技術,因為它最簡單;在后面還會介紹Hibernate和JPA——兩種最流行的基于POJO的ORM框架。PS:除了《Spring in Action》中的這幾種持久化技術,現在更加流行的是Mybatis框架,后續我會專門寫對應的總結和學習筆記。
但是,所有這些持久化框架都需要依賴于具體的數據源,因此在開始學習templates和repositories之前,需要學習在Spring中如何配置數據源——用于連接數據庫。
10.2 配置數據源
Spring提供了幾種配置數據源的方式,列舉如下:
- 通過JDBC驅動定義數據源;
- 從JNDI中查詢數據源;
- 從連接池中獲取數據源;
對于生產級別的應用,我建議使用從數據庫連接池中獲取的數據源;如果有可能,也可以通過JNDI從應用服務器中獲取數據源;接下來首先看下如何配置Spring應用從JNDI獲取數據源。
10.2.1 使用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:
@Bean public JndiObjectFactoryBean dataSource() {JndiObjectFactoryBean jndiObjectFB = new JndiObjectFactoryBean();jndiObjectFB.setJndiName("/jdbc/SpittrDS");jndiObjectFB.setResourceRef(true);jndiObjectFB.setProxyInterface(javax.sql.DataSource.class);return jndiObjectFB; }顯然,在這里Java配置文件需要寫更多代碼,一般而言JavaConfig要比XML配置文件更簡單,這是個例外。
10.2.2 使用數據庫連接池
盡管Spring自身不提供數據連接池,但可以和很多第三方庫集成使用,例如:
- Apache Commons DBCP(http://commons.apache.org/proper/commons-dbcp/)
- c3p0(http://sourceforge.net/projects/c3p0/)
- BoneCP(http://jolbox.com/)
最常用的是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。
@Bean public 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所示:
dbcp1.4的BasicDataSource
對于dbcp2.x系列,如果你希望了解更多BasicDataSource的屬性,可參照官方文檔:dbcp2配置。
10.2.3 使用基于JDBC驅動的數據源
在Spring中最簡單的數據源就是通過JDBC驅動配置的數據源。Spring提供了三個相關的類供開發者選擇(都在org.springframework.jdbc.datasource包中):
- DriverManagerDataSource——每次請求連接時都返回新的連接,用過的連接會馬上關閉并釋放資源;
- SimpleDriverDataSource——功能和DriverManagerDataSource相同,不同之處在于該類直接和JDBC驅動交互,免去了類在特定環境(如OSGi容器)中可能遇到的類加載問題。
- SingleConnectionDataSource——每次都返回同一個連接對象,可以理解為只有1個連接的數據源連接池。
配置這些數據源跟之前配置DBCP的BasicDataSource類似,例如,可以用下列代碼配置DriverManagerDataSource
@Bean public 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="" />由于上述這三個數據源對象對多線程應用的支持都不好,因此強烈建議直接使用數據庫連接池。
10.2.4 使用嵌入式數據源
嵌入式數據源作為應用的一部分運行,非常適合在開發和測試環境中使用,但是不適合用于生產環境。因為在使用嵌入式數據源的情況下,你可以在每次應用啟動或者每次運行單元測試之前初始化測試數據。
使用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>元素。
10.2.5 使用profiles選擇數據源
一般需要在不同的環境(日常環境、性能測試環境、預發環境和生產環境等等)中配置不同的數據源,例如,在開發時非常適合使用嵌入式數據源、在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;@Configuration public 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書寫持久層。
10.3 在Spring應用中使用JDBC
在實際開發過程中有很多持久化技術可供選擇:Hibernate、iBATIS和JPA等。盡管如此,還是有很多應用使用古老的方法即JDBC技術,來訪問數據庫。
使用JDBC技術不需要開發人員學習新的框架,因為它就是基于SQL語言運行的。JDBC技術更加靈活,開發人員可以調整的余地很大,JDBC技術允許開發人員充分利用數據庫的本地特性,而在其他ORM框架中可能做不到如此靈活和可定制。
除了上述提到的靈活性、可定制能力,JDBC技術也有一些缺點。
10.3.1 分析JDBC代碼
開發者使用JDBC技術提供的API可以非常底層得操作數據庫,同時也意味著,開發者需要負責處理數據訪問過程中的各個具體步驟:管理數據庫資源和處理數據庫訪問異常。如果你使用JDBC插入數據庫,在這個例子中,假設需要插入一條spitter數據,則可以使用如下代碼:
@Component public 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和處理異常。另外,上述提到的模板樣式代碼在數據庫訪問過程中又非常重要:釋放資源和處理異常等,這都能提高數據訪問的穩定性。如果沒有這些操作,應用就無法及時處理錯誤、資源始終被占用,會導致內存泄露。因此,開發者需要一個數據庫訪問框架,用于處理這些模板樣式代碼。
10.3.2 使用Spring提供的JDBC模板
Spring提供的JDBC框架負責管理資源和異常處理,從而可以簡化開發者的JDBC代碼。開發者只需要編寫寫入和讀取數據庫相關的代碼即可。
正如在之前的小節中論述過的,Spring將數據庫訪問過程中的模板樣式代碼封裝到各個模板類中了,對于JDBC,Spring提供了下列三個模板類:
- JdbcTemplate——最基本的JDBC模板,這個類提供了簡單的接口,通過JDBC和索引參數訪問數據庫;
- NameParameterJdbcTemplate——這個JDBC模板類是的開發者可以執行綁定了指定參數名稱的SQL,而不是索引參數;
- SimpleJdbcTemplate——這個版本的JDBC模板利用了Java 5的一些特性,例如自動裝箱/拆箱、接口和變參列表等,用于簡化JDBC模板的使用。
從Spring 3.1開始已經將SimpleJdbcTemplate廢棄,它所擁有的Java 5那些特性被添加到原來的JdbcTemplate中了,因此你可以直接使用JdbcTemplate;當你希望在查詢中使用命名參數時,則可以選擇使用NamedParameterJdbcTemplate。
INSERTING DATA USING JDBCTEMPLATE
要使用JdbcTemplate對象,需要為之傳遞DataSource對象。如果使用Java Config配置JdbcTemplatebean,則對應代碼如下:
@Bean public 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 {@Autowiredprivate 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的數據庫訪問異常都是運行時異常,開發者可以自己決定是否捕獲這些異常。
READING DATA WITH JDBCTEMPLATE
使用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()方法包括三個參數:
- SQL字符串,用于從數據庫中查詢數據;
- RowMapper對象,用于從結果集ResultSet中提取數據并構造Spitter對象;
- 變量列表,用于指定查詢參數(這里是通過id查詢)。
這里需要注意SpitterRowMapper類,它實現了RowMapper接口,對于查詢結果,JdbcTemplate調用mapRow()方法——一個ResultSet參數和一個row number參數。mapRow()方法的主要作用是:從結果集中取出對應屬性的值,并構造一個Spitter對象。
和addSpitter()方法相同,findOne()方法也沒有那些JDBC模板樣式代碼,只有純粹的用于查詢Spitter數據的代碼。
10.4 總結
數據就像應用的血液,在某些以數據為中心的業務中,數據本身就是應用。在企業級應用開發中,編寫穩定、簡單、性能良好的數據訪問層非常重要。
JDBC是Java處理關系型數據的基本技術。原生的JDBC技術并不完美,開發者不得不寫很多模板樣式代碼,用于管理資源和處理異常。Spring提供了對應的模板工具類,用于消除這些模板樣式代碼。
后記:最近在項目開發中,遇到一次高并發下數據庫成為性能瓶頸的情況,對數據訪問層的各個階段有了深入的了解:建立數據庫連接、轉換SQL語句、執行SQL語句、獲取執行結果、釋放資源。我們在項目開發中使用的數據庫連接池是德魯伊(DruidDataSource),它的配置跟DBCP類似,在實際開發中,我們需要理解每個配置項的含義,用于性能調優。后續我會寫一篇關于數據庫連接池的文章。
另外,我們現在開發中最常用的是Mybatis框架,具體內容可以參考《Java Persistence With Mybaits 3》一書,也可以參考Mybatis-Spring教程(中文版)
-
在Tomcat配置JNDI數據源的三種方式 - 純白陰影 - ITeye技術網站
— ? 在Tomcat配置JNDI數據源的三種方式 ? ? 在我過去工作的過程中,開發用服務器一般都是Tomcat 數據源的配置往往都是在applicationContext.xml中配置一個dataSource的bean 然后在部署時再修改JNDI配置 我猜是因為Tomcat的配置需要改配置文件 不像JBoss,Weblogic等服務器在管理界面可以直接添加JNDI數據源 也很少人去 ... 杜琪? 136589219.iteye.com → -
java - How to use JNDI DataSource provided by Tomcat in Spring? - Stack Overflow
— It is said in Spring javadoc article about DriverManagerDataSource class, that this class is very simple and that it is recommended 杜琪? stackoverflow.com → -
java - Correct way to utilize p and util namespace in Spring XML Configuration - Stack Overflow
— My goal is to rewrite the sessionFactory section of my xml file into the same format as all other areas in my xml file. I need to use the p-namespace to make things look consistent and neat. The problem that I ran into is using the util/p namespace. 杜琪? stackoverflow.com → -
mybatis - 標簽 - 阿赫瓦里 - 博客園
杜琪? www.cnblogs.com → -
首頁 · alibaba/druid Wiki · GitHub
— druid - 為監控而生的數據庫連接池! 杜琪? github.com → -
如何應對并發(1) - 關于數據索引
— 功能介紹 caoz的心得與分享,只此一家,別無分號。
總結
以上是生活随笔為你收集整理的Spring实战6-利用Spring和JDBC访问数据库的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 九章算术卷第五 商功
- 下一篇: CasperJS基于PhantomJS抓