【从零开发Mybatis】引入XNode和XPathParser

news2025/1/18 20:55:18

引言

在上文,我们发现直接使用 DOM库去解析XML 配置文件,非常复杂,也很不方便,需要编写大量的重复代码来处理 XML 文件的读取和解析,代码可读性以及可维护性相当差,使用起来非常不灵活。

因此,文本将在原先代码的基础上,引入XNode和XPathParser类,让 MyBatis 的配置文件解析更加简洁、灵活和高效。

XNode和XPathParser功能说明

XNode

XNode :用于封装 XML 节点,提供对 XML 节点的访问和操作能力。XNode 的设计主要是为了让开发者能够更方便地处理 XML 文件中的节点数据,而不需要直接使用 DOM 的底层 API。

XPathParser

XPathParser :用于解析 XML 文档并提供便捷的 XML 节点访问机制的类。内部使用XPath 来定位 XML 文档中的节点,使得开发者可以通过简洁的 XPath 表达式来访问 XML 节点。

XNode类实现

XNode 类为 MyBatis 提供了一种简洁的方式处理 XML 文件中的节点信息。通过封装 Node 对象,并提供一系列方法来解析节点的属性、文本内容以及子节点,XNode 类使得 XML 文件的解析变得更加简单和高效。

以下是我们当前版本设计的XNode 源码:

package org.apache.ibatis.parsing;

import org.w3c.dom.CharacterData;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * XML 节点的类封装
 *
 * @author crazy coder
 * @since 2024/9/27
 **/
public class XNode {
    private final Node node;
    private final XPathParser xpathParser;
    private final Properties attributes;
    private final String body;

    // 构造函数
    public XNode(XPathParser xpathParser, Node node) {
        this.xpathParser = xpathParser;
        this.node = node;
        this.attributes = parseAttributes(node);
        this.body = parseBody(node);
    }

    // 私有方法用于解析节点的属性
    private Properties parseAttributes(Node n) {
        Properties attributes = new Properties();
        NamedNodeMap attributeNodes = n.getAttributes();
        if (attributeNodes != null) {
            for (int i = 0; i < attributeNodes.getLength(); i++) {
                Node attribute = attributeNodes.item(i);
                attributes.put(attribute.getNodeName(), attribute.getNodeValue());
            }
        }
        return attributes;
    }

    // 私有方法用于解析节点的文本内容
    private String parseBody(Node node) {
        String data = getBodyData(node);
        if (data == null) {
            NodeList children = node.getChildNodes();
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);
                data = getBodyData(child);
                if (data != null) {
                    break;
                }
            }
        }
        return data;
    }

    // 辅助方法用于获取节点的文本内容
    private String getBodyData(Node child) {
        if (child.getNodeType() == Node.CDATA_SECTION_NODE || child.getNodeType() == Node.TEXT_NODE) {
            return ((CharacterData) child).getData();
        }
        return null;
    }

    // 公开方法用于评估 XPath 表达式并返回一个 XNode
    public XNode evalNode(String expression) {
        return xpathParser.evalNode(node, expression);
    }

    // 公开方法用于获取当前节点的所有子节点
    public List<XNode> getChildren() {
        List<XNode> children = new ArrayList<>();
        NodeList nodeList = node.getChildNodes();
        if (nodeList != null) {
            for (int i = 0, n = nodeList.getLength(); i < n; i++) {
                Node node = nodeList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    children.add(new XNode(xpathParser, node));
                }
            }
        }
        return children;
    }

    // 公开方法用于获取节点的文本内容
    public String getBody() {
        return body;
    }

    // 公开方法用于获取节点的属性
    public Properties getAttributes() {
        return attributes;
    }
}

XNode 类封装了一个 Node 对象,并提供了多种方法来访问和操作这个节点。这个类主要用于解析 XML 文件中的各个节点,并提供了方便的方式来获取节点的属性、文本内容以及子节点等信息,下面是对这个类的主要功能

  1. 构造函数:
    • 初始化 XNode 对象,传入 XPathParser 和 Node 对象。
    • 解析节点的属性和文本内容。
  2. 属性解析:
    • parseAttributes 方法用于解析节点的属性信息,并将其存储在 Properties 对象中。
  3. 文本内容解析:
    • parseBody 方法用于解析节点的文本内容。
    • getBodyData 辅助方法用于获取具体的文本或 CDATA 内容。
  4. 节点评估:
    • evalNode 方法用于评估 XPath 表达式,并返回一个 XNode 对象。
  5. 子节点获取:
    • getChildren 方法用于获取当前节点的所有子节点,并返回一个 XNode 对象列表。
  6. 获取文本内容和属性:
    • getBody 方法用于获取节点的文本内容。
    • getAttributes 方法用于获取节点的属性。
XNode使用场景

XNode 类通常用于解析 MyBatis 配置文件中的节点信息,例如从 元素中提取 SQL 映射信息。它可以方便地处理节点的属性、文本内容以及子节点,从而简化 XML 文件的解析逻辑。

XPathParser类实现

XPathParser 类是一个用于解析 XML 文档并提供便捷的 XML 节点访问机制的类。它利用了 Java 标准库中的 javax.xml.parsers 和 javax.xml.xpath 包来解析 XML 文档,并通过 XPath 表达式来定位和访问 XML 节点。


package org.apache.ibatis.parsing;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.*;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.io.Reader;

/**
 * 用于解析 XML 文档并提供便捷的 XML 节点访问机制的类
 *
 * @author crazy coder
 * @since 2024/9/27
 **/
public class XPathParser {

    private final Document document;
    private XPath xpath;

    public XPathParser(Reader reader) {
        this.xpath = XPathFactory.newInstance().newXPath();
        try {
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            this.document = builder.parse(new InputSource(reader));
        } catch (Exception e) {
            throw new BuilderException("Error creating document instance.  Cause: " + e, e);
        }
    }

    public XNode evalNode(String expression) {
        return evalNode(document, expression);
    }

    public XNode evalNode(Object root, String expression) {
        Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
        if (node == null) {
            return null;
        }
        return new XNode(this, node);
    }

    private Object evaluate(String expression, Object root, QName returnType) {
        try {
            return xpath.evaluate(expression, root, returnType);
        } catch (Exception e) {
            throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
        }
    }
}

主要功能

  1. 构造函数:
    • 初始化 XPathParser 对象,并解析传入的 Reader 中的 XML 文档。
  2. XPath 表达式评估:
    • evalNode 方法用于评估 XPath 表达式,并返回一个 XNode 对象。
    • evaluate 方法是私有的,用于执行 XPath 表达式的评估,并处理可能抛出的异常。
使用场景

XPathParser 类可以用于解析 MyBatis 配置文件或其他 XML 文件,并从中提取特定的节点信息。例如,在 MyBatis 中,可以使用 XPathParser 来解析 文件,并获取 、、 和 等映射节点。

基于XNode和XPathParser重写SqlSession类

原先的SqlSession直接使用原生的DOM库操作XML,相当的烦琐费事,因此我们引入设计了XNode和XPathParser类,以下代码是基于XNode和XPathParser类改造后的SqlSession类:

package org.apache.ibatis.session;

import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.*;
import java.util.*;

/**
 * Sql会话
 *
 * @author crazy coder
 * @since 2024/9/27
 **/
public class SqlSession {
    public String selectOne(String statement, Integer param) throws IOException {

        final String configResource = "MapperConfig.xml";
        InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(configResource);
        Reader reader = new InputStreamReader(in);

        // 读取XML配置文件
        XPathParser xPathParser = new XPathParser(reader);

        XNode configNode = xPathParser.evalNode("/configuration");

        // 解析XML配置信息 - 数据源
        XNode dataSourceNode = configNode.evalNode("dataSource");
        Properties datasourceProperties = new Properties();
        for (XNode pxNode : dataSourceNode.getChildren()) {
            datasourceProperties.put(pxNode.getAttributes().get("name"), pxNode.getAttributes().get("value"));
        }
        // 驱动
        String driver = datasourceProperties.getProperty("driver");
        // 数据库连接 URL
        String url = datasourceProperties.getProperty("url");
        // 数据库用户名
        String username = datasourceProperties.getProperty("username");
        // 数据库密码
        String password = datasourceProperties.getProperty("password");


        // 读取Mapper配置文件
        List<String> resourceMapperList = new ArrayList<>();
        XNode mappersNode = configNode.evalNode("mappers");
        for (XNode pxNode : mappersNode.getChildren()) {
            resourceMapperList.add(pxNode.getAttributes().get("resource").toString());
        }

        // 解析Mapper
        Map<String, String> statementMap = new HashMap<>();
        for (String mapperResource : resourceMapperList) {
            try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(mapperResource)) {
                Reader mapperReader = new InputStreamReader(inputStream);
                XPathParser mapperXPathParser = new XPathParser(mapperReader);
                XNode mapperNode = mapperXPathParser.evalNode("/mapper");
                String namespace = mapperNode.getAttributes().getProperty("namespace");

                XNode selectNode = mapperNode.evalNode("select");
                String id = selectNode.getAttributes().getProperty("id");
                String sql = selectNode.getBody();
                statementMap.put(namespace + "." + id, sql);
            }
        }


        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            // 加载 MySQL JDBC 驱动
            Class.forName(driver);

            // 获取数据库连接
            conn = DriverManager.getConnection(url, username, password);

            // 准备 SQL 语句
            String sql = statementMap.get(statement);

            // 创建预编译语句
            pstmt = conn.prepareStatement(sql);

            // 设置参数
            pstmt.setLong(1, param);

            // 执行 SQL 查询操作
            rs = pstmt.executeQuery();

            // 处理结果集
            StringBuilder result = new StringBuilder();
            while (rs.next()) {
                result.append("id: ").append(rs.getInt("id"))
                        .append(", username: ").append(rs.getString("username"))
                        .append(", email: ").append(rs.getString("email"));

            }
            return result.toString();

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            in.close();
        }

        return "";
    }
}

当前版本SqlSession代码相对于上个版本,主要变更点如下

  • 读取配置文件:
    加载 MapperConfig.xml 配置文件,并使用 XPathParser 的 evalNode 方法找到 /configuration 节点。
  • 解析数据源信息:
    调用XNode的getChildren方法,获取数据源所有子节点(驱动、URL、用户名和密码),通过调用XNode的getAttributes方法获取属性值。
  • 读取 Mapper 文件:
    加载配置文件中指定的 Mapper 文件,并解析 Mapper 文件中的 SQL 映射信息,通过XNode的getBody方法获取SQL。

整体项目结构

在这里插入图片描述

总结

本文我们实现了以下功能:

  • XNode类实现:通过封装 Node 对象,并提供一系列方法来解析节点的属性、文本内容以及子节点,XNode 类使得 XML 文件的解析变得更加简单和高效。
  • XPathParser类实现:通过封装 XML 文档的解析和 XPath 表达式的评估,XPathParser 类使得 XML 文件的解析变得更加简单和高效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2214169.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaEE 多线程第二节 (多线程的简单实现Thread/Runable)

1. 创建线程&#xff08;继承 Thread 类&#xff09;步骤&#xff1a; 继承 Thread 类&#xff1a; 创建一个类并继承 Thread 类&#xff0c;然后重写 run() 方法&#xff0c;在该方法中写入线程执行的代码 class MyThread extends Thread {Overridepublic void run()…

数据恢复超简单!9 个方法任你选!小白也能轻易恢复数据!

在当今数字化的世界中&#xff0c;数据恢复的重要性日益凸显。无论是工作中的重要文档&#xff0c;还是生活中的珍贵照片和视频&#xff0c;一旦丢失&#xff0c;都可能给我们带来极大的困扰。别担心&#xff0c;下面为大家介绍 9 个超简单的数据恢复方法&#xff0c;让小白也能…

C++基础面试题 | 什么是C++中的运算符重载?

文章目录 回答重点&#xff1a;示例&#xff1a; 运算符重载的基本规则和注意事项&#xff1a; 回答重点&#xff1a; C的运算符重载是指可以为自定义类型&#xff08;如类或结构体&#xff09;定义运算符的行为&#xff0c;使其像内置类型一样使用运算符。通过重载运算符&…

它思科技CTO聂玮奇:消除“AI幻觉”,搭建高可靠对话云平台丨数据猿专访

大数据产业创新服务媒体 ——聚焦数据 改变商业 近年来&#xff0c;大模型技术在全球范围内引起了广泛关注和应用热潮。 提到人工智能&#xff0c;很多人会想到它强大的运算能力和广泛的应用场景。如今&#xff0c;语言模型的发展如火如荼&#xff0c;但其中的“幻觉”问题却带…

基于SSM社区医院预约转诊管理系统JAVA|VUE|Springboot计算机毕业设计源代码+数据库+LW文档+开题报告+答辩稿+部署教+代码讲解

源代码数据库LW文档&#xff08;1万字以上&#xff09;开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统&#xff1a;Window操作系统 2、开发工具&#xff1a;IntelliJ IDEA或者Eclipse 3、数据库存储&#xff1a…

SwiftUI 6.0(iOS 18)自定义容器值(Container Values)让容器布局渐入佳境(上)

概述 我们在之前多篇博文中已经介绍过 SwiftUI 6.0&#xff08;iOS 18&#xff09;新增的自定义容器布局机制。现在&#xff0c;如何利用它们对容器内容进行“探囊取物”和“聚沙成塔”&#xff0c;我们已然胸有成竹了。 然而&#xff0c;除了上述鬼工雷斧般的新技巧之外&…

STM32_实验1_建立新工程

1、使用STM32CubeIDE建立一个新工程 1.1选择时钟源为外部晶振时钟。 1.2选择调试方式为 serial wire&#xff08;串行线&#xff09;。 1.3配置时钟树. 1.4选择以 c 和 h 文件型式管理工程文件。 1.5生成 hex 可执行文件。&#xff08;完成后点击锤子&#xff09; 2.串口输出调…

鸿蒙进入“无人区”:该如何闯关?

按照华为方面的说法&#xff0c;“打造鸿蒙操作系统是三大战役&#xff0c;目前已经完成了底座和体验两大战役&#xff0c;第三大战役则是生态。”生态固然重要&#xff0c;但要让鸿蒙与当今世界主流操作系统抗衡&#xff0c;乃至成为新一代操作系统中的翘楚&#xff0c;其实还…

每个程序员都应该了解的硬件知识

作者:shizhaoyang 在追求高效代码的路上,我们不可避免地会遇到代码的性能瓶颈。为了了解、解释一段代码为什么低效,并尝试改进低效的代码,我们总是要了解硬件的工作原理。于是,我们可能会尝试搜索有关某个架构的介绍、一些优化指南或者阅读一些计算机科学的教科书(如:计…

卡码网KamaCoder 94. 城市间货物运输 I

题目来源&#xff1a;94. 城市间货物运输 I C题解1&#xff08;来源代码随想录&#xff09;&#xff1a;Bellman_ford 本题是经典的带负权值的单源最短路问题&#xff0c;此时就轮到Bellman_ford登场了。Bellman_ford算法的核心思想是 对所有边进行松弛n-1次操作&#xff08;…

【 ACM独立出版】第二届通信网络与机器学习国际学术会议(CNML 2024,10月25-27)

官方信息 会议官网&#xff1a;www.cn-ml.org The 2nd International Conference on Communication Networks and Machine Learningwww.cn-ml.org 时间地点&#xff1a;2024年10月25-27日 | 中国-河南-郑州 截稿时间&#xff1a;2024年10日19日 &#xff08;多轮截稿&#x…

51单片机的晾衣架控制系统【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块温湿度传感器光照传感器步进电机按键、LED、蜂鸣器等模块构成。适用于智能晾衣架等相似项目。 可实现功能: 1、LCD1602实时显示温湿度、光照强度和手动/自动信息 2、温湿度传感器DHT11采集温湿度信息 3、光照传感…

【数据结构与算法初阶】顺序表(上)

什么语言实现不重要&#xff0c;理解了思路就行&#xff0c;本篇使用C语言实现 一.顺序表含义(重要) 首先&#xff0c;顺序表属于线性表中的一中&#xff0c;线性表可以用多种方式实现&#xff0c;顺序表只是其中的一种 --------- 线性表是啥呢&#xff0c;通俗的说&#xff0…

lego-loam imageProjection.cpp源码注释(一)

一、主函数 int main(int argc, char** argv){ros::init(argc, argv, "lego_loam");ImageProjection IP;ROS_INFO("\033[1;32m---->\033[0m Image Projection Started.");ros::spin();return 0; }主函数很简单&#xff0c;常规ros初始化ros::init&…

程序员35岁丢了工作,应该怎么活?

35岁对很多程序员来说是个敏感的年龄段。在这个阶段&#xff0c;许多程序员已经有了丰富的工作经验和较高的薪水&#xff0c;但同时也面临着职场上不可忽视的年龄压力。尤其在一些技术密集型的公司&#xff0c;35岁之后的程序员可能被认为“年纪大了”&#xff0c;不再是招聘市…

【C语言】动态内存管理及相关笔试题

文章目录 一、为什么有动态内存分配二、malloc和free1.malloc函数的使用2.free函数的使用 三、calloc和realloc1.calloc函数的使用2.realloc函数的使用 四、常见动态内存分配的错误五、动态内存经典笔试题题1题2题3 六、总结C/C中程序内存区域划分 一、为什么有动态内存分配 我…

Struct Streaming

spark进行实时数据流计算时有两个工具 Spark Streaming:编写rdd代码处理数据流,可以解决非结构化的流式数据 Structured Streaming:编写df代码处理数据流,可以解决结构化和半结构化的流式数据 实时计算 实时计算&#xff0c;通常也称为“实时流计算”、“流式计算” 流数据处…

面腾讯后台开发,二面挂掉了,,,

随着各厂秋招的开启&#xff0c;收到面试邀请的同学也越来越多。在当年和我一起找实习的同学里面&#xff0c;有实力较强的同学收到了腾讯后台开发的校招面试邀请。但面试不止是实力的竞争&#xff0c;也有很重要的运气的因素。 虽然我的同学在腾讯后台开发的二面中挂掉了&…

Mybatis中的映射文件编写原则

先来回顾一下&#xff0c;在Java项目中如何使用Mybatis执行SQL语句&#xff1a; 添加依赖&#xff1a;在项目中添加 MyBatis 和数据库驱动的依赖。配置 MyBatis&#xff1a;创建 MyBatis 的配置文件&#xff0c;配置数据源和 Mapper 映射。创建 POJO 类&#xff1a;定义与数据…

拒绝飞单,微信监控轻松搞定!

微信作为广泛使用的社交工具&#xff0c;其安全性和监控问题受到了广泛关注。对于企业来说&#xff0c;确保客户资源的安全和防止员工“飞单”是重要的管理挑战。以下是一些有效的方法和工具&#xff0c;可以帮助企业提高微信的安全性&#xff0c;防止飞单&#xff0c;从而保护…