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

引言

在上文,我们发现直接使用 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 文件的解析变得更加简单和高效。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习:对评论信息的情感分析,建立模型,自动识别评论信息的情绪状态完整代码实现

评论 思考&#xff1a;向模型中传递数据时&#xff0c;需要提前处理好数据 1、目标&#xff1a;将评论内容转换为词向量。 2、每个词/字转换为词向量长度(维度)200 3、每一次传入的词/字的个数是否就是评论的长度? 应该是固定长度&#xff0c;每次传入数据与图像相似…

DIY我的世界磁力方块

引子 小朋友喜欢我的世界&#xff0c;就像当年我那代对俄罗斯方块的执着&#xff0c;考虑电子游戏伤眼睛&#xff0c;所以最近开始给小朋友买磁力方块。 一个将近1元多的价格&#xff0c;催生我DIY的念头。 正文 Freecad图&#xff0c;A,B,C,D处 放磁铁 5.14g 材料费 最后的成…

Axure中继器单选、多选和重置

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 课程主题&#xff1a;Axure中继器单选、多选和重置 主要内容&#xff1a;根据查询条件&#xff0c;通过单选、多选和重置&#xff0c;从中继器中得到数据 应用场景&…

DockerCompose快速部署Java项目、nginx前端和mysql数据库到centos虚拟机

简介&#xff1a;整理自&#xff1a;SpringCloud微服务开发与实战&#xff0c;java黑马商城项目微服务实战开发&#xff08;涵盖MybatisPlus、Docker、MQ、ES、Redis高级等&#xff09;课程的飞书文档。 DockerCompose介绍 大家可以看到&#xff0c;我们部署一个简单的java项…

stm32实现esp8266连接到TCP服务器(二)未完

1.2 连接到TCP Server 1.2.1 使用网络助手&#xff0c;设立TCP服务器 ​ 编辑 1.2.2 连接服务器 ATCIPSTART"TCP","192.168.1.18",8080 //指令&#xff0c;注意双引号逗号都要半角(英文)输入 CONNECT //结果&#xff1a;成功 OK //结果&#xff1a;成功 …

[C++]ecplise C++新建项目跑hello world

测试通过版本&#xff1a; ecplise-cpp 2024-09 ecplise-cpp 2020-09 【前提】 安装好MinGW环境&#xff0c;实际测试不需要下载什么CDT插件就可以运行了。 步骤&#xff1a; &#xff08;1&#xff09;打开ecplise,选择launch 选择File->New->C/C Project 选择C M…

Java_数组的使用

一、数组的介绍 数组可以存放多个同一类型的数据。数组也是一种数据类型&#xff0c;是引用类型。 即&#xff1a;数&#xff08;数据&#xff09;组&#xff08;一组&#xff09;就是一组数据 二、代码演示 public class Array01 {public static void main(String[] args) …

DMAIC赋能智能家居:解锁未来生活新篇章!

从清晨自动拉开的窗帘&#xff0c;到夜晚自动调暗的灯光&#xff0c;每一处细节都透露着科技的温度与智慧的光芒。而在这场智能革命的浪潮中&#xff0c;DMAIC&#xff08;定义Define、测量Measure、分析Analyze、改进Improve、控制Control&#xff09;作为六西格玛管理的核心方…

React之组件渲染性能优化

关键词&#xff1a; shouldComponentUpdate、PureComnent、React.memo、useMemo、useCallback shouldComponentUpdate 与 PureComnent shouldComponentUpdate 与 PureComnent 用于类组件。虽然官方推荐使用函数组件&#xff0c;但我们依然需要对类组件的渲染优化策略有所了解…

10 排序算法:冒泡排序与快速排序(算法原理、算法实现、时间和空间复杂度分析)

目录 1 十大常见的排序算法 1.1 算法的稳定性 2 冒泡排序 2.1 算法原理 2.2 算法实现 2.3 时间空间复杂度分析 2.3.1 时间复杂度分析 2.3.2 空间复杂度分析 3 快速排序 3.1 算法原理 3.1.1 排序思想 3.1.2 递归过程 3.2 示例 3.2.1 示例 1 3.2.2 示例 2 3.2.3 …

RHCE--网络服务

第一章 例行性工作 1、单一执行的例行性工作&#xff08;at&#xff09; 1.1 查看at命令 at的黑名单&#xff08;deny&#xff09;、白名单&#xff08;allow&#xff09;&#xff1b;两个文件若都不存在则只有root用户能使用 at工作调度对应的系统服务 atd&#xff1a;at的…

N9305高品质mp3音频语音芯片ic在早教故事机的应用方案

随着人们对教育的重视程度不断提高&#xff0c;儿童早教机已经成为了很多家庭的教育必备品。N9305音乐芯片在早教故事机中的应用&#xff0c;不仅为孩子们带来了丰富多彩的故事世界&#xff0c;还以其卓越的音质表现和功能&#xff0c;进一步提升了早教体验。 九芯电子N9305高品…

单片机——ADC采样

1、什么是ADC采样&#xff1f; ADC是指将模拟信号转换成数字信号的过程。通俗理解ADC采样就是采集电路中的电压&#xff0c;通过数值的方式表现出来。以STM32F103系列为例&#xff0c;它可以反应0~4095&#xff0c;换句话说&#xff0c;它采集的电压数值上表现为0~4095&#xf…

前端文件流导出

1、前端代码 ​ /** 导出 */ const handleExport async () > {let config {responseType: blob,headers: {Content-Type: application/json,},};const res await getTargetExport(config);const blob new Blob([res]);const fileName PK目标跟进导出列表.xls;const li…

WEB前端作业1

<!DOCTYPE html> <html><head><meta charset"utf-8"><title>用户注册页面</title></head><style type"text/css">#center{text-align: center;background-color: #e9e9e9;}tr td,th{border:1px solid whi…

linux线程 | 同步与互斥 | 互斥(下)

前言&#xff1a;本篇文章主要讲述linux线程的互斥的知识。 讲解流程为先讲解锁的工作原理&#xff0c; 再自己封装一下锁并且使用一下。 做完这些就要输出一堆理论性的东西&#xff0c; 但博主会总结两条结论&#xff01;&#xff01;最后就是讲一下死锁。 那么&#xff0c; 废…

Java-多线程2

什么是线程&#xff1f; 线程是 cpu调度和执行的单位。 多个线程共享进程的堆和方法区资源&#xff0c;但每个线程有自己的程序计数器、虚拟机栈和本地方法栈。 如何实现线程 继承Thread类 实现步骤&#xff1a; 创建自定义类&#xff0c;继承Thread类 重写run方法 创建自定…

深度学习面试笔试之循环神经网络(RNN)、门控循环单元(GRU)、长短期记忆(LSTM)

深度学习面试笔试之循环神经网络RNN、门控循环单元GRU、长短期记忆LSTM 循环神经网络(RNN)1. 什么是RNN1.1 RNN的应用1.2 为什么有了CNN&#xff0c;还要RNN?1.3 RNN的网络结构1.4 双向RNN1.5 BPTT算法 2. 其它类型的RNN3. CNN与RNN的区别4. 为什么RNN 训练的时候Loss波动很大…

aws(学习笔记第七课) 私有子网使用NAT服务器

aws(学习笔记第七课) AWS的私有子网使用NAT服务器 学习内容&#xff1a; AWS的私有子网使用NAT服务器 1. AWS的私有子网使用NAT服务器 在上面的例子的网络构成图中&#xff0c;可能会发现一个问题。就是Private Subnet的Apache server无法访问互联网。比如&#xff0c;当需要…

MySQL【知识改变命运】10

联合查询 0.前言1.联合查询在MySQL里面的原理2.练习一个完整的联合查询2.1.构造练习案例数据2.2 案例&#xff1a;⼀个完整的联合查询的过程2.2.1. 确定参与查询的表&#xff0c;学⽣表和班级表2.2.2. 确定连接条件&#xff0c;student表中的class_id与class表中id列的值相等2.…