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

embedded/2024/10/21 5:41:26/

引言

在上文,我们发现直接使用 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.ppmy.cn/embedded/129190.html

相关文章

Spring Boot:中小型医院网站开发新选择

3 系统分析 3.1 可行性分析 通过对本基于Spring Boot的中小型医院网站实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、操作可行性、经济可行性和时间可行性四方面进行分析。 3.1.1 技术可行性 本基于Spring Boot的中小型…

01.单例模式设计思想

01.单例模式设计思想 目录介绍 01.单例模式基础介绍 1.1 模式的动机1.2 单例模式特点1.3 单例模式定义1.4 单例使用场景1.5 单例模式思考 02.单例模式设计思考 2.1 为何要用单例2.2 处理资源访问冲突2.3 表示全局唯一类 03.如何实现单例模式 3.1 如何实现一个单例3.2 饿汉式实…

软件设计模式------简单工厂模式

简单工厂模式&#xff08;Simple factory Pattern&#xff09;&#xff0c;又称静态工厂方法(Static Factory Method),属于创新型模式&#xff0c;但它不属于GoF23个设计模式其一。 一、模式动机&#xff1a; 有时需要创建一些来自相同父类的类的实例。 二、定义&#xff1a…

iPhone照片内存怎么清理,参考这些方法

随着拍摄数量的增加&#xff0c;许多iPhone用户常常发现自己的手机存储空间不足&#xff0c;而照片无疑是占用空间的罪魁祸首之一。清理这些照片不仅能释放存储空间&#xff0c;还能提升设备的运行速度。小编将分享一些iPhone照片内存怎么清理的高效策略&#xff0c;助你告别冗…

AsyncTask的工作原理和缺陷

AsyncTask的工作原理及其缺陷 AsyncTask是Android平台提供的一个轻量级的异步任务类&#xff0c;它允许开发者在后台线程中执行耗时操作&#xff0c;并在操作完成后将结果回调到主线程以更新UI。AsyncTask内部封装了线程池和Handler机制&#xff0c;简化了多线程编程的复杂性。…

深入理解Android WebView的加载流程与事件回调

文章目录 一、WebView 加载流程时序图二、WebView 加载流程回调函数说明三、AwContents3.1 主要功能和职责3.2 架构和实现3.3 使用场景 四、利用WebView回调函数检测白屏4.1 使用onPageStarted和onPageFinished检测加载时间4.2 利用onReceivedError和onReceivedHttpError检测加…

微信小程序-WXS

文章目录 微信小程序-WXS概述语法文档内联WXS脚本外联WXS脚本 微信小程序-WXS 概述 WXS&#xff08;WeiXin Script&#xff09;是小程序的一套脚本语言&#xff0c;可以结合 WXML 构建出页面结构。 WXS 的应用场景是“过滤器”&#xff0c;所谓的过滤器是指在渲染数据之前&a…

如何使用Websocket订阅实时股票价格

WebSocket和HTTP请求在工作原理和使用场景上存在显著区别。首先&#xff0c;HTTP是一种无状态的协议&#xff0c;客户端发起请求&#xff0c;服务器响应后&#xff0c;连接通常会关闭。如果客户端需要再次获取数据&#xff0c;必须发起新的请求。这种"请求-响应"模型…