# XML 语言

xmlhtml 都是标准通用标记语言的子集,SGML (SGM) 标准通用标记语言,是一种定义电子文档结构和描述其内容的国际标准语言,具有极好的扩展性。

  • html 用于显示数据,其中元素是固定的,浏览器解析执行。
  • xml 用于传输和存储数据,标签可以是用户自定义的, xml 解析器需要自己写。

# 文档声明

xml 文档首先需要使用文档声明来声明文档,且必须出现在文档第一行:

<?xml version="1.0" encoding="GB2312" standalone="yes"?>
  • version 是版本号, xml 1.0 版本在 1998 年发布,2004 年发布 1.1 版本,但是 1.1 版本不向下兼容 1.0 版本,所以现在使用的依然是 1.0

  • encoding 表示编码格式。

  • standalone 表示文档是否独立,即是否依赖其他文档。

没有文档声明的 xml 文档,不是格式良好的 xml 文档。文档声明必须从 xml 文档的 1 行 1 列开始 也就是必须第一行顶格写。

# XML 元素

元素指的是从(且包括)开始标签直到(且包括)结束标签的部分。元素可包含其他元素、文本或者两者的混合物。元素也可以拥有属性。

一个标签就是一个元素。

要求:

  • 元素都必须有关闭标签,省略关闭标签是非法的。声明不是 XML 的元素,所以第一行的声明,并不需要关闭标签。
  • 大小敏感,必须正确嵌套。
  • 有且必须只有一个根元素。

标签命名应尽可能简短,可以使用下划线_,避免使用连字符 -。

一个元素(标签)可以有多个属性, xml 属性是键值对形式,如 sex = "male" ,值必须加单(双)引号。

# 注释

CDATA 区段中的文本会被解析器忽略 —— <![CDATA[]]>

<script>
<![CDATA[to be or not to be]]>
</script>

# 别名

<typeAliases> 标签,这里留着在 Mybatis 讲。

# 解析 XML

JDK 内置了一个 org.w3c 的 XML 解析库。

首先我们创建一个 test.xml 文件,为了简单,直接把它放在项目目录下(与 src 同级)

<?xml version="1.0" encoding="UTF-8" ?>
<message>
    <to>Mike</to>
    <from>Bob</from>
    <content>please get up</content>
</message>

这是最基本的 xml 内容,也是 w3c 的例子。

再写一下解析代码:

public static void main(String[] args) {
    // 创建 DocumentBuilderFactory 对象
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    // 创建 DocumentBuilder 对象
    try {
        DocumentBuilder builder = factory.newDocumentBuilder();
        // 这里要改成 test.xml 路径,因为直接放在根目录下,所以只需要写文件名
        Document d = builder.parse("test.xml");
        // 每一个标签都作为一个节点
        NodeList nodeList = d.getElementsByTagName("message");  // 可能有很多个名字为 message 的标签
        Node rootNode = nodeList.item(0); // 获取首个
		
        // 一个节点下可能会有很多个节点,比如根节点下就囊括了所有的节点
        NodeList childNodes = rootNode.getChildNodes(); 
        
        // 节点可以是一个带有内容的标签(它内部就还有子节点),也可以是一段文本内容
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node child = childNodes.item(i);
            // 过滤换行符之类的内容,因为它们都被认为是一个文本节点
            if(child.getNodeType() == Node.ELEMENT_NODE)  
                System.out.println(child.getNodeName() + ":" +child.getFirstChild().getNodeValue());
            // 输出节点名称,也就是标签名称,以及标签内部的文本(内部的内容都是子节点,所以要获取内部的节点)
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

结果:

text
to:Mike
from:Bob
content:please get up

Mybatis 也是使用的 JDK 内置的 xml 解析器。

# 初次使用

首先要说明的是,Mybatis 很好用,可以高效代替 JDBC ,但是在某些实际场景中,涉及到复杂的联表查询等操作,还是得乖乖手写 sql 语句,这也警示我们:算法很重要,框架只是节省时间,方便开发。

通过 maven 导入依赖:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

在项目根目录下(web 项目放在 resources 目录)创建 mybatis-config.xml 文件,其实名字随便取:

<?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="${驱动类(含包名)}"/>
        <property name="url" value="${数据库连接URL}"/>
        <property name="username" value="${用户名}"/>
        <property name="password" value="${密码}"/>
      </dataSource>
    </environment>
  </environments>
</configuration>

这里以 web 项目为例,将 xml 配置文件放到 Resources 目录里面,构建一个 SqlSessionFactory

public class SqlUtil {
    private static SqlSessionFactory factory;
    static {
        try {
            factory = new SqlSessionFactoryBuilder().
                build(Resources.getResourceAsReader("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static SqlSession getSqlSession() {
        return factory.openSession(true);
    }
}

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的,我们可以通过 SqlSessionFactory 来创建多个新的会话, SqlSession 对象,每个会话就相当于我不同的地方登陆一个账号去访问数据库,你也可以认为这就是之前 JDBC 中的 Statement 对象,会话之间相互隔离,没有任何关联。

编写一个实体类 Student:

import lombok.Data;
// 使用 Lombok 很方便
@Data
public class Student {
    int sid;
    final String name;
    public Student(String name) {
        this.name = name;
    }
}

根目录下创建一个 mapper 文件夹,新建一个 TestMapper.xml 文件作为映射器:

<?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="TestMapper">
    <select id="selectStudent" resultType="com.cyan.entity.Student">
        select * from student
    </select>
</mapper>

需要注意 resultType 属性的包路径,每个人不一样。

其中 namespace 就是命名空间,每个 Mapper 都是唯一的,因此需要用一个命名空间来区分,它还可以用来绑定一个接口。在里面写入了一个 select 标签,表示添加一个 select 操作,同时 id 作为操作的名称,resultType 指定为我们刚刚定义的实体类,表示将数据库结果映射为 Student 类,然后就在标签中写入我们的查询语句即可。

编写好后再配置文件的 <configuration> 标签下,添加这个 Mapper 映射器:

<mappers>
    <mapper resource="com/cyan/mappers/TestMapper.xml"/>
</mappers>

这里的 TestMapper.xml 放在文件夹内部作为内部资源,使用 resource 指定路径,resource 表示是 Jar 内部的文件。

最后在程序中使用定义好的 Mapper:

public static void main(String[] args) {
    try(SqlSession sqlSession = SqlUtil.getSqlSession) {
        List<Student> student = sqlSession.sqlSelectList("selectStudent");
        student.forEach(System.out.println);
    }
}

# 映射接口

直接通过 sqlSession 调用 mapper 有时会不明确,还涉及到类型转换等,所以我们可以将 mapper.xml 与一个接口相关联,在 mappers 文件夹下面创建一个 TestMapper 接口:

public interface TestMapper {
    List<Student> selectStudent();
}

接下来:

  • 接口与 TestMapper.xml 相关联: TestMapper.xml 的 namespace 改为接口的全类限定名
<mapper namespace="com.cyan.entity.TestMapper">
    <select id="selectStudent" resultType="com.cyan.entity.Student">
        select * from student
    </select>
</mapper>
  • 通过 SqlSession 获取对应的实现类:
public static void main(String[] args) {
    try (SqlSession sqlSession = MybatisUtil.getSession(true)){
        TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
        List<Student> student = testMapper.selectStudent();
        student.forEach(System.out::println);
    }
}

TestMapper 接口是 Mybatis 通过动态代理生成的实现类。而不是预先定义好的:

System.out.println(
	sqlSession.getMapper(TestMapper.class).getClass();
);
// 结果:com.sun.proxy.$Proxy4

尽管此时 Mybatis 使用起来非常方便,但是配置 TestMapper.xml ,映射接口还是有点低效,之后会有更快捷的办法。

# 参考

xml 部分:

  • https://www.w3school.com.cn/xml/xml_cdata.asp
  • https://www.cnblogs.com/noteless/archive/2018/08/01/9400633.html

Mybatis 部分:

  • https://www.yuque.com/qingkongxiaguang/javaweb/gn0syt#af990acc