MyBatis系列教程五一文读懂Mybatis的执行流程
在前面的章节中系统学习了Mybatis框架的使用,但是学习框架并不能只停留于如何使用的阶段,需要深入去了解Mybatis的执行流程。在本章节内,将详细了解Mybatis的实现细节。
根据前面的章节,可以总结出Mybatis执行流程主要分为以下4个阶段:获取SQLSessionFactory获取SQLSession执行SQL语句
下面就3个阶段进行分析。5.1 创建SqlSessionFactroy
在创建SqlSessionFactory对象之前,需要先创建SqlSessionFactoryBuilder对象,该对象的主要作用就是解析Mybatis的全局配置文件并将配置文件抽象为Configuration对象。下面就从以下代码入手,了解该过程。public static void main( String[] args ) throws FileNotFoundException { InputStream is = App.class.getClassLoader().getResourceAsStream("mybatis-config.xml"); //1.进入创建SqlSessionFactory流程 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession session = sqlSessionFactory.openSession(); }
从注释1处入手,代码如下:public SqlSessionFactory build(InputStream inputStream) { //可以看出此处调用了重载方法。 return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { //1.此处解析xml配置文件 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //2.先调用了parse.parse()方法,该方法会将XML的配置抽象为Configuration对象 //3.调用build方法返回DefaultSessionFactory对象 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } //忽略解析的细节可以看出该方法的作用就是将XML文件解析为Configuration对象 public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
至此,已经大致了解了创建SqlSessionFactory的流程,过程如下:SqlSessionFactoryBuilder读取全局配置文件SqlSessionFactory读取配置文件后解析配置文件将配置文件解析为Configuration对象SqlSessionFactory通过Configuration对象创建了DefaultSQLSessionFactory对象
以上过程的时序图如下:5.2 创建SqlSession
当创建完SqlSessionFactroy后,可以通过SqlSessionFactory的openSession方法创建SqlSession对象。SqlSession session = sqlSessionFactory.openSession();
继续查看openSession源码。
DefaultSqlSessionFactory:public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //通过Configuration对象获取数据源和事务配置信息 final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //获取sql语句执行器,从表面上看是SqlSession执行了sql语句,其实是Executor //而Executor实际上是对JDBC中Statement对象的封装 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(); } }
在执行完以上方法后就可获得SqlSession对象,这个过程比较简单,需要注意的是Mybatis中真正执行sql语句的对象并不是SqlSession,而是Executor对象。接下来将进入最重要的环节,执行sql语句。
5.3 执行Sql语句
在经历前面两步的过程后,已经获取到SqlSession对象了,接下来,以查询为例查看Sql语句的具体执行流程。查询代码如下: public static void main( String[] args ) throws FileNotFoundException { InputStream is = App.class.getClassLoader().getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Listlist = mapper.findAllUser(); }
从上面的代码可以看出直接调用了mapper中定义的findAllUser()方法,但是mapper是一个接口,findAllUser又是一个抽象方法,并没有方法体,那么Mybaits是怎么执行的呢?在这里需要先了解MapperProxy。这个类的作用是为Mapper接口生成代理类,也就是子类对象。5.3.1MapperProxy
首先从sqlSession.getMapper()方法入手,从前面的代码中可以知道此时的sqlSession,实际上是DefaultSqlSession对象,因此进入DefaultSqlSession对象中的getMapper()方法,代码如下:
DefaultSqlSession:@Override public T getMapper(Class type) { return configuration.getMapper(type, this); }
从代码中可以看出该方法仅仅是从Configuration对象中读取了配置。接下来进入configuration.getMapper()方法,代码如下:
Configuration:public T getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
在Configuration对象的getMapper方法中调用了mapperRegistry的getMapper()方法,继续进入MapperRegistry的getMapper()方法。
MapperRegistry:public T getMapper(Class type, SqlSession sqlSession) { //首先创建代理工厂类对象mapperProxyFactory 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.newInstance()方法。
MapperProxyFactorypublic T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } protected T newInstance(MapperProxy mapperProxy) { //在这里创建接口的实现类 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
在经历了以上步骤后,Mybaits生成了Mapper接口的代理类,接下来就可以执行sql语句了。
5.3.2 Executor
此时执行findAllUser()方法时,Mybatis会进入MapperProxy的invoke()方法,进入此方法查看源码:
MapperProxy:public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { //代码会执行到此处 return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { //交由mapperMethod.execute()方法 return mapperMethod.execute(sqlSession, args); }
继续查看MapperMethod的execute()方法:
MapperMethod:public Object execute(SqlSession sqlSession, Object[] args) { Object result; //判断SQL语句类型 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()) { 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); 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; }
可以看出在这个方法中主要对SQL语句所做的操作进行了判断,并且执行对用的操作。在示例中我们查询了所有的用户,因此会进入executeForMany()方法。
MapperMethod:private Object executeForMany(SqlSession sqlSession, Object[] args) { List result; Object param = method.convertArgsToSqlCommandParam(args); //1.判断SQL有没有分页 if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.selectList(command.getName(), param, rowBounds); } else { //2.没有分页,因此代码执行到此处 result = sqlSession.selectList(command.getName(), param); }
在该方法中判断SQL语句是否有分页,因为示例中没有分页,因此代码会执行到注释2处。继续查看此方法,会发现调用了SqlSession的selectList()方法。
DefaultSqlSession:public List selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); }
继续进入重载的selectList()方法:
DefaultSqlSession:public List selectList(String statement, Object parameter, RowBounds rowBounds) { try { //1.从Configuration中获取MappedStatements,MappedStatements即Mapper文件抽象的对象 MappedStatement ms = configuration.getMappedStatement(statement); //2.调用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(); } }
在该方法中获取了MappedStatement后调用了query()方法,继续进入该方法。因为在示例中并未开启二级缓存,因此在此处直接调用BaseExecutor的方法。
BaseExecutor:public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
继续进入query()方法:
BaseExecutor:public List 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++; list = resultHandler == null ? (List) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //1.核心代码在此处 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; }
进入此query方法后,核心查询在注释1处,继续进入queryFromDatabase()方法。
BaseExecutor:private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { //1.本质上时调用了这个方法 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()方法: @Override public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); //1.最终会调用query方法。 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }
继续进入注释1处的query方法,代码最终会执行到PreparedStatementHandler中的query()方法,代码如下: public List query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps); }
代码执行到此处已经是我们所熟悉的JDBC了,在执行完SQL语句后,接下来就要对查询结果进行解析。整个过程的时序图如下:
5.4 小结
总结上述流程可以梳理为以下步:加载配置文件解析为Configuration对象使用Configuration对象获取DefaultSqlSessionFactory对象通过DefaultSqlSessionFactory对象获取DefaultSqlSession对象通过DefaultSqlSession创建MapperProxy对象MapperProxy调用MapperMthod方法判断SQL操作类型判断是否开启缓存二级缓存将SQL语句交由BaseExecutor处理判断在一级缓存中是否可以命中SQL语句执行结果,如果有则返回结果如果没有数据在调用RoutingStatemenHandler选择SQL语句执行方式默认情况下,MappedStatement为预编译执行,所以使用PrepareStatementHandler最后通过DefaultResultSetHandler封装结果给客户端
整个过程流程图如下:
MyBatis执行流程
总结
在本文内初步了解了MyBatis框架执行SQL语句的流程,MyBatis框架源码将会在下一个专栏内更新,敬请期待!!!