一遍文章带你看懂:《MyBatis 源码分析》

【MyBatis 运行过程】

一遍文章带你看懂:《MyBatis 源码分析》_第1张图片

 

 

 

传统的 JDBC 编程查询数据库的代码和过程总结:

(一)、加载驱动。

(二)、创建连接,Connection 对象。

(三)、根据 Connection 创建 Statement 或者 PreparedStatement 来执行 sql 语句。

(四)、返回结果集到 ResultSet 中。

(五)、手动将 ResultSet 映射到 JavaBean 中。

一遍文章带你看懂:《MyBatis 源码分析》_第2张图片

 

 

 

编码方式实现 MyBatis 查询数据库,方便大家理解,不使用 SpringMybatis,加入 Spring 后整体流程会复杂很多。使用 MyBatis 后能将原来的传统的 JDBC 编程编的如此简单,具体流程总结:

(一)、使用配置文件构建 SqlSessionFactory。

(二)、使用 SqlSessionFactory 获得 SqlSession,SqlSession 相当于传统 JDBC 的 Conection。

(三)、使用 SqlSession 得到 Mapper。

(四)、用 Mapper 来执行 sql 语句,并返回结果直接封装到 JavaBean 中。

一遍文章带你看懂:《MyBatis 源码分析》_第3张图片

 

 

 

总结

一遍文章带你看懂:《MyBatis 源码分析》_第4张图片

 

 

 

【MyBatis 源码分析】

下面来具体分析 MyBatis 代码的执行过程

(一)、整体架构

一遍文章带你看懂:《MyBatis 源码分析》_第5张图片

 

 

 

(二)、源码分析

大部分框架的代码流程:

一遍文章带你看懂:《MyBatis 源码分析》_第6张图片

 

 

 

(三)、我们的配置文件:

一遍文章带你看懂:《MyBatis 源码分析》_第7张图片

 

 

 

 

一遍文章带你看懂:《MyBatis 源码分析》_第8张图片

 

 

 

(四)、SqlSession 的实现流程

一遍文章带你看懂:《MyBatis 源码分析》_第9张图片

 

 

 

 

SqlSession 的接口定义:(里面定义了增删改查和提交回滚等方法)

一遍文章带你看懂:《MyBatis 源码分析》_第10张图片

 

 

 

接下来用 sqlSession 获取对应的 Mapper:

 

 

 

DefaultSqlSession 的 getMapper 实现:

一遍文章带你看懂:《MyBatis 源码分析》_第11张图片

 

 

 

MapperRegistry 里 getMapper 的最终实现:

这里就要说明一下,我们的接口里面只定义了抽象的增删改查,而这个接口并没有任何实现类,那么这个 xml 到底是如何与接口关联起来并生成实现类那?

一遍文章带你看懂:《MyBatis 源码分析》_第12张图片

 

 

 

接下来我们看看 newInstance 的具体实现:

一遍文章带你看懂:《MyBatis 源码分析》_第13张图片

 

 

 

(五)、正常流程的动态代理:

一遍文章带你看懂:《MyBatis 源码分析》_第14张图片

 

 

 

与传统的动态代理相比,MyBatis 的接口是没有实现类的,那么它又是怎么实现动态代理的那?

MapperProxy 的源码:

一遍文章带你看懂:《MyBatis 源码分析》_第15张图片

 

 

 

MapperMethod 的定义:

一遍文章带你看懂:《MyBatis 源码分析》_第16张图片

 

 

 

进入 DefaultSqlSession 执行对应的 sql 语句:

一遍文章带你看懂:《MyBatis 源码分析》_第17张图片

 

 

 

Executor 的实现类里面执行 query 方法:

一遍文章带你看懂:《MyBatis 源码分析》_第18张图片

 

 

 

【手动实现一个简单的 MyBatis】

一遍文章带你看懂:《MyBatis 源码分析》_第19张图片

 

 

 

(一)、创建 SqlSessionFactory 实例。

(二)、实例化过程,加载配置文件创建 Configuration 对象。

(三)、通过 factory 创建 SqlSession。

(四)、通过 SqlSession 获取 mapper 接口动态代理。

(五)、动态代理回调 SqlSession 中某查询方法。

(六)、SqlSession 将查询方法转发给 Executor。

(七)、Executor 基于 JDBC 访问数据库获取数据。

(八)、Executor 通过反射将数据转换成 POJO并返回给 SqlSession。

(九)、将数据返回给调用者。

项目整体使用 Maven 构建,mybatis-demo 是脱离 Spring 的 MyBatis 使用的例子。paul-mybatis 是我们自己实现的 mybatis 框架。

一遍文章带你看懂:《MyBatis 源码分析》_第20张图片

 

 

 

首先按照我们以前的使用 mybatis 代码时的流程,创建 mapper 接口,xml 文件,和 POJO以及集一些配置文件。

1、接口:TUserMapper

一遍文章带你看懂:《MyBatis 源码分析》_第21张图片

 

 

 

2、xml 文件:

一遍文章带你看懂:《MyBatis 源码分析》_第22张图片

 

 

 

3、实体类,属性应该与数据库想匹配:

一遍文章带你看懂:《MyBatis 源码分析》_第23张图片

 

 

 

4、数据库连接配置文件、db.properties:

 

 

 

关注 xml 文件,mapper 文件里的 namespace,id,resultType 和 sql 语句都要存储起来,我们定义一个 POJO 来存储这些信息。

一遍文章带你看懂:《MyBatis 源码分析》_第24张图片

 

 

 

创建一个 Configuration 类,用来保存所有配置文件和 xml 文件里的信息:

一遍文章带你看懂:《MyBatis 源码分析》_第25张图片

 

 

 

有了配置类之后,我们可以通过这个配置类构建一个 SqlSessionFactory 了。

5、 SqlSessionFactory 抽象模版

 

 

 

Default 实现类主要完成了两个功能,加载配置信息到 Configuration 对象里,实现创建 SqlSession 的功能。

  package com.paul.mybatis.factory;
  import com.paul.mybatis.confiuration.Configuration;
  import com.paul.mybatis.confiuration.MappedStatement;
  import com.paul.mybatis.sqlsession.DefaultSqlSession;
  import com.paul.mybatis.sqlsession.SqlSession;
  import org.dom4j.Document;
  import org.dom4j.DocumentException;
  import org.dom4j.Element;
  import org.dom4j.io.SAXReader;
  import java.io.File;
  import java.io.IOException;
  import java.io.InputStream;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Properties;
  /**
   *
   * 1.初始化时就完成了 configuration 的实例化
   * 2.工厂类,生成 sqlSession
   *
   */
  public class DefaultSqlSessionFactory implements SqlSessionFactory{
      //希望Configuration 是单例子并且唯一的
      private final Configuration configuration = new Configuration();
      // xml 文件存放的位置
      private static final String MAPPER_CONFIG_LOCATION = "mappers";
      // 数据库信息存放的位置
      private static final String DB_CONFIG_FILE = "db.properties";
      public DefaultSqlSessionFactory() {
          loadDBInfo();
          loadMapperInfo();
      }
      private void loadDBInfo() {
          InputStream db = this.getClass().getClassLoader().getResourceAsStream(DB_CONFIG_FILE);
          Properties p = new Properties();
          try {
              p.load(db);
          } catch (IOException e) {
              e.printStackTrace();
          }
          //将配置信息写入Configuration 对象
          configuration.setJdbcDriver(p.get("jdbc.driver").toString());
          configuration.setJdbcUrl(p.get("jdbc.url").toString());
          configuration.setJdbcUsername(p.get("jdbc.username").toString());
          configuration.setJdbcPassword(p.get("jdbc.password").toString());
      }
      //解析并加载xml文件
      private void loadMapperInfo(){
          URL resources = null;
          resources = this.getClass().getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
          File mappers = new File(resources.getFile());
          //读取文件夹下面的文件信息
          if(mappers.isDirectory()){
              File[] files = mappers.listFiles();
              for(File file:files){
                  loadMapperInfo(file);
              }
          }
      }
      private void loadMapperInfo(File file){
          SAXReader reader = new SAXReader();
          //通过read方法读取一个文件转换成Document 对象
          Document document = null;
          try {
              document = reader.read(file);
          } catch (DocumentException e) {
              e.printStackTrace();
          }
          //获取根结点元素对象
          Element e = document.getRootElement();
          //获取命名空间namespace
          String namespace = e.attribute("namespace").getData().toString();
          //获取select,insert,update,delete子节点列表
          List selects = e.elements("select");
          List inserts = e.elements("select");
          List updates = e.elements("select");
          List deletes = e.elements("select");
          List all = new ArrayList<>();
          all.addAll(selects);
          all.addAll(inserts);
          all.addAll(updates);
          all.addAll(deletes);
          //遍历节点,组装成 MappedStatement 然后放入到configuration 对象中
          for(Element ele:all){
              MappedStatement mappedStatement = new MappedStatement();
              String id = ele.attribute("id").getData().toString();
              String resultType = ele.attribute("resultType").getData().toString();
              String sql = ele.getData().toString();
              mappedStatement.setId(namespace+"."+id);
              mappedStatement.setResultType(resultType);
              mappedStatement.setNamespace(namespace);
              mappedStatement.setSql(sql);
              configuration.getMappedStatement().put(namespace+"."+id,mappedStatement);
          }
      }
      @Override
      public SqlSession openSession() {
          return new DefaultSqlSession(configuration);
      }
  }

在 SqlSessionFactory 里创建了 DefaultSqlSession,我们看看它的具体实现。SqlSession里面应该封装了所有数据库的具体操作和一些获取 mapper 实现类的方法。使用动态代理生成一个加强类。这里面最终还是把数据库的相关操作转给 SqlSession,使用 mapper 能使编程更加优雅。

SqlSession 接口,定义模版方法

一遍文章带你看懂:《MyBatis 源码分析》_第26张图片

 

 

 

Default 的 SqlSession 实现类。里面需要传入 Executor,这个 Executor 里面封装了 JDBC 操作数据库的流程。我们重点关注 getMapper 方法。

一遍文章带你看懂:《MyBatis 源码分析》_第27张图片

 

 

 

动态代理的 InvocationHandler

一遍文章带你看懂:《MyBatis 源码分析》_第28张图片

 

 

 

最后来看我们的测试类

一遍文章带你看懂:《MyBatis 源码分析》_第29张图片

 

 

 

整个项目的源码在项目源码,如果你觉得或多或少对你有些帮助。

你可能感兴趣的:(MyBatis,源码分析)