- 前言
- 第一部分 核心实现
- 第 1 章 Spring 整体架构和环境搭建
- 第 2 章 容器的基本实现
- 第 3 章 默认标签的解析
- 第 4 章 自定义标签的解析
- 第 5 章 bean 的加载
- 第 6 章 容器的功能扩展
- 第 7 章 AOP
- 第二部分 企业应用
- 第 8 章 数据库连接 JDBC
- 第 9 章 整合 MyBatis
- 第 10 章 事务
- 第 11 章 SpringMVC
- 第 12 章 远程服务
- 第 13 章 Spring 消息
8.3 query 功能的实现
在之前的章节中我们介绍了 update 方法的功能实现,那么在数据库操作中查找操作也是使用率非常高的函数,同样我们也需要了解它的实现过程。使用方法如下:
List<User> list = jdbcTemplate.query("select * from user where age=?",new
Object[]{20},new int[]{java.sql.Types.INTEGER} ,new UserRowMapper());
跟踪 jdbcTemplate 中的 query 方法。
public <T> List<T> query(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper)
throws DataAccessException {
return query(sql, args, argTypes, new RowMapperResultSetExtractor<T>
(rowMapper));
}
public <T> T query(String sql, Object[] args, int[] argTypes, ResultSetExtractor<T> rse)
throws DataAccessException {
return query(sql, newArgTypePreparedStatementSetter(args, argTypes), rse);
}
上面函数中与 update 方法中都同样使用了 newArgTypePreparedStatementSetter。
public <T> T query(String sql, PreparedStatementSetter pss, ResultSetExtractor<T> rse)
throws DataAccessException {
return query(new SimplePreparedStatementCreator(sql), pss, rse);
}
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>() {
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();
}
}
}
});
}
可以看到整体套路与 update 差不多的,只不过在回调类 PreparedStatementCallback 的实现中使用的是 ps.executeQuery() 执行查询操作,而且在返回方法上也做了一些额外的处理。
rse.extractData(rsToUse) 方法负责将结果进行封装并转换至 POJO,rse 当前代表的类为 RowMapperResultSetExtractor,而在构造 RowMapperResultSetExtractor 的时候我们又将自定义的 rowMapper 设置了进去。调用代码如下:
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 进行转换。
之前讲了 update 方法以及 query 方法,使用这两个函数示例的 SQL 都是带有参数的,也就是带有“?”的,那么还有另一种情况是不带有“?”的,Spring 中使用的是另一种处理方式。例如:
List<User> list = jdbcTemplate.query("select * from user", new UserRowMapper());
跟踪进入:
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
return query(sql, new RowMapperResultSetExtractor<T>(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 + "]");
}
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
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);
}
}
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}
与之前的 query 方法最大的不同是少了参数及参数类型的传递,自然也少了 Prepared StatementSetter 类型的封装。既然少了 PreparedStatementSetter 类型的传入,调用的 execute 方法自然也会有所改变了。
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessary ForNative
Statements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
T result = action.doInStatement(stmtToUse);
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.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
这个 exexute 与之前的 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 对象。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论