前言
在理解了MyBatis初始化加载过程后,咱们也应该钻研看看SQL执行过程是怎么执行?这样咱们对于Mybatis的整个执行流程都相熟了,在开发遇到问题也能够很快定位到问题。
更重要的,在面试中遇到面试官征询Mybatis的知识点的时候,能够很顺畅的把这一套流程讲进去,面试官也会感觉你已把握Mybatis知识点了,可能就不问了。连忙瞄瞄
简介SQL执行过程
通过MyBatis初始化加载Sql执行过程所需的信息后,咱们就能够通过 SqlSessionFactory
对象失去 SqlSession
,而后执行 SQL 语句了,接下来看看Sql执行具体过程,SQL大抵执行流程图如下所示:
接下来咱们来看看每个执行链路中的具体执行过程,
SqlSession
SqlSession 是 MyBatis 裸露给内部应用的对立接口层,通过 SqlSessionFactory
创立,且其是蕴含和数据库打交道所有操作接口。
上面通过时序图形容 SqlSession 对象的创立流程:
在生成SqlSession
的同时,基于executorType
初始化好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) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
最顶层的SqlSession接口已生成,那咱们能够来看看sql的执行过程下一步是怎么的呢?怎么应用代理类MapperProxy
。
MapperProxy
MapperProxy
是 Mapper
接口与SQL 语句映射的要害,通过 MapperProxy
能够让对应的 SQL 语句跟接口进行绑定的,具体流程如下:
MapperProxy
代理类生成流程MapperProxy
代理类执行操作
MapperProxy
代理类生成流程
其中,MapperRegistry
是 Configuration
的一个属性,在解析配置时候会在MapperRegistry
中缓存了 MapperProxyFactory
的 knownMappers
变量Map
汇合。
`MapperRegistry
会依据mapper
接口类型获取已缓存的MapperProxyFactory
,MapperProxyFactory
会基于SqlSession
来生成MapperProxy
代理对象,
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } }
当调用SqlSession
接口时,MapperProxy
怎么是实现的呢?MyBatis
的 Mapper
接口 是通过动静代理实现的,调用 Mapper
接口的任何办法都会执行 MapperProxy::invoke()
办法,
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //Object类型执行 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } //接口默认办法执行 if (method.isDefault()) { if (privateLookupInMethod == null) { return this.invokeDefaultMethodJava8(proxy, method, args); } return this.invokeDefaultMethodJava9(proxy, method, args); } } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); }
但最终会调用到mapperMethod::execute()
办法执行,次要是判断是 INSERT
、UPDATE
、DELETE
、SELECT
语句去操作,其中如果是查问的话,还会判断返回值的类型。
public Object execute(SqlSession sqlSession, Object[] args) { Object result; Object param; switch(this.command.getType()) { case INSERT: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); break; case UPDATE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); break; case DELETE: param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); break; case SELECT: if (this.method.returnsVoid() && this.method.hasResultHandler()) { this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { result = this.executeForMap(sqlSession, args); } else if (this.method.returnsCursor()) { result = this.executeForCursor(sqlSession, args); } else { param = this.method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(this.command.getName(), param); if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); } if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } }
通过以上的剖析,总结出
Mapper
接口理论对象为代理对象MapperProxy
;MapperProxy
继承InvocationHandler
,实现invoke
办法;MapperProxyFactory::newInstance()
办法,基于 JDK 动静代理的形式创立了一个MapperProxy
的代理类;- 最终会调用到
mapperMethod::execute()
办法执行,实现操作。 - 而且更重要一点是,
MyBatis
应用的动静代理和广泛动静代理有点区别,没有实现类,只有接口,MyBatis 动静代理类图构造如下所示:
已以SELECT
为例, 调用会SqlSession ::selectOne()
办法。持续往下执行,会执行 Executor::query()
办法。
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { List var5; try { MappedStatement ms = this.configuration.getMappedStatement(statement); var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception var9) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9); } finally { ErrorContext.instance().reset(); } return var5; }
执行到Executor
类,那么咱们来看看其到底有什么?
Executor
Executor
对象为SQL 的执行引擎,负责增删改查的具体操作,顶层接口SqlSession
中都会有一个 Executor
对象,能够了解为 JDBC 中 Statement
的封装版。
Executor
是最顶层的是执行器,它有两个实现类,别离是BaseExecutor
和 CachingExecutor
BaseExecutor
是一个抽象类,实现了大部分Executor
接口定义的性能,升高了接口实现的难度。BaseExecutor
基于适配器设计模式之接口适配会有三个子类,别离是SimpleExecutor
、ReuseExecutor
和BatchExecutor
。SimpleExecutor
: 是 MyBatis 中默认简略执行器,每执行一次update
或select
,就开启一个Statement
对象,用完立即敞开Statement
对象ReuseExecutor
: 可重用执行器, 执行update
或select
,以sql作为key
查找Statement
对象,存在就应用,不存在就创立,用完后,不敞开Statement
对象,而是搁置于Map<String, Statement>
内,供下一次应用。简言之,就是重复使用Statement
对象BatchExecutor
: 批处理执行器,用于执行update(没有select,JDBC批处理不反对select将多个 SQL 一次性输入到数据库,
CachingExecutor
: 缓存执行器,为Executor
对象减少了二级缓存的相干功:先从缓存中查问后果,如果存在就返回之前的后果;如果不存在,再委托给Executor delegate
去数据库中取,delegate
能够是下面任何一个执行器。
在Mybatis
配置文件中,能够指定默认的ExecutorType
执行器类型,也能够手动给DefaultSqlSessionFactory
的创立SqlSession
的办法传递ExecutorType
类型参数。
看完Exector
简介之后,持续跟踪执行流程链路剖析,SqlSession
中的 JDBC
操作局部最终都会委派给 Exector
实现,Executor::query()
办法,看看在Exector
的执行是怎么的?
每次查问都会先通过CachingExecutor
缓存执行器, 会先判断二级缓存中是否存在查问 SQL ,如果存在间接从二级缓存中获取,不存在即为第一次执行,会间接执行SQL 语句,并创立缓存,都是由CachingExecutor::query()
操作实现的。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql); return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //获取查问语句对应的二级缓存 Cache cache = ms.getCache(); //sql查问是否存在在二级缓存中 if (cache != null) { //依据 <select> 节点的配置,判断否须要清空二级缓存 this.flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { this.ensureNoOutParams(ms, boundSql); //查问二级缓存 List<E> list = (List)this.tcm.getObject(cache, key); if (list == null) { //二级缓存没用相应的后果对象,调用封装的Executor对象的 query() 办法 list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //将查问后果保留到二级缓存中 this.tcm.putObject(cache, key, list); } return list; } } //没有启动二级缓存,间接调用底层 Executor 执行数据数据库查问操作 return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
如果在通过CachingExecutor
缓存执行器(二级缓存)没有返回值的话,就会执行BaseExecutor
以及其的实现类,默认为SimpleExecutor
,首先会在一级缓存中获取查问后果,取得不到,最终会通过SimpleExecutor:: ()
去数据库中查问。
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 (this.closed) { throw new ExecutorException("Executor was closed."); } else { //是否革除本地缓存 if (this.queryStack == 0 && ms.isFlushCacheRequired()) { this.clearLocalCache(); } List list; try { ++this.queryStack; //从一级缓存中,获取查问后果 list = resultHandler == null ? (List)this.localCache.getObject(key) : null; //获取到后果,则进行解决 if (list != null) { this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //取得不到,则从数据库中查问 list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { --this.queryStack; } if (this.queryStack == 0) { //执行提早加载 Iterator var8 = this.deferredLoads.iterator(); while(var8.hasNext()) { BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next(); deferredLoad.load(); } this.deferredLoads.clear(); if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { this.clearLocalCache(); } } return list; } }
那么SimpleExecutor::doQuery()
如何去数据库中查问获取到后果呢?其实执行到这边mybatis的执行过程就从 Executor
转交给 StatementHandler
解决,
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; List var9; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = this.prepareStatement(handler, ms.getStatementLog()); var9 = handler.query(stmt, resultHandler); } finally { this.closeStatement(stmt); } return var9; }
这样咱们的执行链路剖析已到StatementHandler
了,当初让咱们去一探到底其原理
StatementHandler
StatementHandler
负责解决Mybatis
与JDBC之间Statement
的交互,即Statement
对象与数据库进行交互,其为顶级接口,有4个实现类,其中三个是Statement
对象与数据库进行交互类, 另外一个是路由性能的,
RoutingStatementHandler
: 对Statement
对象没有实际操作,次要负责另外三个StatementHandler
的创立及调用, 而且在MyBatis
执行时,应用的StatementHandler
接口对象实际上就是RoutingStatementHandler
对象。SimpleStatementHandler
: 治理 Statement 对象, 用于简略SQL的解决 。PreparedStatementHandler
: 治理 Statement 对象,预处理SQL的接口 。CallableStatementHandler
:治理 Statement 对象,用于执行存储过程相干的接口 。
在经验过Executor
后,基于初始化加载到MapperState
中的StatementType
的类型通过Configuration.newStatementHandler()
办法中的RoutingStatementHandler
生成StatementHandler
理论解决类。
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch(ms.getStatementType()) { case STATEMENT: this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } }
当初先以PreparedStatementHandler
预处理为例,接着Sql的执行链路来剖析,StatementHandler::query()
到StatementHandler::execute()
真正执行Sql查问操作。
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { String sql = boundSql.getSql(); statement.execute(sql); return resultSetHandler.handleResultSets(statement); }
但执行真正查问操作之前,还进行哪些解决呢?还会进行ParameterHandler
对 SQL 参数的预处理:对参数进行动静Sql映射,那么ParameterHandler
又如何实现对参数进行动静映射的呢?
ParameterHandler
ParameterHandler
参数处理器, 用来设置参数规定的,负责为sql 语句参数动静赋值,其有两个接口
- getParameterObject: 用于读取参数
- setParameters: 用于对 PreparedStatement 的参数赋值
当SimpleExecutor
执行结构PreparedStatementHandler
完,会调用parameterize()
办法将PreparedStatement
对象里SQL转交ParameterHandler
实现类 DefaultParameterHandler::setParameters()
办法 设置 PreparedStatement
的占位符参数 。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); //参数动静赋值 handler.parameterize(stmt); return stmt; }
DefaultParameterHandler::setParameters()
如何对SQL进行动静赋值呢?在执行前将已装载好的BoundSql对象信息进行应用
public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); //获取待动静赋值参数列表的封装parameterMappings List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); //是否为输出参数 if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; //获取待动静参数属性名 String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } //// 在通过 SqlSource 的parse 办法失去parameterMappings 的具体实现中,咱们会失去parameterMappings 的 typeHandler TypeHandler typeHandler = parameterMapping.getTypeHandler(); //获取jdbc数据类型 JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { // typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException | SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } }
执行完SQL 参数的预处理,当StatementHandler::execute()
真正执行查问操作执行完后,有返回后果,须要对返回后果进行ResultSetHandler
解决,当初看看最初的后果的解决流程。
ResultSetHandler
ResultSetHandler
后果解析器,将查问后果的ResultSet
转换成映射的对应后果(java DTO
等),其有三接口
handleResultSets()
:处理结果集handleCursorResultSets()
:批量处理结果集handleOutputParameters()
:解决存储过程返回的后果集
其默认的实现为DefaultResultSetHandler
,次要性能为:
- 解决
Statement
执行后产生的后果集生成绝对的输入后果、 - 解决存储过程执行后的输入参数
那看看DefaultResultSetHandler::handleResultSets()
如何解决?
- 当有多个
ResultSet
的后果汇合,每个ResultSet对应一个Object 对象,如果不思考存储过程,一般的查问只有一个ResultSet ResultSetWrapper
封装了ResultSet
后果集,其属性蕴含ResultSet
,ResultMap
等
@Overridepublic List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); //当有多个ResultSet的后果汇合,每个ResultSet对应一个Object 对象 final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; //取得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象 ResultSetWrapper rsw = getFirstResultSet(stmt); //取得 ResultMap 数组 List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); // <3.1> 校验 while (rsw != null && resultMapCount > resultSetCount) { //取得 ResultMap 对象 ResultMap resultMap = resultMaps.get(resultSetCount); //解决 ResultSet ,将后果增加到 multipleResults 中 handleResultSet(rsw, resultMap, multipleResults, null); //取得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象 rsw = getNextResultSet(stmt); //清理 cleanUpAfterHandlingResultSet(); // resultSetCount ++ resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } //如果是 multipleResults 单元素,则取首元素返回 return ollapseSingleResultList(multipleResults);}
其实在ResultSetHandler
后果集解决是比较复杂的,这里只是简略的介绍一下,有趣味的能够再深入研究一下,前期有空也会写。
执行到这边,Mybatis SQL执行根本完了,会把转换后的后果集返回到操作者。
论断
在SQL执行过程次要波及了SqlSession
,MapperProxy
,Executor
,StatementHandler
,ParameterHandler
以及ResultSetHandler
,包含参数动静绑定,Sql执行查询数据库数据,后果返回集映射等,而且每个环节波及的内容都很多,每个接口都能够抽出独自剖析,后续有工夫再一一具体的看看。前面还是再剖析一下插件的利用。
各位看官还能够吗?喜爱的话,动动手指导个赞????呗!!谢谢反对!
欢送扫码关注,原创技术文章第一工夫推出