MyBatis技术内幕

前言

博主mybatis源码学习相关资料:

MyBatis技术内幕

myBatis网站:

mybatis3官网文档地址

简介

ORM 简介

ORM概念

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。

简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

ORM在业务逻辑层和数据库层之间充当了桥梁的作用。

ORM由来

让我们从O/R开始。字母O起源于”对象”(Object),而R则来自于”关系”(Relational)。

几乎所有的软件开发过程中都会涉及到对象和关系数据库。在用户层面和业务逻辑层面,我们是面向对象的。当对象的信息发生变化的时候,我们就需要把对象的信息保存在关系数据库中。

按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂很多SQL语句用来增加、读取、修改、删除相关数据,而这些代码通常都是重复的。

ORM优势

ORM解决的主要问题是对象和关系的映射。它通常把一个类和一个表一一对应,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段。

ORM提供了对数据库的映射,不用直接编写SQL代码,只需像操作对象一样从数据库操作数据。

让软件开发人员专注于业务逻辑的处理,提高了开发效率。

ORM的劣势

ORM的缺点是会在一定程度上牺牲程序的执行效率。

ORM用多了SQL语句就不会写了,关系数据库相关技能退化…

ORM总结

ORM只是一种工具,工具确实能解决一些重复,简单的劳动。这是不可否认的。

但我们不能指望某个工具能一劳永逸地解决所有问题,一些特殊问题还是需要特殊处理的。

但是在整个软件开发过程中需要特殊处理的情况应该都是很少的,否则所谓的工具也就失去了它存在的意义。

常见的持久化框架

当前 Java ORM 框架产品有很多,常见的框架有 Hibernate 和 MyBatis,其主要区别如下。

Hibernate

Hibernate 框架是一个全表映射的框架。通常开发者只要定义好持久化对象到数据库表的映射关系,就可以通过 Hibernate 框架提供的方法完成持久层操作。

开发者并不需要熟练地掌握 SQL 语句的编写,Hibernate 框架会根据编制的存储逻辑,自动生成对应的 SQL,并调用 JDBC 接口来执行,所以其开发效率会高于 MyBatis 框架。

然而 Hibernate 框架自身也存在一些缺点,例如:

  • 多表关联时,对 SQL 查询的支持较差;
  • 更新数据时,需要发送所有字段;
  • 不支持存储过程;
  • 不能通过优化 SQL 来优化性能。

这些问题导致其只适合在场景不太复杂且对性能要求不高的项目中使用。

Hibernate 官网:http://hibernate.org/

MyBatis

MyBatis 框架是一个半自动映射的框架。这里所谓的 “半自动” 是相对于 Hibernate 框架全表映射而言的,MyBatis 框架需要手动匹配提供 POJO、SQL 和映射关系,而 Hibernate 框架只需提供 POJO 和映射关系即可。

与 Hibernate 框架相比,虽然使用 MyBatis 框架手动编写 SQL 要比使用 Hibernate 框架的工作量大,但 MyBatis 框架可以配置动态 SQL 并优化 SQL、通过配置决定 SQL 的映射规则,以及支持存储过程等。对于一些复杂的和需要优化性能的项目来说,显然使用 MyBatis 框架更加合适。

MyBatis 框架可应用于需求多变的互联网项目,如电商项目;Hibernate 框架可应用于需求明确、业务固定的项目,如 OA 项目、ERP 项目等。

学习地址:https://mybatis.org/mybatis-3/zh/getting-started.html

MyBatis示例

项目案例为一个普通的Maven的Java项目

相关依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>

</dependencies>

创建SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE TABLE `inter_employee` (
`id` varchar(32) NOT NULL COMMENT 'id',
`name` varchar(32) NOT NULL COMMENT '姓名',
`email` varchar(128) NOT NULL COMMENT '邮箱',
`sex` char(1) NOT NULL COMMENT '性别 0-男;1-女;2-未知',
`dept_id` bigint NOT NULL COMMENT '所属部门id',
`org_id` varchar(32) NOT NULL COMMENT '所属机构id',
`status` char(1) NOT NULL COMMENT '状态 0-无效;1-有效',
`created_by` varchar(32) DEFAULT NULL COMMENT '创建人',
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
`updated_by` varchar(32) DEFAULT NULL COMMENT '更新人',
`updated_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='机构员工信息';

注入数据

1
2
3
4
5
6
INSERT INTO `inter_employee`(`id`, `name`, `email`, `sex`, `dept_id`, `org_id`, `status`, `created_by`, `created_time`, `updated_by`, `updated_time`) VALUES ('a999b75045ac4c51a537b4a2f3631da8', 'will——5', '1@qqc.om', '1', 1001, 'a007fd3f3d4242b880d9eae31514ba1f', '1', 'admin', '2020-06-03 22:11:26', NULL, '2020-06-03 22:11:26');
INSERT INTO `inter_employee`(`id`, `name`, `email`, `sex`, `dept_id`, `org_id`, `status`, `created_by`, `created_time`, `updated_by`, `updated_time`) VALUES ('08b8755629e8482da80e329e6b8b7221', 'will——3', '1@qqc.om', '1', 1001, 'fa67adfd262a4c4b9c763031fbe5e11c', '1', 'admin', '2020-06-03 22:09:17', NULL, '2020-06-03 22:09:17');
INSERT INTO `inter_employee`(`id`, `name`, `email`, `sex`, `dept_id`, `org_id`, `status`, `created_by`, `created_time`, `updated_by`, `updated_time`) VALUES ('4da05f39ba5b4b5e8a9aa583e649165f', 'will——2', '1@qqc.om', '1', 1001, '253daef00ca54b1ea7064bdc491006d7', '1', 'admin', '2020-06-03 22:09:10', NULL, '2020-06-03 22:09:10');
INSERT INTO `inter_employee`(`id`, `name`, `email`, `sex`, `dept_id`, `org_id`, `status`, `created_by`, `created_time`, `updated_by`, `updated_time`) VALUES ('5ad23b1c5a9545298d99455b7fc9c12c', 'will——1', '1@qqc.om', '1', 1001, 'e39ef78e0a834846b103492a887aa95e', '0', 'admin', '2020-06-03 21:49:30', 'admin', '2020-06-03 22:11:01');
INSERT INTO `inter_employee`(`id`, `name`, `email`, `sex`, `dept_id`, `org_id`, `status`, `created_by`, `created_time`, `updated_by`, `updated_time`) VALUES ('7c6ee3c522b34d48bdaaa467eb97265b', 'will——4', '1@qqc.om', '1', 1001, '2f8a94f7433a45a6bf5c0b5d29d0bd39', '1', 'admin', '2020-06-03 22:09:21', NULL, '2020-06-03 22:09:21');
INSERT INTO `inter_employee`(`id`, `name`, `email`, `sex`, `dept_id`, `org_id`, `status`, `created_by`, `created_time`, `updated_by`, `updated_time`) VALUES ('a7da1e401baa48a5af095e4169c6ddbb', 'will——6', '1@qqc.om', '1', 1001, '20b14675a4e54345aa042baed441b3e4', '1', 'admin', '2020-06-03 22:11:30', NULL, '2020-06-03 22:11:30');

创建MODEL对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.bossYang.myBatisTest.bean;

import lombok.Data;

/**
* @ClassName InterEmployee
* @Description TODO
* @Author will
* @Date 2020/11/8 9:55 PM
*/
@Data
public class InterEmployee {
private String id;
private String name;
private String email;
private String sex;
private String deptId;
private String orgId;
private String status;
private String createdBy;
private String createdTime;
private String updatedBy;
private String updatedTime;
}

创建mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/beetsql_demo"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/interEmployee.xml"/>
</mappers>
</configuration>

创建interEmployee.xml,实现查询功能

  • resources目录下面创建mapper文件夹
  • 创建表对应的xml【interEmployee.xml】文件,实现最基本的一个查询SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bossYang.myBatisTest.dao.InterEmployeeDao">
<select id = "queryInterEmployeeById" resultType="com.bossYang.myBatisTest.bean.InterEmployee" parameterType="com.bossYang.myBatisTest.bean.InterEmployee">
SELECT
t.id id,
t.`name` `name`,
t.email email,
t.dept_id deptId,
t.org_id orgId,
t.sex sex,
t.`status` `status`,
t.created_by createdBy,
t.created_time createdTime,
t.updated_by updatedBy,
t.updated_time updatedTime
FROM inter_employee t
WHERE t.id = #{id}
</select>
</mapper>

实现查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.bossYang.myBatisTest;

import com.bossYang.myBatisTest.bean.InterEmployee;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
* @ClassName MyBatisInitDemo
* @Description TODO
* @Author will
* @Date 2020/11/8 10:03 PM
*/
public class MyBatisInitDemo {
public static void main(String[] args) throws IOException {
String myBatisCfgResource = "config/mybatis-config.xml";
InputStream resource = Resources.getResourceAsStream(myBatisCfgResource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
InterEmployee param = new InterEmployee();
param.setId("08b8755629e8482da80e329e6b8b7221");

InterEmployee interEmployee = sqlSession.selectOne("com.bossYang.myBatisTest.dao.InterEmployeeDao.queryInterEmployeeById", param);
if (interEmployee != null) {
System.out.println("interEmployee = " + interEmployee);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}
}

MyBatis整体架构

MyBatis 整体架构分为三层:基础支持层、核心处理层和接口层。

image-20201114212618759

基础支持层

  • 反射模块

    Java 中的反射虽然功能强大,但对大多数开发人员来说,写出高质量的反射代码还是有一定难度的。M y Ba tis 中专门提供了反射模块,该模块对Java 原生的反射进行了良好的封装,提供了更加简洁易用的API ,方便上层使调用,并且对反射操作进行了一系列优化,例如缓存了类的元数据,提高了反射操作的性能。

  • 类型转换模块

    正如前面示例所示 ,MyBatis 为简化配置文件提供了别名机制 ,该机制是类型转换模 块的主要功能之一 。类型转换模块的另一个功能是实现 JDBC 类型与 Java 类型之间的 转换,该功能在为 SQL 语句绑定实参以及 映射查询结果集 时都会涉及 。在为 SQL 语 句绑定实参时,会将数据由 Java 类型转换成 JDBC 类型 ;而在映射结果集时 ,会将数 据由 JDBC 类型转换成 Java 类型 。

  • 日志模块

    无论在开发测试环境中 ,还是在线上生产环境中 ,日志在整个系统中的地位都是非常 重要的 。良好的日志功能可以帮助开发 人员和测试人员快速定位 Bug 代码 ,也可以帮 助运维人员快速定位性能瓶颈、等问题 。目前的 Java 世界中存在很多优秀的日志框架 ,例如 Log4j 、Log4j2, slf4j 等 。MyBatis 作为一个设计优良的框架 ,除了提供详细的日 志输出信息 ,还要能够集成多种日志框架 ,其日志模块的 一个主要功能就是集成第 三 方日志框架

  • 资源加载模块

    资源加载模块主要是对类加载器进行封装 ,确定类加载器的使用顺序 ,并提供了加载 类文件以及其他资源文件的功能 。

  • 解析器模块

    解析器模块的主要提供了两个功能 :一个功能是对 XPath 进行封装 ,为 MyBatis 初始 化时解析 mybatis-config.xml 配置文件以及映射配 置文件提供支持 ;另一个功能是为处 理动态 SQL 语句中的占位符提供支持 。

  • 数据源模块

    数据源是实际开发中常用的组件之 一。现在开源的数据源都提供了比较丰富的功能 , 例如 ,连接池功能 、检测连接状态等 ,选择性能优秀的数据源组件对于提升 ORM 框 架乃至整个应用的性能都是非常重要的 。MyBatis 自身提供了相应的数据源实现 ,当 然 MyBatis 也提供了与第三方数据源集成的接口 ,这些功能都位于数据源模块之中 。

  • 事务管理

    MyBatis 对数据库中的事务进行了抽象 ,其自身提供了相 应的事务接口和简单实现 。 在很多场景中 ,MyBatis 会与 Spring 框架集成 ,并由 Spring 框架管理事务 。

  • 缓存模块

    My Batis 中提供了一级缓存和二级缓存,而这两级缓存都是依赖于基础支持层中的缓存模块实现的。这里需要读者注意的是, MyBatis 中自带的这两级缓存与MyBatis 以及整个应用是运行在同一个JVM中的,共享同一块堆内存。如果这两级缓存中的数据量较大, 则可能影响系统中其他功能的运行,所以当需要缓存大量数据时,优先考虑使用Redis 、Memcache 等缓存产品。

    img

  • Binding 模块

    通过前面的示例我们知道 ,在调用 SqISession 相应方法执行数据库操作时 ,需要指定映射文件中定义的 SQL 节点,如果出现拼写错误,我们只能在运行时才能发现相应的 异常 。为了尽早发现这种错误 ,MyBatis 通过 Binding 模块将用户自定义的 Mapper 接 口与映射配置文件关联起来 ,系统可以通过调用自定义 Mapper 接口中的方法执行相应 的 SQL 语句完成数据库操作 ,从而避免上述问题 。

核心处理层

  • 配置解析

    在MyBatis 初始化过程中,会加载mybatis-config.xml 配置文件、映射配置文件以及Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到Configuration 对象中.

  • SOL 解析与scripting 模块

    拼凑 SQL 语句是一件烦琐且易出错的过程 ,为了将开发人员从这项枯燥无趣的工作中 解脱出来 ,MyBatis 实现动态 SQL 语句的功能 ,提供了多种动态 SQL 语句对应的节点 , 例如 ,<where>节点、<if>节点、<foreach>节点等。通过这些节点的组合使用,开发人 员可以写出几乎满足所有需求的动态 SQL 语句。

    MyBatis 中的 scripting 模块会根据用户传入的实参 ,解析映射文件中定义的 动态 SQL 节点,并形成数据库可执行的 SQL 语句。之后会处理 SQL 语句中的占位符 ,绑定用 户传入的实参 。

  • SOL 执行

    SQL 语句 的执行涉及多个组件 ,其中比较重要的是 Executor 、StatementHandler 、 ParameterHandler 和 R巳sultSetHandler 。Executor 主要负责维护一级缓存和二级缓存, 并提供事务管理 的相关操作,它会将数据库相关操作委托给 StatementHandler 完成。

    StatementHandler 首先通过 ParameterHandler 完成 SQL 语句的实参绑定 ,然后通过 java.sql.Statement 对象执行 SQL 语句并得到结果集,最后通过ResultSetHandler 完成结 果集的映射,得到结果对象并返回 。

image-20201130182017018

  • 插件

    Mybatis 自身的功能虽然强大 ,但是并不能完美切 合所有的应用场景 ,因此 MyBatis 提供了插件接口 ,我们可以通过添加用户自定义插件的方式对 MyBatis 进行扩展 。用 户自定义插件也可以改变 Mybatis 的默认行为 ,例如 ,我们可以拦截 SQL 语句并对其 进行重写 。由于用户自定义插件会影响 MyBatis 的核心行为 ,在使用自定义插件之前, 开发人员需要了解 MyBatis 内部的原理,这样才能编写出安全 、高效的插件 。

接口层

接口层相对简单 ,其核心是 SqlSession 接口 ,该接口中定义了 MyBatis 暴露给应用程序调 用的 API ,也就是上层应用与 MyBatis 交互的桥梁 。接口层在接收到调用请求时 ,会调用核心 处理层的相应模块来完成具体的数据库操作。

基础支持层

image-20201114212618759

基础支持层位于 MyBatis 整体架构的最底层,支撑着 MyBatis 的核心处理层 ,是整个框架的基石 。基础支持层 中封装了多个较为通用的 、独立的模块 ,不仅仅为 MyBatis 提供基础支撑 ,也可以在合适的场 景中直接复用。

解析器模块

在 MyBatis 中涉及多个 XML 配置文件 ,因此我们首先介绍 XML 解析的相关内容。XML解析常见的方式有三种 ,分别是:DOM ( Document Object Model ) 解析方式和 SAX ( Simple API for XML )解析方式 ,以及从 JDK 6.0 版本开始,JDK 开始支持的 StAX ( Streaming API for XML ) 解析方式 。在开始介绍 MyBatis 的 XML 解析功能之前 ,先介绍这几种常见的 XML 处理方式。

DOM

DOM 是基于树形结构的 XML 解析方式 ,它会将整个 XML 文档读入内存并构建 一个 DOM 树,基于这棵树形结构对各个节点(Node ) 进行操作。XML 文档中的每个成分都是 一个节点: 整个文档是一个文档节点 ,每个 X扣E标签对应一个元素节点 ,包含在 X岛1L 标签中的文本是文 本节点,每一个 XML 属性是一个属性节点 ,注释属于注释节点 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8" ?>
<inventory>
<book year="2000">
<title>Snow Crash</title>
<author>Neal Stephenson</author>
<publisher>Spectra</publisher>
<isbn>0553380958</isbn>
<price>14.95</price>
</book>
<book year="2005">
<title>Burning Tower</title>
<author>Larry Niven</author>
<author>Jerry Pournelle</author>
<publisher>Pocket</publisher>
<isbn>0743416910</isbn>
<price>5.99</price>
</book>
<book year="1995">
<title>Zodiac</title>
<author>Neal Stephenson</author>
<publisher>Spectra</publisher>
<isbn>0553573862</isbn>
<price>7.50</price>
</book>
<!-- more books -->
</inventory>

dom解析后树状结构示例:

image-20201221170217672

DOM 解析方式最主要的好处是易于编程 ,可以根据需求在树形结构的各节点之间导航。例 如导航到当前节点的父节点 、兄弟节点、子节点等都是比较方便的 ,这样就可以轻易地获取到 自己需要的数据,也可以很容易地添加和修改树中的元素 。因为要将整个 XML 文档加载到内 存中井构造树形结构 ,当 XML 文档的数据量较大时 ,会造成较大的资源消耗 。

SAX

SAX 是基于事件模型的XML 解析方式 ,它并不需要将整个 XML文档加载到内存中 ,而只需将 XML文档的一部分加载到内存中 ,即可开始解析,在处理过程中井不会在内存中记录XML中的数据,所以占用的资源比较小。当程序处理过程中满足条件时,也可以立即停止解析 过程,这样就不必解析剩余的 XML 内容。

当 SAX 解析器解析到某类型节点时 ,会触发注册在该类型节点上的回调函数,开发人员可以根据自己感兴趣的事件注册相应的回调函数 。一般情况下 ,开发人员只需继承 SAX 提供的 DefaultHandler 基类,重写相应事件的处理方法并进行注册即可

SAX 的缺点也非常明显,因为不存储XML文挡的结构 ,所以需要开发人员自己负 责维护 业务逻辑涉及的多层节点之间的关系 ,例如,某节点与其父节点之间的父子关系 、与其子节点 之间的父子关系 。当 XML 文档非常复杂时 ,维护节点间关系的复杂度较高 ,工作量也就会 比 较大 。另一方面 ,因为是流式处理 ,所以处理过程只能从 XML 文档开始向后单向进行 ,无法 像 DOM 方式那样 ,自由导航到之前处理过的节点上重新处理 ,也无法支持 XPath 。SAX 没有 提供写 XML 文档的功能 。

SAX解析后树状结构示例:

image-20201221173148569

StAX

JAXP 是JDK 提供的一套用于解析XML的API , 它很好地支持DOM和SAX 解析方式,JAXP 是JavaSE 的一部分,它由javax.xml 、org.w3c . dom 、orgnl.sax 包及其子包组成。从JDK6 . 0 开始, JAXP 开始支持另一种XML 解析方式,也就是下面要介绍的StAX解析方式。

StAX是一个基于JAVA API用于解析XML文档,类似SAX解析器的方式。但两种API之间有两个区别

  • StAX是PULL API,其中作为SAX是PUSH API。这意味着如果StAX解析器,客户端应用程序需要询问StAX解析器从XML获取信息它所需要的,但如果是SAX解析器,客户端应用程序需要获取信息时,SAX解析器会通知客户端应用程序的信息是可用的。
  • StAX的API可以读取和写入XML文档。使用SAX API,XML可以是只读的。

SAX的缺点:

  • 因为它是在一个处理的方式,而不是随机访问XML文档。
  • 如果需要跟踪的数据分析器已经看到或更改项目的顺序,必须编写代码和数据存储以自己方式处理。

StAX解析后树状结构示例:

XPath

简介

XPath是一门在 XML 文档中查找信息的语言。XPath 用于在 XML 文档中通过元素和属性进行导航。

XPath 是一门在 XML 文档中查找信息的语言, 可用来在 XML 文档中对元素和属性进行遍历。XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointer 同时被构建于 XPath 表达之上。因此,对 XPath 的理解是很多高级 XML 应用的基础。
XPath非常类似对数据库操作的SQL语言,或者说JQuery,它可以方便开发者抓起文档中需要的东西。

XPath常用表达式

表达式含义
nodename选取指定节点的所有子节点
/从根节点选取指定节点
//根据指定的表达式,在整个文档中选取匹配的节点,这里并不会考虑匹配节点在文档中的位置
.选取当前节点
..选取当前节点的父节点
@选取属性
*匹配任何元素节点
@*匹配任何属性节点
node()匹配任何类型的节点
text()匹配文本节点
|选取若干个路径
[]指定某个条件,用于查找某个特定节点或包含某个指定值的节点

在JDK 5.0 版本中推出了javax且nl 叩ath 包, 它是一个引擎和对象模型独立的XPath 库。Java 中使用XPath 编程的代码模式比较固定,下面先通过一个示例简单介绍DOM 解析方式和XPath 库的使用方式。

Xpath步骤Demo

创建inventory.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8" ?>
<inventory>
<book year="2000">
<title>Snow Crash</title>
<author>Neal Stephenson</author>
<publisher>Spectra</publisher>
<isbn>0553380958</isbn>
<price>14.95</price>
</book>
<book year="2005">
<title>Burning Tower</title>
<author>Larry Niven</author>
<author>Jerry Pournelle</author>
<publisher>Pocket</publisher>
<isbn>0743416910</isbn>
<price>5.99</price>
</book>
<book year="1995">
<title>Zodiac</title>
<author>Neal Stephenson</author>
<publisher>Spectra</publisher>
<isbn>0553573862</isbn>
<price>7.50</price>
</book>
<!-- more books -->
</inventory>
CodeDemo
创建工厂对象
1
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
开启验证
1
2
3
4
5
6
//开启验证
documentBuilderFactory.setValidating(true);
documentBuilderFactory.setNamespaceAware(false);
documentBuilderFactory.setIgnoringComments(true);
documentBuilderFactory.setCoalescing(false);
documentBuilderFactory.setExpandEntityReferences(true);
使用工厂创建文档实例对象
1
2
//创建DocumentBuilder
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
创建并赋值异常机制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ErrorHandler errorHandler =  new ErrorHandler(){

public void warning(SAXParseException exception) throws SAXException {
System.out.println("warning:" + exception.getMessage());
}

public void error(SAXParseException exception) throws SAXException {
System.out.println("error:" + exception.getMessage());
}

public void fatalError(SAXParseException exception) throws SAXException {
System.out.println("fatalError:" + exception.getMessage());
}
};
//设置异常处理对象
documentBuilder.setErrorHandler(errorHandler);
创建XPath工厂对象
1
2
3
4
//加载对象
String myBatisCfgResource = "test/inventory.xml";
InputStream resource = Resources.getResourceAsStream(myBatisCfgResource);
Document document = documentBuilder.parse(resource)
创建XPath对象
1
2
3
4
//创建XpathFactory
XPathFactory xPathFactory = XPathFactory.newInstance();
//创建XPath对象
XPath xPath = xPathFactory.newXPath();
编译表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 通过XPath表达式得到结果 ,第一个参数指定了XPath表达式进行查询的上下文节点 ,也就是在指定
* 节点下查找符合XPath的节点。 本例中的上下文节点是整个文档;第二个参数 指定了XPath表达式
* 的返回类型。
*/
XPathExpression expression = xPath.compile("//book[author='Neal Stephenson']/title/text()");
Object result = expression.evaluate(document, XPathConstants.NODESET);
System.out.println("查询作者为Neal Stephenson的图书的标题:");
NodeList nodeList = (NodeList)result;
for (int i = 0; i < nodeList.getLength(); i++) {
System.out.println(nodeList.item(i).getNodeValue());
}
System.out.println("查询1997年之后的图书的标题:");
nodeList = (NodeList) xPath.evaluate("//book[@year>1997]/title/text()", document, XPathConstants.NODESET);
for (int i = 0; i < nodeList.getLength(); i++) {
System.out.println(nodeList.item(i).getNodeValue());
}
System.out.println("查询1997年之后的图书的属性和标题:");
nodeList = (NodeList) xPath.evaluate("//book[@year>1997]/@*|//book[@year>1997]/title/text()", document, XPathConstants.NODESET);
for (int i = 0; i < nodeList.getLength(); i++) {
System.out.println(nodeList.item(i).getNodeValue());
}
整体案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package com.bossYang.myBatisTest;

import org.apache.ibatis.io.Resources;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import java.io.InputStream;

/**
* @ClassName XpathTest
* @Description TODO
* @Author will
* @Date 2020/11/28 6:08 PM
*/
public class XpathTest {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

//开启验证
documentBuilderFactory.setValidating(true);
documentBuilderFactory.setNamespaceAware(false);
documentBuilderFactory.setIgnoringComments(true);
documentBuilderFactory.setCoalescing(false);
documentBuilderFactory.setExpandEntityReferences(true);

//创建DocumentBuilder
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

ErrorHandler errorHandler = new ErrorHandler(){

public void warning(SAXParseException exception) throws SAXException {
System.out.println("warning:" + exception.getMessage());
}

public void error(SAXParseException exception) throws SAXException {
System.out.println("error:" + exception.getMessage());
}

public void fatalError(SAXParseException exception) throws SAXException {
System.out.println("fatalError:" + exception.getMessage());
}
};


//设置异常处理对象
documentBuilder.setErrorHandler(errorHandler);
//加载对象
String myBatisCfgResource = "test/inventory.xml";
InputStream resource = Resources.getResourceAsStream(myBatisCfgResource);
Document document = documentBuilder.parse(resource);

//创建XpathFactory
XPathFactory xPathFactory = XPathFactory.newInstance();
//创建XPath对象
XPath xPath = xPathFactory.newXPath();
//编译XPath表达式
/**
* 通过XPath表达式得到结果 ,第一个参数指定了XPath表达式进行查询的上下文节点 ,也就是在指定
* 节点下查找符合XPath的节点。 本例中的上下文节点是整个文档;第二个参数 指定了XPath表达式
* 的返回类型。
*/
XPathExpression expression = xPath.compile("//book[author='Neal Stephenson']/title/text()");
Object result = expression.evaluate(document, XPathConstants.NODESET);
System.out.println("查询作者为Neal Stephenson的图书的标题:");
NodeList nodeList = (NodeList)result;
for (int i = 0; i < nodeList.getLength(); i++) {
System.out.println(nodeList.item(i).getNodeValue());
}
System.out.println("查询1997年之后的图书的标题:");
nodeList = (NodeList) xPath.evaluate("//book[@year>1997]/title/text()", document, XPathConstants.NODESET);
for (int i = 0; i < nodeList.getLength(); i++) {
System.out.println(nodeList.item(i).getNodeValue());
}
System.out.println("查询1997年之后的图书的属性和标题:");
nodeList = (NodeList) xPath.evaluate("//book[@year>1997]/@*|//book[@year>1997]/title/text()", document, XPathConstants.NODESET);
for (int i = 0; i < nodeList.getLength(); i++) {
System.out.println(nodeList.item(i).getNodeValue());
}
}
}

注意:

XPathExpression. evaluate()方法的第二参数,它指定了XP础表达式查找的结果类型,在XPathConstants 类中提供了nodeset、boolean 、number、string 和Node 五种类型。
另外,如果XPath表达式只使用一次, 可以跳过编译步骤直接调用XPath 对象的evaluate()方法进行查询。但是如果同一个XPath 表达式要重复执行多次,则建议先进行编译,然后进行查询,这样性能会好一点。


MyBatis技术内幕
https://github.com/yangxiangnanwill/yangxiangnanwill.github.io/2024/01/03/好好码代码吖/JAVA/MyBatis/MyBatis技术内幕/
作者
will
发布于
2024年1月3日
许可协议