栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

带你揭开mybatis源码的层层面纱

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

带你揭开mybatis源码的层层面纱

目录

概述

架构设计

Mybatis的调用链路简介 

构件描述

总体流程

源码剖析

读取xml配置文件

源码分析-执⾏SQL流程

先简单介绍SqlSession :

源码剖析-executor

源码剖析-StatementHandler

Mapper代理⽅式:

源码剖析-getmapper()


概述

之前的文章写过一篇自定义持久层框架,实现了Mybatis相似的功能。如果想快速掌握mybatis,强烈推荐简单阅读一下这篇文章。

自定义持久层框架

源码地址

https://gitee.com/li_gang_x/my-project-model

有了上面的基础,阅读mybatis源码就不那么陌生了,你会有些许熟悉,似曾相识的感觉。下面我将带领读者进入到mybatis框架,领略大佬们是怎么实现这个持久层框架的,怎么优雅的实现。废话不多说,下面进入主题。


架构设计

要想 整体把握一个框架,不管要属性怎么使用,而且要知其所以然。整体把握一个框架,首先就要对框架整体有一个认知。  

我们把 Mybatis 的功能架构分为三层: (1) API 接⼝层:提供给外部使⽤的接⼝ API ,开发⼈员通过这些本地 API 来操纵数据库。接⼝层⼀接收到调⽤请求就会调⽤数据处理层来完成具体的数据处理。 MyBatis 和数据库的交互有两种⽅式:
  • a. 使⽤传统的MyBatis提供的API ;
  • b. 使⽤Mapper代理的⽅式(生成代理对象进行实现)
(2) 数据处理层:负责具体的 SQL 查找、 SQL 解析、 SQL 执⾏和执⾏结果映射处理等。它主要的⽬的是根据调⽤的请求完成⼀次数据库操作。 (3) 基础⽀撑层:负责最基础的功能⽀撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共⽤的东⻄,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的⽀撑

Mybatis的调用链路简介 

构件描述
  • SqlSession
        作为MyBatis ⼯作的主要顶层 API ,表示和数据库交互的会话,完成必要数         据库增删改查功能
  • Executor
        MyBatis执⾏器,是 MyBatis 调度的核⼼,负责 SQL 语句的⽣成和查询缓存的维护
  • StatementHandler
        封装了JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参数、将Statement 结果集转换成List 集合。
  • ParameterHandler
        负责对⽤户传递的参数转换成JDBC Statement 所需要的参数,
  • ResultSetHandler
        负责将JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合;
  • TypeHandler
        负责java 数据类型和 jdbc 数据类型之间的映射和转换
  • MappedStatement
        MappedStatement维护了⼀条< select | update | delete | insert >节点的封装
  • SqlSource

        负责根据用户传递的parameterObject,动态生成Sql语句,将信息封装到BoundSql对象中并返回

  • BoundSql
        表示动态⽣成的SQL 语句以及相应的参数信息

总体流程
  1. 加载配置并初始化
  2. 接收调⽤请求
  3. 处理操作请求
  4. 返回处理结果
  (1) 加载配置并初始化 触发条件: 加载配置⽂件 配置来源于两个地⽅, ⼀个是配置⽂件 ( 主配置⽂件 conf.xml,mapper ⽂件 *.xml), — 个是 java 代码中的 注解, 将 主配置⽂件 内容解析封装到 Configuration , 将 sql的配置信息 加载成为⼀个 mappedstatement 对象,存储在内存之中 (2) 接收调⽤请求 触发条件 :调⽤ Mybatis 提供的 API 传⼊参数:为 SQL 的 ID 和传⼊参数对象 处理过程: 将请求传递给下层的请求处理层进⾏处理。 (3) 处理操作请求 触发条件: API 接⼝层传递请求过来 传⼊参数:为 SQL 的 ID 和传⼊参数对象 处理过程:
  • (A) 根据SQL的ID查找对应的MappedStatement对象。
  • (B) 根据传⼊参数对象解析MappedStatement对象,得到最终要执⾏的SQL和执⾏传⼊参数。
  • (C) 获取数据库连接,根据得到的最终SQL语句和执⾏传⼊参数到数据库执⾏,并得到执⾏结果。
  • (D) 根据MappedStatement对象中的结果映射配置对得到的执⾏结果进⾏转换处理,并得到最终的处理结果。
  • (E) 释放连接资源。
(4) 返回处理结果 将最终的处理结果返回  

源码剖析

读取xml配置文件
public class SqlSessionFactoryBuilder {

  //SqlSessionFactoryBuilder有9个build()方法
  //发现mybatis文档老了,http://www.mybatis.org/core/java-api.html,关于这块对不上

  //以下3个方法都是调用下面第4种方法
  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }

  //第4种方法是最常用的,它使用了一个参照了XML文档或更特定的SqlMapConfig.xml文件的Reader实例。
  //可选的参数是environment和properties。Environment决定加载哪种环境(开发环境/生产环境),包括数据源和事务管理器。
  //如果使用properties,那么就会加载那些properties(属性配置文件),那些属性可以用${propName}语法形式多次用在配置文件中。和Spring很像,一个思想?
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        //委托XMLConfigBuilder来解析xml文件,并构建
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
        //这里是捕获异常,包装成自己的异常并抛出的idiom?,最后还要reset ErrorContext
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  //以下3个方法都是调用下面第8种方法
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  //第8种方法和第4种方法差不多,Reader换成了InputStream
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
    
  //最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}

 有通过Reader获取和通过Io流获取的两种方法,下面主要写通过输入流获取的方法

Inputstream inputstream = Resources.getResourceAsStream("mybatisconfig.xml");
 //这⼀⾏代码正是初始化⼯作的开始。
 SqlSessionFactory factory = new
SqlSessionFactoryBuilder().build(inputStream);
//第8种方法和第4种方法差不多,Reader换成了InputStream
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
  //最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
MyBatis 在初始化的时候,会将 MyBatis 的配置信息全部加载到内存中,使⽤ org.apache.ibatis.session.Configuration 实例来维护 下⾯进⼊对配置⽂件解析部分: ⾸先对 Configuration 对象进⾏介绍:
  Configuration 对象的结构和 xml 配置⽂件的对象⼏乎相同。 回顾⼀下 xml 中的配置标签有哪些: properties ( 属性 ) , settings ( 设置 ) , typeAliases ( 类型别名 ) , typeHandlers ( 类型处理 器 ) , objectFactory ( 对象⼯⼚ ) , mappers ( 映射器 ) 等 Configuration 也有对应的对象属性来封 装它们 也就是说,初始化配置⽂件信息的本质就是创建 Configuration 对象,将解析的 xml 数据封装到 Configuration 内部属性中

   
   
   
   
   
   
   
   
   
   
   
   
   
  
   
   
   
  

  

  //解析配置
  public Configuration parse() {
    //如果已经解析过了,报错
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;

    
    //根节点是configuration
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
//解析配置
  private void parseConfiguration(XNode root) {
    try {
      //分步骤解析
      //issue #117 read properties first
      //1.properties
      propertiesElement(root.evalNode("properties"));
      //2.类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //3.插件
      pluginElement(root.evalNode("plugins"));
      //4.对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      //5.对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.设置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.环境
      environmentsElement(root.evalNode("environments"));
      //8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9.类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10.映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

 

介绍⼀下 MappedStatement : 作⽤: MappedStatement 与 Mapper 配置⽂件中的⼀个 select/update/insert/delete 节点相对应。 mapper 中配置的标签都被封装到了此对象中,主要⽤途是描述⼀条 SQL 语句。 初始化过程: 回顾刚开始介绍的加载配置⽂件的过程中,会对 mybatis-config.xm l 中的各个标签都进⾏解析,其中有mappers 标签⽤来引⼊ mapper.xml ⽂件或者配置 mapper 接⼝的⽬录。