2.5 SQL执行
接下来我们看看SQL语句的具体执行过程是怎么样的 复制代码
List<User> list = mapper.selectUserList(); 复制代码
由于所有的Mapper都是JDK动态代理对象,所以任意的方法都是执行触发管理类MapperProxy的invoke()方法 复制代码
2.5.1 MapperProxy.invoke()
我们直接进入到invoke方法中 复制代码
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // toString hashCode equals getClass等方法,无需走到执行SQL的流程 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { // 提升获取 mapperMethod 的效率,到 MapperMethodInvoker(内部接口) 的 invoke // 普通方法会走到 PlainMethodInvoker(内部类) 的 invoke return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } 复制代码
然后进入到PlainMethodInvoker的invoke方法
@Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { // SQL执行的真正起点 return mapperMethod.execute(sqlSession, args); } 复制代码
2.5.2 mapperMethod.execute()
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { // 根据SQL语句的类型调用SqlSession对应的方法 case INSERT: { // 通过 ParamNameResolver 处理args[] 数组 将用户传入的实参和指定参数名称关联起来 Object param = method.convertArgsToSqlCommandParam(args); // sqlSession.insert(command.getName(), param) 调用SqlSession的insert方法 // rowCountResult 方法会根据 method 字段中记录的方法的返回值类型对结果进行转换 result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { // 返回值为空 且 ResultSet通过 ResultHandler处理的方法 executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { // 返回值为 单一对象的方法 Object param = method.convertArgsToSqlCommandParam(args); // 普通 select 语句的执行入口 >> result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; } 复制代码
在这一步,根据不同的type(INSERT、UPDATE、DELETE、SELECT)和返回类型:
1)调用convertArgsToSqlCommandParam()将方法参数转换为SQL的参数。
2)调用sqlSession的insert()、update()、delete()、selectOne ()方法。我们以查询为例,会走到selectOne()方法。
Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); 复制代码
2.5.3 sqlSession.selectOne
这里来到了对外的接口的默认实现类DefaultSqlSession。
selectOne()最终也是调用了selectList()
@Override public <T> T selectOne(String statement, Object parameter) { // 来到了 DefaultSqlSession // Popular vote was to return null on 0 results and throw exception on too many. 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; } } 复制代码
在SelectList()中,我们先根据command name(Statement ID)从Configuration中拿到MappedStatement。ms里面有xml中增删改查标签配置的所有属性,包括id、
statementType、sqlSource、useCache、入参、出参等等
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); // 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰 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是第二步openSession的时候创建的,创建了执行器基本类型之后,依次执行了二级缓存装饰,和插件包装。
所以,如果有被插件包装,这里会先走到插件的逻辑。如果没有显式地在settings中配置cacheEnabled=false,再走到CachingExecutor的逻辑,然后会走到BaseExecutor的query()方法。
插件后面讲,这里先跳过。
2.5.4 CachingExecutor.query()
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 获取SQL BoundSql boundSql = ms.getBoundSql(parameterObject); // 创建CacheKey:什么样的SQL是同一条SQL? >> CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } 复制代码
二级缓存的CacheKey是怎么构成的呢?或者说,什么样的查询才能确定是同一个查询呢?
在BaseExecutor的createCacheKey方法中,用到了六个要素:
cacheKey.update(ms.getId()); // com.msb.mapper.BlogMapper.selectBlogById cacheKey.update(rowBounds.getOffset()); // 0 cacheKey.update(rowBounds.getLimit()); // 2147483647 = 2^31-1 cacheKey.update(boundSql.getSql()); cacheKey.update(value); cacheKey.update(configuration.getEnvironment().getId()); 复制代码
也就是说,方法相同、翻页偏移相同、SQL相同、参数值相同、数据源环境相同,才会被认为是同一个查询。
CacheKey的实际值举例(toString()生成的),debug可以看到:
-1381545870:4796102018:com.msb.mapper.BlogMapper.selectBlogById:0:2147483647:select * from blog where bid = ?:1:development 复制代码
注意看一下CacheKey的属性,里面有一个List按顺序存放了这些要素。
private static final int DEFAULT_MULTIPLIER = 37; private static final int DEFAULT_HASHCODE = 17; private final int multiplier; private int hashcode; private long checksum; private int count; private List<Object> updateList 复制代码
怎么比较两个CacheKey是否相等呢?如果一上来就是依次比较六个要素是否相等,要比较6次,这样效率不高。有没有更高效的方法呢?继承Object的每个类,都有一个hashCode ()方法,用来生成哈希码。它是用来在集合中快速判重的。
在生成CacheKey的时候(update方法),也更新了CacheKey的hashCode,它是用乘法哈希生成的(基数baseHashCode=17,乘法因子multiplier=37)。
hashcode = multiplier * hashcode + baseHashCode; 复制代码
Object中的hashCode()是一个本地方法,通过随机数算法生成(OpenJDK8 ,默认,可以通过-XX:hashCode修改)。CacheKey中的hashCode()方法进行了重写,返回自己生成的hashCode。 复制代码
为什么要用37作为乘法因子呢?跟String中的31类似。
CacheKey中的equals也进行了重写,比较CacheKey是否相等。
@Override public boolean equals(Object object) { if (this == object) { return true; } if (!(object instanceof CacheKey)) { return false; } final CacheKey cacheKey = (CacheKey) object; if (hashcode != cacheKey.hashcode) { return false; } if (checksum != cacheKey.checksum) { return false; } if (count != cacheKey.count) { return false; } for (int i = 0; i < updateList.size(); i++) { Object thisObject = updateList.get(i); Object thatObject = cacheKey.updateList.get(i); if (!ArrayUtil.equals(thisObject, thatObject)) { return false; } } return true; } 复制代码
如果哈希值(乘法哈希)、校验值(加法哈希)、要素个数任何一个不相等,都不是同一个查询,最后才循环比较要素,防止哈希碰撞。
CacheKey生成之后,调用另一个query()方法。
2.5.5 BaseExecutor.query方法
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); // cache 对象是在哪里创建的? XMLMapperBuilder类 xmlconfigurationElement() // 由 <cache> 标签决定 if (cache != null) { // flushCache="true" 清空一级二级缓存 >> flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); // 获取二级缓存 // 缓存通过 TransactionalCacheManager、TransactionalCache 管理 @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 写入二级缓存 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } // 走到 SimpleExecutor | ReuseExecutor | BatchExecutor return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 异常体系之 ErrorContext 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()) { // flushCache="true"时,即使是查询,也清空一级缓存 clearLocalCache(); } List<E> list; try { // 防止递归查询重复处理缓存 queryStack++; // 查询一级缓存 // ResultHandler 和 ResultSetHandler的区别 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; } 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 的区别,看doUpdate // 默认Simple 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; } 复制代码
2.5.6 SimpleExecutor.doQuery方法
@Override 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(); // 注意,已经来到SQL处理的关键对象 StatementHandler >> StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 获取一个 Statement对象 stmt = prepareStatement(handler, ms.getStatementLog()); // 执行查询 return handler.query(stmt, resultHandler); } finally { // 用完就关闭 closeStatement(stmt); } } 复制代码
在configuration.newStatementHandler()中,new一个StatementHandler,先得到RoutingStatementHandler。 复制代码
RoutingStatementHandler里面没有任何的实现,是用来创建基本的StatementHandler的。这里会根据MappedStatement里面的statementType决定StatementHandler的类
型。默认是PREPARED(STATEMENT、PREPARED、CALLABLE)。
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // StatementType 是怎么来的? 增删改查标签中的 statementType="PREPARED",默认值 PREPARED switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: // 创建 StatementHandler 的时候做了什么? >> 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()); } } 复制代码
StatementHandler里面包含了处理参数的ParameterHandler和处理结果集的ResultSetHandler。
这两个对象都是在上面new的时候创建的。
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); } 复制代码
这三个对象都是可以被插件拦截的四大对象之一,所以在创建之后都要用拦截器进行包装的方法。 复制代码
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); // 植入插件逻辑(返回代理对象) parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); // 植入插件逻辑(返回代理对象) resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; } 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; } 复制代码
创建Statement
用new出来的StatementHandler创建Statement对象。
执行查询操作,如果有插件包装,会先走到被拦截的业务逻辑。
// 执行查询 return handler.query(stmt, resultHandler); 复制代码
进入到PreparedStatementHandler中处理
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 到了JDBC的流程 ps.execute(); // 处理结果集 return resultSetHandler.handleResultSets(ps); } 复制代码
执行PreparedStatement的execute()方法,后面就是JDBC包中的
PreparedStatement的执行了。ResultSetHandler处理结果集,如果有插件包装,会先走到被拦截的业务逻辑。
总结:调用代理对象执行SQL操作的流程
2.6 MyBatis核心对象
对象 | 相关对象 | 作用 |
Configuration | MapperRegistry<br/>TypeAliasRegistry<br/>TypeHandlerRegistry | 包含了MyBatis的所有的配置信息 |
SqlSession | SqlSessionFactory<br/>DefaultSqlSession | 对操作数据库的增删改查的API进行了封装,提供给应用层使用 |
Executor | BaseExecutor<br/>SimpleExecutor<br/>BatchExecutor<br/>ReuseExecutor | MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护 |
StatementHandler | BaseStatementHandler<br>SimpleStatementHandler<br/>PreparedStatementHandler<br/>CallableStatementHandler | 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合 |
ParameterHandler | DefaultParameterHandler | 把用户传递的参数转换成JDBC Statement 所需要的参数 |
ResultSetHandler | DefaultResultSetHandler | 把JDBC返回的ResultSet结果集对象转换成List类型的集合 |
MapperProxy | MapperProxyFactory | 触发管理类,用于代理Mapper接口方法 |
MappedStatement | SqlSource<br/>BoundSql | MappedStatement维护了一条<select|update|delete|insert>节点的封装,表示一条SQL包括了SQL信息、入参信息、出参信息 |