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

简介: # 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 XML Java
通过MyBatis的XML配置实现灵活的动态SQL查询
总结而言,通过MyBatis的XML配置实现灵活的动态SQL查询,可以让开发者以声明式的方式构建SQL语句,既保证了SQL操作的灵活性,又简化了代码的复杂度。这种方式可以显著提高数据库操作的效率和代码的可维护性。
246 18
|
8月前
|
SQL Java 数据库连接
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
|
8月前
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
1995 1
|
7月前
|
SQL Java 数据库连接
MyBatis动态SQL字符串空值判断,这个细节99%的程序员都踩过坑!
本文深入探讨了MyBatis动态SQL中字符串参数判空的常见问题。通过具体案例分析,对比了`name != null and name != &#39;&#39;`与`name != null and name != &#39; &#39;`两种写法的差异,指出后者可能引发逻辑混乱。为避免此类问题,建议在后端对参数进行预处理(如trim去空格),简化MyBatis判断逻辑,提升代码健壮性与可维护性。细节决定成败,严谨处理参数判空是写出高质量代码的关键。
925 0
|
3月前
|
SQL Java 数据库连接
SSM相关问题-1--#{}和${}有什么区别吗?--Mybatis都有哪些动态sql?能简述一下动 态sql的执行原理吗?--Spring支持的几种bean的作用域 Scope
在MyBatis中,`#{}`是预处理占位符,可防止SQL注入,适用于大多数参数传递场景;而`${}`是直接字符串替换,不安全,仅用于动态表名、列名等特殊场景。二者在安全性、性能及使用场景上有显著区别。
77 0
|
6月前
|
SQL XML Java
菜鸟之路Day35一一Mybatis之XML映射与动态SQL
本文介绍了MyBatis框架中XML映射与动态SQL的使用方法,作者通过实例详细解析了XML映射文件的配置规范,包括namespace、id和resultType的设置。文章还对比了注解与XML映射的优缺点,强调复杂SQL更适合XML方式。在动态SQL部分,重点讲解了`&lt;if&gt;`、`&lt;where&gt;`、`&lt;set&gt;`、`&lt;foreach&gt;`等标签的应用场景,如条件查询、动态更新和批量删除,并通过代码示例展示了其灵活性与实用性。最后,通过`&lt;sql&gt;`和`&lt;include&gt;`实现代码复用,优化维护效率。
518 5
|
8月前
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
241 15
|
8月前
|
SQL Java 数据库连接
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
|
8月前
|
SQL 缓存 Java
框架源码私享笔记(02)Mybatis核心框架原理 | 一条SQL透析核心组件功能特性
本文详细解构了MyBatis的工作机制,包括解析配置、创建连接、执行SQL、结果封装和关闭连接等步骤。文章还介绍了MyBatis的五大核心功能特性:支持动态SQL、缓存机制(一级和二级缓存)、插件扩展、延迟加载和SQL注解,帮助读者深入了解其高效灵活的设计理念。
|
8月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
766 29

推荐镜像

更多
  • DNS