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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 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;
    }
目录
相关文章
|
1月前
|
SQL 存储 JSON
SQL,解析 json
SQL,解析 json
71 8
|
1月前
|
SQL 关系型数据库 MySQL
数据库导入SQL文件:全面解析与操作指南
在数据库管理中,将SQL文件导入数据库是一个常见且重要的操作。无论是迁移数据、恢复备份,还是测试和开发环境搭建,掌握如何正确导入SQL文件都至关重要。本文将详细介绍数据库导入SQL文件的全过程,包括准备工作、操作步骤以及常见问题解决方案,旨在为数据库管理员和开发者提供全面的操作指南。一、准备工作在导
273 0
|
20天前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
1月前
|
SQL Java 数据库连接
mybatis使用四:dao接口参数与mapper 接口中SQL的对应和对应方式的总结,MyBatis的parameterType传入参数类型
这篇文章是关于MyBatis中DAO接口参数与Mapper接口中SQL的对应关系,以及如何使用parameterType传入参数类型的详细总结。
37 10
|
15天前
|
SQL 监控 安全
员工上网行为监控软件:SQL 在数据查询监控中的应用解析
在数字化办公环境中,员工上网行为监控软件对企业网络安全和管理至关重要。通过 SQL 查询和分析数据库中的数据,企业可以精准了解员工的上网行为,包括基础查询、复杂条件查询、数据统计与分析等,从而提高网络管理和安全防护的效率。
26 0
|
1月前
|
SQL 监控 数据库
SQL语句是否都需要解析及其相关技巧和方法
在数据库管理中,SQL(结构化查询语言)语句的使用无处不在,它们负责数据的查询、插入、更新和删除等操作
|
2月前
|
SQL XML Java
mybatis复习03,动态SQL,if,choose,where,set,trim标签及foreach标签的用法
文章介绍了MyBatis中动态SQL的用法,包括if、choose、where、set和trim标签,以及foreach标签的详细使用。通过实际代码示例,展示了如何根据条件动态构建查询、更新和批量插入操作的SQL语句。
mybatis复习03,动态SQL,if,choose,where,set,trim标签及foreach标签的用法
|
1月前
|
SQL 存储 数据库
SQL语句是否都需要解析及其相关技巧与方法
在数据库管理系统中,SQL(Structured Query Language)语句作为与数据库交互的桥梁,其执行过程往往涉及到一个或多个解析阶段
|
1月前
|
SQL Oracle 关系型数据库
SQL整库导出语录:全面解析与高效执行策略
在数据库管理和维护过程中,整库导出是一项常见的需求,无论是为了备份、迁移还是数据分析,掌握如何高效、准确地导出整个数据库至关重要
|
1月前
|
SQL 安全 Windows
SQL安装程序规则错误解析与解决方案
在安装SQL Server时,用户可能会遇到安装程序规则错误的问题,这些错误通常与系统配置、权限设置、依赖项缺失或版本不兼容等因素有关

热门文章

最新文章

推荐镜像

更多
下一篇
无影云桌面