• 第三章-Mybatis源码解析-以xml方式走流程-mapper解析(三)


    3.2.2.6 sqlSource生成

    先看看 SqlSource 是什么,从源码看,它就是一个接口类,只有一个方法,获取 BoundSql ,BoundSql 是真正包装sql的类,里面有原 sql 字符以及对应的参数信息。

    public interface SqlSource {
      BoundSql getBoundSql(Object parameterObject);
    }
    
    public class BoundSql {
      private final String sql;
      private final List<ParameterMapping> parameterMappings;
      private final Object parameterObject;
      private final Map<String, Object> additionalParameters;
      private final MetaObject metaParameters;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    章节3.2.2.5中提到,生成SqlSource需要 langDriver,由上文得知,默认的 langDriver 是 XMLLanguageDriver,那么我们进入该类中创建SqlSource的方法

    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        // 结合前面的内容,发现有很多地方用到Builder,看图`3-1`,它们的基类都是BaseBuilder
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        // 开始解析,继续往里探
        return builder.parseScriptNode();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    图3-1

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    走进**XMLScriptBuilder 类的 parseScriptNode **方法

    private void initNodeHandlerMap() {
        // 先看这个方法,看到下面的key是不是很熟悉,写sql时,经常会用到,sql中包含这些标签的,都会被当作 Dynamic 的 sqlSource,需要继续解析,还有${}包装的条件语句,也会被当作 Dynamic 的
        nodeHandlerMap.put("trim", new TrimHandler());
        nodeHandlerMap.put("where", new WhereHandler());
        nodeHandlerMap.put("set", new SetHandler());
        nodeHandlerMap.put("foreach", new ForEachHandler());
        nodeHandlerMap.put("if", new IfHandler());
        nodeHandlerMap.put("choose", new ChooseHandler());
        nodeHandlerMap.put("when", new IfHandler());
        nodeHandlerMap.put("otherwise", new OtherwiseHandler());
        nodeHandlerMap.put("bind", new BindHandler());
    }
    
    public SqlSource parseScriptNode() {
        // 解析 Dynamic 的 sqlSource,最终拿到半成品 MixedSqlNode(组合sql节点)
        MixedSqlNode rootSqlNode = parseDynamicTags(context);
        SqlSource sqlSource;
        // 是否 Dynamic,在 initNodeHandlerMap 方法中已经介绍过了
        if (isDynamic) {
            // 这一步就是创建了 DynamicSqlSource,并且 DynamicSqlSource 内部也没有做什么,就是对 configuration, rootSqlNode 两个赋值
            sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
        } else {
            // parameterType 是上面调用链一路带过来的,就是标签 parameterType 中设置的字符器对应的 Class,继续往里探,看后面`RawSqlSource 的创建`描述
            sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
        }
        return sqlSource;
    }
    
    protected MixedSqlNode parseDynamicTags(XNode node) {
        List<SqlNode> contents = new ArrayList<>();
        // 拿到并遍历子节点
        NodeList children = node.getNode().getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            XNode child = node.newXNode(children.item(i));
            if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
                // 文本节点,也就是非等的标签节点
                String data = child.getStringBody("");
                TextSqlNode textSqlNode = new TextSqlNode(data);
                // 这里主要是判断语句是不是包含${},如果有,则也定义为 Dynamic,并放到contents集合中,作下一步处理
                if (textSqlNode.isDynamic()) {
                    contents.add(textSqlNode);
                    isDynamic = true;
                } else {
                    // Static 的,也存放到contents集合中
                    contents.add(new StaticTextSqlNode(data));
                }
            } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
                // 等的标签节点
                String nodeName = child.getNode().getNodeName();
                // 取出 initNodeHandlerMap 方法中定义的 nodeName 对应的 handler,这里以标签为例
                NodeHandler handler = nodeHandlerMap.get(nodeName);
                if (handler == null) {
                    throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
                }
                
                handler.handleNode(child, contents);
                isDynamic = true;
            }
        }
        return new MixedSqlNode(contents);
    }
    
    // 标签的继续解析
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        // 又回到parseDynamicTags方法
        MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
        // 包装成 WhereSqlNode,也存放到contents集合中
        WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
        targetContents.add(where);
    }
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    RawSqlSource 的创建

    public class RawSqlSource implements SqlSource {
    
        private final SqlSource sqlSource;
    
        public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
            this(configuration, getSql(configuration, rootSqlNode), parameterType);
        }
    
        public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
            // 构建 SqlSourceBuilder 对象
            SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
            // 参数类型
            Class<?> clazz = parameterType == null ? Object.class : parameterType;
            // 将sql解析后封装成sqlSource,继续下探,看`关联1`
            sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
        }
    
        private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
            // 创建 DynamicContext 对象,DynamicContext 可以理解成拼接一个完整sql的过程类,所以叫context上下文,内部主要作用就是拼接sql和获取sql
            DynamicContext context = new DynamicContext(configuration, null);
            // rootSqlNode 此时应该是 MixedSqlNode,继续下探
            rootSqlNode.apply(context);
            // 再取出更完整的半成品sql,供下一步使用
            return context.getSql();
        }
    
        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return sqlSource.getBoundSql(parameterObject);
        }
    
    }
    
    public boolean apply(DynamicContext context) {
        // 遍历List,此时node对应的是 StaticTextSqlNode,继续下探
        contents.forEach(node -> node.apply(context));
        return true;
    }
    
    public class StaticTextSqlNode implements SqlNode {
        private final String text;
    
        public StaticTextSqlNode(String text) {
            this.text = text;
        }
    
        @Override
        public boolean apply(DynamicContext context) {
            // 通过 DynamicContext 将sql拼接起来,内部其实就是一个StringBuilder在工作
            context.appendSql(text);
            return true;
        }
    
    }
    
    // 关联1
    public class SqlSourceBuilder extends BaseBuilder {
    
        private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
    
        public SqlSourceBuilder(Configuration configuration) {
            super(configuration);
        }
        // 这个方法其实就是把#{}包起来的部分全部解析成jdbc时PreparedStatement表示语句时的参数,用?来表示
        public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
            ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
            GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
            // 有兴趣的读者可以去里面看看具体解析过程,就是对字符串中的字符一个个遍历判断
            String sql = parser.parse(originalSql);
            // 最终封装成 StaticSqlSource 返回,handler.getParameterMappings() 这个要解释一下,后续做参数赋值时需要用到,也就是维护#{}里面的参数名、类型等信息,后续设置参数值时会根据名称和类型来处理
            return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
        }
    }
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    sqlSource生成后,最终还要创建MappedStatement,前者只是把mapper中的sql都解析出来了,后者是要把整个sql相关的全部串起来,包括参数值的设置、缓存、keyGenerator、结果集等,最终执行器执行时,都跟这有关。那么,接下来看看如何生成MappedStatement,先看看 MappedStatement 有什么,能做什么。

    // 部分代码省略了,比如get/set方法
    public final class MappedStatement {
      // 所对应的mapper文件地址,比如xxxx/xx/xx/xxMapper.xml
      private String resource;
      // 全局配置信息维护类
      private Configuration configuration;
      // id 也就是命名空间
      private String id;
      // 下面的这些属性值在前面的章节都有介绍,这里就不赘述了
      private Integer fetchSize;
      private Integer timeout;
      private StatementType statementType;
      private ResultSetType resultSetType;
      private SqlSource sqlSource;
      private Cache cache;
      private ParameterMap parameterMap;
      private List<ResultMap> resultMaps;
      private boolean flushCacheRequired;
      private boolean useCache;
      private boolean resultOrdered;
      private SqlCommandType sqlCommandType;
      // 这个介绍下,简单点,就把它想成类似主键值的生成的行为
      private KeyGenerator keyGenerator;
      // keyProperty : selectKey 语句结果应该被设置到的目标属性。如果生成列不止一个,可以用逗号分隔多个属性名称
      private String[] keyProperties;
      // keyColumn	返回结果集中生成列属性的列名。如果生成列不止一个,可以用逗号分隔多个属性名称。
      private String[] keyColumns;
      private boolean hasNestedResultMaps;
      private String databaseId;
      private Log statementLog;
      private LanguageDriver lang;
      // resultSets	这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。一般用在存储过程调用
      private String[] resultSets;
    
      MappedStatement() {
        // constructor disabled
      }
    
      public static class Builder {
        // 创建 MappedStatement 对象
        private MappedStatement mappedStatement = new MappedStatement();
    
        public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
          // 根据传参设置 MappedStatement 对象属性值
          mappedStatement.configuration = configuration;
          mappedStatement.id = id;
          mappedStatement.sqlSource = sqlSource;
          mappedStatement.statementType = StatementType.PREPARED;
          mappedStatement.resultSetType = ResultSetType.DEFAULT;
          mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build();
          mappedStatement.resultMaps = new ArrayList<>();
          mappedStatement.sqlCommandType = sqlCommandType;
          mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          String logId = id;
          if (configuration.getLogPrefix() != null) {
            logId = configuration.getLogPrefix() + id;
          }
          mappedStatement.statementLog = LogFactory.getLog(logId);
          mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
        }
    
       
      }
    
    
      public BoundSql getBoundSql(Object parameterObject) {
        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;
      }
    
      private static String[] delimitedStringToArray(String in) {
        if (in == null || in.trim().length() == 0) {
          return null;
        } else {
          return in.split(",");
        }
      }
    
    }
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    介绍完 MappedStatement 后,现在从创建它的入口开始介绍,如何创建它,继续往下看,看 MapperBuilderAssistant.addMappedStatement 方法

    public MappedStatement addMappedStatement(
        String id,
        SqlSource sqlSource,
        StatementType statementType,
        SqlCommandType sqlCommandType,
        Integer fetchSize,
        Integer timeout,
        String parameterMap,
        Class<?> parameterType,
        String resultMap,
        Class<?> resultType,
        ResultSetType resultSetType,
        boolean flushCache,
        boolean useCache,
        boolean resultOrdered,
        KeyGenerator keyGenerator,
        String keyProperty,
        String keyColumn,
        String databaseId,
        LanguageDriver lang,
        String resultSets) {
    
        if (unresolvedCacheRef) {
            throw new IncompleteElementException("Cache-ref not yet resolved");
        }
    
        id = applyCurrentNamespace(id, false);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        // 这一步就是通过 MappedStatement.Builder 创建 MappedStatement 对象,并给其属性赋值
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
            .resource(resource)
            .fetchSize(fetchSize)
            .timeout(timeout)
            .statementType(statementType)
            .keyGenerator(keyGenerator)
            .keyProperty(keyProperty)
            .keyColumn(keyColumn)
            .databaseId(databaseId)
            .lang(lang)
            .resultOrdered(resultOrdered)
            .resultSets(resultSets)
            .resultMaps(getStatementResultMaps(resultMap, resultType, id))
            .resultSetType(resultSetType)
            .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
            .useCache(valueOrDefault(useCache, isSelect))
            .cache(currentCache);
        // 获取参数映射数据,看`关联1`
        ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
        if (statementParameterMap != null) {
            // 将 parameterMap 赋值到 mappedStatement 中,后面有用
            statementBuilder.parameterMap(statementParameterMap);
        }
        // 这一步也没做什么,就是把 mappedStatement.resultMaps 的List,设置为不可修改的List
        MappedStatement statement = statementBuilder.build();
        // 将 MappedStatement 对象,添加到 configuration 对应的 HashMap中,key = 语句id(命名空间+标签语句定义的 id),value = statement
        configuration.addMappedStatement(statement);
        return statement;
    }
    // 关联1
    private ParameterMap getStatementParameterMap(
        String parameterMapName,
        Class<?> parameterTypeClass,
        String statementId) {
        parameterMapName = applyCurrentNamespace(parameterMapName, true);
        ParameterMap parameterMap = null;
        // 这一块是对 parameterMap 的处理,前面已经讲了,这个属性已经被废弃了,就不浪费篇章来讲了
        if (parameterMapName != null) {
            try {
                parameterMap = configuration.getParameterMap(parameterMapName);
            } catch (IllegalArgumentException e) {
                throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
            }
        } else if (parameterTypeClass != null) {
            // 用 parameterType 设置的值的 Class 来构建一个 parameterMap,此时还没有对参数进行赋值处理
            List<ParameterMapping> parameterMappings = new ArrayList<>();
            parameterMap = new ParameterMap.Builder(
                configuration,
                statementId + "-Inline",
                parameterTypeClass,
                parameterMappings).build();
        }
        return parameterMap;
    }
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    到此,MappedStatement 的创建已经完成了,为执行sql前的准备,全部做足了。

    后续将通过抖音视频/直播的形式分享技术,由于前期要做一些准备和规划,预计2024年6月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。
    在这里插入图片描述

  • 相关阅读:
    从Mpx资源构建优化看splitChunks代码分割
    Java接口统一格式模板以及获取前一天时间固定时间方法(add和set区别)
    [毕业设计源码下载]精品基于Python实现的学生在线选课系统[包运行成功]
    一篇读懂 Linux 用户管理
    vue集成海康h5player实现播放
    一文读懂Python异常
    sql刷题595. 大的国家
    Qt入门(七)——TCP传输协议(利用多线程实现多个客户机与服务器连接)
    Hololens2部署很慢可能是unity工程选择不对
    最常用的结构体初始化方式
  • 原文地址:https://blog.csdn.net/zhang527294844/article/details/136353272