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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: # 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
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
26天前
|
SQL Java 数据库连接
mybatis使用四:dao接口参数与mapper 接口中SQL的对应和对应方式的总结,MyBatis的parameterType传入参数类型
这篇文章是关于MyBatis中DAO接口参数与Mapper接口中SQL的对应关系,以及如何使用parameterType传入参数类型的详细总结。
30 10
|
20天前
|
SQL 监控 数据库
SQL语句是否都需要解析及其相关技巧和方法
在数据库管理中,SQL(结构化查询语言)语句的使用无处不在,它们负责数据的查询、插入、更新和删除等操作
|
28天前
|
SQL 存储 数据库
SQL语句是否都需要解析及其相关技巧与方法
在数据库管理系统中,SQL(Structured Query Language)语句作为与数据库交互的桥梁,其执行过程往往涉及到一个或多个解析阶段
|
20天前
|
SQL 数据可视化 BI
SQL语句及查询结果解析:技巧与方法
在数据库管理和数据分析中,SQL语句扮演着至关重要的角色
|
26天前
|
SQL 监控 关系型数据库
SQL错误代码1303解析与处理方法
在SQL编程和数据库管理中,遇到错误代码是常有的事,其中错误代码1303在不同数据库系统中可能代表不同的含义
|
26天前
|
SQL 存储 关系型数据库
SQL默认索引是什么:深入解析与技巧
在SQL数据库中,索引是一种用于提高查询性能的重要数据结构
|
26天前
|
SQL 开发框架 .NET
ASP.NET连接SQL数据库:实现过程与关键细节解析an3.021-6232.com
随着互联网技术的快速发展,ASP.NET作为一种广泛使用的服务器端开发技术,其与数据库的交互操作成为了应用开发中的重要环节。本文将详细介绍在ASP.NET中如何连接SQL数据库,包括连接的基本概念、实现步骤、关键代码示例以及常见问题的解决方案。由于篇幅限制,本文不能保证达到完整的2000字,但会确保
|
27天前
|
Java 数据库连接 Maven
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和MyBatis Generator,使用逆向工程来自动生成Java代码,包括实体类、Mapper文件和Example文件,以提高开发效率。
81 2
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
|
27天前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
45 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块

推荐镜像

更多