应用
JdbcTemplate jdbcTemplate=new JdbcTemplate(dataSource);
String sql="select * from user1 where username=?";
User user=jdbcTemplate.queryForObject(sql, new RowMapper<User>(){
@Override
public User mapRow(ResultSet res, int arg1) throws SQLException {
// TODO Auto-generated method stub
String username=res.getString("username");
String pass=res.getString("pass");
User user=new User();
user.setUsername(username);
user.setPass(pass);
System.out.println(res);
return user;
}
},"zengyuan");
在使用jdbcTemplate一直有个疑问,这个mapRow方法中的ResultSet对象是如何传进去的,关于这个问题,开始有以下几点猜想:
① 因为是spring,所以本身就首先想到了AOP,可能是将一个返回ResultSet的对象的通知直接织入到mapRow的方法之中。
② 后台调用mapRow方法,然后通过创建对象的形式将ResultSet放入该方法中。
源码分析
第一步:查看queryForObject()方法
源码:
@Override
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args) throws DataAccessException {
List<T> results = query(sql, args, new RowMapperResultSetExtractor<T>(rowMapper, 1));
return DataAccessUtils.requiredSingleResult(results);
}
可以看到该方法调用了query()方法,并创建了一个RowMapperResultSetExtractor()对象,将我创建的RowMapper对象传入到该类中,我们进入该类中,这个类很重要,我们来分析下源码:
public class RowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T>> {
private final RowMapper<T> rowMapper;
private final int rowsExpected;
/**
* Create a new RowMapperResultSetExtractor.
* @param rowMapper the RowMapper which creates an object for each row
*/
public RowMapperResultSetExtractor(RowMapper<T> rowMapper) {
this(rowMapper, 0);
}
/**
* Create a new RowMapperResultSetExtractor.
* @param rowMapper the RowMapper which creates an object for each row
* @param rowsExpected the number of expected rows
* (just used for optimized collection handling)
*/
public RowMapperResultSetExtractor(RowMapper<T> rowMapper, int rowsExpected) {
Assert.notNull(rowMapper, "RowMapper is required");
this.rowMapper = rowMapper;
this.rowsExpected = rowsExpected;
}
@Override
public List<T> extractData(ResultSet rs) throws SQLException {
List<T> results = (this.rowsExpected > 0 ? new ArrayList<T>(this.rowsExpected) : new ArrayList<T>());
int rowNum = 0;
while (rs.next()) {
results.add(this.rowMapper.mapRow(rs, rowNum++));
}
return results;
}
可以看到该类结构如下:
① RowMapper rowMapper;
该变量是通过构造器将我们创建的RowMapper对象赋值给该类变量。
② int rowsExpected;
该int型变量也是通过构造器赋值的,初始值设为1.该变量表示取出来的记录数量。
③ extractData()方法
该方法很重要,我们看下该方法首先创建了一个大小为rowsExpected的集合容器,然后调用RowMapper的mapRow()方法,将我们封装的对象装入集合中。
到这一步我已经猜到,现在只需调用RowMapperResultSetExtractor的extractData()方法,然后返回集合对象就可以了,果不其然,我们继续往下看代码。
第二步:调用query()方法
其实jdbcTemplate类中用到了很多的方法重载,所以有时候你会看晕,我们顺着第一步的query()点进去,可以看到以下代码:
@Override
public <T> T query(String sql, Object[] args, ResultSetExtractor<T> rse) throws DataAccessException {
return query(sql, new ArgPreparedStatementSetter(args), rse);
我们可以看到在query()方法中继续调用了重载的query()方法,我想这一步传进去的new ArgPreparedStatementSetter(args)对象,我就不分析了,大家应该能够想到这个类中肯定是向PreparedStatement中注入参数,接着继续下一步:
@Override
public <T> T query(String sql, PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {
return query(new SimplePreparedStatementCreator(sql), pss, rse);
}
我只能说大师的代码真的包装了好多层,不过每一层都是有用的,估计到这大家有些着急了,不过也终于见到庐山真面目了,我们来分析下这个query()方法,源码如下:
public <T> T query(
PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
throws DataAccessException {
Assert.notNull(rse, "ResultSetExtractor must not be null");
logger.debug("Executing prepared SQL query");
return execute(psc, new PreparedStatementCallback<T>() {
@Override
public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
ResultSet rs = null;
try {
if (pss != null) {
pss.setValues(ps);
}
rs = ps.executeQuery();
ResultSet rsToUse = rs;
if (nativeJdbcExtractor != null) {
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
}
return rse.extractData(rsToUse);
}
finally {
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
});
}
大家可以看到在该方法中返回了一个execute方法,我们先进入该方法中看下:
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
if (logger.isDebugEnabled()) {
String sql = getSql(psc);
logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
}
Connection con = DataSourceUtils.getConnection(getDataSource());
PreparedStatement ps = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
ps = psc.createPreparedStatement(conToUse);
applyStatementSettings(ps);
PreparedStatement psToUse = ps;
if (this.nativeJdbcExtractor != null) {
psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);
}
T result = action.doInPreparedStatement(psToUse);
handleWarnings(ps);
return result;
}
可以大致看到该方法调用了PreparedStatementCallback类中的doInPreparedStatement方法,在这个方法中我们传入了PreparedStatement ,并且调用了executeQuery方法执行sql语句返回ResultSet对象,然后调用我们前面说的RowMapperResultSetExtractor类中的extractData方法,返回最后封装的集合对象。
到这里我们就得到了我们最后想要的结果。
总结
讲了这么多,确实有些乱,不过大家仔细的看下程序执行的过程基本都能看懂,我总结下JdbcTemplate,大致如下:
① 该模板类的底层还是对jdbc的代码进行了封装,不过返回对象的时候需要我们自己进行封装,在这点上我觉得手动封装这块完全可以交给spring的ioc属性注入就可以实现。
② 该类的底层用到了一个关键类RowMapperResultSetExtractor,该类作用是创建list集合,并将查询到的对象加入该集合中返回;还有一个关键方法exec()方法,该方法执行jdbc代码返回ResultSet结果集。
③ 我们可以看到query()方法,调用了几次重载,第一次作用是创建RowMapper对象,第二次调用是PerparedStatement的sql参数注入,而最后一次调用是返回了结果集集合对象。这点开始我是有疑问的,为啥不一次性的调用query()方法直接执行以上三步呢?最后想了想,如果用一个方法实现,那么程序的复用性就很差,你不同的需求肯定会造成大量的代码重复,所以只能这样不断的拆分小功能,然后利用组合的方式实现我们所需要的功能,这种设计思想很重要,值得学习。