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

【MyBatis 运行过程】

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

(一)、加载驱动。

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

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

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

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

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

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

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

(三)、使用 SqlSession 得到 Mapper。

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

总结

【MyBatis 源码分析】

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

(一)、整体架构

(二)、源码分析

大部分框架的代码流程:

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

(四)、SqlSession 的实现流程

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

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

DefaultSqlSession 的 getMapper 实现:

MapperRegistry 里 getMapper 的最终实现:

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

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

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

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

MapperProxy 的源码:

MapperMethod 的定义:

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

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

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

(一)、创建 SqlSessionFactory 实例。

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

(三)、通过 factory 创建 SqlSession。

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

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

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

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

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

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

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

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

1、接口:TUserMapper

2、xml 文件:

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

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

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

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

有了配置类之后,我们可以通过这个配置类构建一个 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();

          }

          //获取根结点元素对象<mapper>

          Element e = document.getRootElement();

          //获取命名空间namespace

          String namespace = e.attribute("namespace").getData().toString();

          //获取select,insert,update,delete子节点列表

          List<Element> selects = e.elements("select");

          List<Element> inserts = e.elements("select");

          List<Element> updates = e.elements("select");

          List<Element> deletes = e.elements("select");

          List<Element> 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 接口,定义模版方法

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

动态代理的 InvocationHandler

最后来看我们的测试类

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容