在上一篇博文《基于SpringJDBC的类mybatis形式SQL语句管理的思考与实现》中,我们实现了采用XML文件对SQL进行管理。在本篇博文中,我们将对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; }
对于第二个优势而言,我们需要对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; } }
对于第三个优势而言,我们只需要为上面封装的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; } }
至此,我们对于JdbcTemplate的封装就完成了,我们只需要简单调用最终的JDBCAccessContext类中的方法就可以优雅的进行数据库操作。这个设计方案中还存在一些缺陷,比如每次取值时的列名过滤会影响性能等,前路漫漫,砥砺前行。
感谢您的阅览,劳烦点个赞。