Spring是一個輕量級的應用程序框架。在許多情況中,Spring都能夠良好地代換傳統的由Java EE應用程序服務器所提供的服務。Spring既是綜合性的也是模塊化的。基于其分層架構,它能夠使開發者靈活地單獨使用其任何一部分。Spring由許多模塊組成,例如IoC容器,AOP,MVC,持久性,DAO和remoting。這些模塊都是相當松耦合的:其中,一些模塊的使用根本不需要另一些模塊。以前,簡直還沒有象Spring應用程序這樣的:你可以選擇使用一些,大多數,或所有的Spring框架支持的組件來構建你的應用程序。
Spring框架所提供的JDBC支持與其它Spring部分并非是緊耦合的,這極有利于代碼的可維護性。本文將向你展示任何直接使用JDBC(也即是,不通過一些O/R映射框架本身使用JDBC)的應用程序是如何從Spring中受益的。
二. 傳統型JDBC
傳統型JDBC有許多積極的方面使之在許多J2SE和J2EE應用程序開發中占有重要地位。然而,也有一些特征使其難于使用:
· 開發者需要處理大量復雜的任務和基礎結構,例如大量的try-catch-finally-try-catch塊。
· 應用程序需要復雜的錯誤處理以確定連接在使用后被正確關閉,這樣以來使得代碼變得冗長,膨脹,并且重復。
· JDBC中使用了極不明確性的SQLException異常。
· JDBC沒有引入具體的異常子類層次機制。
相應于任何一種錯誤,都只是拋出SQLException異常-無論它來源于JDBC驅動程序還是來源于數據庫,這使得程序員很難理解到底是哪里實際出現了錯誤。例如,如果SQL對象是無效的或已經被鎖定,那么將拋出一個SQLException異常。調試這樣的異常需要一定的時間來檢查SQL狀態值和錯誤代碼。更有甚者,SQL狀態值和錯誤代碼的含義在各種數據庫之間都有些差別。
事實證明,編寫JDBC代碼并不是一項容易的工作-存在大量的重復性的工作。為了說明問題,下面是一個例子-使用傳統型JDBC來從數據庫中得到一個可用任務的列表。
| package com.spring.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Vector; public class TraditionalJDBC { public Vector getTasksNames() { Connection con = null; PreparedStatement pstmt = null; ResultSet rs = null; Vector task = new Vector(); try { con = getConnection(); pstmt = con.prepareStatement( "select TASKNAME from tasks"); rs = pstmt.executeQuery(); while (rs.next()) { task.add(rs.getString(1)); } } catch (SQLException e) { System.out.println(e); } finally { try { rs.close(); pstmt.close(); con.close(); } catch (SQLException e1) { System.out.println(e1); } } return task; } private Connection getConnection()throws SQLException { try { DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver()); return DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott","tiger"); } catch (SQLException sqle) { System.out.println(sqle); return null; } } public static void main(String[] args) { TraditionalJDBC obj = new TraditionalJDBC(); Vector task = obj.getTasksNames(); for (int i = 0; i < task.size(); i++) { System.out.println(task.elementAt(i)); } } } |
除了實際查詢數據庫的SQL代碼外,上面的示例中需要巨大數量的例程代碼。getConnection()方法與我們的任務無關,而即使是getTasksNames()方法也僅包含特定于當前任務的兩行代碼。剩下的都是一些普通的復雜的任務代碼。
JDBC的許多積極方面使得它在許多J2SE和J2EE應用程序中仍然占有重要地位。然而,正如你所見,有一些特征使其比我們可能想像的要更難于使用。JDBC這些乏味并且有時挫敗人性的特征已經導致出現了許多公共的可以利用的JDBC抽象框架(例如SQLExecutor和Apache Jakarta Commons DBUtils),還有數不清的自家生產性JDBC應用程序框架。一種公共的可以利用的JDBC抽象框架正是Spring框架的JDBC抽象。
三. Spring JDBC簡介
Spring所提供的JDBC抽象框架由四個不同的包組成:
· 核心包包含JdbcTemplate。這個類是一個基礎類之一-由Spring框架的JDBC支持提供并使用。
· 數據源包是實現單元測試數據庫存取代碼的重要的一部分。它的DriverManagerDataSource能夠以一種類似于你已經習慣于JDBC中的用法:只要創建一個新的DriverManagerDataSource并且調用setter方法來設置DriverClassName,Url,Username和Password。
· 對象包中包含類,用于描述RDBMS查詢、更改和存儲過程為線程安全的、可重用的對象。
· 支持包-你可以從這里找到SQLException翻譯功能和一些工具類。
1) 模板設計模式
Spring JDBC實現模板設計模式,這意味著,代碼中的重復的復雜的任務部分是在模板類中實現的。這種方式簡化了JDBC的使用,因為由它來處理資源的創建和釋放。這有助于避免普通錯誤,例如忘記關閉連接等。它執行核心JDBC工作流任務,如語句創建和執行,而讓應用程序代碼來提供SQL并且提取結果。
2) Spring JDBC異常處理
Spring框架特別強調在傳統型JDBC編程中所面臨的與下列方案有關的問題:
· Spring提供一個抽象異常層,把冗長并且易出錯誤的異常處理從應用程序代碼移到由框架來實現。框架負責所有的異常處理;應用程序代碼則能夠專注于使用適當的SQL提取結果。
· Spring提供了一個重要的異常類層次,以便于你的應用程序代碼中可以使用恰當的SQLException子類。
借助于一個抽象異常層,我們成功地實現了數據庫獨立性而不必改變異常處理。例如,如果你把你的數據庫從PostgreSQL改變為Oracle,那么你不必把異常處理從OracleDataException改變到PostgresDataException。Spring能夠捕獲應用程序服務器特定的異常并拋出一個Spring數據異常。
當處理異常時,Spring檢查來自一個數據庫連接的元數據可用性以決定數據庫產品。它使用這種知識來把SQLException映射到其自己異常層次中的具體的異常上。因此,我們不需要擔心專門性的SQL狀態或錯誤代碼問題;Spring的數據存取異常不是JDBC特定的,因此你的DAO不必綁定到JDBC(由于其可能拋出的異常)。
四. Spring JDBC示例
在下面兩個列表中,我們將使用前面用傳統型JDBC實現的業務邏輯為例并且展示使用Spring JDBC版本是多么容易。首先,我們從一個簡單的接口開始。
| package com.spring.jdbc; import java.util.List; public interface TasksDAO { public List getTasksNames(); } |
接下來,我們提供針對于TasksDAO接口的一種實現。
| package com.spring.jdbc; import java.util.Iterator; import java.util.List; import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapperResultReader; import org.springframework.jdbc.core.support.JdbcDaoSupport; public class TasksJdbcDAO extends JdbcDaoSupport public List getTasksNames() { taskDao.setDataSource(ds); |
在上面的例子中,普通的和復雜的任務代碼已經被移交到框架中。還應注意,借助于Spring JDBC,我們如何利用控制反轉(IoC)容器來提供一種DataSource-我們僅把它注入到TasksJdbcDAO對象中。
控制反轉背后的概念通常被表達為"不要找我,讓我找你好了"。IoC把一些任務移交到了框架中,并且脫離出了應用程序代碼。不是讓你的代碼調用一個傳統的類庫,而是一個IoC框架調用你的代碼。存在于許多API中的生命周期回調,例如相應于會話EJB的setSessionContext()方法,正是展示了這種方法。
DataSource必須被注入到這個類中(或者其超類中),這是通過setDataSource()方法實現的。所有配置細節都遠離了業務邏輯或客戶端代碼;這增加你的應用程序的松耦合性并因此而提高了程序的可測試性和可維護性。作為選擇,我們還能在JNDI或servlet容器中建立一個DataSource,并用編程方式來檢索它,然后把它注入到DAO對象中。下面是一個你可以使用的示例Spring bean配置文件-SpringConfig.xml:
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="dataSourceDBDirect" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/> <property name="username" value="scott"/> <property name="password" value="tiger"/> </bean> </beans> |
這個文件指示Spring bean容器實例化一個dataSourceDBDirect bean-它基于org.springframework.jdbc.datasource.DriverManagerDataSource類創建。
1) 基于Spring JDBC實現一個業務層
我們已經看到了一個簡單的使用Spring JDBC的例子,在這種情況下,它從Spring BeanFactory(控制反轉容器)中得到極少的幫助。現在,我們將超越這個簡單的例子。讓我們來探討一下如何基于Spring JDBC實現業務服務。首先,讓我們創建一個客戶端-一個為終端用戶提供輸出的應用程序。該客戶端使用了一個服務,一個遵守下面的Service接口的業務服務:
| package com.spring.jdbc; import java.util.List; public interface Service { public List getTasksNames(); public void setTasksDao(TasksDAO taskDAO); } |
客戶端需要存取一個業務服務對象。它將使用Spring BeanContainer來"抓住"這樣的一個服務對象。客戶端僅能針對接口編程并且依賴容器來提供一種實際的實現。而且,這個ServiceImpl類必須實現所有的存在于業務服務接口中的方法。該代碼看上去如下所示:
| package com.spring.jdbc; import java.util.List; public class ServiceImpl implements Service{ TasksDAO taskDAO; public void setTasksDao(TasksDAO taskDAO) { this.taskDAO=taskDAO; } public List getTasksNames() { List tasks = taskDAO.getTasksNames(); return tasks; } } |
你應該已經注意到,該服務需要一個TasksJdbcDAO。反過來,這個對象實現了TasksDAO接口。因此,我們將通過BeanFactory來把DAO注入到該服務中。在此,我們碰巧有一個TasksJdbcDAO類-bean工廠可以使用它來實現這一目的。然而,既然這個類派生于JdbcDaoSupport,那么我們知道我們需要注入一個DataSource或讓bean工廠為我們注入該DataSource。現在這個bean配置文件看上去如下所示:
| <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="dataSourceDBDirect" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/> <property name="username" value="scott"/> <property name="password" value="tiger"/> </bean> <bean id="tasksDAO" class="com.spring.jdbc.TasksJdbcDAO"> <property name="dataSource"> <ref local="dataSourceDBDirect"/> </property> </bean> <bean id="service" class="com.spring.jdbc.ServiceImpl"> <property name="tasksDao"> <ref local="tasksDAO"/> </property> </bean> </beans> |
我們看到服務bean使得tasksDao bean被注入-它反過來又使dataSourceDBDirect對象被注入。當我們請求服務bean時,我們通過一個具有DataSource的DAO得到它。至此,一切就緒。因此,當客戶端存取bean容器以得到服務對象時,會發生什么呢?該bean容器實例化并且注入一個DataSource和一個TasksDAO-在把服務返回到客戶端之前。現在,我們的客戶端變得相當簡單了。它需要與BeanFactory進行通訊,"抓住"一個服務對象并處理它:
| package com.spring.jdbc; import java.util.Iterator; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Client extends RuntimeException { public static void main(String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("SpringConfig.xml"); Service service = (Service)ctx.getBean("service"); Iterator tskIter = service.getTasksNames().iterator(); while (tskIter.hasNext()) { System.out.println(tskIter.next().toString()); } } } |
你必須注意,在此Client派生于RuntimeException異常類。Spring拋出了RuntimeExceptions而不是檢查的異常-RuntimeExceptions不應該被捕獲。由于在你的代碼中捕獲所有異常是一種復雜的任務,所以Spring開發者決定拋出RuntimeExceptions以便實現如果你不捕獲一個異常的話,那么你的應用程序將會中斷而且用戶會得到該應用程序異常。使用它們的第二個理由是,絕大多數異常都是不可恢復的,因此你的應用程序邏輯不能以任何方式來再次處理它們。
五. 另外的優點
除了上面描述的Spring框架帶給JDBC中的優點外,與你的JDBC應用程序一起使用Spring框架還存在另外一些優點。這些優點包括:
· Spring框架提供了
org.springframework.jdbc.support.nativejdbc.NativeJdbcExtractor接口和這個接口的一些實現(例如SimpleNativeJdbcExtractor)。對于經由一個Oracle連接或ResultSet訪問Oracle特征的情況來說,這些內容是非常有用的。
· 對于創建oracle.sql.BLOB(二進制大型對象)和oracle.sql.CLOB(字符大型對象)實例來說,Spring提供了類org.springframework.jdbc.support.lob.OracleLobHandler。
· Spring提供的OracleSequenceMaxValueIncrementer類提供了一個Oracle序列的下一個值。它有效地提供了與你直接使用下列命令:"select someSequence.nextval from dual"(其中,someSequence是在Oracle數據庫中的你的序列的名字)所提供的一樣的信息。這種方法的優點是,DataFieldMaxValueIncrementer接口可以用于一個DAO層次中而不必緊密地耦合于Oracle特定的實現。
六. 結論
本文集中討論了使用Spring來編寫更可維護的和更不易出現錯誤的JDBC代碼。Spring JDBC提供了一些優點,例如更為干凈的代碼,更好的異常與資源處理,并且能夠集中于業務問題而不是復雜的任務代碼。另外,值得注意的是,使用Spring框架能夠使用極少的代碼就可以實現實質上與傳統型JDBC相同的功能。