Spring對JDBC
進行了良好的封裝,通過提供相應的模闆和輔助類,在相當程度上降低
了JDBC操作的複雜性。并且得益于Spring良好的隔離設計,JDBC封裝類庫可以脫離
Spring Context獨立使用,也就是說,即使系統并沒有采用Spring作為結構性架構,我們
也可以單獨使用Spring的JDBC部分(spring-dao.jar)來改善我們的代碼。
作為對比,首先讓我們來看一段傳統的JDBC代碼:
Connection conn =null;
Statement stmt = null;
try {
conn = dataSource.getConnection();
stmt = con.createStatement();
stmt.executeUpdate("UPDATE user SET age = 18 WHERE id = 'erica'");
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException ex) {
logger.warn("Exception in closing JDBC Statement", ex);
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException ex) {
logger.warn("Exception in closing JDBC Connection", ex);
}
}
}
類似上面的代碼非常常見。
為了執行一個SQL語句,我們必須編寫22行代碼,而其中
21行與應用邏輯并無關聯,并且,這樣的代碼還會在系統其他地方(也許是每個需要資料
庫通路的地方)重複出現。 于是,大家開始尋找一些設計模式以改進如此的設計,Template模式的應用是其中一
種典型的改進方案。
Spring的JDBC封裝,很大一部分就是借助Template模式實作,它提供了一個優秀的
JDBC模闆庫,借助這個工具,我們可以簡單有效的對傳統的JDBC編碼方式加以改進。
下面是借助Spring JDBC Template修改過的代碼,這段代碼完成了與上面代碼相同
的功能。
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("UPDATE user SET age = 10 WHERE id = 'erica'");
可以看到,兩行代碼完成了上面需要19行代碼實作的功能。所有備援的代碼都通過合理
的抽象彙集到了JdbcTemplate中。
無需感歎,借助Template模式,我們大緻也能實作這樣一個模闆,不過,Spring的設計
者已經提前完成了這一步驟。org.springframework.jdbc.core.JdbcTemplate中包含了
這個模闆實作的代碼,經過Spring設計小組精心設計,這個實作可以算的上是模闆應用的
典範。特别是回調(CallBack)的使用,使得整個模闆結構清晰高效。值得一讀。
Tips:實際開發中,可以将代碼中寫死的SQL語句作為Bean的一個String類型屬性,借
助DI機制在配置檔案中定義,進而實作SQL的參數化配置。
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate
.update(
"UPDATE user SET age = ? WHERE id = ?",
new PreparedStatementSetter() {
public void setValues(PreparedStatementSetter ps)
throws SQLException {
ps.setInt(1, 18);
ps.setString(2, "erica");
}
}
);
可以看到,上面引用了update方法的另一個版本,傳入的參數有兩個,第一個用于建立
PreparedStatement的SQL。第二個參數是為PreparedStatement設定參數的
PreparedStatementSetter。
第二個參數的使用方法比較獨到,我們動态建立了一個PreparedStatementSetter類,
并實作了這個抽象類的setValues方法。之後将這個類的引用作為參數傳遞給update。
update接受參數之後,即可調用第二個參數提供的方法完成PreparedStatement的初始
化。
Spring JDBC Template中大量使用了這樣的Callback機制,這帶來了極強的靈活性和
擴充性。
上面示範了update方法的使用(同樣的操作适用于update、insert、delete)。下面是
一個查詢的示例。
final List userList = new ArrayList();
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate
.query( "SELECT name, sex, address FROM user WHERE age > 18",
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getString("name"));
user.setSex(rs.getString("sex"));
user.setAddress(rs.getString("address"));
userList.add(product);
}
}
);
這裡傳入query方法的有兩個參數,第一個是Select查詢語句,第二個是一個
RowCallbackHandler執行個體,我們通過RowCallbackHandler對Select語句得到的每行記
錄進行解析,并為其建立一個User資料對象。實作了手動的OR映射。
此外,我們還可以通過JdbcTemplate.call方法調用存儲過程。
query、update方法還有其他很多不同參數版本的實作,具體調用方法請參見Spring
JavaDoc。
JdbcTemplate與事務
上例中的JdbcTemplate操作采用的是JDBC預設的AutoCommit模式,也就是說我們還
無法保證資料操作的原子性(要麼全部生效,要麼全部無效),如:
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("UPDATE user SET age = 10 WHERE id = 'erica'");
jdbcTemplate.update("UPDATE user SET age = age+1 WHERE id = 'erica'");
由于采用了AutoCommit模式,第一個update操作完成之後被自動送出,資料庫
中”erica”對應的記錄已經被更新,如果第二個操作失敗,我們無法使得整個事務復原到最
初狀态。對于這個例子也許無關緊要,但是對于一個金融帳務系統而言,這樣的問題将導緻
緻命錯誤。
為了實作資料操作的原子性,我們需要在程式中引入事務邏輯,在JdbcTemplate中引入
事務機制,在Spring中有兩種方式:
1. 代碼控制的事務管理
2. 參數化配置的事務管理
下面就這兩種方式進行介紹。
u 代碼控制的事務管理
首先,進行以下配置,假設配置檔案為(Application-Context.xml):
再對上面的例子進行一些改進,通過PrepareStatement執行update操作以避免SQL
Injection 漏洞
<beans>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>net.sourceforge.jtds.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:jtds:sqlserver://127.0.0.1:1433/Sample</value>
</property>
<property name="username">
<value>test</value>
</property>
<property name="password">
<value>changeit</value>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransac
onManager">
<property name="dataSource">
<ref local="dataSource" />
</property>
</bean>
<bean id="userDAO" class="net.xiaxin.dao.UserDAO">
<property name="dataSource">
<ref local="dataSource" />
</property>
<property name="transactionManager">
<ref local="transactionManager" />
</property>
</bean>
</beans>
配置中包含了三個節點:
Ø dataSource
這裡我們采用了apache dhcp元件提供的DataSource實作,并為其配置了
JDBC驅動、資料庫URL、使用者名和密碼等參數。
Ø transactionManager
針對JDBC DataSource類型的資料源,我們選用了
DataSourceTransactionManager
作為事務管理元件。
如果需要使用基于容器的資料源(JNDI),我們可以采用如下配置:
<bean id="dataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>jdbc/sample</value>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTrans
actionManager"
/>
userDAO
申明了一個UserDAO Bean,并為其指定了dataSource和
transactionManger資源。
UserDAO對應的代碼如下:
public class UserDAO {
private DataSource dataSource; private PlatformTransactionManager transactionManager;
public PlatformTransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(PlatformTransactionManager
transactionManager) {
this.transactionManager = transactionManager;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void insertUser() {
TransactionTemplate tt =
new TransactionTemplate(getTransactionManager());
tt.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
JdbcTemplate jt = new JdbcTemplate(getDataSource());
jt.update(
"insert into users (username) values ('xiaxin');");
jt.update(
"insert into users (id,username) values(2,
'erica');");
return null; }
});
}
} 可以看到,在insertUser方法中,我們引入了一個新的模闆類:
org.springframework.transaction.support.TransactionTemplate。
TransactionTemplate封裝了事務管理的功能,包括異常時的事務復原,以及操作成
功後的事務送出。和JdbcTemplate一樣,它使得我們無需在瑣碎的try/catch/finally代碼
中徘徊。 在doInTransaction中進行的操作,如果抛出未捕獲異常将被自動復原,如果成功執行,
則将被自動送出。
這裡我們故意制造了一些異常來觀察資料庫操作是否復原(通過在第二條語句中更新自
增ID字段故意觸發一個異常):
編寫一個簡單的TestCase來觀察實際效果:
InputStream is = new FileInputStream("Application-Context.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
UserDAO userDAO = (UserDAO) factory.getBean("userDAO");
userDAO.insertUser();
相信大家多少覺得上面的代碼有點淩亂,Callback類的編寫似乎也有悖于日常的程式設計
習慣(雖然筆者覺得這一方法比較有趣,因為它巧妙的解決了筆者在早期自行開發資料通路
模闆中曾經遇到的問題)。
如何進一步避免上面這些問題?Spring 的容器事務管理機制在這裡即展現出其強大
的能量。
作者:少帥