MyBatis工作原理

2022-09-14 21:22:32 浏览数 (1)

1、MyBatis工作原理

前面我们学习了MyBatis的一些简单案例实现,下面我们来了解一下MyBatis工作原理。

1.1、执行流程图

说明:

mybatis应用程序通过SqlSessionFactoryBuilder从SqlMapConfig.xml配置文件来构建SqlSessionFactory;

然后,SqlSessionFactory的实例直接开启一个SqlSession,再通过SqlSession实例获得Mapper对象并运行Mapper映射的SQL语句,完成对数据库的CRUD和事务提交,之后关闭SqlSession。

1.2、工作原理图

说明:

(1)读取 MyBatis 配置文件:SqlSessionConfig.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。

(2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 SqlSessionConfig.xml中加载。SqlSessionConfig.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 对结果集的解析过程。

2、MyBatis核心组件

2.1、核心组件介绍

MyBatis 的核心组件分为 4 个部分:构造器、工厂接口、会话、映射器。

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

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

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

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

2.2、核心组件关系图

3、构造器/工厂接口

3.1、构造器/工厂接口介绍

3.1.1、构造器

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

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

3.1.2、工厂接口

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

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,而 SqlSessionFactory 唯一的作用就是生产 MyBatis 的核心接口对象 SqlSession,所以它的责任是唯一的。

3.2、XML 构建 SqlSessionFactory

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

3.2.1、配置SqlMapConfig.xml文件
代码语言:javascript复制
<?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>
    <!-- 配置 mybatis 的环境 -->
    <environments default="mysql">
        <!-- 配置 mysql 的环境 -->
        <environment id="mysql">
            <!-- 配置事务的类型 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置连接数据库的信息:用的是数据源(连接池) -->
            <dataSource type="POOLED">
                <!-- <property name="driver" value="com.mysql.jdbc.Driver"/>-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.123:3306/mybatis?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="Root12345"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 告知 mybatis 映射配置的位置 -->
    <mappers>
        <mapper class="com.tyschool.mb001.user.dao.IUserDao"/>
    </mappers>
</configuration>

注意:

元素的定义,这里描述的是数据库。它里面的 元素是配置事务管理器,这里采用的是 MyBatis 的 JDBC 管理器方式。

元素配置数据库,其中属性 type=“POOLED” 代表采用 MyBatis 内部提供的连接池方式,最后定义一些关于 JDBC 的属性信息。

元素代表引入的那些映射器,在谈到映射器时会详细讨论它。

3.2.2、生成 SqlSessionFactory
代码语言:javascript复制
SqlSessionFactory factory = null;
String resource = "SqlMapConfig.xml";
InputStream is;
try {
    InputStream is = Resources.getResourceAsStream(resource);
    factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
    e.printStackTrace();
}

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

4、会话

4.1、会话介绍

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

SqlSession 的作用类似于一个 JDBC 中的 Connection 对象,代表着一个连接资源的启用。具体而言,它的作用有 3 个:

(1)获取 Mapper 接口。

(2)发送 SQL 给数据库。

(3)控制数据库事务。

4.2、创建SqlSession

代码语言:javascript复制
SqlSession sqlSession = SqlSessionFactory.openSession();

注意,SqlSession 只是一个门面接口,它有很多方法,可以直接发送 SQL。在 MyBatis 中,真正干活的是 Executor,我们会在底层看到它。

代码语言:javascript复制
//定义 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();
    }
}

5、映射器

5.1、映射器介绍

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

(1)描述映射规则。

(2)提供 SQL 语句,并可以配置 SQL 参数类型、返回类型、缓存刷新等信息。

(3)配置缓存。

(4)提供动态 SQL。

5.2、映射器作用

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

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

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

5.3、映射器分类

映射器有二种方式:XML 文件形式和注解形式

5.3.1、XML文件形式
代码语言:javascript复制
<?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.tianyi.dao.IUserDao">
    <!-- 配置查询所有操作 -->
    <select id="findAll" resultType="com.tianyi.javabean.User">
        select * from user
    </select>
</mapper>
代码语言:javascript复制
package com.tianyi.dao;

import com.tianyi.javabean.User;
import java.util.List;
public interface IUserDao {
    List<User> findAll();
}
代码语言:javascript复制
<mappers>
	<mapper resource="com/tianyi/dao/IUserDao.xml"/>
</mappers>
5.3.2、注解形式
代码语言:javascript复制
package com.tianyi.dao;

import org.apache.ibatis.annotations.Select;
import java.util.List;

public interface IUserDao {
		@Select("select * from user")
    List<User> findAll();
}
代码语言:javascript复制
<mappers>
	<mapper class="com.tianyi.dao.IUserDao"/>
</mappers>

完全等同于 XML 方式创建映射器。也许你会觉得使用注解的方式比 XML 方式要简单得多。如果它和 XML 方式同时定义时,XML 方式将覆盖掉注解方式,所以 MyBatis 官方推荐使用的是 XML 方式。

5.3.3、为什么选择XML文件形式

SQL复杂情况

在SQL 比较复杂,如果放入 @Select 中会明显增加注解的内容。如果把大量的 SQL 放入 Java 代码中,显然代码的可读性也会下降。

如果考虑SQL的动态参数,注解的方式会更加复杂,不利于日后的维护和修改。

XML比注解灵活

XML 可以相互引入,而注解是不可以的,所以在一些比较复杂的场景下,使用 XML 方式会更加灵活和方便。

6、SQL执行

MyBatis 执行 SQL 语句的两种方式:SqlSession发送SQL和Mapper发送SQL

6.1、SqlSession发送SQL

有了映射器就可以通过 SqlSession 发送 SQL 了

代码语言:javascript复制
public List<User> findAll() {
        SqlSession session =factory.openSession();
        List <User> users=session.selectList("com.tianyi.dao.IUserDao.findAll");
        session.close();
        return users;
    }

6.2、Mapper发送SQL

代码语言:javascript复制
SqlSession session = factory.openSession();
IUserDao userDao = session.getMapper(IUserDao.class);
List <User>users = userDao.findAll();

注意:

通过 SqlSession 的 getMapper 方法来获取一个 Mapper 接口,就可以调用它的方法了。因为 XML 文件或者接口注解定义的 SQL 都可以通过“类的全限定名 方法名”查找,所以 MyBatis 会启用对应的 SQL 进行运行,并返回结果。

6.3、为什么选择Mapper发送SQL

使用 Mapper 接口编程可以消除 SqlSession 带来的功能性代码,提高可读性,而 SqlSession 发送 SQL,需要一个 SQL id 去匹配 SQL,比较晦涩难懂。

使用 Mapper 接口,则是完全面向对象的语言,更能体现业务的逻辑。

使用 userDao.findAll()方式,IDEA 会提示错误和校验,而使用 sqlSession.selectList()语法,只有在运行中才能知道是否会产生错误。

目前使用 Mapper 接口编程已成为主流,尤其在 Spring 中运用 MyBatis 时,Mapper 接口的使用就更为简单。

7、核心组件的生命周期

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不需要长期存在。

SqlSessionFactoryBuilder最佳作用域是方法作用域(也就是局部方法变量)。

SqlSessionFactory

SqlSessionFactory 它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用。

SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。

SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况。所以我们要把SqlSessionFactory 写成一个单例对象,让它在应用中被共享。

SqlSessionFactory 的最佳作用域是应用作用域。

SqlSession

SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。

所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用异常处理语句来保证其正确关闭。

SqlSession 的最佳的作用域是请求或方法作用域。

Mapper

Mapper 是一个接口,它由 SqlSession 所创建,所以它的最大生命周期至多和 SqlSession 保持一致,尽管它很好用,但是由于 SqlSession 的关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于 SqlSession 的生命周期。

Mapper 代表的是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它。

0 人点赞