王有志,一个分享硬核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 的用户:

<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 接口,添加两个对应的方法声明:

public interface UserMapper {

  List<UserDO> selectAll();

  UserDO selectFirstUser();
}

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

@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.xxml

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 的方式一模一样,代码如下所示:

@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的重载方法:

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 语句如下:

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 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:

<configuration>
<!-- 省略数据库配置的部分 --> <mappers>
<mapper resource="mapper/UserMapper.xml"/>
<mapper resource="mapper/CompanyMapper.xml"/>
</mappers>
</configuration>

最后我们修改测试代码:

@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 技术的金融摸鱼侠王有志,我们下次再见!

MyBatis 应用的组成的更多相关文章

  1. 【分享】标准springMVC+mybatis项目maven搭建最精简教程

    文章由来:公司有个实习同学需要做毕业设计,不会搭建环境,我就代劳了,顺便分享给刚入门的小伙伴,我是自学的JAVA,所以我懂的.... (大图直接观看显示很模糊,请在图片上点击右键然后在新窗口打开看) ...

  2. Java MyBatis 插入数据库返回主键

    最近在搞一个电商系统中由于业务需求,需要在插入一条产品信息后返回产品Id,刚开始遇到一些坑,这里做下笔记,以防今后忘记. 类似下面这段代码一样获取插入后的主键 User user = new User ...

  3. [原创]mybatis中整合ehcache缓存框架的使用

    mybatis整合ehcache缓存框架的使用 mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓 ...

  4. 【SSM框架】Spring + Springmvc + Mybatis 基本框架搭建集成教程

    本文将讲解SSM框架的基本搭建集成,并有一个简单demo案例 说明:1.本文暂未使用maven集成,jar包需要手动导入. 2.本文为基础教程,大神切勿见笑. 3.如果对您学习有帮助,欢迎各种转载,注 ...

  5. mybatis plugins实现项目【全局】读写分离

    在之前的文章中讲述过数据库主从同步和通过注解来为部分方法切换数据源实现读写分离 注解实现读写分离: http://www.cnblogs.com/xiaochangwei/p/4961807.html ...

  6. MyBatis基础入门--知识点总结

    对原生态jdbc程序的问题总结 下面是一个传统的jdbc连接oracle数据库的标准代码: public static void main(String[] args) throws Exceptio ...

  7. Mybatis XML配置

    Mybatis常用带有禁用缓存的XML配置 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ...

  8. MyBatis源码分析(一)开篇

    源码学习的好处不用多说,Mybatis源码量少.逻辑简单,将写个系列文章来学习. SqlSession Mybatis的使用入口位于org.apache.ibatis.session包中的SqlSes ...

  9. (整理)MyBatis入门教程(一)

    本文转载: http://www.cnblogs.com/hellokitty1/p/5216025.html#3591383 本人文笔不行,根据上面博客内容引导,自己整理了一些东西 首先给大家推荐几 ...

  10. MyBatis6:MyBatis集成Spring事物管理(下篇)

    前言 前一篇文章<MyBatis5:MyBatis集成Spring事物管理(上篇)>复习了MyBatis的基本使用以及使用Spring管理MyBatis的事物的做法,本文的目的是在这个的基 ...

随机推荐

  1. Cocos Creator 2.x升级至Cocos Creator 3.x

    1.导入类时,批量导入 2.导入 override...关键字时,批量导入 3.this.node.scale = 0.6-->this.node.setScale(0.6, 0.6); 4.n ...

  2. 【Azure 应用服务】Azure Function Python函数中,如何获取Event Hub Trigger的消息Event所属于的PartitionID呢?

    问题描述 在通过Azure Function消费Event Hub中的消息时,我们从Function 的 Trigger Details 日志中,可以获得当前Funciton中处理的消息是哪一个分区( ...

  3. 【Azure 微服务】Service fabric升级结构版本失败问题

    问题描述 Service fabric升级结构版本失败,Service Fabric的可靠性层是白银层,持久性层为青铜层,当把节点从6个直接在虚拟规模集(VMSS)中缩放成了3个.从而引起了Servi ...

  4. docker 安装 es-head 以及Content-Type header请求头错误解决

    拉取es-head镜像,启动 docker pull mobz/elasticsearch-head:5 docker run -itd --name es-head -p 9100:9100 mob ...

  5. obsidian 日记本倒序汇总 获取标题显示 插件dataviewjs list

    obsidian 日记本倒序汇总 获取标题显示 插件dataviewjs list // dataviewjs function removeDuplicate(arr) { return arr.f ...

  6. liunx 前台打包的两个报错 Invalid value used in weak set - MIS国产化服务器不支持打包

    错误1 Invalid value used in weak set Webpack4使用 mini-css-extract-plugin 最新版 压缩css 报 "Invalid valu ...

  7. inputNextFocus vue - js 跳转 下一个 tab

    inputNextFocus vue - js 跳转 下一个 tab <template> <Input v-model="val1" ref="inp ...

  8. Codeforces Round #844:C. Equal Frequencies

    一.来源:Problem - C - Codeforces 二.题面 三.思路 先考虑一个子问题模型:我们现在有用\(m_1\)种随机字母组成的n个数,各字母个数未定,现在需要使这n个数变为\(m_2 ...

  9. 一道题开始认识Symbol

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 最近每天学习的时候,发现了一道很有趣的面试题 1.const [a, b] = { a: 100, b: 200 } 2.console. ...

  10. OpenCvSharp+Yolov5Net+Onnx 完整Demo

    效果 工程 代码 using Microsoft.ML.OnnxRuntime; using OpenCvSharp; using System; using System.Collections.G ...