我们接着看四大组件的StatementHandler。
首先还是来看代码
接上一篇末尾
SimpleExecutor的doQuery
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
//在这里初始化了除了Excutor的其他三大组件,注意了,这行代码完成的工作很重要,而Executor对象则在初始化得到DefaultSqlSession对象的时候就已经注入到其中了。
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
从上述代码我们已经知道初始化StatementHandler的时机了,那么下面我们就来看看StatementHandler的构造以及具体实现吧。
StatementHandler介绍
先看看接口的组成
public interface StatementHandler {
//prepare方法是用来编译SQL的
Statement prepare(Connection connection)
throws SQLException;
//设置参数化Sql ps.setParameter()
void parameterize(Statement statement)
throws SQLException;
void batch(Statement statement)
throws SQLException;
int update(Statement statement)
throws SQLException;
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
这里有几个重要的方法,prepare,parameterize和query,update,他们的作用是不一样的。
在MyBatis实现了statementHandler的有四个类:
RoutingStatementHandler,这是一个封装类,它不提供具体的实现,只是根据Executor的类型,创建不同的类型StatementHandler。
SimpleStatementHandler,这个类对应于JDBC的Statement对象,用于没有预编译参数的SQL的运行。
PreparedStatementHandler 这个用于预编译参数SQL的运行。
CallableStatementHandler 它将实存储过程的调度。
在MyBatis中,Configuration对象会采用new RoutingStatementHandler()来生成StatementHandler对象,换句话说我们真正使用的是RoutingStatementHandler对象,然后它会根据Executor的类型去创建对应具体的statementHandler对象(SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler)。
然后利用具体statementHandler的方法完成所需要的功能。那么这个具体的statementHandler是保存在RoutingStatementHandler对象的delegate属性的,所以当我们拦截statementHandler的时候就要常常访问它了。
StatementHandler的初始化
很明显从上述代码中可以看出,StatementHandler是通过
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
初始化的。
那我们就来看看这行代码的具体实现
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//生成一个StatementHandler实例,StatementHandler有多个实现类,具体调用哪个实现类,通过mappedStatement的相关信息决定,我们后面会具体分析。RoutingStatementHandler只是一个路由信息类。具体的实现类还需要看情况。
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//这行代码就不多讲了,之前讲Mybatis插件的时候已经讲过了
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
RoutingStatementHandler的构造方法
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//我们实际使用的StatementHandler实例对象有三种,分别对应三种原生JDBC的Statement
//分别是Statement (通过查询) PreparedStatement (预编译的Statement 执行参数化查询 数据库系统会对sql语句进行预编译处理(如果JDBC驱动支持的话),预处理语句将被预先编译好,这条预编译的sql查询语句能在将来的查询中重用)和CallableStatement (存储过程)
//关于PreparedStatement Statement的区别后面会有文章来专门介绍。
//我们这里根据StatementType,调用的是 delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
我们接着看PreparedStatementHandler构造方法的具体实现
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//这里调用了PreparedStatementHandler的父类BaseStatementHandler的构造方法,上述的三个类都继承了这个类,这个类的构造方法初始化了很多东西,是非常重要的,这里的设计,很明显的使用了模板方法模式。
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//依赖倒转
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
//初始化parameterHandler和resultSetHandler,就是在这里初始化了剩下的两大组件,而这两大组件都是statementHandler的属性
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
SimpleExecutor的prepareStatement方法
得到handler对象后,方法继续往下执行, stmt = prepareStatement(handler, ms.getStatementLog());
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//得到JDBC Connection
Connection connection = getConnection(statementLog);
//这里就要到了上面说到的StatementHandler的一个重要方法perpare方法,用来预编译sql
stmt = handler.prepare(connection);
handler.parameterize(stmt);
return stmt;
}
public Statement prepare(Connection connection) throws SQLException {
return delegate.prepare(connection);
}
BaseStatementHandler的prepare
public Statement prepare(Connection connection) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
instantiateStatement抽象方法
很明显又是模板方法模式
PreparedStatementHandler 的instantiateStatement
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
//如果需要生成相应的键值,这次Debug并没有要求键值,
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
}
//如果在Mapper配置文件中设置了resultSetType,
else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
//这种就是我们在使用原生JDBC时最常用的方法了
return connection.prepareStatement(sql);
}
}
PreparedStatement prepareStatement(String sql,
int resultSetType,
int resultSetConcurrency)
throws SQLException
其中两个参数的意义是:
resultSetType 是设置 ResultSet 对象的类型可滚动,或者是不可滚动。取值如下: ResultSet.TYPE_FORWARD_ONLY 只能向前滚动
ResultSet.TYPE_SCROLL_INSENSITIVE 和 Result.TYPE_SCROLL_SENSITIVE 这两个 方法都能够实现任意的前后滚动,使用各种移动的 ResultSet 指针的方法。二者的区别在于前者对于修改不敏感,而后者对于修改敏感。 resultSetConcurency 是设置 ResultSet 对象能够修改的,取值如下:
ResultSet.CONCUR_READ_ONLY 设置为只读类型的参数。
ResultSet.CONCUR_UPDATABLE 设置为可修改类型的参数。
所以prepare方法就是完成SQL的预编译,只是把原生JDBC预编译的过程封装了一下,比较容易理解。
handler.parameterize(stmt);
上面我们在prepare方法里面预编译了SQL。那么我们这个时候希望设置参数。在Statement中我们是使用parameterize方法进行设置参数的。
RoutingStatementHandler
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
关于具体的实现要涉及到Mybatis的另一个重要组件,接下来会有专门的博客来介绍。
handler.\query(stmt, resultHandler);
当Statement经过stmt = prepareStatement(handler, ms.getStatementLog());
处理后(使用了prepare预编译,使用了parameterize设置参数),
将其传给query(stmt, resultHandler);得到我们想要的返回内容。
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//执行查询操作
ps.execute();
//通过ResultSetHandler来处理结果集
return resultSetHandler.<E> handleResultSets(ps);
}
这个的具体实现以后会细说,到这里,一次完整的查询就结束了。
通过了四大组件的配合,才完成了这一次最简单的查询。
在得到DefaultSqlSession对象时初始化了Executor执行器。
然后在调用Executor的doQuery方法查询的时候,会初始化StatementHandler对象,
然后在初始化StatementHandler的过程中,会初始化ParameterHandler和ResultSetHandler对象,
生成的StatementHandler对象会保存这两个对象的引用。
直到这时,Executor的职责就差不多完成了,通过其他的三大组件来完成接下来的数据库查询工作并返回结果。
总结
StatementHandler是MyBatis四大对象里面最重要的对象,它的方法是十分重要的,也是我们插件的基础。
当我们需要改变sql的时候,显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。
当我们需要修改参数的时候我们可以在调用parameterize方法前修改逻辑。或者使用ParameterHandler来改造设置参数。
我们需要控制组装结果集的时候,也可以在query方法前后加入逻辑,或者使用ResultHandler来改造组装结果。
懂的这些方法,才能理解我需要拦截什么对象,如何处理插件,这是MyBatis的核心内容。