• 执行增删改查时获取SQL语句


    mapper.xml中的sql会在MybatisAutoConfiguration中构建SqlSessionFactory时得到解析,如果有where if之类的标签会被解析成DynamicSqlSource,如果是普通的查询语句(select * from departments where department_id=#{depId})则会被解析成RawSqlSource,这个属性会被存在configuration的mappedstatements属性中,属性名称为sqlSource。然后执行查询时,会从sqlSource中拿到对应的原始sql,然后再进行解析,得到完整的sql。方法就是对应的SqlSource类的getBoundsql方法。

    查询为例
    查询方法由SqlSessionTemplate中创建DefaultSqlSession,然后执行DefaultSqlSession的selectList方法。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    然后继续执行查询方法
    执行executor.query方法,executor为CachingExecutor类型。

    在这里插入图片描述

    如果引入了pagehelper依赖,这里执行前会进入分页拦截器
    在这里插入图片描述
    关键方法是这个getBoundsql,就是通过这个方法获取到sql的。
    在这里插入图片描述
    BoundSql

    public class BoundSql {
        // 一个完整的 SQL 语句,可能会包含问号 ? 占位符
        private final String sql;
        // 参数映射列表,SQL 中的每个 #{xxx} 占位符都会被解析成相应的 ParameterMapping 对象
        private final List<ParameterMapping> parameterMappings;
        // 运行时参数,即用户传入的参数,比如 Article 对象,或是其他的参数
        private final Object parameterObject;
        // 附加参数集合,用于存储一些额外的信息,比如 datebaseId 等
        private final Map<String, Object> additionalParameters;
        // additionalParameters 的元信息对象
        private final MetaObject metaParameters;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    getBoundSql方法

      public BoundSql getBoundSql(Object parameterObject) {
      	//获取BoundSql 
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        //获取参数映射
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings == null || parameterMappings.isEmpty()) {
          boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
        }
    
        // check for nested result maps in parameter mappings (issue #30)
        for (ParameterMapping pm : boundSql.getParameterMappings()) {
          String rmId = pm.getResultMapId();
          if (rmId != null) {
            ResultMap rm = configuration.getResultMap(rmId);
            if (rm != null) {
              hasNestedResultMaps |= rm.hasNestedResultMaps();
            }
          }
        }
    
        return boundSql;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    1.动态解析

    这里如果参数是${}这样的,就会走DynamicSqlSource类的getBoundSql
    在这里插入图片描述
    然后执行MixedSqlNode类apply方法把 ${}的内容替换成真实数据
    传入的是原始sql
    在这里插入图片描述
    TextSqlNode类的apply方法
    在这里插入图片描述
    GenericTokenParser类的parse方法
    在这里插入图片描述
    关键方法
    在这里插入图片描述
    TextSqlNode类的handleToken方法
    从context拿到值,根据传进来的key
    在这里插入图片描述
    这时拿到的sql就是完整的sql了
    在这里插入图片描述
    解析if where 动态标签
    DynamicSqlSource类的getBoundSql方法

      public BoundSql getBoundSql(Object parameterObject) {
        DynamicContext context = new DynamicContext(configuration, parameterObject);
        //拿到原始sql 解析if标签 如果判断正确 会拼装if标签下的sql 解析${} 但不会解析#{}
        rootSqlNode.apply(context);
        
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        //调用SqlSourceBuilder的parse方法 解析#{}变成? 这和一开始解析mapper中普通sql时的#{}是相同的方法
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        //拿到BoundSql 并返回 这时只有#{}中的内容还是?后面要处理
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        context.getBindings().forEach(boundSql::setAdditionalParameter);
        return boundSql;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    拿到sql继续执行查询操作

    关键方法
    rootSqlNode.apply(context)
    在这里插入图片描述

    2.静态解析

    如果是#{}
    则会走StaticSqlSource类的getBoundSql方法
    在这里插入图片描述
    执行完参数还是 问号?
    在这里插入图片描述
    然后继续执行查询方法,应该是在最终查询时将实际数据传入的。
    继续CachingExecutor的query方法
    在这里插入图片描述
    然后执行BaseExecutor类的query方法
    没有缓存的话去数据库查询

    在这里插入图片描述
    继续BaseExecutor类的queryFromDatabase方法
    先把key放入缓存,然后继续执行doQuery方法

    在这里插入图片描述
    SimpleExecutor类的doQuery方法
    在这里插入图片描述
    关键方法 prepareStatement() 方法
    SimpleExecutor类的prepareStatement方法
    在这里插入图片描述
    关键方法parameterize
    就是这个方法给 ?赋的值
    RoutingStatementHandler类的parameterize方法
    在这里插入图片描述
    DefaultParameterHandler类的setParameters方法

      public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        //从boundSql拿到参数的标识
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
          for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            if (parameterMapping.getMode() != ParameterMode.OUT) {
              Object value;
              //拿到属性名
              String propertyName = parameterMapping.getProperty();
              if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                value = boundSql.getAdditionalParameter(propertyName);
              } else if (parameterObject == null) {
                value = null;
              } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                value = parameterObject;
              } else {
              	//parameterObject就是传入的值
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                //根据key拿到value 这样就拿到了我们传入的值
                value = metaObject.getValue(propertyName);
              }
              TypeHandler typeHandler = parameterMapping.getTypeHandler();
              JdbcType jdbcType = parameterMapping.getJdbcType();
              if (value == null && jdbcType == null) {
                jdbcType = configuration.getJdbcTypeForNull();
              }
              try {
                // 由类型处理器 typeHandler 向 ParameterHandler 设置参数
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
              } catch (TypeException | SQLException e) {
                throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
              }
            }
          }
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    值不是拼到sql 而是保存在columnMap中,,也就是不拼接sql,而是采用传参的方式。
    在这里插入图片描述

    至此,${} 和#{}的赋值就完成了,还需要分析的是《where》这种标签是怎么解析的,最后就是执行最终的查询了。
    在这里插入图片描述
    todo 查询后对结果进行解析

  • 相关阅读:
    day-65 代码随想录算法训练营(19)图论 part 04
    开发者配置项、开发者选项自定义
    陈宏申:浅谈京东电商商品文案挖掘难点与优化实践
    接口自动化测试的概述及流程梳理~
    精度误差问题与eps
    苏生不惑出品:2024 批量下载知乎回答,文章和想法,导出 excel 和 pdf
    排序算法(一)
    基础复习——数据库SQLite——SQL的基本语法——数据库管理器SQLiteDatabase——数据库帮助器SQLiteOpenHelper...
    老知识复盘-SQL从提交到执行到底经历了什么 | 京东云技术团队
    MySQL高级学习笔记
  • 原文地址:https://blog.csdn.net/weixin_46666822/article/details/130425109