前言
在Mybatis源码-SqlSession获取文章中曾经晓得,Mybatis
中获取SqlSession
时会创立执行器Executor
并存放在SqlSession
中,通过SqlSession
能够获取映射接口的动静代理对象,动静代理对象的生成能够参考Mybatis源码-加载映射文件与动静代理,能够用下图进行概括。
所以,映射接口的动静代理对象理论执行办法时,执行的申请最终会由MapperMethod
的execute()
办法实现。本篇文章将以MapperMethod
的execute()
办法作为终点,对Mybatis
中的一次理论执行申请进行阐明,并联合源码对执行器Executor
的原理进行阐释。本篇文章不会对Mybatis
中的缓存进行阐明,对于Mybatis
中的一级缓存和二级缓存相干内容,会在后续的文章中独自进行剖析,为了屏蔽Mybatis
中的二级缓存的烦扰,须要在Mybatis
的配置文件中增加如下配置以禁用二级缓存。
<settings> <setting name="cacheEnabled" value="false"/></settings>
注释
本节将以一个理论的查问例子,以单步跟踪并联合源码的办法,对Mybatis
的一次理论执行申请进行阐明。给定映射接口如下所示。
public interface BookMapper { Book selectBookById(int id);}
给定映射文件如下所示。
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.mybatis.learn.dao.BookMapper"> <resultMap id="bookResultMap" type="com.mybatis.learn.entity.Book"> <result column="b_name" property="bookName"/> <result column="b_price" property="bookPrice"/> </resultMap> <select id="selectBookById" resultMap="bookResultMap"> SELECT b.id, b.b_name, b.b_price FROM book b WHERE b.id=#{id} </select></mapper>
Mybatis
的执行代码如下所示。
public class MybatisTest { public static void main(String[] args) throws Exception { String resource = "mybatis-config.xml"; SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder() .build(Resources.getResourceAsStream(resource)); //获取SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //获取映射接口的动静代理对象 BookMapper bookMapper = sqlSession.getMapper(BookMapper.class); //执行一次查问操作 System.out.println(bookMapper.selectBookById(1)); }}
基于上述的映射接口,映射文件和执行代码,最终执行查问操作时,会调用到MapperMethod
的execute()
办法并进入查问的逻辑分支,这部分源码如下所示。
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { ...... case SELECT: //依据理论执行的办法的返回值的状况进入不同的逻辑分支 if (method.returnsVoid() && method.hasResultHandler()) { //无返回值状况 executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { //返回值为汇合的状况 result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { //返回值为map的状况 result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { //返回值为迭代器的状况 result = executeForCursor(sqlSession, args); } else { //上述情况之外的状况 //将办法的入参转换为Sql语句的参数 Object param = method.convertArgsToSqlCommandParam(args); //调用DefaultSqlSession的selectOne()办法执行查问操作 result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; ...... } ...... return result;}
已知映射接口中的每个办法都会对应一个MapperMethod
,MapperMethod
中的SqlCommand
会批示该办法对应的MappedStatement
信息和类型信息(SELECT,UPDATE等),MapperMethod
中的MethodSignature
会存储该办法的参数信息和返回值信息,所以在上述的MapperMethod
的execute()
办法中,首先依据SqlCommand
的批示的类型进入不同的逻辑分支,本示例中会进入SELECT的逻辑分支,而后又会依据MethodSignature
中批示的办法返回值状况进入不同的查问分支,本示例中的办法返回值既不是汇合,map或迭代器,也不是空,所以会进入查问一条数据的查问分支。本示例中单步跟踪到这里时,数据如下所示。
在MapperMethod
中的execute()
办法中会调用到DefaultSqlSession
的selectOne()
办法执行查问操作,该办法实现如下所示。
@Overridepublic <T> T selectOne(String statement) { return this.selectOne(statement, null);}@Overridepublic <T> T selectOne(String statement, Object parameter) { //查问操作会由selectList()实现 List<T> list = this.selectList(statement, parameter); if (list.size() == 1) { //查问后果只有一个时,返回查问后果 return list.get(0); } else if (list.size() > 1) { //查问后果大于一个时,报错 throw new TooManyResultsException( "Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; }}
DefaultSqlSession
的selectOne()
办法中会将查问申请交由DefaultSqlSession
的selectList()
办法实现,如果selectList()
办法返回的后果汇合中只有一个返回值,就将这个返回值返回,如果多于一个返回值,就报错。DefaultSqlSession
的selectList()
办法如下所示。
@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { //从Configuration中的mappedStatements缓存中获取MappedStatement MappedStatement ms = configuration.getMappedStatement(statement); //调用Executor的query()办法执行查问操作 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(); }}
在DefaultSqlSession
的selectList()
办法中,会先依据statement参数值在Configuration
中的mappedStatements缓存中获取MappedStatement
,statement参数值其实就是MapperMethod
中的SqlCommand
的name
字段,是MappedStatement
在mappedStatements缓存中的惟一标识。获取到MappedStatement
后,就会调用Executor
的query()
办法执行查问操作,因为禁用了二级缓存,所以这里的Executor
实际上为SimpleExecutor
。本示例中单步跟踪到这里时,数据如下所示。
SimpleExecutor
的类图如下所示。
SimpleExecutor
和BaseExecutor
之间应用了模板设计模式,调用SimpleExecutor
的query()
办法时会调用到BaseExecutor
的query()
办法,如下所示。
@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //获取Sql语句 BoundSql boundSql = ms.getBoundSql(parameter); //生成CacheKey CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //调用重载的query()办法 return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}
持续看BaseExecutor
中的重载的query()
办法,如下所示。
@Overridepublic <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++; //先从一级缓存中依据CacheKey命中查问后果 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 (BaseExecutor.DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { clearLocalCache(); } } return list;}
上述的query()
办法大部分逻辑是在为Mybatis
中的一级缓存服务,这里临时不剖析,除开缓存的逻辑,上述query()
办法做的事件能够概括为:先从缓存中获取查问后果,获取到则返回缓存中的查问后果,否则间接查询数据库。上面剖析间接查询数据库的逻辑,queryFromDatabase()
办法的实现如下所示。
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 { //调用doQuery()进行查问操作 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;}
上述queryFromDatabase()
办法中,会调用BaseExecutor
定义的形象办法doQuery()
进行查问,本示例中,doQuery()
办法由SimpleExecutor
进行了实现,如下所示。
@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); //创立RoutingStatementHandler StatementHandler handler = configuration.newStatementHandler( wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //实例化Statement stmt = prepareStatement(handler, ms.getStatementLog()); //执行查问 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); }}
上述的doQuery()
办法中,做了三件事件,第一件事件
是创立RoutingStatementHandler
,实际上RoutingStatementHandler
正如其名字所示,仅仅只是做一个路由转发的作用,在创立RoutingStatementHandler
时,会依据映射文件中CURD标签上的statementType属性决定创立什么类型的StatementHandler
并赋值给RoutingStatementHandler
中的delegate字段,后续对RoutingStatementHandler
的所有操作均会被其转发给delegate,此外在初始化SimpleStatementHandler
,PreparedStatementHandler
和CallableStatementHandler
时还会一并初始化ParameterHandler
和ResultSetHandler
。映射文件中CURD标签上的statementType属性与StatementHandler
的对应关系如下。
statementType属性 | 对应的StatementHandler | 作用 |
---|---|---|
STATEMENT | SimpleStatementHandler | 间接操作SQL ,不进行预编译 |
PREPARED | PreparedStatementHandler | 预编译SQL |
CALLABLE | CallableStatementHandler | 执行存储过程 |
RoutingStatementHandler
与SimpleStatementHandler
,PreparedStatementHandler
和CallableStatementHandler
的关系能够用下图示意。
在创立RoutingStatementHandler
之后,还会为RoutingStatementHandler
植入插件逻辑。Configuration
的newStatementHandler()
办法实现如下。
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { //创立RoutingStatementHandler StatementHandler statementHandler = new RoutingStatementHandler( executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); //为RoutingStatementHandler植入插件逻辑 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler;}
持续剖析doQuery()
办法中的第二件事件
,即实例化Statement
,prepareStatement()
办法实现如下所示。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; //获取到Connection对象并为Connection对象生成动静代理对象 Connection connection = getConnection(statementLog); //通过Connection对象的动静代理对象实例化Statement stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt;}
prepareStatement()
办法中首先会从Transaction
中将数据库连贯对象Connection
对象获取进去并为其生成动静代理对象以实现日志打印性能的加强,而后会通过Connection
的动静代理对象实例化Statement
,最初会解决Statement
中的占位符,比方将PreparedStatement
中的?
替换为理论的参数值。
持续剖析doQuery()
办法中的第三件事件
,即执行查问。本篇文章的示例中,映射文件的CURD标签没有对statementType属性进行设置,因而查问的操作最终会被RoutingStatementHandler
路由转发给PreparedStatementHandler
的query()
办法,如下所示。
@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; //调用到JDBC的逻辑了 ps.execute(); //调用ResultSetHandler解决查问后果 return resultSetHandler.handleResultSets(ps);}
如上所示,在PreparedStatementHandler
的query()
办法中就会调用到JDBC
的逻辑向数据库进行查问,最初还会应用曾经初始化好并植入了插件逻辑的ResultSetHandler
解决查问后果并返回。
至此,对Mybatis
的一次理论执行申请的阐明到此为止,本篇文章中的示例以查问为例,增删改大体相似,故不再赘述。
总结
Mybatis
中的执行器Executor
会在创立SqlSession
时一并被创立进去并被寄存于SqlSession
中,如果禁用了二级缓存,则Executor
理论为SimpleExecutor
,否则为CachingExecutor
。Mybatis
中的一次理论执行,会由所执行办法对应的MapperMethod
的execute()
办法实现,在execute()
办法中,会依据执行操作的类型(增改删查)调用SqlSession
中的相应的办法,例如insert()
,update()
,delete()
和select()
等,MapperMethod
在这其中的作用就是MapperMethod
关联着本次执行办法所对应的SQL
语句以及入参和出参等信息。在SqlSession
的insert()
,update()
,delete()
和select()
等办法中,SqlSession
会将与数据库的操作交由执行器Executor
来实现,无论是在SimpleExecutor
还是CachingExecutor
中,如果抛开缓存相干的逻辑,这些Executor
均会先依据映射文件中CURD标签的statementType字段创立相应的StatementHandler
,创立StatementHandler
的过程中还会一并将解决参数和处理结果的ParameterHandler
和ResultSetHandler
创立进去,创立好StatementHandler
之后,会基于StatementHandler
实例化Statement
,最初在StatementHandler
中基于实例化好的Statement
实现和数据库的交互,基于创立好的ResultSetHandler
解决交互后果并将后果返回。