在映射器Mapper的动态代理中,了解到mybatis是如何通过Mapper接口的全限定名和方法名来运行SQL的,接下来我们要看看SqlSession底层是怎么运行SQL的。
实际上,SqlSession的执行过程是通过Executor、StatementHandler、ParameterHandler和ResultSetHandler来完成数据库操作和结果返回的,这就是SqlSession的四大对象。
Executor是一个执行器,SqlSession是一个门面,真正干活的是执行器,它是一个真正执行Java与数据库交互的对象。
mybatis中有3种执行器,可以在mybaits配置文件中的defaultExecutorType属性进行选择,对于spring boot项目则在application配置文件中配置mybatis.configuration.default-executor-type属性。
在Configuration类中有如下方法,该方法根据配置的执行器类型去确定创建哪一种Executor。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
//缓存用CachingExecutor进行包装Executor
executor = new CachingExecutor(executor);
}
//在运用插件时,拦截Executor
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
上述代码中的interceptorChain.pluginAll(executor);
是运行插件的关键,它拦截Executor,并构建一层层的动态代理对象,可以修改在调度真实的Executor方法之前执行配置插件的代码,这个就是插件的原理。
在映射器Mapper的动态代理中,我们知道最后就是通过SqlSession对象去运行对象的SQL。通过跟踪代码,在Mapper的代理对象(即MapperProxy的实例)中的invoke()
方法中,最后是执行了mapperMethod.execute(sqlSession, args);
,而在execute()
方法中,是采用了命令模式跳转到需要的方法中,在上文中,我们看了executeForMany()
方法,其中有下面这段代码:
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
可以看到,最终就是通过SqlSession对象去运行对象的SQL,在单线程环境下,SqlSession的实现类是DefaultSqlSession,它的selectList()
方法如下:
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这里用到执行器Executor的query()
方法,定位到Executor的实现类BaseExecutor,代码如下:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
跟踪list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
,看到如下代码:
private <E> List<E> queryFromDatabase(MappedStatement ms,
Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key,
BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//由Executor的三种类型实现
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
上面代码中的doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
是由Executor的三种类型来实现的,默认是SIMPLE,即SimpleExecutor,如下:
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//从该映射器节点MappedStatement中获取配置信息Configuration
Configuration configuration = ms.getConfiguration();
//根据Configuration构建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//对SQL编译和参数进行初始化
stmt = prepareStatement(handler, ms.getStatementLog());
//使用StatementHandler的query()方法,把ResultHandler传递进去
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
//调用StatementHandler的prepare()进行了预编译和基础的设置
stmt = handler.prepare(connection, transaction.getTimeout());
//通过StatementHandler的parameterize()来设置参数
handler.parameterize(stmt);
return stmt;
}
根据上面代码,可以看到最终的操作落到StatementHandler上。
数据库会话器就是专门处理数据库会话的,在Configuration中,mybatis通过如下方式生成StatementHandler:
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;
}
//对象适配器,它的作用是给3个接口对象的使用提供一个统一且简易的适配器
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());
}
}
很显然,mybatis是通过RoutingStatementHandler的对象来创建真实对象的,RoutingStatementHandler不是真实的服务对象,它是通过**适配器模式**来找到对应的StatementHandler来执行的。
在mybaits中,RoutingStatementHandler分为3种:
SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler,他们分别对应的是JDBC的Statement、PreparedStatement(预编译处理)和CallableStatement(存储过程处理)。
我们在Executor部分说到,Executor在执行查询时(如doQuery()
方法)会执行StatementHandler的prepare、parameterize和query方法。其中PreparedStatementHandler的prepare方法如下:
public abstract class BaseStatementHandler implements StatementHandler {
//......
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//对SQL进行预编译,做一些基础配置,由不同的StatementHandler的实现
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);
}
}
//...
}
接着,通过parameterize()
方法设置参数,如下:
@Override
public void parameterize(Statement statement) throws SQLException {
//显然使用了ParameterHandler
parameterHandler.setParameters((PreparedStatement) statement);
}
最后执行query()
查询方法——执行SQL返回结果,如下:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//java.sql.PreparedStatement 与数据库交互
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//包装结果集ResultSet
return resultSetHandler.handleResultSets(ps);
}
Executor先调用StatementHandler的prepare()方法预编译SQL,同时设置一些基本运行的参数。
然后用parameterize()方法启用ParameterHandler设置参数,完成预编译,执行查询,update()也是这样的。
如果是查询,mybatis会使用ResultSetHandler封装结果返回给调用者。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PCYTHBIz-1586362133085)(http://ver-oss.oss-cn-beijing.aliyuncs.com/blog/image/20191226/20191226_03285195.png “SqlSession内部运行”)]