【从零开发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 表达式并返回一个 XNodepublic 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");// 数据库连接 URLString 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());}// 解析MapperMap<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.mzph.cn/web/55453.shtml

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

相关文章

o1快慢思考的风又吹到了Agent!

智能体&#xff08;Agent&#xff09;通过自然对话与用户互动有两个任务&#xff1a;交谈和规划/推理。对话回应必须基于所有可用信息&#xff0c;行动必须有助于实现目标。与用户交谈和进行多步推理和规划之间的二分法&#xff0c;类似卡尼曼引入的人类快速思考和慢速思考系统…

库卡ForceTorqueControl(二)

1. 基准坐标系RCS 基准坐标系 RCS 是力 / 力矩控制的参考系。基准坐标系的原点始终是当前的TCP。 1.1 BASE 的 RCS 姿态 基准坐标系的姿态与当前基础坐标系&#xff08;基座坐标系&#xff09;的姿态一致。它不取决于刀具的姿态。基准坐标系的原点是当前的 TCP。 示例&#xff…

【数据库设计】概念结构设计

引入——整体解释 上次我们讲完了关系模型&#xff0c;这次我们来讲新的章节&#xff1a;数据库设计 该怎样有效地管理和存储现实中的数据&#xff1f;答案是设计一个优秀的数据库。现实中的数据转化成关系表中的数据需要经过四个主要的设计步骤。 现实世界需求分析——>…

java常用工具包

Java标准库&#xff08;Java Standard Library&#xff09; 比喻&#xff1a;就像你厨房里的基础调料&#xff0c;没有它们&#xff0c;你很难做出美味的菜肴。Java标准库包含了进行基本编程所需的所有核心类和方法&#xff0c;如字符串处理、集合框架、输入输出操作等。 关键…

C++ 内存布局 - Part6: 虚继承

1. 关于虚继承 虚继承可以在菱形继承体系中&#xff0c;防止派生类中有多份重复祖基类内容。如下图所示&#xff0c;如果是常规继承&#xff0c;Class Final中会有两份Class Base的内容。通过虚继承&#xff0c;即Derived1 虚继承自Base, Derived2 也虚继承自Base, 那么Final中…

003_ipc概述及信号

【背景】 程序运行起来后&#xff0c;每个模块都有自己的进程&#xff0c;那么不同的模块如何进行通讯或者数据交换呢&#xff1f; 上面这张图说明了linux的ipc是继承最初的Unix 的IPC逻辑的&#xff0c;那么具体关系和概述讲解&#xff0c;请参考此链接的原文&#xff1a;htt…

mac 桌面版docker no space left on device

报错信息 docker pull镜像时报&#xff1a; failed to register layer: Error processing tar file(exit status 1): write /home/admin/oceanbase_bak/bin/observer: no space left on device 解决 增加 docker 虚拟磁盘大小。 调整完点击重启即可。

助力语音技术发展,景联文科技提供语音数据采集服务

语音数据采集是语音识别技术、语音合成技术以及其他语音相关应用的重要基础。采集高质量的语音数据有助于提高语音识别的准确性&#xff0c;同时也能够促进语音技术的发展。 景联文科技作为专业的数据采集标注公司&#xff0c;支持语音数据采集。可通过手机、专业麦克风阵列、专…

两个案例全面阐述全链路测试怎么做

首先我们先针对全链路功能测试部分进行一下讲解。去年的时候&#xff0c;有一家电商公司可能知道我一直在帮银行做相关的测试&#xff0c;就请我帮他们去做一些规划。这个平台有虚拟订单&#xff0c;也有实体订单&#xff0c;方式不太一样。 还涉及到分账分佣以及跟银行的对接…

大数据-174 Elasticsearch Query DSL - 全文检索 full-text query 匹配、短语、多字段 详细操作

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

计算机网络基础(1)

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络基础 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. 计算机网…

简易CPU设计入门:验证取指令模块【未完成】

项目代码下载 还是请大家首先准备好本项目所用的源代码。如果已经下载了&#xff0c;那就不用重复下载了。如果还没有下载&#xff0c;那么&#xff0c;请大家点击下方链接&#xff0c;来了解下载本项目的CPU源代码的方法。 下载本项目代码 准备好了项目源代码以后&#xff…

MySQL详细学习攻略 MySQL基础非常全面教程 MySQL安装教程

MySQL安装教程 章节目录 一、MySQL简介与安装前准备 二、Windows系统下MySQL的安装 三、Linux系统下MySQL的安装 四、MySQL安装后的基本配置 五、MySQL服务的启动与停止 六、MySQL客户端工具的使用 七、MySQL安装常见问题与解决方案 一、MySQL简介与安装前准备 重点内容知识…

Flythings学习(四)串口通信

文章目录 1 串口编程基本步骤1.1 打开串口1.2 配置串口 1.3 读串口1.4 发送串口1.5 关闭串口 2 综合使用3 如何在软件上保证串口稳定通信4 flythings中的串口通讯5 协议接收部分使用和修改方法6 通讯协议数据怎么和UI控件对接 1 串口编程基本步骤 串口通信有5个步骤 1.打开串口…

YOLOv11模型改进-注意力机制-引入自适应稀疏自注意力ASSA

随着目标检测领域的快速发展&#xff0c;YOLO系列模型凭借其端到端、高效的检测性能逐渐成为工业界和学术界的标杆。然而&#xff0c;如何进一步优化YOLOv11的特征提取能力&#xff0c;减少冗余信息并提升模型对复杂场景的适应性&#xff0c;仍是一个值得深入探讨的问题。为此&…

Android:记录一个打包发布版的release包以后闪退的问题

个人感觉其实release闪退的问题挺难排查的&#xff0c;因为release包运行起来as捕获不到相应的应用程序进程&#xff0c;从而不易查看到日志&#xff0c;也是我玩得不溜&#xff0c;大家有不同的方法可以评论区探讨&#xff0c;我也定期回复一些评论一起讨论。以下是我遇到的情…

【数据结构】宜宾大学-计院-实验三

线性表的应用——实现两多项式的相加 课前准备&#xff1a;实验学时&#xff1a;2实验目的&#xff1a;实验内容&#xff1a;实验结果&#xff1a;实验报告:&#xff08;及时撰写实验报告&#xff09;实验测试结果&#xff1a;代码实现&#xff1a;&#xff08;C/C&#xff09;…

安宝特方案 | AR技术在轨交行业的应用优势

随着轨道交通行业不断向智能化和数字化转型&#xff0c;传统巡检方式的局限性日益凸显。而安宝特AR眼镜以其独特的佩戴方式和轻便设计&#xff0c;为轨道交通巡检领域注入了创新活力&#xff0c;提供了全新的解决方案。 01 多样化佩戴方法&#xff0c;完美适应户外环境 安宝特…

访问控制列表(课内实验)

实验2&#xff1a;访问控制列表 实验目的及要求&#xff1a; 通过实验&#xff0c;进一步的理解标准ACL与扩展ACL的工作原理及执行过程。理解通配符的概念&#xff0c;熟练掌握标准ACL与扩展ACL的配置指令&#xff0c;掌握将访问控制列表应用VTY线路上&#xff0c;并且能够判断…

鸿蒙开发 四十五 鸿蒙状态管理(嵌套对象界面更新)

当运行时的状态变量变化&#xff0c;UI重新渲染&#xff0c;在ArkUI中称为状态管理机制&#xff0c;前提是变量必须被装饰器修饰。不是状态变量的所有更改都会引起刷新&#xff0c;只有可以被框架观测到的更改才会引起UI刷新。其中boolen、string、number类型&#xff0c;可观察…