Mybatis源码解读-配置加载和Mapper的生成
问题
- Mybatis四大对象的创建顺序?
- Mybatis插件的执行顺序?
工程创建
环境:Mybatis(3.5.9)
简单示例
这里只放出main方法的示例,其余类请看demo工程。
public static void main(String[] args) throws Exception { // 配置文件路径 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 1.读取配置,创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 2.通过工厂获取SqlSession SqlSession session = sqlSessionFactory.openSession(); try { // 3.获取mapper代理对象 StudentMapper mapper = session.getMapper(StudentMapper.class); // 4.执行查询,此处才真正连接数据库 System.out.println(mapper.selectByName("张三")); } finally { // 5.关闭连接 session.close(); } }
Mapper的创建
我们使用Mybatis操作数据库,主要是通过mapper对象(在hibernate中叫dao对象)。
那么,我们不按顺序从读取配置初始化开始讲,直接看看mapper对象是如何获取与执行的。
-
获取mapper
// StudentMapper mapper = session.getMapper(StudentMapper.class); DefaultSqlSession.getMapper(Class<T> type) --> Configuration.getMapper(Class<T> type, SqlSession sqlSession) --> MapperRegistry.getMapper(Class<T> type, SqlSession sqlSession) --> MapperProxyFactory.newInstance(SqlSession sqlSession) --> MapperProxyFactory.newInstance(MapperProxy<T> mapperProxy) 咱们来看看MapperProxyFactory.newInstance(MapperProxy
mapperProxy)的实现 protected T newInstance(MapperProxy<T> mapperProxy) { // 可以转换成这样,返回的是StudentMapper的代理对象 // final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, StudentMapper.class, methodCache); // Proxy.newProxyInstance(StudentMapper.class.getClassLoader(), new Class[] { StudentMapper.class }, mapperProxy); return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } 也就是说,实际返回的是
MapperProxy
对象,StudentMapper
被代理了。 -
执行mapper的方法
已知mapper对象被代理了,那么执行mapper的所有方法,都会先经过
MapperProxy
的invoke方法public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 如果执行的是Object的方法,则直接执行,不继续处理mybatis的逻辑 if (Object.class.equals(method.getDeclaringClass())) { // 举例,如果执行的是mapper.toString(),则进入此判断 return method.invoke(this, args); } else { // cachedInvoker(method):创建MapperMethodInvoker并缓存起来 return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } cachedInvoker(method)返回的是
PlainMethodInvoker
,继续进去看看// PlainMethodInvoker的方法 public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); } // MapperMethod#execute(SqlSession sqlSession, Object[] args) public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { ...... break; } case UPDATE: { ...... break; } case DELETE: { ...... break; } case SELECT: ...... break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } ...... return result; } 终于,看到了熟悉insert、update关键字,这里就是具体解析执行sql,并返回结果的逻辑。咱们先略过。回去看看是如何加载配置以及生成SqlSession的。
SqlSessionFactory
SqlSessionFactory
的生成过程如下
public SqlSessionFactory build(InputStream inputStream) { return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // xml配置解析类 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // build方法返回DefaultSqlSessionFactory // 主要看parser.parse() 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. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
下面来看看parser.parse()方法
// XMLConfigBuilder#parse() public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // parser.evalNode("/configuration"):获取configuration节点 // 例如:<configuration> xxx </configuration> // parseConfiguration才是重点 parseConfiguration(parser.evalNode("/configuration")); return configuration; } // 这是重点 private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // 环境配置 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 映射器配置 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
详细XML的配置请参考官网:mybatis – MyBatis 3 | 配置
这里,咱们只讲环境配置,其他的篇幅有限,请自行查看源码。
SqlSession
接下来看看SqlSession
的创建
// DefaultSqlSessionFactory#openSession() --> // DefaultSqlSessionFactory#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); // 上面那两个对象,在创建SqlSessionFactory时,就已经创建好了 // 通过事务工厂创建事务 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 创建mybatis四大对象之一的Executor 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(); } }
看看四大对象之一Executor
的创建
// Configuration#newExecutor(Transaction transaction, ExecutorType executorType) 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进行二次包装 executor = new CachingExecutor(executor); } // 这块是mybatis的插件处理,用代理的方式,以后再开文章讲 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
Mapper的执行
Mapper的创建一节,讲到mapper执行会被代理。
下面就以StudentMapper为例,讲讲mapper的执行。
public interface StudentMapper { List<Student> selectByName(@Param("name") String name); }
当执行selectByName时候,进入到MapperMethod#execute(SqlSession sqlSession, Object[] args)方法。
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { ...... // 忽略insert、update、delete的逻辑,直接看select case SELECT: // 如果返回null或者设置了自定义的结果处理器 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; // 如果返回集合或者数组,我们的查询会进到这里,因为selectByName返回值是List // 这是入口 } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); // 如果返回map } 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; }
继续看executeForMany
方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; // 参数转换,如果参数有注解,则会转成map,且可使用param1, param2 // 例如:@Param("name")会转成 {"name":xxx, "param1": xxx} Object param = method.convertArgsToSqlCommandParam(args); // 是否分页 if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.selectList(command.getName(), param, rowBounds); } else { // 这是入口 result = sqlSession.selectList(command.getName(), param); } // 如果result不能强转成方法的返回值(在此例子中getReturnType就是List<Studet>) if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; }
继续看,因为案例中没用到分页,所以执行的是sqlSession.selectList(command.getName(), param);
// DefaultSqlSession#selectList(String statement, Object parameter) --> // DefaultSqlSession#selectList(String statement, Object parameter, RowBounds rowBounds) --> // DefaultSqlSession#selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) --> private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { // MapperStatement在前面解析xml时,就已经创建了 // 忘了就看看创建SqlSessionFactory时是如何解析xml文件的mappers节点的 MappedStatement ms = configuration.getMappedStatement(statement); // 执行器执行查询方法 return executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
继续看,executor.query方法,Mybatis-PageHelper插件就是通过拦截query方法,插入分页参数的。
// CachingExecutor public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 对sql进行预处理 BoundSql boundSql = ms.getBoundSql(parameterObject); // 创建一级缓存的key CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); // 这是入口 return 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(); // 有缓存的逻辑 if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @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; } } // delegate是SimpleExecutor // 这是入口 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } // BaseExecutor 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 (closed) { throw new ExecutorException("Executor was closed."); } // 是否要清除缓存,默认设置是如果非select方法,都会清除缓存。 if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; // 从一级缓存提取数据 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 { // 这是入口 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 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(); // 重点又来了,mybatis四大对象的3个,在这里创建 // 按顺序是:ParameterHandler、ParameterHandler、StatementHandler // 又一个装饰器模式,实际创建的是PreparedStatementHandler(默认),但是使用RoutingStatementHandler又包了一层 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 创建jdbc的statement对象,直到这里,才会真正获取数据库连接 stmt = prepareStatement(handler, ms.getStatementLog()); // 执行查询,并使用resultHandler处理结果 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }
答案
- 创建顺序为:Executor、ParameterHandler、ParameterHandler、StatementHandler
- 插件的执行顺序,如果都命中同一个方法,那么顺序为,越晚注册的插件,越先执行(因为代理)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构