由浅入深分析Mybatis源码执行流程

JDBC

在开发过中不免和数据库进行交互,数据库有MySQL、Oracle、SQLServer、Db2等等。如果每使用一种数据库都要猿们学习一种新的操作方式…头发他不允许!

什么是JDBC

JDBC规范定义接口,具体的实现由各大数据库厂商来实现。

JDBC是Java访问数据的标准规范,只要会调用JDBC接口中的方法即可操纵数据库。当然真正的数据库操作还是需要数据库的驱动类,数据库驱动类是由厂商提供的。我们在使用JDBC规范时制定数据库驱动类即可。

使用JDBC好处:

  1. 程序员需要访问数据,只要会调用JDBC接口方法即可,不关心类是如何实现的。
  2. 使用同一套Java代码,进行少量修改就可以访问其他支持JDBC规范的数据库。
    在这里插入图片描述

JDBC连接数据库

连接步骤为:

  1. 注册驱动
  2. 获取数据库连接对象Connection
  3. 定义SQL语句
  4. 获取执行SQL对象Statement
  5. 执行SQL,接收返回结果
  6. 处理结果
  7. 释放资源
public class JDBCTest {

    @Test
    public void test() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet rs = null;

        try {
            // 加载数据库驱动
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 通过驱动管理类获取数据库链接connection = DriverManager
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/framework?characterEncoding=utf-8&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8",
                    "root", "24862486");

            // 定义sql语句 ?表示占位符
            String sql = "select * from user where username = ?";

            // 获取预处理 statement
            preparedStatement = connection.prepareStatement(sql);

            // 设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的
            preparedStatement.setString(1, "老李");

            // 向数据库发出 sql 执行查询,查询出结果集
            rs = preparedStatement.executeQuery();

            // 遍历查询结果集
            while (rs.next()) {
                System.out.println(rs.getString("id") + " " + rs.getString("username") + " " + rs.getString("address"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

JDBC的缺陷

  1. 频繁的连接和释放,浪费资源,影响性能(可以用数据库连接池解决此问题);
  2. SQL硬编码问题,不便于后期的维护,SQL变化要改动Java编码;不符合对修改关闭,对扩展开放原则
  3. 参数硬编码问题,参数对应where条件,必须一一对应,否则报错;
  4. 结果集解析存在硬编码,SQL变化导致解析代码变化。

Mybatis框架

Mybatis是一款优秀的持久层框架,支持定制化SQL、存储过程以及高级映射。Mybatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集,它可以使用简单的XML或注解来配置和映射SQL信息,将Java中的POJOs和数据库记录互相映射。

Mybatis解决了JDBC硬编码的缺陷,减少了重复代码,同时处理了很多细节问题,包括事务处理,安全性,数据流控制等问题。先看一下Mybatis如何使用的

Mybatis的使用

做一个简单的需求,根据用户ID查询用户信息

代码放在GitHub上:https://github.com/EamonHu/FrameWork/tree/master/mybatis_demo

依赖导入pom.xml

 <dependencies>
        <!-- mysql依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>

        <!-- 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.4</version>
        </dependency>
    </dependencies>

全局配置文件

<?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>
    <properties  resource="db.properties"></properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${db.driver}" />
                <property name="url" value="${db.url}" />
                <property name="username" value="${db.username}" />
                <property name="password" value="${db.password}" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 1.使用相对于类路径的资源
            注意在resources下新建层级文件夹
         -->
        <!--<mapper resource="cn/eamon/mybatis/dao/UserDao.xml" />-->

        <!-- 2.使用绝对路径加载资源 -->
        <!-- <mapper url="file:///F:/Code/Framework/mybatis_demo/src/main/resources/cn/eamon/mybatis/dao/UserDao.xml" />-->

        <!-- 3.使用mapper接口类路径,加载映射文件
            要求mapper接口名称和mapper映射文件名称相同,且在同一个目录中
         -->
        <!--<mapper class="cn.eamon.mybatis.dao.UserDao" />-->

        <!-- 4.指定包下所有mapper接口,来加载映射文件 -->
        <package name="cn.eamon.mybatis.dao"/>
    </mappers>
</configuration>

数据库配置文件

db.driver=com.mysql.cj.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/framework?characterEncoding=utf-8&serverTimezone=UTC
db.username=root
db.password=24862486

POJO

public class User {

    private int id;

    private String username;

    private Date birthday;

    private String sex;

    private String address;
    //get set方法省略
}

mapper映射文件

<?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="cn.eamon.mybatis.dao.UserDao">
    <!-- 注意事项: -->
    <!-- 1:如果parameterType为简单类型(基本类型+String类),#{}中的参数名称可以任意 -->
    <!-- 2:如果parameterType为POJO类型,#{}中的参数名称必须和POJO中的属性名称一致 -->
    <!-- 3:如果resultType为POJO类型,SELECT中的列名和POJO中的属性名称一致 -->

    <!-- 根据ID获取用户信息 -->
    <select id="findUserById" parameterType="int"
            resultType="cn.eamon.mybatis.po.User">
        SELECT * FROM user WHERE id = #{id}
    </select>

    <!-- 根据名称模糊查询用户列表 -->
    <select id="findUsersByName" parameterType="java.lang.String"
        resultType="cn.eamon.mybatis.po.User">
        select * from user where username like '%${value}%'
    </select>
</mapper>

dao

public interface UserDao {
    /**
     * 通过用户ID查询用户信息
     * @param id 用户ID
     * @return 用户信息
     * @throws Exception 执行SQL异常
     */
    User findUserById(int id) throws Exception;

    /**
     * 通过用户名模糊查询你用户信息列表
     * @param name  用户名
     * @return 用户信息列表
     * @throws Exception 执行SQL异常
     */
    List<User> findUsersByName(String name) throws Exception;
}

// dao实现类
public class UserDaoImpl implements UserDao {

    private SqlSessionFactory sqlSessionFactory;

    // 注入SqlSessionFactory
    public UserDaoImpl(SqlSessionFactory sqlSessionFactory){
        this.sqlSessionFactory = sqlSessionFactory;
    }

    public User findUserById(int id) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = null;
        try{
            user = sqlSession.selectOne("cn.eamon.mybatis.dao.UserDao.findUserById", id);
            System.out.println(user);
        }finally {
            sqlSession.close();
        }
        return user;
    }

    public List<User> findUsersByName(String name) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> users = null;
        try{
            users = sqlSession.selectList("cn.eamon.mybatis.dao.UserDao.findUsersByName", name);
            System.out.println(users);
        }finally {
            sqlSession.close();
        }
        return users;
    }
}

测试代码

public class MybatisXMLTest {
    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws Exception{
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    }

    @Test
    public void testFindUsersId() throws Exception {
        UserDao userDao = new UserDaoImpl(sqlSessionFactory);
        User user = userDao.findUserById(1);
        System.out.println(JSON.toJSONString(user));
    }
}

输出结果:{“address”:“北京海淀”,“id”:1,“sex”:“2”,“username”:“张三”}

Mybatis框架设计

为了解Mybatis执行流程,先看看它的架构

在这里插入图片描述

接口层

接口层是Mybatis提供给开发人员的一套API,主要是用SqlSession接口,通过SQLSession接口和Mapper接口。开发人员可以通过Mybatis框架调用SQL命令以及关联参数。

数据处理层

数据处理成是Mybatis框架内部的核心实现,来完成对映射文件的解析与数据处理:

  1. 参数解析与参数绑定
  2. SQL解析
  3. SQL执行
  4. 结果集映射和处理

支撑层

之层层来完成Mybatis与数据库基本连接方式以及SQL命令与配置文件对,主要负责:

  1. Mybatis与数据库连接方式管理
  2. Mybatis对事务管理
  3. 配置文件的加载(基于xml/基于注解)
  4. Mybatis查询缓存管理

架构流程图

在这里插入图片描述

  • Mybatis配置文件
    • SqlMapConfig.xml:Mybatis的全局配置文件,配置Mybatis的运行环境等
    • Mapper.xml:sql映射文件,需要在SqlMapConfig.xml中加载
  • SqlSeessionFactory:SQLSessionFactoryBuilder通过SqlMapConfig.xml文件构造SqlSessionFactory,即会话工厂
  • SqlSession:通过会话工厂创造sqlSession会话,开发人员通过sqlSession接口对数据库进行增删改查
  • Executor执行器:Executor接口有两个实现,一个是基本执行器(默认),一个是缓存执行器,sqlSession是通过executor接口来操作数据库的。
  • MappedStatement:是mybatis一个底层封装对象,包装了mybatis的配置信息以及SQL映射信息等。mapper.xml文件中的select/insert/update/delete标签对应一个MappedStatement对象,标签id对应的是MappedStatement的id。
    • Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、
      pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql
      中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
    • Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、
      pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象
      中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

Mybatis执行流程

Mybatis的执行流程如下:

在这里插入图片描述

大概分为五个步骤,通过分析每个步骤,看Mybatis如何执行的

加载配置文件

// 1. 获取配置文件的流文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");

Resources是ibatis.io包下面的类,也就是一个io流,用于读写文件,通过getResourceAsStream把xml文件加载进来,把配置文件解析为一个流。

生成SqlSessionFactory

// 2. 获取SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这是使用到了构建者模式,先new一个SqlSessionFactoryBuilder,因为配置文件编写的多样性,不同的配置文件生成不同的Configuration对象,故使用构建者模式SqlSessionFactoryBuilder生成不同的Configuration类。build方法:
在这里插入图片描述

接着看到build方法中新建了一个XMLConfigBuilder类,然后调用了它的parse方法,返回了一个Configuration类;
在这里插入图片描述

接着回来,反回了Configuration,接着调用build方法,这时候才new出来了一个DefaultSqlSessionFactory,至此,第二步走完了获得了SqlSessionFactory对象。
在这里插入图片描述

获取SqlSession

// 3.获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();

看看openSession()方法
在这里插入图片描述

通过配置文件生成事务管理Transaction,接着得到执行器Executor,相当于JDBC中的statement,最后返回了sqlSession

其中Executor
在这里插入图片描述

处理器默认先走缓存,如果没有开启缓存默认常规执行器。几种处理器:

  1. SimpleExecutor:常规执行器,每次执行都会创建一个statement,用完后关闭
  2. ReuseExecutor:可重用执行器将statement存入map中,操作map中的statement而不会重复创建statement
  3. BatchExecutor:批处理型处理器,doUpdate预处理存储过程或批处理操作,doQuery提交并执行过程。
  4. CachingExecutor:缓存执行器,见名知义,它就是Mybatis的二级缓存

动态代理生成mapper接口的代理对象

// 4.操作Mapper接口
AnnotationUser mapper = sqlSession.getMapper(AnnotationUser.class);

在这里插入图片描述

通过JDK动态代理,帮mapper接口生成代理实现类
在这里插入图片描述

代理对象执行代理方法

// 5.代理对象执行findUserById
User user = mapper.findUserById(1);

findUserById方法实际是调用MapperProxy的invoke方法
在这里插入图片描述

查看execute方法
在这里插入图片描述

进入selectOne方法
在这里插入图片描述

实际调用的是selectList方法
在这里插入图片描述

获取MappedStatement,然后执行query方法
在这里插入图片描述

boundSql的解析主要是通过对#{}字符的解析,将其替换成?。最后均包装成预表达式供PrepareStatement调用执行
在这里插入图片描述

调用doQuery
在这里插入图片描述

BatchExecutor的query,万转千山终是见JDBC
在这里插入图片描述

站在巨人的肩膀上能够看得更远,感谢以下博客在学习的路上给了指导

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值