mybatis 原理解析
先看mybatis的简单样例代码:
DataSource ds = new DataSource();
JdbcTransactionFactory jtf = new JdbcTransactionFactory();
Environment env = new Environment("dev", jtf, ds);
Configuration conf = new Configuration(env);
conf.addMappers("mapper path");
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(conf);
try(SqlSession ss = ssf.openSession()){
ss.selectOne("key", args);
or
ss.getMapper(Mapper.class).method(arg);
}
以上简单代码使用java code形式代替config.xml, 可以更加直观了解mybatis做了什么. 其实mybatis执行sql获取结果封装成对象所需要的外部条件只需要DataSource和sql, SqlSession内部也只是对
传统的Datasource操作做了封装. 以上代码最重要的类是Configuration, 封装了datasource, SqlSession的构建也依赖于他.
首先来分析Configuration.addMappers做了什么, 通过源码调用过Configuration.MapperRegistry.addMappers(packageName)-获取packageName下的所有class(Object的子类)-> MapperRegistry.addMapper(Class) 该class未被解析过则放MapperRegistry中的Map<Class, MapperProxyFactory>中, 构建MapperAnnotationBuilder(Configuration, Class)去解析-> MapperAnnotationBuilder.parse 首先判断class是否解析过(通过class的全限定名, 解析过得放入Configuration中的Set<String> loadedResources中), 未解析的话就会去加载对应位置的Mapper.xml(class的全限定名+.xml), 通过XMLMapperBuilder.parse
解析的结果存储在Configuration的Map<String, MappedStatement> mappedStatements中, 其中key为mapper.xml 中的namespace+id, value为MappedStatement,该类包含了sql的详细信息(sql, cache, 参数map, 返回值类型, useCache,
timeout等),(这也就是为什么mapper.xml必须要和mapper.java位于相同目录下, 其实通过MapperAnnoattionBuilder.loadXmlResource的执行过程可知,我们可以通过XMLMapperBuilder自己手动加载指定目录下的mapper.xml, spring也是这么做的), 加载对应的mapper.xml文件后也会去解析Class所有方法, 因为mybatis提供了给予注解的方式(@Select, @Delete)同样会加载到Configuration的mappedStatements中, 如果同时提供了注解和mapper.xml 声明会报错.
接下来看SqlSession的创建和执行过程. new SqlSessionFactoryBuilder().build(conf)-> new DefaultSqlSessionFactory(config) , SqlSessionFactory.openSession() -> new DefaultSqlSession(configuration, executor, autoCommit). 这里比较重要的类就是Executor, 文档没有给出详细的说明, 但通过其声明的接口可知,所有的数据库操作都是通过Executor执行的, 而Executor的实现类有SimpleExecurot, ClosedExecutor, ResuExecutor, BatchExecutor, CacingExecutor(Executor的类型可以通过Configuration中的defaultExecutorType设置, 默认为ExecutorType.SIMPLE, CachingExecutor通过cacheEnable), 以SimpleExecutor为例, DefaultSqlSession.selectOne -> 从 Configuration 中通过key获取MappedStatement交由Executor.query(MappedStatement, wrapCollection(parameter), RowBounds, ResultHandler)-> BaseExecutor.query 从MappedStatement获取BoundSql, 构建CacheKey, ResultHandler==null 从localcaceh获取结果否则queryFromDatabase-> doQuery(MappedStatement, parameter, RouwBounds, ResultHandler) -> SimpleExecutor实现对应的方法, 通过Configuration.newStatementHandler(Executor, MappedStatement, parameter, RowBounds, ResultHandler, BoundSql)构造StatementHandler, StatementHandler通过MappedStatement获取PreparedStatement,然后StatementHandler.query(Statement, ResultHandler), 这里具体的StatementHandler为RoutingStatementHandler采用委托模式具体的StatementHandler 根据MappedStatement.getStatementType而定( 默认的为StatementType.PREPARED) -> PreparedStatementHandler.query, PreparedStatement.execute().
return ResultSetHandler.handleResultSets(preparedStatement).
还有一种方式是通过SqlSession.getMapper去执行, Mapper为接口, 显然此处使用了jdk的动态代理, 从Configuration中的mapperRegistry中获取代理对象工厂进而获取代理对象, 所有的Mapper接口都会生成对应的MapperProxy,MapperProxy implements InvocationHandler, invoke方法通过MapperMethod.execute(SqlSession, args), 还是通过SqlSession 去执行, 通过方法名和类名就可以从Configuration中获取对应的MapperStatement.
mybatis Configuration 中的InterceptorChain 可以对StatementHandler, Executor, ParameterHandler 进行拦截处理, 实现原理也是基于jdk动态代理, 相应的interceptor要声明@Interceptors其中@Signature中type参数为接口