MyBatis应用的组成

本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!

王有志,一个分享硬核Java技术的互金摸鱼侠

加入Java人的提桶跑路群:共同富裕的Java人

大家好,我是王有志。在上一篇文章的最后,我们写了一个简单的例子,今天我们就通过这个例子来看一看一个标准的 MyBatis 应用程序由哪些组件组成。

最后,文末会解答小伙伴在私信中提出的问题:当存在多个 Mapper.xml,且每个文件中都有同名的查询语句时在 MyBatis 应用中该如何进行查询?

Tips :文章中的示例,指的是《MyBatis 入门》正文中出现的简单示例和附录中"不使用 XML 构建 SqlSessionFactory"的例子,如无特别说明,默认为正文中的简单示例。

MyBatis 应用的组成

我们先来回忆一下构建简单示例的过程:

  1. 创建数据对象 UserDO,用于映射数据库中的 user 表;
  2. 创建接口 UserDAO,作为 MyBatis 映射器的命名空间;
  3. 创建映射器文件 UserMapper.xml,并编写了查询全部 user 表数据的 SQL 语句
  4. 创建 MyBatis 的核心配置文件 mybatis-config.xml,配置了数据库信息和映射器

以上的 4 步是我们在开始使用 MyBatis 前进行的前期配置工作,接下来是我们在应用程序中使用 MyBatis 的步骤:

  1. 通过 Resources 读取 mybatis-config.xml 文件,获取 Reader 对象;
  2. 通过 Reader 对象构建出 SqlSessionFactory,即 SQL 会话工厂;
  3. 通过 SqlSessionFactory 获取 SqlSession,即 SQL 会话
  4. 通过 SqlSession 执行 UserMapper.xml 中的 SQL 语句,并获取到查询结果。

这 4 步是我们在应用程序中使用 MyBatis 的过程,综合以上两部的内容我们大概可以构建出如下图所示的 MyBatis 应用的基本组成:

图中的部分组件已经在我们的示例中出现过了, 但是如 Executor,MappedStatement,Configuration,ResultHandler 等组件并没有在我们的示例中出现。这是因为它们大都出现在 SqlSession 和 SqlSessionFactory 内部封装的调用过程中,因此我们在平时使用时可能"见不到"它们,但这并不是说它们不重要,相反它们是 MyBatis 中起到关键作用的组件。

关于它们我还会在 MyBatis 系列的源码篇着重进行分析,不过在此之前,我会按照图中自下向上的顺序,逐一对这些组件的作用做一个简单的说明。

Tips:Reader 是 Java 中 io 包下的工具类,因此在下文中不会出现关于 Reader 的内容。

Mapper.xml

Mapper.xml 是 MyBatis 的核心之一,是用于定义 SQL 语句和映射规则的 XML 文件,由核心配置文件 mybatis-config.xml 加载到 MyBaits 应用程序中。

Mapper.xml 的主要作用包括:

  • 定义 SQL 语句:MyBatis 的 SQL 语句编写在 Mapper.xml 中(MyBatis 也支持通过注解的方式编写 SQL 语句),通过 MyBatis 提供的 XML 标签可以实现动态查询条件和嵌套查询等复杂的 SQL 语句;
  • 映射结果集到 Java 对象:通过 MyBatis 的标签可以实现数据库表中的字段与 Java 对象中的字段的映射关系,可以实现一对一,一对多等复杂关系的映射;
  • 接口方法绑定:Mapper.xml 中定义的 SQL 语句可以通过标签中的 id 字段与对应的 Mapper 接口中的方法进行绑定,通过调用 Mapper 接口的方法 MyBatis 将会执行 Mapper.xml 中的 SQL 语句。

下面我们对之前的示例稍作修改,来感受下 MyBatis 的中 Mapper.xml 与 Mapper 接口的方法绑定。

首先,在 UserMapper.xml 中定义一个新的查询语句,用于查询 id = 1 的用户:

xml 复制代码
<select id="selectFirstUser" resultType="com.wyz.entity.UserDO" >
  select user_id, name, age, gender, id_type, id_number from user where user_id = 1
</select>

Tips :通常我们不会在 Mapper.xml 中编写如user_id = 1这类硬编码,这里仅仅是为了举例说明,千万不要学~~~

接着我们修改 UserDAO 接口,添加两个对应的方法声明:

java 复制代码
public interface UserMapper {

  List<UserDO> selectAll();

  UserDO selectFirstUser();
}

最后我们修改测试代码,通过 SqlSession 实例获取 UseDAO 接口的实例,并调用接口中的方法:

java 复制代码
@Test
public void test() {
	SqlSession sqlSession = sqlSessionFactory.openSession();
	UserDAO userDAO = sqlSession.getMapper(UserDAO.class);

	List<UserDO> users = userDAO.selectAll();
	for(UserDO user : users) {
		System.out.println(user.getName());
	}

	UserDO user = userDAO.selectFirstUser();
	System.out.println(user.getName());
}

可以看到,这里我们通过 SqlSession 实例获取到接口 UserDAO 的实例,分别调用了接口中的方法并能够成功获取到数据,这表明我们已经将 UserMapper.xml 中编写的 SQL 语句与 UserDAO 接口中的方法绑定到了一起。

关于 Mapper.xml 的更多用法,我会在 MyBatis 系列的第 4 篇文章中和大家分享。

mybatis-config.xml

mybatis-config.xml 是 MyBatis 应用中的核心配置文件,该文件中包含了 MyBatis 应用程序在运行时所需要的各种配置信息。

示例中,我只做了最基础的环境配置(数据库事务管理器配置,数据源配置)和映射器配置(加载映射器 UserMapper.xml),但实际上 mybatis-config.xml 中还提供了非常多的配置内容,如:别名配置(使用 typeAliases 标签),插件配置(使用 plugins 标签)和对象工厂配置(使用 objectFactory 标签)等等。

关于 mybatis-config.xml 的更多用法,我会在 MyBatis 系列的第 3 篇文章中和大家分享。

Resources

MyBatis 提供的资源加载工具,用于各种资源文件的加载和访问。Resources 提供了良好的封装,使用起来非常简单,只需要通过相对路径,即可将资源文件加载到应用程序中。

XMLConfigBuilder

XMLConfigBuilder 继承自 BaseBuilder,负责解析 MyBatis 中的 XML 配置文件(mybatis-config.xml) ,并通过调用XMLConfigBuilder#parse方法构建出 Configuration 对象。

BaseBuilder 有多个子类:

BaseBuilder 的子类分别负责解析不同的文件,如:XMLConfigBuilder 负责解析 mybatis-config.xml 文件,XMLMapperBuilder 负责解析 Mapper.xml 文件等等。

Configuration

Configuration 是核心配置文件 mybatis-config.xml 在 Java 应用程序中的体现,是 MyBatis 在整个运行周期中的配置信息管理器,包含了 MyBatis 运行期间所需要的全部配置信息和映射器。

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder 使用了建造者模式,用来根据配置信息生成 SqlSessionFactory。

SqlSessionFactoryBuilder 提供了多个SqlSessionFactoryBuilder#build的重载方法,分别接受 Reader,InputStream 和 Configuration 三种方式输入的配置信息。

示例中,我们已经在SqlSessionFactoryBuilder#build方法中使用了 Reader 和 Configuration,而使用 InputStream 的方式与使用 Reader 的方式一模一样,代码如下所示:

java 复制代码
@BeforeClass
public static void init() throws IOException {
  InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
  sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  inputStream.close();
}

SqlSessionFactoryBuilder 的唯一作用是创建 SqlSessionFactory,当它完成了这个使命后,我们就应该毫不犹豫的抛弃它,因此 SqlSessionFactoryBuilder 应该作为方法内的局部变量出现,生命周期仅在这个方法中,就像示例中的那样。

SqlSessionFactory

SqlSessionFactory 是 MyBatis 中的接口,也是 MyBatis 的核心组件之一,SqlSessionFactory 使用了工厂方法,定义了 MyBatis 获取 SqlSession 的规范

MyBatis 官方对于 SqlSessionFactory 的定位是每个 MyBatis 应用的核心:

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。

SqlSessionFactory 作为 MyBatis 应用程序中的核心,生命周期与整个 MyBatis 应用程序相同,随着应用的创建而创建,应用的停止而销毁。

SqlSessionFactory 有两个实现类:

DefaultSqlSessionFactory 是 SqlSessionFactory 的默认实现类,用于获取非线程安全的 SqlSession 实例,通过 DefaultSqlSessionFactory 获取的 SqlSession 实例在使用时还需要手动关闭(同时会提交事务),即调用 SqlSession#close方法。

SqlSessionFactory 接口提供了多个SqlSessionFactory#openSession的重载方法:

java 复制代码
public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);

  SqlSession openSession(Connection connection);

  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);

  SqlSession openSession(ExecutorType execType, boolean autoCommit);

  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType, Connection connection);
}

使用无参的SqlSession#openSession方法可以获取具有如下特性的 SqlSession 实例:

  • 不会自动提交数据库事务;
  • 通过 mybatis-config.xml 配置的数据源获取的 Connection 实例;
  • 使用数据源默认的事务隔离级别;
  • 不会复用预处理语句,也不会批量进执行更新语句。

那么对于其它SqlSession#openSession重载方法中的参数,我们能够很轻松得想到它们的作用:

  • boolean autoCommit,设置 SqlSession 是否自动提交;
  • Connection connection,设置 SqlSession 中使用的 Connection 实例(允许通过其它数据源获取);
  • TransactionIsolationLevel level,设置 SqlSession 中使用的事务隔离级别。

至于 ExecutorType 参数,它是用来选择 MyBatis 执行器的,MyBatis 中定义了 3 种类型的执行器:

  • ExecutorType#SIMPLE,该执行器会为每条 SQL 语句创建新的 PreparedStatement 实例;
  • ExecutorType#REUSE,该执行器会复用 PreparedStatement 实例;
  • ExecutorType#BATCH,该执行器会批量执行所有更新语句。

使用哪个SqlSession#openSession的重载方法,需要我们根据具体的业务场景来进行选择。

Tips:因为 SqlSessionManager 同时也实现了 SqlSession 接口,而且在使用过程中更多的是作为 SqlSession 的实现而使用,所以我会将 SqlSessionManager 放在 SqlSession 的章节中进行说明。

SqlSession

SqlSession 是 MyBatis 的接口,同样也是 MyBatis 的核心组件之一,定义了 MyBatis 与数据库交互的规范,提供了执行 SQL 语句,提交/回滚事务以及获取映射器(Mapper 接口)实例的方法。

SqlSession 有两个实现类:

DefaultSqlSession 是 SqlSession 的默认实现类,通过 DefaultSqlSessionFactory 获取

DefaultSqlSession 与 SqlSessionManager 的主要区别体现在两个方面:

  • 线程安全:
    • DefaultSqlSession 不是线程安全的 SqlSession 实例(也可以说是通过 DefaultSqlSessionFactory 获取的 SqlSession 实例不是线程安全的)
    • SqlSessionManager 提供了线程安全的 SqlSession 实例
  • 事务管理:
    • DefaultSqlSession 需要手动提交事务,或者在执行SqlSession#close方法时自动提交事务
    • 通过 SqlSessionManager 执行 SQL 语句时,会自动的进行事务提交。

SqlSession 实例的生命周期对应一次数据库会话 ,当我们通过 SqlSessionFactory 获取 SqlSession 实例时是 SqlSession 生命周期的开始,而我们调用SqlSession#close方法后,是 SqlSession 实例的生命周期的结束,这期间的过程通常对应着一项业务操作从开始到结束的过程,因此我们可以认为 SqlSession 实例的生命周期是一次业务操作从开始到结束的时间

特别提醒,虽然每个 SqlSession 实例都有与之对应的 Connection 实例,且数据库交互是由 Connection 实例完成的,但由于数据库连接池的存在,调用SqlSession#close方法后,SqlSession 实例只是将 Connection 实例"归还"到数据库连接池中,而不是调用Connection#close来关闭 Connection 实例,因此我们不能将 SqlSession 实例的生命周期与 Connection 实例的生命周期画上等号。

Tips:通过 SqlSession 执行 SQL 语句是 iBATIS 时代的用法,在当下的环境中,特别是在 MyBatis 与 Spring 集成后,我们通常会选择通过 SqlSession 实例获取映射器实例后直接调用接口方法,即在文章开头中解释映射器接口方法绑定时的使用方式。

Executor

Executor 是 MyBatis 中的接口,同样是 MyBatis 中的核心组件。Executor 接口定义了 MyBatis 与数据库交互的规范。不同 Executor 的实现类提供了不同的特性,例如:SimpleExecutor 每次都会创建 PreparedStatement 对象,ReuseExecutor 会复用已经存在的 PreparedStatement 对象,BatchExecutor 用于批量执行 SQL 更新语句,CachingExecutor 提供了查询结果的缓存能力。

MyBatis 中 Executor 的体系结构如下:

关于 Executor 体系的中各实现类的具体作用与功能,我会在 MyBatis 系列的后续文章中继续和大家分享。

MappedStatement

MappedStatement 中封装了 Mapper.xml 文件中映射的 SQL 语句信息,包括 SQL 语句的 id,SQL 语句,参数映射信息,结果集映射信息,以及缓存策略等。

StatementHandler

StatementHandler 是 MyBatis 中的接口,负责 MyBatis 中的 SQL 处理,如预编译,参数设置,SQL 语句执行等。

MyBatis 中 StatementHandler 的体系结构如下:

ResultSetHandler

ResultHandler 是 MyBatis 中的接口,依旧是 MyBatis 中的核心组件。ResultHandler 只有一个实现类 DefaultResultSetHandler,负责将数据库返回的结果集映射为 Java 对象 ,需要注意的是 ResultSetHandler 与 ResultHandler 是不同的,ResultSetHandler 负责 MyBatis 内部将结果集映射为 Java 对象,而 ResultHandler 提供了对结果集数据的二次处理能力,允许开发者进行自定义,会在 ResultSetHandler 处理完结果集的映射后调用ResultHandler#handlerResult方法。

问题答疑

上一篇文章中,我们只配置了一个 UserMapper.xml,因此有些小伙伴产生了迷惑,当存在多个 Mapper.xml,且每个文件中都有同名的查询语句时在 MyBatis 应用中该如何进行查询?

一句话概括就是通过 namespace + id 方式来关联到唯一的 SQL 语句映射上。类似于,当 Java 应用程序中存在多个同名 Java 类时,我们可以通过全限名的方式访问不同的 Java 类。

我们先随便建一个表,SQL 语句如下:

sql 复制代码
create table company (
  id              int          not null primary key,
  company_name    varchar(50)  not null,
  company_address varchar(500) not null
);

接着按照上篇文章中的方式分别创建 company 表对应的 CompanyDO,CompanyDAO 和 CompanyMapper.xml,其中 CompanyMapper.xml 需要定义与 UserMapper.xml 中同名的查询语句,CompanyMapper.xml 内容如下:

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="com.wyz.dao.CompanyDAO">
    <select id="selectAll" resultType="com.wyz.entity.CompanyDO" >
        select id, company_name, company_address from company
    </select>
</mapper>

接着我们修改 mybatis-config.xml 文件,添加映射文件 CompanyMapper.xml:

xml 复制代码
<configuration>
  <!-- 省略数据库配置的部分 -->

  <mappers>
    <mapper resource="mapper/UserMapper.xml"/>
    <mapper resource="mapper/CompanyMapper.xml"/>
  </mappers>
</configuration>

最后我们修改测试代码:

java 复制代码
@Test
public void testSelectAll() {
  SqlSession sqlSession = sqlSessionFactory.openSession();
  List<UserDO> users = sqlSession.selectList("com.wyz.dao.UserDAO.selectAll");
  for(UserDO userDo:users) {
    log.info(userDo.getName());
  }
}

这样我们就可以通过 namespace + id 的方式映射到指定的 SQL 语句了


好了,今天的内容就到这里了,如果本文对你有帮助的话,希望多多点赞支持,如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核 Java 技术的金融摸鱼侠 王有志,我们下次再见!

相关推荐
守护者17015 分钟前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
源码哥_博纳软云16 分钟前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
禾高网络18 分钟前
租赁小程序成品|租赁系统搭建核心功能
java·人工智能·小程序
学会沉淀。24 分钟前
Docker学习
java·开发语言·学习
如若12325 分钟前
对文件内的文件名生成目录,方便查阅
java·前端·python
初晴~1 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
盖世英雄酱581361 小时前
InnoDB 的页分裂和页合并
数据库·后端
小_太_阳1 小时前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾1 小时前
scala借阅图书保存记录(三)
开发语言·后端·scala
黑胡子大叔的小屋2 小时前
基于springboot的海洋知识服务平台的设计与实现
java·spring boot·毕业设计