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;
    }
目录
相关文章
|
SQL Java 数据库连接
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
3242 1
|
Java 数据库连接 API
Java 对象模型现代化实践 基于 Spring Boot 与 MyBatis Plus 的实现方案深度解析
本文介绍了基于Spring Boot与MyBatis-Plus的Java对象模型现代化实践方案。采用Spring Boot 3.1.2作为基础框架,结合MyBatis-Plus 3.5.3.1进行数据访问层实现,使用Lombok简化PO对象,MapStruct处理对象转换。文章详细讲解了数据库设计、PO对象实现、DAO层构建、业务逻辑封装以及DTO/VO转换等核心环节,提供了一个完整的现代化Java对象模型实现案例。通过分层设计和对象转换,实现了业务逻辑与数据访问的解耦,提高了代码的可维护性和扩展性。
502 1
|
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;`实现代码复用,优化维护效率。
1287 5
|
12月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
361 0
|
SQL 存储 Java
Mybatis源码解析:详述初始化过程
以上就是MyBatis的初始化过程,这个过程主要包括SqlSessionFactory的创建、配置文件的解析和加载、映射文件的加载、SqlSession的创建、SQL的执行和SqlSession的关闭。这个过程涉及到了MyBatis的核心类和接口,包括SqlSessionFactory、SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、Configuration、SqlSession和Executor等。通过这个过程,我们可以看出MyBatis的灵活性和强大性,它可以很好地支持定制化SQL、存储过程以及高级映射,同时也避免了几
299 20
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
611 15
|
SQL Java 数据库连接
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
|
Java 关系型数据库 数据库连接
Javaweb之Mybatis入门程序的详细解析
本文详细介绍了一个MyBatis入门程序的创建过程,从环境准备、Maven项目创建、MyBatis配置、实体类和Mapper接口的定义,到工具类和测试类的编写。通过这个示例,读者可以了解MyBatis的基本使用方法,并在实际项目中应用这些知识。
372 11
|
SQL 缓存 Java
框架源码私享笔记(02)Mybatis核心框架原理 | 一条SQL透析核心组件功能特性
本文详细解构了MyBatis的工作机制,包括解析配置、创建连接、执行SQL、结果封装和关闭连接等步骤。文章还介绍了MyBatis的五大核心功能特性:支持动态SQL、缓存机制(一级和二级缓存)、插件扩展、延迟加载和SQL注解,帮助读者深入了解其高效灵活的设计理念。

推荐镜像

更多
  • DNS