MyBatis原理系列(一)-手把手带你阅读MyBatis源码
MyBatis原理系列(二)-手把手带你了解MyBatis的启动流程
MyBatis原理系列(三)-手把手带你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的关系
MyBatis原理系列(四)-手把手带你了解MyBatis的Executor执行器
MyBatis原理系列(五)-手把手带你了解Statement、StatementHandler、MappedStatement间的关系
MyBatis原理系列(六)-手把手带你了解BoundSql的创建过程
MyBatis原理系列(七)-手把手带你了解如何自定义插件
MyBatis原理系列(八)-手把手带你了解一级缓存和二级缓存
MyBatis原理系列(九)-手把手带你了解MyBatis事务管理机制
在上篇文章中我们介绍了Executor的三种执行器,并且在执行update和query方法的时候,都会有一个Statement和StatementHandler的操作。当时我们一笔带过了,在这篇文章中我们将介绍Statement,StatementHandler的关系,以及它们的实现细节。
1. Statement 对象
我们先来了解下Statement对象,在原始JDBC操作中,会有加载驱动,设置属性,获取连接,创建Statement对象...等一系列操作。Statement对象在JDBC操作中就是向数据库发送sql语句,并获取到执行结果。Statement对象有三种,分别是Statement,PreparedStatement,CallableStatement。它们的继承关系如下
- Statement:可以发送字符串类型的sql,不支持传递参数。适用于静态sql语句。
- PreparedStatement: 预编译的sql语句,接受参数传入,并且可以防止sql注入,提高安全性。Sql语句会编译在数据库系统,适用于多次执行相同的sql,因此性能高于Statement。
- CallableStatement:在执行存储过程时使用,并且可以接受参数传入。
Statement 接口方法有这么多,主要就是执行更新,查询,获取结果等操作。
2. StatementHandler 对象
2.1 StatementHandler 对象初识
StatementHandler 对象从字面意义上来讲就是管理Statement对象的了。它有两个直接实现,一个是BaseStatementHandler,另一个是RoutingStatementHandler。然后BaseStatementHandler有三个实现分别是SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler,他们分别管理的就是上面讲到的Statement,PreparedStatement和CallableStatement对象了。继承关系如下图,是不是很像Executor的继承关系,BaseStatementHandler是使用了适配器模式,减少了实现接口的复杂性,RoutingStatementHandler则是包装了以上三种Handler,作为一个代理类进行操作。
StatementHandler 接口的方法如下
/**
* @author Clinton Begin
*/
public interface StatementHandler {
// 创建Statement对象
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
// 对Sql中的占位符进行赋值
void parameterize(Statement statement)
throws SQLException;
// 添加到批处理操作中
void batch(Statement statement)
throws SQLException;
// 执行更新操作
int update(Statement statement)
throws SQLException;
// 执行查询操作并且返回结果
List query(Statement statement, ResultHandler resultHandler)
throws SQLException;
Cursor queryCursor(Statement statement)
throws SQLException;
// 获取BoundSql对象
BoundSql getBoundSql();
// 获取参数处理器
ParameterHandler getParameterHandler();
}
2.2 StatementHandler 对象创建
StatementHandler 是在哪里创建的呢?在手把手带你了解MyBatis的Executor执行器中,在执行doQuery和doUpdate方法时,都会创建StatementHandler对象。
以SimpleExecutor为例,创建StatementHandler对象实际由Configuration对象创建的。
// SimpleExecutor的doUpdate方法
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 1. 创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 2. 创建Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 3. 执行sql操作
return handler.update(stmt);
} finally {
// 2. 关闭Statement
closeStatement(stmt);
}
}
Configuration的newStatementHandler方法中,创建的是RoutingStatementHandler对象。我们知道RoutingStatementHandler实际是对三种StatementHandler的一种包装。
// Configuration的newStatementHandler方法
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 加载插件
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
继续点击去看,根据MappedStatement对象的类型,创建出具体的StatementHandler对象。如果MappedStatement没有指出具体的StatementType(),那么StatementType默认是PREPARED类型的。
// 实际的处理器,SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler三种处理器中的一种
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql 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创建的过程,实际调用的是BaseStatementHandler的构造方法。
// PreparedStatementHandler的构造方法
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 实际调用的是BaseStatementHandler的构造方法
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
至此,我们了解到了,其实三种StatementHandler都是用的BaseStatementHandler的构造方法创建的。
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;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
2.3 Statement对象创建
StatementHandler对象创建出来了,就可以创建Statement对象了。也是以SimpleExecutor执行器为例子。
// 1. 创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 2. 创建Statement
stmt = prepareStatement(handler, ms.getStatementLog());
...
SimpleExecutor 的 prepareStatement方法 中主要做了以下三步:
- 获取数据库连接
- 创建Statement对象
- 设置sql参数
// SimpleExecutor 的 prepareStatement方法
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 1. 获取数据库连接
Connection connection = getConnection(statementLog);
// 2. 创建Statement对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 3. 设置sql参数
handler.parameterize(stmt);
return stmt;
}
我们继续看看prepare()方法做了什么,这个方法BaseStatementHandler给出了默认实现,因此三个StatementHandler用的都是这个实现。主要做了以下工作
- 初始化Statement对象
- 设置超时时间
- 设置查询大小
- 出现异常关闭Statement对象
// BaseStatementHandler的prepare方法
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 1. 初始化Statement对象
statement = instantiateStatement(connection);
// 2. 设置超时时间
setStatementTimeout(statement, transactionTimeout);
// 3. 设置查询大小
setFetchSize(statement);
return statement;
} catch (SQLException e) {
// 4. 关闭Statement对象
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
可以Statement对象的初始化操作是在instantiateStatement方法中进行的,我们继续看看instantiateStatement这个方法又做了什么操作。好的,在BaseStatementHandler中instantiateStatement方法被设计为抽象方法,由子类实现,这点也体现出了模板方法的设计模式。
// BaseStatementHandler的instantiateStatement方法
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
现在以SimpleStatementHandler为例子,最终调用的还是connection.createStatement()方法,回到了最初的起点,也就是MyBatis对JDBC操作进行了包装。
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 实际还是调用的connection.createStatement()方法
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.createStatement();
} else {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
获取到了Statement 对象,就可以快乐执行execute方法,向数据库发送sql语句执行了。
3. MappedStatement 对象
有眼尖的同学在前面会看到Executor在执行doUpdate的时候,会传入MappedStatement对象,那么MappedStatement和Statement,StatementHandler对象间有什么关联呢。
我们在用MyBatis配置sql的时候,insert/update/delete/select等标签下面都会包含一段sql,MappedStatement就是对sql标签的信息描述。
// 一个select标签会对应一个MappedStatement对象
MappedStatement 类的私有属性如下
// mapper配置文件名,如:userMapper.xml
private String resource;
// 配置类
private Configuration configuration;
// 命名空间+标签id 如com.example.demo.dao.TTestUserMapper.selectByPrimaryKey
private String id;
private Integer fetchSize;
// 超时时间
private Integer timeout;
// sql对象类型 STATEMENT, PREPARED, CALLABLE 三种之一
private StatementType statementType;
// 结果集类型
private ResultSetType resultSetType;
// sql语句
private SqlSource sqlSource;
// 缓存
private Cache cache;
// 参数映射关系
private ParameterMap parameterMap;
// 结果映射关系,可以自定义多个ResultMap
private List resultMaps;
private boolean flushCacheRequired;
// 是否启用缓存
private boolean useCache;
// 结果是否排序
private boolean resultOrdered;
// sql语句类型,INSERT, UPDATE, DELETE, SELECT
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
其中最主要的方法还是getBoundSql方法,对动态sql进行解析,获取最终的sql语句。关于SqlSource,BoundSql相关的内容我们将在其它文章中介绍。
public BoundSql getBoundSql(Object parameterObject) {
// 获取BoundSql对象,BoundSql对象是对动态sql的解析
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 获取参数映射
List parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
4. 总结
这篇文章带大家详细了解Statement对象,StatementHandler以及其三种实现,还有MappedStatement也做了简单介绍,Statement对象作为主要组件,了解其创建和原理对我们整体了解MyBatis会很有帮助。