前言
上一篇文章分析了Executor执行器,最终的数据库操作交给了StatementHandler来做,本文我们将分析Mybatis中的StatementHandler的作用
在分析之前,我们先回顾一下JDBC中
的Statement
/PreparedStatement
/CallableStatement
接口的知识
如果说Connection
是一座联通应用程序和数据库的桥梁
,那么Statement
/PreparedStatement
/CallableStatement
就可以理解为来往通行的车辆
,只有车辆
才能携带物品(数据)
到数据库
和从数据库中带回数据
给应用程序
JDBC提供了三种车辆
,并各有各的特性和适用场景
Statement:不接受参数,适用于运行
静态 SQL 语句
,提供了三种执行方法(如boolean execute(String SQL)
,int executeUpdate(String SQL)
,ResultSet executeQuery(String SQL)
)且参数都是一个静态的SQL语句(即直接可以在数据库中执行的SQL语句
)PreparedStatement:继承了Statement接口,并在此基础上增加了动态参数的能力,可计划多次使用同一个SQL语句(注意这里的SQL是带有
?
符号,数据库不能直接执行的),接口中提供了很多setXXX()
方法,来动态的设置最终执行前的SQL语句参数,注意
PreparedStatement的执行方法和Statement是不一样的(如:boolean execute()
,int executeUpdate()
,ResultSet executeQuery()
)CallableStatement:访问数据库
存储过程
的时候使用,也接受动态参数
另外,JDBC的Connection
接口中定义了以下重载的方法来分别获取不同的Statement
实例
// 创建一个Statement实例,省略其他方法...
Statement createStatement() throws SQLException;
// 创建一个PreparedStatement实例,注意会有一个String类型的预SQL传入(带有?号的那种),省略其他方法...
PreparedStatement prepareStatement(String sql)
throws SQLException;
// 创建一个CallableStatement实例,注意也会有一个String类型的预SQL传入(带有?号的那种),省略其他方法...
CallableStatement prepareCall(String sql) throws SQLException;
回顾了JDBC
的Statement
之后,我们这里再回过头来看看Mybatis提供的StatementHandler
的作用,顾名思义,StatementHandler就是一个Statement的处理器
,正是由于底层JDBC有三种Statement,且从其被创建
,到参数化
(Statement实例不需要),再到执行
数据库操作等,各有各的不同
,那Mybatis就需要屏蔽掉底层的实现
,抽象出一个Statement的处理器接口,即StatementHandler
这里其实有点抽象工厂
的味道
StatementHandler.java
抽象出底层JDBC中三种不同的Statement的创建/参数化处理/执行SQL,以及获取BoundSql,ParamterHandler等功能
public interface StatementHandler {
// 创建Statement实例
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
// 参数化Statement实例
void parameterize(Statement statement)
throws SQLException;
//
void batch(Statement statement)
throws SQLException;
// 执行Update ,返回更新成功的记录数
int update(Statement statement)
throws SQLException;
// 执行Query,同时处理返回值
List query(Statement statement, ResultHandler resultHandler)
throws SQLException;
Cursor queryCursor(Statement statement)
throws SQLException;
// 获取BoundSql实例
BoundSql getBoundSql();
// 获取参数处理器
ParameterHandler getParameterHandler();
}
既然接口定义好了,那就需要相应的实现,活总得有人干
-
UML类结构如下
由上图可知,三种不同实现的Handler会创建三种不同的Statement实例,BaseStatementHandler中会实现接口中的prepare方法以定义三个实现类中的共同部分,真正的实例化Statement的部分由抽象方法instantiateStatement()完成
BaseStatementHandler.java
public abstract class BaseStatementHandler implements StatementHandler {
// 属性
protected final Configuration configuration;
protected final ObjectFactory objectFactory;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final ResultSetHandler resultSetHandler;
protected final ParameterHandler parameterHandler;
protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final RowBounds rowBounds;
protected BoundSql 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;
// 1. 创建ParameterHandler,并插件化代理对象返回
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// 2. 创建ResultSetHandler,并插件化代理对象返回
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
// 覆写StatementHandler的prepare方法
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 注意这里实际上调用了抽象方法,由子类覆写
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
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);
}
}
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
}
我们这里只举一个PreparedStatementHandler
为例
PreparedStatementHandler.java
public class PreparedStatementHandler extends BaseStatementHandler {
// 构造函数
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
// 真实的实例化Statement方法
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 1. 取出BoundSql中的sql,带有?符号的那种
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
// 2. 调用connection的prepareStatement方法获取PreparedStatement实例返回
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
}
至此,我们搞清楚了Mybatis中的statement的创建过程,那到了这里我们三种不同的StatementHandler由谁来创建呢?又怎么知道SimpleExecutor的doQuery的第二部中我们到底实例化了哪种StatementHandler实现呢?
// SimpleExecutor
doQuery(...){
// ...
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// ...
}
可以看到这里每次调用执行器
的doQuery
方法,都会利用configuration
来实例化一个新的StatementHandler
的实现,至于问什么用configuration来做,是因为StatementHandler是Mybatis中的四大核心接口
之一,是需要提供给开发人员扩展功能
// Configuration类
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 这里new了一个RoutingStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 注意这里返回了一个被开发人员自定义插件代理后的StatementHandler
// 有类似调用的地方,都是Mybatis提供给开发人员,自定义插件用的,这里使用了JDK的动态代理,后续会以单独文章分析
statementHandler = (StatementHandler)
interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
RoutingStatementHandler.java
其实就是内部持有一个StatementHandler的属性,所有实现接口的方法都调用了内部属性的方法而已
public class RoutingStatementHandler implements StatementHandler {
// 内部持有一个真正的StatementHandler实例的代理引用
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根据MappedStatement的type类型创建不同的StatementHandler实例
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());
}
}
// 覆写接口方法...
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
// ...
}
总结
至此,整个StatementHandler分析完毕,statementHandler创建之后得到的实际上是一个经过开发人员自定义插件后返回的代理对象
,之后就开始调用这个代理对象的prepare
方法,parameterize
方法,query
方法/update
方法等完成整个数据库操作