統一的數據訪問異常體系
問題:DAO模式下,數據訪問接口需要拋出異常,如果異常特定于某種實現,那么使用這個接口的代碼將會與特定的實現綁定,如果更換實現,接口需要拋出新的異常。
解決:
- 在DAO實現類內部,將數據訪問異常封裝后以unchecked Exception拋出,因為數據庫異常一般也無法恢復。
- DAO實現類提取特定的異常的信息,根據信息拋出不同類型的異常。
DataAccessException
JDBC
問題:
- 繁瑣的API,完成一個操作有太多雷同的代碼
- API使用容易出錯,需要關閉很多資源。
- 異常處理能力不足,一個SQLException,沒有子類化,ErrorCode的規范各個數據庫廠商來制定
解決:
JdbcTemplate
- 封裝所有基于JDBC的數據訪問代碼
- 對SQLException提供的異常信息進行了統一轉譯,將JDBC數據異常納入Spring異常
- 還做了對Spring事務的集成
JdbcTemplate
模板方法模式+CallBack
- 通過模板方法模式固定流程,包括獲取資源,執行sql,以及處理異常,關閉資源
- 對于獲取到的資源,通過傳入CallBack來操作資源,在CallBack中的方法實現需要的邏輯
CallBack公開的資源:
- ConnectionCallback
- StatementCallback
- PreparedStatementCallback
- CallableStatementCallback
public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(getDataSource());
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null) {
// Extract native JDBC Connection, castable to OracleConnection or the like.
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
else {
// Create close-suppressing Connection proxy, also preparing returned Statements.
conToUse = createConnectionProxy(con);
}
return action.doInConnection(conToUse);
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
}
finally {
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
execute即為模板方法,固定了執行流程,包括獲取資源,捕獲異常并轉譯以及關閉資源,暴露出來的資源由Callback進行使用,CallBack內部的邏輯才是訪問數據庫的用戶邏輯。不同類型的CallBack暴露了不同層級的資源。
DataSourceUtils
獲取Connection,綁定到線程
SQLException轉譯
通過SQLExceptionTranslator將SQLException轉譯到DataAccessException
數據訪問
public void execute(final String sql) throws DataAccessException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
}
class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
@Override
public Object doInStatement(Statement stmt) throws SQLException {
stmt.execute(sql);
return null;
}
@Override
public String getSql() {
return sql;
}
}
execute(new ExecuteStatementCallback());
}
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL query [" + sql + "]");
}
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
ResultSet rsToUse = rs;
if (nativeJdbcExtractor != null) {
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
}
return rse.extractData(rsToUse);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}
JdbcTemplate中關于數據訪問的函數非常的多,但細分之后,基本所有的函數最終都是通過StatementCallback來實現的,如上面的兩種,函數內部實現了一個StatementCallback類,然后將SQL語句傳入,完成操作,他們的區別在于execute(String)沒有返回結果,而query需要處理ResultSet。
那么從最簡單的開始
- queryFor*()
queryFor*函數的參數的第一個都是String,代表SQL語句- queryForMap
參數可以加Object...作為查詢參數,或者Object[]以及int[]作為查詢參數以及參數類型
返回一行結果,Map<String, Object> - queryForObject
參數除了上述的Object[]之外,一般還有一個Class<T>,代表返回的對象類型,或者是RowMapper<T>
返回一行結果,T - queryForList
參數除了上述的Object[]作為查詢參數以外,如果還有Class<T>,那么返回對象,否則返回Map
返回List,List<T>或者List<Map<String, Object>>
- queryForMap
對于queryForMap和queryForObject來說,它們也是調用queryForList實現的,但是保證List中只有一個元素。
而對于上面所有的queryFor*函數來說,它們取到的ResultSet都是通過RowMapper處理的,而RowMapper其實也是會被包裝成RowMapperResutlSetExtractor,調用上面的第二個函數。
如果加了查詢參數,那么不用StatementCallback而是用PreparedStatementCallback,本質也是一樣的。
- query()
所以也就是上面的queryFor把參數包裝好以后,會調用query函數,有參數的使用PreparedStatementCallback,沒參數的使用StatementCallback,而對于ResultSet的處理,有三種回調接口:- RowMapper
處理單行結果,結果由ResultSetExtractor組裝成List - RowCallbackHandler
處理單行結果,結果自己處理 - ResutlSetExtractor
自己處理ResultSet
區別:《Spring揭秘》p278
- RowMapper
從上面的函數也可以看到,查詢的時候這些ResultSetExtractor最終也是在StatementCallback中使用的。
NamedParameterJdbcTemplate
使用變量名而不是?作為查詢參數的占位符