MyBatis 源码分析-技术分享

本文深入剖析MyBatis框架的内部结构,详细介绍其主要组件的功能与作用,包括Configuration、SqlSession、Executor、StatementHandler、ParameterHandler、ResultSetHandler、TypeHandler、MappedStatement、SqlSource、BoundSql等,同时探讨了MyBatis的初始化过程、运行机制及缓存策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
        •  
  • 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 {
    • interceptorChain.pluginAll(executor) 在configuration 内部注册所有的 plugin8248e5d9dcda47e9cbaeb39c5514c316c09.jpg
    • 本质就是getSignatureMap 方法,扫描所有注解,对配置的Signature 方法进行 动态代理
      • 4c781061dd6c10dc53ba63705dcb8b18f4c.jpg
    • 代理类就是public class Plugin implements InvocationHandler
    • 执行Plugin 的invoke 会判断该方法是否被代理(signatureMap 里面有没有)
      • 1880d85f8d3c7dd0a8c9cac089c61f45abc.jpg
    • 如果有执行 intercept 方法
      • eb8e35e2c92ee98da035e32ecf9680358e7.jpg
    • 该方法最后一行执行的proceed 方法,其实就是该方法的invoke 执行
      • 00420ec3273c67d1c01aec2d5c725703ba4.jpg
  • 手写TypeHandler:
    • 详见 催收系统CalendarTypeHandler
  • 缓存:
    • 一级缓存:
      • 一级缓存默认开启,SqlSession 级别的
      • 验证:
        • 相同的查询,连续查询两遍,记录查询用时,会发现第二次快得多
        • update、 insert、delete 等语句会触发清除缓存
      • 一级缓存,在存在俩sqlsession 时,可能存在脏数据的情况
        • 比如,sqlsessionA 两次相同查询t 表中间,
        • sqlsessionB 更新了t表数据,
        • sqlsessionA 第二次查询的数据就是可能已被修改的脏数据
    • 二级缓存:
      • 二级缓存默认关闭,SqlSessionFactory 级别的 
      • 更不靠谱,开启方式:
        • d12a327bc43e297aa1ded20b29d55fb768b.jpg
  • N+1问题:
    • 存在级联查询(嵌套查询)中
      • 外层查询的一条结果数据,由内层查询获得
      • 外层查询一次,获得结果数N ,就要进行N 次内层查询
        • (官方不鼓励使用,这样产生大量1+N次查询)
    • 由于1+N 问题的性能损耗,可以考虑配合使用 延时加载
      • d101d0d6509292ae24f0659116b91af4497.jpg
    • 官网解释:
      • 5845b01e3aa3089bc8aabd09142db4c1f6f.jpg
  • lazy loading 是怎么做到的?
    • 懒加载在级联查询时用到了,SimpleStatementHandler 里面query结果
      • a81c4c8420dc04e41207cc9a7748be98dc5.jpg
    • DefaultResultSetHandler 处理结果
    • handleResultSets -->handleResultSets -->...getRowValue-->createResultObject 
      • 17fe760ba07e21165b9d1488d7f1d265ee0.jpg
    • 如果有嵌套查询且开启了懒加载 那么会使用代理工厂来处理(代理工厂类型cglib或javasissit类型(默认))
    • 针对某一个属性,当执行
      • 4d3fe3554a50a436b798466e4026f38e8e4.jpg
      • 新版本,已经变化:
        • be54fa64d5c64613f5a753440cdefe427e6.jpg
      • 解释为什么是“或”的关系:lazyLoadTriggerMethods 包含该方法的时候,
        • 说明对象再进行 equals、clone比较,需要所有属性全部查询来才能进行
          • protected Set<String> lazyLoadTriggerMethods =
            • new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
    • 在嵌套查询的时候 get/set 方法会触发 ResultLoaderMap LoadPair load() 方法去查询(我看源代码的理解),
    • 我找到了触发函数lazyLoadTriggerMethods 里面没有get/is 
      • 依赖的是PropertyNamer.isGetter(methodName)

 

 

 

 

转载于:https://my.oschina.net/u/3847203/blog/3019311

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值