工作原理

image.png

1.读取 MyBatis 配置文件

mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。

2.加载映射文件

映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。


3.构造会话工厂

通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。

4.创建会话对象

由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

5.Executor 执行器

MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

6.MappedStatement 对象

在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。

7.输入参数映射

输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。

8. 输出结果映射

输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。


核心组件

我们先来看 MyBatis 的“表面现象”——组件,并且讨论它们的作用,然后讨论它们的实现原理。MyBatis 的核心组件分为 4 个部分。

1)SqlSessionFactoryBuilder(构造器):它会根据配置或者代码来生成 SqlSessionFactory,采用的是分步构建的 Builder 模式。

2)SqlSessionFactory(工厂接口):依靠它来生成 SqlSession,使用的是工厂模式。

3)SqlSession(会话):一个既可以发送 SQL 执行返回结果,也可以获取 Mapper 的接口。在现有的技术中,一般我们会让其在业务逻辑代码中“消失”,而使用的是 MyBatis 提供的 SQL Mapper 接口编程技术,它能提高代码的可读性和可维护性。

4)SQL Mapper(映射器):MyBatis 新设计存在的组件,它由一个 Java 接口和 XML 文件(或注解)构成,需要给出对应的 SQL 和映射规则。它负责发送 SQL 去执行,并返回结果。

用一张图来展示 MyBatis 核心组件之间的关系,如图 1 所示。
image.png
注意,无论是映射器还是 SqlSession 都可以发送 SQL 到数据库执行,下面学习这些组件的用法。

1.SqlSessionFactory

使用 MyBatis 首先是使用配置或者代码去生产 SqlSessionFactory,而 MyBatis 提供了构造器SqlSessionFactoryBuilder

它提供了一个类 org.apache.ibatis.session.Configuration作为引导,采用的是 Builder 模式。具体的分步则是在 Configuration 类里面完成的,当然会有很多内容,包括你很感兴趣的插件。

在 MyBatis 中,既可以通过读取配置的 XML 文件的形式生成 SqlSessionFactory,也可以通过 Java 代码的形式去生成 SqlSessionFactory

推荐采用 XML 的形式,因为代码的方式在需要修改的时候会比较麻烦。当配置了 XML 或者提供代码后,MyBatis 会读取配置文件,通过 Configuration 类对象构建整个 MyBatis 的上下文。

注意,SqlSessionFactory 是一个接口,在 MyBatis 中它存在两个实现类:SqlSessionManager 和DefaultSqlSessionFactory。

一般而言,具体是由 DefaultSqlSessionFactory 去实现的,而 SqlSessionManager 使用在多线程的环境中,它的具体实现依靠 DefaultSqlSessionFactory,它们之间的关系如图 1 所示。
image.png
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,而 SqlSessionFactory 唯一的作用就是生产 MyBatis 的核心接口对象 SqlSession,所以它的责任是唯一的。我们往往会采用单例模式处理它,下面讨论使用配置文件和 Java 代码两种形式去生成 SqlSessionFactory 的方法。

使用 XML 构建 SqlSessionFactory

首先,在 MyBatis 中的 XML 分为两类,一类是基础配置文件,通常只有一个,主要是配置一些最基本的上下文参数和运行环境;另一类是映射文件,它可以配置映射关系、SQL、参数等信息。

先看一份简易的基础配置文件,我们把它命名为 mybatis-config.xml,放在工程类路径下,其内容如下所示

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  3. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  4. <configuration>
  5. <typeAliases><!--别名-->
  6. <typeAliases alias="user" type="com.mybatis.po.User"/>
  7. </typeAliases>
  8. <!-- 数据库环境 -->
  9. <environments default="development">
  10. <environment id="development">
  11. <!-- 使用JDBC的事务管理 -->
  12. <transactionManager type="JDBC" />
  13. <dataSource type="POOLED">
  14. <!-- MySQL数据库驱动 -->
  15. <property name="driver" value="com.mysql.jdbc.Driver" />
  16. <!-- 连接数据库的URL -->
  17. <property name="url"
  18. value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" />
  19. <property name="username" value="root" />
  20. <property name="password" value="1128" />
  21. </dataSource>
  22. </environment>
  23. </environments>
  24. <!-- 将mapper文件加入到配置文件中 -->
  25. <mappers>
  26. <mapper resource="com/mybatis/mapper/UserMapper.xml" />
  27. </mappers>
  28. </configuration>

我们描述一下 MyBatis 的基础配置文件:

  • 元素定义了一个别名 user,它代表着 com.mybatis.po.User 这个类。这样定义后,在 MyBatis 上下文中就可以使用别名去代替全限定名了。
  • 元素的定义,这里描述的是数据库。它里面的 元素是配置事务管理器,这里采用的是 MyBatis 的 JDBC 管理器方式。
  • 元素配置数据库,其中属性 type=”POOLED” 代表采用 MyBatis 内部提供的连接池方式,最后定义一些关于 JDBC 的属性信息。
  • 元素代表引入的那些映射器,在谈到映射器时会详细讨论它。


有了基础配置文件,就可以用一段很简短的代码来生成 SqlSessionFactory 了,如下所示。

SqlSessionFactory factory = null;
String resource = "mybatis-config.xml";
InputStream is;
try {
    InputStream is = Resources.getResourceAsStream(resource);
    factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
    e.printStackTrace();
}

首先读取 mybatis-config.xml,然后通过 SqlSessionFactoryBuilder 的 Builder 方法去创建 SqlSessionFactory。整个过程比较简单,而里面的步骤还是比较烦琐的,只是 MyBatis 采用了 Builder 模式为开发者隐藏了这些细节。这样一个 SqlSessionFactory 就被创建出来了。

采用 XML 创建的形式,信息在配置文件中,有利于我们日后的维护和修改,避免了重新编译代码,因此推荐这种方式。

使用代码创建 SqlSessionFactory

虽然不推荐使用这种方式,但是我们还是谈谈如何使用它。
通过代码来实现与使用 XML 构建 SqlSessionFactory 一样的功能——创建 SqlSessionFactory,代码如下所示。

// 数据库连接池信息
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword ("1128");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setDefeultAutoCommit(false);

// 采用 MyBatis 的 JDBC 事务方式
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment ("development", transactionFactory, dataSource);

// 创建 Configuration 对象
Configuration configuration = new Configuration(environment);

// 注册一个 MyBatis 上下文别名
configuration.getTypeAliasRegistry().registerAlias("role", Role.class);

// 加入一个映射器
configuration.addMapper(RoleMapper.class);

//使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory
SqlSessionFactory SqlSessionFactory =
new SqlSessionFactoryBuilder().build(configuration);
return SqlSessionFactory;

注意代码中的注释,它和 XML 方式实现的功能是一致的,只是方式不太一样而已。但是代码冗长,如果发生系统修改,那么有可能需要重新编译代码才能继续,所以这不是一个很好的方式。

除非有特殊的需要,比如在配置文件中,需要配置加密过的数据库用户名和密码,需要我们在生成 SqlSessionFactory 前解密为明文的时候,才会考虑使用这样的方式。


2.SqlSession

在 MyBatis 中,SqlSession 是其核心接口。在 MyBatis 中有两个实现类,DefaultSqlSession 和 SqlSessionManager。

DefaultSqlSession 是单线程使用的,而 SqlSessionManager 在多线程环境下使用。SqlSession 的作用类似于一个 JDBC 中的 Connection 对象,代表着一个连接资源的启用。具体而言,它的作用有 3 个:

  • 获取 Mapper 接口。
  • 发送 SQL 给数据库。
  • 控制数据库事务。


先来掌握它的创建方法,有了 SqlSessionFactory 创建的 SqlSession 就十分简单了,如下所示。

SqlSession sqlSession = SqlSessionFactory.openSession();

注意,SqlSession 只是一个门面接口,它有很多方法,可以直接发送 SQL。它就好像一家软件公司的商务人员,是一个门面,而实际干活的是软件工程师。在 MyBatis 中,真正干活的是 Executor,我们会在底层看到它。

SqlSession 控制数据库事务的方法,如下所示

//定义 SqlSession
SqlSession sqlSession = null;
try {
    // 打开 SqlSession 会话
    sqlSession = SqlSessionFactory.openSession();
    // some code...
    sqlSession.commit();    // 提交事务
} catch (IOException e) {
    sqlSession.rollback();  // 回滚事务
}finally{
    // 在 finally 语句中确保资源被顺利关闭
    if(sqlSession != null){
        sqlSession.close();
    }
}

这里使用 commit 方法提交事务,或者使用 rollback 方法回滚事务。因为它代表着一个数据库的连接资源,使用后要及时关闭它,如果不关闭,那么数据库的连接资源就会很快被耗费光,整个系统就会陷入瘫痪状态,所以用 finally 语句保证其顺利关闭。


3.Sql Mapper

映射器是 MyBatis 中最重要、最复杂的组件,它由一个接口和对应的 XML 文件(或注解)组成。
它可以配置以下内容:

  • 描述映射规则。
  • 提供 SQL 语句,并可以配置 SQL 参数类型、返回类型、缓存刷新等信息。
  • 配置缓存。
  • 提供动态 SQL。

两种实现映射器的方式,XML 文件形式和注解形式。不过在此之前,先用以下 SQL 语句创建 role 表

CREATE TABLE `role` (
    `id` bigint(20) NOT NULL,
    `role_name` varchar(20) DEFAULT NULL,
    `note` varchar(20) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

并且定义一个 POJO,它十分简单,如下所示。

package com.mybatis.po;
public class Role{
  private Longid;
  private String roleName;
  private String note;
/**setter and getter**/
}

映射器的主要作用就是将 SQL 查询到的结果映射为一个 POJO,或者将 POJO 的数据插入到数据库中,并定义一些关于缓存等的重要内容。

注意,开发只是一个接口,而不是一个实现类。初学者可能会产生一个很大的疑问,那就是接口不是不能运行吗?

是的,接口不能直接运行。MyBatis 运用了动态代理技术使得接口能运行起来,入门阶段只要懂得 MyBatis 会为这个接口生成一个代理对象,代理对象会去处理相关的逻辑即可。

用 XML 实现映射器

用 XML 定义映射器分为两个部分:接口和 XML。先定义一个映射器接口,如下所示。

package com.mybatis.mapper;
import com.mybatis.po.Role;
public interface RoleMapper {
    public Role getRole(Long id);
}

在用 XML 方式创建 SqlSession 的配置文件中有这样一段代码:
<mapper resource="com/mybatis/mapper/RoleMapper.xml" />
它的作用就是引入一个 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.mybatis.mapper.RoleMapper"> 
    <select id="getRole" parameterType="long" resultType="role">
        SELECT id,role_name as roleName,note FROM role WHERE id =#{id}
    </select>
</mapper>

有了这两个文件,就完成了一个映射器的定义。XML 文件还算比较简单,我们稍微讲解一下:

元素中的属性 namespace 所对应的是一个接口的全限定名,于是 MyBatis 上下文就可以通过它找到对应的接口。