MyBatis 源码分析
- MyBatis的主要成员
- Configuration
- MyBatis所有的配置信息都保存在Configuration对象之中,
- 配置文件中的大部分配置都会存储到该类中
- SqlSession
- 作为MyBatis工作的主要顶层API,
- 表示和数据库交互时的会话,完成必要数据库增删改查功能
- Executor
- MyBatis执行器,
- 是MyBatis 调度的核心,
- 负责SQL语句的生成和查询缓存的维护
- StatementHandler
- 封装了JDBC Statement操作,
- 负责对JDBC statement 的操作,
- 如设置参数等
- ParameterHandler
- 负责对用户传递的参数转换成JDBC Statement 所对应的数据类型
- ResultSetHandler
- 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
- TypeHandler
- 负责java数据类型和jdbc数据类型(也可以说是数据表列类型)之间的映射和转换
- MappedStatement
- MappedStatement维护一条<select|update|delete|insert>节点的封装
- SqlSource
- 负责根据用户传递的parameterObject,
- 动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
- BoundSql
- 表示动态生成的SQL语句以及相应的参数信息
-
knownMappers.put(type, new MapperProxyFactory<T>(type));
- sqlSessionFactory.openSession();
-
openSessionFromDataSource
-
- Configuration
- spring-mybatis
-
ClassPathMapperScanner doScan方法的真正调用地方
-
definition.setBeanClass(this.mapperFactoryBean.getClass()); 注册 beanDefinition class 为mapperFactoryBean
-
-
xmlConfigBuilder
-
configuration = xmlConfigBuilder.getConfiguration();
-
-
XMLStatementBuilder
-
builderAssistant.addMappedStatement...
-
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(...
-
-
-
XMLMapperBuilder 解析xml 配置文件,包括 mapper.xml 的配置
-
xmlMapperBuilder.parse();
-
bindMapperForNamespace 绑定 mapper
-
configuration.addMapper(boundType);
-
mapperRegistry.addMapper(type);
-
knownMappers.put(type, new MapperProxyFactory<T>(type));
-
-
-
-
-
-
return this.sqlSessionFactoryBuilder.build(configuration);
-
return new DefaultSqlSessionFactory(config);
-
-
MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
-
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } }
-
-
SqlSessionTemplate 本质是 SqlSession 的装饰器
-
this.sqlSessionProxy = (SqlSession) newProxyInstance( // sqlSessionProxy 本质是 SqlSession的动态代理 new Class[] { SqlSession.class }, new SqlSessionInterceptor());
-
-
if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); // 初始化插件 if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered plugin: '" + plugin + "'"); } } }
-
interceptorChain.addInterceptor(interceptor);
-
protected final InterceptorChain interceptorChain = new InterceptorChain(); // InterceptorChain 是configuration 的成员变量
- 插件的调用是在 创建四大 Handler 的时候 执行 pluginAll
- 是在程序执行阶段
比如:doUpdate 时候,执行 newParameterHandler
-
StatementHandler handler = configuration.newStatementHandler(
-
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; }
-
-
- 是在程序执行阶段
-
-
processPropertyPlaceHolders
- 执行属性的处理,简单的说,
- 就是把xml中${XXX}中的XXX替换成属性文件中的相应的值
- 执行属性的处理,简单的说,
-
findCandidateComponents
-
if (isCandidateComponent(sbd)) {
-
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); }
-
-
-
- 初始化阶段:
- 程序运行阶段:
- 基本运行:
-
public static void main(String[] args) { //定义 SqlSessionFactory SqlSessionFactory sqlSessionFactory = null; try { //使用配置文件创建 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build( Resources.getResourceAsReader("mybatis-config.xml")); } catch (IOException ex) { //打印异常. Logger.getLogger(MainCh1.class.getName()).fatal("创建 SqlSessionFactory失败", ex); return; } //定义 sqlSession SqlSession sqlSession = null; try { //用sqlSessionFactory创建sqlSession sqlSession = sqlSessionFactory.openSession(); //获取Mapper UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //执行Mapper接口方法. UserPO user = userMapper.findUser(1); //打印信息 System.err.println(user.getUsername()); } finally { //使用完后要记得关闭sqlSession资源 if (sqlSession != null) { sqlSession.close(); } } }
-
- spring 托管:
- 添加配置
<import resource="spring-mybatis.xml"/>
- 添加配置
- 基本运行:
- 插件:
- 以分页插件为例:
- 需要 继承 Interceptor
-
@Intercepts( { @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}), } )
public class PageInterceptor implements Interceptor {
-
- 需要 继承 Interceptor
- interceptorChain.pluginAll(executor) 在configuration 内部注册所有的 plugin
- 本质就是getSignatureMap 方法,扫描所有注解,对配置的Signature 方法进行 动态代理
- 代理类就是public class Plugin implements InvocationHandler
- 执行Plugin 的invoke 会判断该方法是否被代理(signatureMap 里面有没有)
- 如果有执行 intercept 方法
- 该方法最后一行执行的proceed 方法,其实就是该方法的invoke 执行
- 以分页插件为例:
- 手写TypeHandler:
- 详见 催收系统CalendarTypeHandler
- 缓存:
- 一级缓存:
- 一级缓存默认开启,SqlSession 级别的
- 验证:
- 相同的查询,连续查询两遍,记录查询用时,会发现第二次快得多
- update、 insert、delete 等语句会触发清除缓存
- 一级缓存,在存在俩sqlsession 时,可能存在脏数据的情况
- 比如,sqlsessionA 两次相同查询t 表中间,
- sqlsessionB 更新了t表数据,
- sqlsessionA 第二次查询的数据就是可能已被修改的脏数据
- 二级缓存:
- 二级缓存默认关闭,SqlSessionFactory 级别的
- 更不靠谱,开启方式:
- 一级缓存:
- N+1问题:
- 存在级联查询(嵌套查询)中
- 外层查询的一条结果数据,由内层查询获得
- 外层查询一次,获得结果数N ,就要进行N 次内层查询
- (官方不鼓励使用,这样产生大量1+N次查询)
- 由于1+N 问题的性能损耗,可以考虑配合使用 延时加载
- 官网解释:
- 存在级联查询(嵌套查询)中
- lazy loading 是怎么做到的?
- 懒加载在级联查询时用到了,SimpleStatementHandler 里面query结果
- DefaultResultSetHandler 处理结果
- handleResultSets -->handleResultSets -->...getRowValue-->createResultObject
- 如果有嵌套查询且开启了懒加载 那么会使用代理工厂来处理(代理工厂类型cglib或javasissit类型(默认))
- 针对某一个属性,当执行
- 新版本,已经变化:
- 解释为什么是“或”的关系:lazyLoadTriggerMethods 包含该方法的时候,
- 说明对象再进行 equals、clone比较,需要所有属性全部查询来才能进行
- protected Set<String> lazyLoadTriggerMethods =
- new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
- protected Set<String> lazyLoadTriggerMethods =
- 说明对象再进行 equals、clone比较,需要所有属性全部查询来才能进行
- 在嵌套查询的时候 get/set 方法会触发 ResultLoaderMap LoadPair load() 方法去查询(我看源代码的理解),
- 我找到了触发函数lazyLoadTriggerMethods 里面没有get/is
- 依赖的是PropertyNamer.isGetter(methodName)
- 懒加载在级联查询时用到了,SimpleStatementHandler 里面query结果