query功能的实现
上一篇文章介绍了update方法的功能实现,那么杂数据库操作中查找操作也是使用率非常高的函数,同样我们也需要了解它的实现过程。使用方法如下:
List<User> list = jdbcTemplate.query("select * from user",new UserRowMapper());
跟踪jdbcTemplate中的query方法:
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}
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 + "]");
}
/**
* Callback to execute the query.
*/
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}
可以看出整体的套路与update差不多的,只不过在回调类PreparedStatementCallback的实现中使用的是ps.executeQuery()执行查询操作,而且在返回方法上也做了一些额外的处理。
rse.extractData(rs)方法负责将结果进行封装并转换至POJO,rse当前代表的类为RowMapperResultSetExtractor,而在构造RowMapperResultSetExtractor的时候我们又将自定义的rowMapper设置了进去,调用的代码如下:
public List<T> extractData(ResultSet rs) throws SQLException {
List<T> results = (this.rowsExpected > 0 ? new ArrayList<>(this.rowsExpected) : new ArrayList<>());
int rowNum = 0;
while (rs.next()) {
results.add(this.rowMapper.mapRow(rs, rowNum++));
}
return results;
}
上面的代码并没有上面复杂的逻辑,只是对返回结果进行遍历并以此使用rowMapper进行转换。
前面讲述的update和query的方法,使用的都是SQL中带有参数的,也就是带有“?”的,那么还有一种不带有“?” 的,Spring使用的是另外一种处理方式。
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return result(query(sql, new RowMapperResultSetExtractor<>(rowMapper)));
}
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 + "]");
}
/**
* Callback to execute the query.
*/
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}
与之前的query方法最大的不同是少了参数以及参数类型的传递,自然也就少了PreparedStatementSetter类型的封装,既然少了PreparedStatementSetter类型的传入,调用的execute方法自然也就会有所变化了。
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("StatementCallback", sql, ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
这个execute与之前的execute并无太大的差别,都是做了一些常规的处理,诸如获取连接、释放连接等,但是,有一个地方是不一样的,就是Statement的创建。这里直接使用connection创建,而带有参数的SQL使用的是PreparedStatementCreator类来创建。一个是普通的Statement,另一个是PreparedStatement,两者究竟有何区别呢?
PreparedStatement接口继承Statement,并与之在两方面有所不同。
❤ PreparedStatement实例包含以编译的SQL语句,这就是使语句“准备好”。包含于PreparedStatement对象中的SQL语句可具有一个或者多个IN参数。IN参数的值在SQL语句创建时未被指定。相反的,该语句为每个IN参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句被执行之前,通过适当的setXXX方法来提供。
❤ 由于PreparedStatement对象已经预编译过,所以其执行速度要快于Statement对象。因此,多次执行的SQL语句经常创建为PreparedStatement对象,以提高效率。
作为Statement的子类,PreparedStatement继承了Statement的所有功能。另外,它还添加了一整套方法,用于设置发送给数据库以取代IN参数占位符的值,同时,三种方法execute、executeQuery、executeUpdate已被更改以使之不再需要参数。这些方法的Statement形式(接收SQL语句参数的形式)不应该用于PreparedStatement对象。
queryForObject
Spring中不仅仅为我们提供了query方法,还在此基础上做了封装,提供了不同类型的query方法。
我们以queryForObject为例,来讨论一下Spring是如何在返回结果的基础上进行封装的。
public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {
return queryForObject(sql, getSingleColumnRowMapper(requiredType));
}
public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
List<T> results = query(sql, rowMapper);
return DataAccessUtils.nullableSingleResult(results);
}
其实最大的不同还是对于RowMapper的使用,getSingleColumnRowMapper类中的mapRow:
public T mapRow(ResultSet rs, int rowNum) throws SQLException {
//验证返回结果数
ResultSetMetaData rsmd = rs.getMetaData();
int nrOfColumns = rsmd.getColumnCount();
if (nrOfColumns != 1) {
throw new IncorrectResultSetColumnCountException(1, nrOfColumns);
}
// 抽取第一个结果进行处理
Object result = getColumnValue(rs, 1, this.requiredType);
if (result != null && this.requiredType != null && !this.requiredType.isInstance(result)) {
// Extracted value does not match already: try to convert it.
try {
return (T) convertValueToRequiredType(result, this.requiredType);
}
catch (IllegalArgumentException ex) {
throw new TypeMismatchDataAccessException(
"Type mismatch affecting row number " + rowNum + " and column type '" +
rsmd.getColumnTypeName(1) + "': " + ex.getMessage());
}
}
return (T) result;
}
convertValueToRequiredType:转换对应的类型。
protected Object convertValueToRequiredType(Object value, Class<?> requiredType) {
if (String.class == requiredType) {
return value.toString();
}
else if (Number.class.isAssignableFrom(requiredType)) {
if (value instanceof Number) {
// 将原始Number类型的实体转换为Number类
return NumberUtils.convertNumberToTargetClass(((Number) value), (Class<Number>) requiredType);
}
else {
//将String转换为Number类
return NumberUtils.parseNumber(value.toString(),(Class<Number>) requiredType);
}
}
else if (this.conversionService != null && this.conversionService.canConvert(value.getClass(), requiredType)) {
return this.conversionService.convert(value, requiredType);
}
else {
throw new IllegalArgumentException(
"Value [" + value + "] is of type [" + value.getClass().getName() +
"] and cannot be converted to required type [" + requiredType.getName() + "]");
}
}