发布时间:2022-08-19 12:46
一.本章概述
对于Mybatis的配置文件,映射文件在之前的博客已经分析过,之后正式进入Sql执行分析阶段。Sql执行整体过程还是比较清晰的,但是其中有很多细节值得琢磨。
Sql执行过程涉及到的模块:
1.SqlSession获取
2.参数解析
3.缓存机制
4.连接获取
5.结果映射
这章计划先把sql执行流程串起来,有个基本的执行印象,然后再各个击破,抽丝剥茧分析模块细节。
二.基本用法
在获取到 SqlSessionFactory后,可以使用以下代码执行sql
try (SqlSession session = sqlSessionFactory.openSession()) { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(101); }
或者
try (SqlSession session = sqlSessionFactory.openSession()) { Blog blog = session.selectOne("org.apache.ibatis.domain.blog.mappers.BlogMapper.selectBlogWithPostsUsingSubSelect", 1); }
三.sql流程分析
1.获取SqlSession
sqlSessionFactory.openSession()
DefaultSqlSessionFactory:public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); //创建事务管理器 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //创建执行器 final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }看看创建执行器代码:
Configuration: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; }
2. SqlSession 已经得到了,看看这行代码: BlogMapper mapper = session.getMapper(BlogMapper.class)
DefaultSqlSessionpublicT getMapper(Class type) { //在这一步会根据type接口创建type的代理对象 return configuration.getMapper(type, this); } Configuration:publicT getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } MapperRegistry:publicT getMapper(Class type, SqlSession sqlSession) { //映射文件接口绑定时,已经将 接口保存到 knownMappers中 final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory ) knownMappers.get(type); if (mapperProxyFactory == null) {//未绑定,抛异常 throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { //创建代理对象 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } MapperProxyFactory:public T newInstance(SqlSession sqlSession) { final MapperProxymapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance(MapperProxymapperProxy) { //jdk动态代理 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
通过jdk动态代理方式,得到的 接口是一个代理对象,即MapperProxy,看看这个对象的 invoke方法
MapperProxy: @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (method.isDefault()) { if (privateLookupInMethod == null) {//jdk1.8 接口默认方法 return invokeDefaultMethodJava8(proxy, method, args); } else { return invokeDefaultMethodJava9(proxy, method, args); } } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 从缓存中获取 MapperMethod 对象,若缓存未命中,则创建 MapperMethod 对象 final MapperMethod mapperMethod = cachedMapperMethod(method); // 调用 execute 方法执行 SQL return mapperMethod.execute(sqlSession, args); }MapperMethod :
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); 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()) { // 如果方法返回值为 void,但参数列表中包含 ResultHandler,表明 // 使用者想通过 ResultHandler 的方式获取查询结果,而非通过返回值 // 获取结果 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()) { // 执行查询操作,并返回一个 Cursor 对象 result = executeForCursor(sqlSession, args); } else { // 执行查询操作,并返回一个结果 // 获取参数名对应的参数值 Object param = method.convertArgsToSqlCommandParam(args); //调用 SqlSession的方法 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()); } // 如果方法的返回值为基本类型,而返回值却为 null,此种情况下应抛出异常 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; }这里看看SqlSession的selectOne方法:
publicT selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List 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; } } @Override publicList selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } @Override public List selectList(String statement, Object parameter, RowBounds rowBounds) { try { //statement就是id,即dao层接口方法:全限定类名+方法名 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(); } }
3. Blog blog = session.selectOne("org.apache.ibatis.domain.blog.mappers.BlogMapper.selectBlogWithPostsUsingSubSelect", 1);
直接调用SqlSession的selectOne方法,传入MappedStatement的id,参数,和 session.getMapper() 殊途同归。
DefaultSqlSession:@Override publicT selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List 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; } } @Override publicList selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } @Override public List selectList(String statement, Object parameter, RowBounds rowBounds) { try { //statement就是id,即dao层接口方法:全限定类名+方法名 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(); } }
4.Executor执行sql操作
执行器的流程如下:
以查询为例,分析Executor执行流程:
BaseExecutor:@Override publicList query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 根据入参,解析动态sql语句 BoundSql boundSql = ms.getBoundSql(parameter); //创建缓存key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } @Override publicList 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 list; try { queryStack++; //从一级缓存中获取缓存项,从这里可以看出 mybatis无法关闭一级缓存。。 list = resultHandler == null ? (List ) 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(); /** * 一级缓存存在的弊端,在集群节点中,如果两台服务器上查询同一语句,此时缓存内容相同 * 如果一台执行更新,重新查询后,会将更新后的数据存入缓存,此时两条服务器中的一级缓存 * 内容不一致 * * 通过设置 * * 本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 * */ if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482,清空缓存,此时一级缓存失效。。 clearLocalCache(); } } return list; },默认session * privateList queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; //向缓存中存储占位符? localCache.putObject(key, EXECUTION_PLACEHOLDER); try { //模板方法,子类具体实现,实现真正查询操作 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; } SimpleExecutor:@Override publicList doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); //创建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); } } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; //获取数据库连接,和数据源类型相关 Connection connection = getConnection(statementLog); //创建statement stmt = handler.prepare(connection, transaction.getTimeout()); //设置参数值,将 ? 替换成真正的 参数 handler.parameterize(stmt); return stmt; }PreparedStatementHandler:@Override publicList query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; //执行sql ps.execute(); //处理结果 return resultSetHandler.handleResultSets(ps); }
四.总结
sql执行流程已经分析完了,但很多细节需要继续完善;下一节将分析 参数解析,看看Mybatis如何将接口传入的参数解析到sql语句中。
最后,来一张sql执行流程图:
结语:我反对这门亲事!