天天看點

持久層封裝 JDBC

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 的容器事務管理機制在這裡即展現出其強大

的能量。

作者:少帥