JdbcTemplate簡易封裝
在本篇博文中,我們將對SpringJDBC提供的JdbcTemplate進行簡易封裝,使其更加的易用,更加貼近上篇博文中對于SQL管理的設計。
我們希望在使用將要封裝的這個工具進行數據庫操作時有以下幾個優勢:
-
不處理數據獲取異常
-
不關心日志記錄
-
即可以使用我們XML文件中的SQL語句,也可以使用在業務方法中定義的SQL語句。(因為我們設計的XML文件并不能夠非常友好的支持條件查詢,所以對于需要條件查詢的情況,我們需要在業務方法中編寫查詢SQL)
對于第一個優勢而言,我們需要替換在JdbcTemplate中使用的 org.springframework.jdbc.core.RowMapper<T>接口。這個接口提供了一個T mapRow(ResultSet rs, int rowNum) throws SQLException方法,這個方法用于從查詢出的結果集(ResultSet)中依次取出每行每列的數據。我們之所以要替換這個接口,主要是為了處 理這個方法中ResultSet形參獲取列值時的一個缺陷,當我們獲取一個并不存在的列的值時會拋出SQLException。所以我們的目標是,如果我 們嘗試獲取一個不存在的列的值時不拋出錯誤,而是返回一個默認值。
為了達到這個目標,我們給出一個設計方案:當每次查詢返回ResultSet后,我們將這個ResultSet中的列名放入一個Map,以列名作為鍵,值可以是任意值。當我們執行getString(String columnLabel)、getDouble(String columnLabel)等這些取值方法時,我們先把傳入的列名(columnLabel)拿到Map中查詢是否存在,如果存在再調用ResultSet原生的getString(String columnLabel)、getDouble(String columnLabel)等這些方法進行取值,反之返回一個默認值,通常我們可以設置為null。源碼如下:
package com.kiiwow.framework.database.jdbc;import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.Date; import java.util.HashMap; import java.util.Map;
import org.springframework.jdbc.core.simple.ParameterizedRowMapper;
/* RowMapper @author leon.gan @param <T> / public abstract class RowMapper<T> implements ParameterizedRowMapper<T> { private Map<String, Integer> columnIndexes;
/* 每次實際取值之前先判斷列名是否在結果集中存在 */ protected int findColumn(ResultSet resultSet, String columnName) throws SQLException { if (columnIndexes == null) { buildIndexes(resultSet); } Integer index = columnIndexes.get(columnName); if (index == null) return -1; return index; }
/* 將結果集中的列名和一個迭代數字放入map中映射 */ private void buildIndexes(ResultSet resultSet) throws SQLException { columnIndexes = new HashMap<String, Integer>(); ResultSetMetaData meta = resultSet.getMetaData(); int count = meta.getColumnCount(); for (int i = 1; i < count + 1; i++) { String column = meta.getColumnName(i); columnIndexes.put(column.toLowerCase(), i); } }
protected String getString(ResultSet resultSet, String column) throws SQLException { return getString(resultSet, column, null); }
protected String getString(ResultSet resultSet, String column, String defaultValue) throws SQLException { int columnIndex = findColumn(resultSet, column); if (columnIndex > 0) return resultSet.getString(columnIndex); return defaultValue; }
protected Date getDate(ResultSet resultSet, String column) throws SQLException { return getDate(resultSet, column, null); }
protected Date getDate(ResultSet resultSet, String column, Date defaultValue) throws SQLException { int columnIndex = findColumn(resultSet, column); if (columnIndex > 0) return resultSet.getTimestamp(columnIndex); return defaultValue; }
protected int getInt(ResultSet resultSet, String column) throws SQLException { return getInt(resultSet, column, 0); }
protected int getInt(ResultSet resultSet, String column, int defaultValue) throws SQLException { int columnIndex = findColumn(resultSet, column); if (columnIndex > 0) return resultSet.getInt(columnIndex); return defaultValue; }
protected double getDouble(ResultSet resultSet, String column) throws SQLException { return getDouble(resultSet, column, 0); }
protected double getDouble(ResultSet resultSet, String column, double defaultValue) throws SQLException { int columnIndex = findColumn(resultSet, column); if (columnIndex > 0) return resultSet.getDouble(columnIndex); return defaultValue; }
public abstract T mapRow(ResultSet resultSet, int rowNum) throws SQLException; }</pre>
對于第二個優勢而言,我們需要對JdbcTemplate的方法執行進行日志記錄,同時替換其原生的RowMapper接口。我們順便調整了其原生形參的順序,以使代碼看起來更易讀(僅個人喜好)。源碼如下:
package com.kiiwow.framework.database.jdbc;import java.sql.ResultSet; import java.sql.SQLException; import java.util.List;
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.JdbcTemplate;
import com.kiiwow.framework.log.LogNameConstants; import com.kiiwow.framework.util.StopWatch;
/* JDBCAccess JDBC執行工具類,僅對原生方法添加了日志記錄,并調整了形參列表的順序,同時使用自定義的RowMapper來替換原生RowMapper @author leon.gan / public class JDBCAccess {
private final Logger logger = LoggerFactory.getLogger(LogNameConstants.SQL_LOGGER); private JdbcTemplate jdbcTemplate;
public <T> List<T> find(String sql, RowMapper<T> rowMapper, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.query(sql, params, rowMapper); } finally { logger.debug("find, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } }
public <T> T findUniqueResult(String sql, RowMapper<T> rowMapper, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.queryForObject(sql, params, rowMapper); } finally { logger.debug("findUniqueResult, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } }
public int findInteger(String sql, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.queryForInt(sql, params); } finally { logger.debug("findInteger, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } }
public String findString(String sql, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.queryForObject(sql, params, new RowMapper<String>() { @Override public String mapRow(ResultSet resultSet, int rowNum) throws SQLException { return resultSet.getString(1); } }); } finally { logger.debug("findString, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } }
public int insert(String sql, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.update(sql, params); } finally { logger.debug("insert, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } } public int update(String sql, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.update(sql, params); } finally { logger.debug("update, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } } public int delete(String sql, Object... params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.update(sql, params); } finally { logger.debug("delete, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } }
public int[] batchExecute(String sql, List<Object[]> params) { StopWatch watch = new StopWatch(); try { return jdbcTemplate.batchUpdate(sql, params); } finally { logger.debug("batchExecute, sql={}, params={}, elapsedTime={}", new Object[]{sql, params, watch.elapsedTime()}); } }
public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; }
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }</pre>
對于第三個優勢而言,我們只需要為上面封裝的JDBCAcess類中的每個方法提供兩種傳參方式即可,使其既支持XML文件中的SQL,也支持業務方法中定義的SQL。對于XML文件中定義的SQL,我們使用上一篇博文中設計的SqlMappingAnalyzer類進行解析獲取具體SQL即可。源碼如下(字數限制,已去掉注釋,但代碼很好懂):
package com.kiiwow.framework.database.jdbc;import java.util.List;
import com.kiiwow.framework.platform.sqlmapping.SqlMappingAnalyzer;
/* 對JDBCAccess的封裝,對每一個操作分別提供兩種形式: 1) 自定義SQL模式,比如在條件查詢時,在SQL映射文件中是無法提供動態查詢SQL的,這個時候就采用自行編寫 2) 指定映射SQL模式,通過指定了SQL別名從SQL映射文件中找到指定的SQL @author leon.gan / public final class JDBCAccessContext {
private JDBCAccess jdbcAccess; public <T> List<T> findWithOutSqlMapping(String sql, RowMapper<T> rowMapper, Object... params) { return jdbcAccess.find(sql, rowMapper, params); } public <T> List<T> find(String alias, RowMapper<T> rowMapper, Object... params) { String sql = SqlMappingAnalyzer.getSpecificSql(alias); return jdbcAccess.find(sql, rowMapper, params); } public <T> T findUniqueResultWithOutSqlMapping(String sql, RowMapper<T> rowMapper, Object... params) { return jdbcAccess.findUniqueResult(sql, rowMapper, params); }
public <T> T findUniqueResult(String alias, RowMapper<T> rowMapper, Object... params) { String sql = SqlMappingAnalyzer.getSpecificSql(alias); return jdbcAccess.findUniqueResult(sql, rowMapper, params); }
public int findIntegerWithOutSqlMapping(String sql, Object... params) { return jdbcAccess.findInteger(sql, params); }
public int findInteger(String alias, Object... params) { String sql = SqlMappingAnalyzer.getSpecificSql(alias); return jdbcAccess.findInteger(sql, params); }
public String findStringWithOutSqlMapping(String sql, Object... params) { return jdbcAccess.findString(sql, params); }
public String findString(String alias, Object... params) { String sql = SqlMappingAnalyzer.getSpecificSql(alias); return jdbcAccess.findString(sql, params); } public int insertWithOutSqlMapping(String sql, Object... params) { return jdbcAccess.insert(sql, params); }
public int insert(String alias, Object... params) { String sql = SqlMappingAnalyzer.getSpecificSql(alias); return jdbcAccess.insert(sql, params); }
public int updateWithOutSqlMapping(String sql, Object... params) { return jdbcAccess.update(sql, params); }
public int update(String alias, Object... params) { String sql = SqlMappingAnalyzer.getSpecificSql(alias); return jdbcAccess.update(sql, params); }
public int deleteWithOutSqlMapping(String sql, Object... params) { return jdbcAccess.delete(sql, params); } public int delete(String alias, Object... params) { String sql = SqlMappingAnalyzer.getSpecificSql(alias); return jdbcAccess.delete(sql, params); } public int[] batchExecuteWithOutSqlMapping(String sql, List<Object[]> params) { return jdbcAccess.batchExecute(sql, params); } public int[] batchExecute(String alias, List<Object[]> params) { String sql = SqlMappingAnalyzer.getSpecificSql(alias); return jdbcAccess.batchExecute(sql, params); }
public void setJdbcAccess(JDBCAccess jdbcAccess) { this.jdbcAccess = jdbcAccess; }
public JDBCAccess getJdbcAccess() { return jdbcAccess; } }</pre>
至此,我們對于JdbcTemplate的封裝就完成了,我們只需要簡單調用最終的JDBCAccessContext類中的方法就可以優雅的進行數據庫操作。這個設計方案中還存在一些缺陷,比如每次取值時的列名過濾會影響性能等,前路漫漫,砥礪前行。
來自:http://my.oschina.net/devleon/blog/529083