Mybatis源码分析 2:解析XML并映射到Sql

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: # XMLStatementBuilder:对单个XNode节点进行解析,得到具体的SqlSource并以此生成MappedStatement## parseStatementNode方法:```JAVAprivate final MapperBuilderAssistant builderAssistant; // 记录了当前mapper的namespace等基础信息private

XMLStatementBuilder:对单个XNode节点进行解析,得到具体的SqlSource并以此生成MappedStatement

parseStatementNode方法:

private final MapperBuilderAssistant builderAssistant; // 记录了当前mapper的namespace等基础信息
private final XNode context; // 对应当前要解析的语句
private final String requiredDatabaseId;

public void parseStatementNode() {
        // 1. 通过xnode.getStringAttribute获取id、databaseId、节点名称
        String id = this.context.getStringAttribute("id"); 
        String databaseId = this.context.getStringAttribute("databaseId"); 
        if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
            String nodeName = this.context.getNode().getNodeName(); 
            // 2. 将节点名称转换为SqlCommandType类型
            SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); 
            // 3. 判断是否为SELECT、是否需要使用缓存、是否清空缓存、是否对结果排序
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
            boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
            boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
            // 4. 构造XMLIncludeTransformer,解析include节点
            XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant); // 获取基础配置信息
            includeParser.applyIncludes(this.context.getNode());
            // 5. lang属性指定LanguageDriver,解析<selectKey>节点
            String parameterType = this.context.getStringAttribute("parameterType");
            Class<?> parameterTypeClass = this.resolveClass(parameterType);
            String lang = this.context.getStringAttribute("lang");
            LanguageDriver langDriver = this.getLanguageDriver(lang);
            this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
            //  6. 拼接当前mapper的namespace和当前XNode的id作为当前语句的keyStatementId
            String keyStatementId = id + "!selectKey";
            keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
            Object keyGenerator;
            if (this.configuration.hasKeyGenerator(keyStatementId)) {
                keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
            } else {
                keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
            }
            // 7. 创建具体SqlSource
            SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
            // 8. 构造MappedStatement并插入MapperBuilderAssistant的configuration
            StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
            Integer fetchSize = this.context.getIntAttribute("fetchSize");
            Integer timeout = this.context.getIntAttribute("timeout");
            String parameterMap = this.context.getStringAttribute("parameterMap");
            String resultType = this.context.getStringAttribute("resultType");
            Class<?> resultTypeClass = this.resolveClass(resultType);
            String resultMap = this.context.getStringAttribute("resultMap");
            String resultSetType = this.context.getStringAttribute("resultSetType");
            ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
            if (resultSetTypeEnum == null) {
                resultSetTypeEnum = this.configuration.getDefaultResultSetType();
            }

            String keyProperty = this.context.getStringAttribute("keyProperty");
            String keyColumn = this.context.getStringAttribute("keyColumn");
            String resultSets = this.context.getStringAttribute("resultSets");
            this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
        }
    }
  1. 通过xnode.getStringAttribute获取id、databaseId、节点名称
  2. 将节点名称转换为SqlCommandType类型,SqlCommandType包含update、insert、delete、select、flush和unknown
  3. 判断是否为SELECT、是否需要使用缓存、是否清空缓存、是否对结果排序
  4. 构造XMLIncludeTransformer,调用applyIncludes方法解析include节点
public void applyIncludes(Node source) {
        Properties variablesContext = new Properties();
        Properties configurationVariables = this.configuration.getVariables();
        Optional var10000 = Optional.ofNullable(configurationVariables);
        Objects.requireNonNull(variablesContext);
        var10000.ifPresent(variablesContext::putAll);
        // 递归处理子节点,将include节点,替换为sqlFragment节点,将文本TEXT节点中的${}占位符进行替换
        this.applyIncludes(source, variablesContext, false);
    }
  1. 通过Xnode的lang属性指定LanguageDriver,并对\<selectKey>节点进行解析
  2. 拼接当前mapper的namespace和当前XNode的id作为当前语句的keyStatementId
  3. 创建具体SqlSource:XMLLanguageDriver调用XMLScriptBuilder进行构建
public class XMLLanguageDriver implements LanguageDriver{
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        return builder.parseScriptNode();
    }
}
  1. 构造MappedStatement,插入MapperBuilderAssistant的configuration,configuration的插入会根据key是否可缩写来插入一次或两次:
public V put(String key, V value) {
            // 判断是否要抛异常
            if (key.contains(".")) {
                    String shortKey = this.getShortName(key);
                    if (super.get(shortKey) == null) {
                        super.put(shortKey, value);
                    } else {
                        super.put(shortKey, new Ambiguity(shortKey));
                    }
                }
                return super.put(key, value);
        }

例如要插入com.xx.xx.xxxMapper.deleteByPrimaryKey,会插入(com.xx.xx.xxxMapper.deleteByPrimaryKey, ms) 和 (deleteByPrimaryKey, ms)

XMLScriptBuilder:继承BaseBuilder,用于创建SqlSource

SqlSource:构造xml表达式对应的具体sql

  • Represents the content of a mapped statement read from an XML file or an annotation.
  • It creates the SQL that will be passed to the database out of the input parameter received from the user.

SqlSource 接口只有一个方法:根据参数对象获取BoundSql对象

public interface SqlSource {
    BoundSql getBoundSql(Object var1);
}

SqlSource有四个实现类:

  1. DynamicSqlSource: 处理动态sql语句
  2. ProviderSqlSource: 处理注解形式的sql。
  3. RawSqlSource:处理静态sql语句,内部调用了StaticSqlSource
  4. StaticSqlSource:不会对原 SQL 语句进行任何处理

XMLScriptBuilder 内部类:将XNode转为对应的SqlNode

NodeHandler

XMLScriptBuilder定义类内部接口NodeHandler和实现这个接口的内部类xxxHandler

xxxHandler的handleNode方法逻辑:

  1. 调用parseDynamicTags方法得到MixedSqlNode
//parseDynamicTags判断是否是动态sql
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));
            String nodeName;
            // 判断是否是普通的文本sql
            if (child.getNode().getNodeType() != 4 && child.getNode().getNodeType() != 3) {
                if (child.getNode().getNodeType() == 1) {
                    // 对于标签节点,根据nodeName获取NodeHandler
                    nodeName = child.getNode().getNodeName();
                    NodeHandler handler = (NodeHandler)this.nodeHandlerMap.get(nodeName);
                    if (handler == null) {
                        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
                    }
                    // 对当前标签进行处理
                    handler.handleNode(child, contents);
                    // 有标签则为动态sql
                    this.isDynamic = true;
                }
            } else {
                // 对文本节点进行处理
                nodeName = child.getStringBody("");
                TextSqlNode textSqlNode = new TextSqlNode(nodeName);
                // 根据文本中是否有${}判断是否是静态的文本节点
                if (textSqlNode.isDynamic()) {
                    contents.add(textSqlNode);
                    this.isDynamic = true;
                } else {
                    contents.add(new StaticTextSqlNode(nodeName));
                }
            }
        }
}
  1. 对标签进行处理
  2. 根据标签和MixedSqlNode来初始化对应的xxxSqlNode
  3. 将xxxSqlNode当入List列表

以ForEachHandler为例:

private class ForEachHandler implements NodeHandler {
        public ForEachHandler() {
        }

        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
            // 1. 调用parseDynamicTags方法得到MixedSqlNode
            MixedSqlNode mixedSqlNode = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
            // 2. 解析foreach的XNode中的标签和属性
            String collection = nodeToHandle.getStringAttribute("collection");
            String item = nodeToHandle.getStringAttribute("item");
            String index = nodeToHandle.getStringAttribute("index");
            String open = nodeToHandle.getStringAttribute("open");
            String close = nodeToHandle.getStringAttribute("close");
            String separator = nodeToHandle.getStringAttribute("separator");
            // 3. 根据标签和MixedSqlNode来初始化对应的ForEachSqlNode
            ForEachSqlNode forEachSqlNode = new ForEachSqlNode(XMLScriptBuilder.this.configuration, mixedSqlNode, collection, index, item, open, close, separator);
            // 4. 将ForEachSqlNode当入List<SqlNode>列表
            targetContents.add(forEachSqlNode);
        }
    }

SqlNode

SqlNode有以下实现:

  • 标签节点:
  1. TrimSqlNode: trim标签,TrimHandler处理
  2. WhereSqlNode: where 标签,WhereHandler处理
  3. SetSqlNode:对应 set 标签,SetHandler处理
  4. ForEachSqlNode :foreach 标签,ForEachHandler处理
  5. IfSqlNode:对应 if 、when标签,IfHandler处理
  6. ChooseSqlNode:choose 标签,ChooseHandler处理
  7. VarDeclSqlNode : bind 标签,BindHandler处理
  • 文本节点:
  1. StaticTextSqlNode:纯文本SQL
  2. TextSqlNode:包含 ${} 的SQL

XMLScriptBuilder 创建方法:初始化内部NodeHandler类

public class XMLScriptBuilder extends BaseBuilder {
    private final XNode context;
    private boolean isDynamic;
    private final Class<?> parameterType;
    private final Map<String, NodeHandler> nodeHandlerMap;

    public XMLScriptBuilder(Configuration configuration, XNode context) {
        this(configuration, context, (Class)null);
    }

    public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
        super(configuration);
        this.nodeHandlerMap = new HashMap();
        this.context = context;
        this.parameterType = parameterType;
        this.initNodeHandlerMap();
    }

    private void initNodeHandlerMap() {
        // 初始化XMLScriptBuilder时对NodeHandler的map进行初始化
        this.nodeHandlerMap.put("trim", new TrimHandler());
        this.nodeHandlerMap.put("where", new WhereHandler());
        this.nodeHandlerMap.put("set", new SetHandler());
        this.nodeHandlerMap.put("foreach", new ForEachHandler());
        this.nodeHandlerMap.put("if", new IfHandler());
        this.nodeHandlerMap.put("choose", new ChooseHandler());
        this.nodeHandlerMap.put("when", new IfHandler());
        this.nodeHandlerMap.put("otherwise", new OtherwiseHandler());
        this.nodeHandlerMap.put("bind", new BindHandler());
    }

    private interface NodeHandler {
        void handleNode(XNode var1, List<SqlNode> var2);
    }
}

XMLScriptBuilder 解析方法:构造SqlSource

public SqlSource parseScriptNode() {
        // 1. 查看是sql是否需要动态生成,对this.isDynamic赋值
        MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
        Object sqlSource;
        // 2. 根据是否需要动态生成来初始化不同的SqlSource
        if (this.isDynamic) {
            // 动态则生成 DynamicSqlSource
            sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
        } else {
            // 否则生成RawSqlSource
            sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
        }

        return (SqlSource)sqlSource;
    }
目录
相关文章
|
3月前
|
SQL Java 数据库连接
canal-starter 监听解析 storeValue 不一样,同样的sql 一个在mybatis执行 一个在数据库操作,导致解析不出正确对象
canal-starter 监听解析 storeValue 不一样,同样的sql 一个在mybatis执行 一个在数据库操作,导致解析不出正确对象
|
4月前
|
XML Web App开发 JavaScript
XML DOM 解析器
XML DOM 解析器
|
4月前
|
XML Web App开发 JavaScript
XML DOM 解析器
XML DOM 解析器
|
4月前
|
XML Java 数据格式
手动开发-简单的Spring基于XML配置的程序--源码解析
手动开发-简单的Spring基于XML配置的程序--源码解析
100 0
|
SQL Java 关系型数据库
V$SQLAREA解析
V$SQLAREA lists statistics on shared SQL area and contains one row per SQL string.
828 0
|
5月前
|
关系型数据库 MySQL 网络安全
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")
5-10Can't connect to MySQL server on 'sh-cynosl-grp-fcs50xoa.sql.tencentcdb.com' (110)")
|
7月前
|
SQL 存储 监控
SQL Server的并行实施如何优化?
【7月更文挑战第23天】SQL Server的并行实施如何优化?
169 13
|
7月前
|
SQL
解锁 SQL Server 2022的时间序列数据功能
【7月更文挑战第14天】要解锁SQL Server 2022的时间序列数据功能,可使用`generate_series`函数生成整数序列,例如:`SELECT value FROM generate_series(1, 10)。此外,`date_bucket`函数能按指定间隔(如周)对日期时间值分组,这些工具结合窗口函数和其他时间日期函数,能高效处理和分析时间序列数据。更多信息请参考官方文档和技术资料。
|
7月前
|
SQL 存储 网络安全
关系数据库SQLserver 安装 SQL Server
【7月更文挑战第26天】
88 6
|
7月前
|
存储 SQL C++
对比 SQL Server中的VARCHAR(max) 与VARCHAR(n) 数据类型
【7月更文挑战7天】SQL Server 中的 VARCHAR(max) vs VARCHAR(n): - VARCHAR(n) 存储最多 n 个字符(1-8000),适合短文本。 - VARCHAR(max) 可存储约 21 亿个字符,适合大量文本。 - VARCHAR(n) 在处理小数据时性能更好,空间固定。 - VARCHAR(max) 对于大文本更合适,但可能影响性能。 - 选择取决于数据长度预期和业务需求。
554 1

推荐镜像

更多