您当前的位置: 首页 >  mybatis

墨家巨子@俏如来

暂无认证

  • 1浏览

    0关注

    188博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

吃透Mybatis源码-Mybatis初始化(一)

墨家巨子@俏如来 发布时间:2022-01-02 21:47:29 ,浏览量:1

来来来,给俏如来扎起。感谢老铁们对俏如来的支持,2021一路有你,2022我们继续加油!你的肯定是我最大的动力

博主在参加博客之星评比,点击链接 , https://bbs.csdn.net/topics/603957267 疯狂打Call!五星好评 ⭐⭐⭐⭐⭐ 感谢。

前言

Mybatis是Java 项目开发使用率非常高的一款持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

同时Mybatis也是面试过程中被高频问到的一门技术,今天我就带大家一起来对Mybatis的重要原理及其源码进行一个分析。

Mybatis的入门案例

我们需要先写一个简单的入门案例,根据入门来分析Mybatis的执行原理

第一步:我们需要导入Mybatis的基础依赖



  org.mybatis
  mybatis
  3.4.6



  mysql
  mysql-connector-java
  5.1.47

第二步:然后准备一个mybatis的xml配置文件


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

    
    
    
        
        
        
    
    
            
        
    
    
        
            
            
                
                
                
                
            
        
    
    
        
    

数据库配置文件如下

mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/test?useSSL=false
mysql.username=root
mysql.password=admin

第三步:然后编写SQL映射文件StudnetMapper.xml , 先来个简单的查询


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


    
        
        
    

    
        select * from student
    

     
        select id,username from student where id = #{id}
    

第四步:编写实体类,和mapper映射器接口

public interface StudentMapper {
    List selectAll();
    Student selectById(Long id);
}

第五步:编写测试类

public class StudentTest {

    @Test
    public void test() throws IOException {
        //加载配置
        InputStream inputStream= Resources.getResourceAsStream("mybatis-config.xml");
        //创建一个sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        
        Student student = sqlSession.selectOne("cn.whale.mapper.StudentMapper.selectById",1L);
        System.out.println(student);

        //使用最原始方式: namespace.statementid 执行
        List  objects = sqlSession.selectList("cn.whale.mapper.StudentMapper.selectAll");
        objects.stream().forEach( System.out::println);
        
  		//使用mapper映射器接口方式执行
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List students = mapper.selectAll();
        students.stream().forEach( System.out::println);
    }

}

入门案例开发完毕。

Mybatis执行流程

下面是Mybatis的组成结构图,可能你看起来会有点懵逼,但是没关系,每个组件我们都会在后面说道,这里先混个眼熟就行了 在这里插入图片描述 这里说几个比较重要的组件

  • SqlSessionFactoryBuilder : 用到了建造者模式,用来解析配置文件和创建SqlSessionFactory
  • SqlSessionFactory : 用来创建SqlSession的工厂类,全局唯一。
  • SqlSession : 这个是MyBatis链接数据库的一次会话对象,如果Mybatis和数据库的链接中断,会话也就结束。所以SqlSession不应该是单利的。
  • Configuration :配置对象,主要包括mybatis-config.xml中的配置项目
  • Executor : 执行器,SqlSesion底层执行的时候用到

然后我们先来分析一下Mybatis的执行流程,根据上面的测试案例我们可以分为两部分。创建SqlSession和执行SqlSession。首先第一行代码 :InputStream inputStream= Resources.getResourceAsStream("mybatis-config.xml") 的作用是加载Mybatis配置文件,然后通过SqlSessionFactoryBuilder().build解析配置文件,以及mapper.xml 映射文件,并创建一个 SqlSessionFactory

然后是通过 sqlSessionFactory.openSession() 创建SqlSession,SqlSession是和数据库的一次会话对象。然后是通过sqlSession.selectList来执行语句,可以通过“namespace”加上“statementid”来找到要执行的SQL,也可以通过获取Mapper映射器接口的方式来执行,第二种方式最终会采用第一种方式去执行SQL。

执行完SQL会有结果集,然后会调用结果映射器处理结果集并返回,下面这个图是宏观层面的,Mybatis执行流程 在这里插入图片描述 Mybatis执行流程如下

  1. 初始化阶段,加载配置文件
  2. 根据配置文件,创建SqlSessionFactoryBuider,执行build方法来创建SqlSessionFactory,build方法会解析配置文件,然后封装到一个Configuration对象中。Configuration会保存在创建的SqlSessionFactory
  3. 通过SqlSessionFactory来创建SqlSesion,底层会创建一个Executor执行器保存在SqlSession中
  4. 然后就是SqlSesson的执行了,SqlSession会调用 executor 执行器去执行
  5. 执行器中会创建一个StatementHandler,调用StatementHandler去执行Statement语句,当然执行Statement语句前涉及到参数的处理
  6. 执行完成之后使用ResultSetHandler映射结果为实体对象并返回
源码解析-SqlSessionFactory的创建

代码入手肯定是new SqlSessionFactoryBuilder().build(inputStream),直接看源码见:org.apache.ibatis.session.SqlSessionFactoryBuilder#build(j…)

 public SqlSessionFactory build(InputStream inputStream) {
   return build(inputStream, null, null);
 }
 
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
    	//1. 创建一个XML配置的Builder,inputStream是通过Resources.getResourceAsStream加载的xml配置
    	//environment 和 properties都是null
    	//这一步会创建一个 Configuration对象
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //2. parser.parse()解析一个configuration对象,然后创建SqlSessionFactory 
      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.
      }
    }
  }

Mybatis的源码相比Spring来说要简单太多了,首先是通过Resources.getResourceAsStream加载的xml配置(inputStream)交给 XMLConfigBuilder ,创建一个XML解析器在new XMLConfigBuilder(…)代码中会创建Configuration对象。然后调用parser.parse() 解析配置,解析之后得到一个 Configuration 对象,交给builder方法去创建一个默认的DefaultSqlSessionFactory,同时把Configuraton对象保存给SqlSessionFactory。

XMLConfigBuilder是BaseBuilder的实现类,用作配置文件解析,针对于不同的构建目标还有不同的子类 在这里插入图片描述

我们先来看parser.parse()方法的源码,然后再看build方法的源码,见org.apache.ibatis.builder.xml.XMLConfigBuilder#parse

  public Configuration parse() {
  	//parsed是用来判断是否已经解析过了
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //解析配置,使用的是XPathParser解析器来解析一个configuration根节点。
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

使用xPathParser解析器来解析配置文件,/configuration 就是mybatis-config.xml配置中的根节点,继续看parseConfiguration方法的源码

 //解析配置项目
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      //1.拿到properties元素,也就是  
      propertiesElement(root.evalNode("properties"));
      //2.处理  元素,并设置到Properties 对象中
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //3.处理   用户配置的别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //4.处理  插件
      pluginElement(root.evalNode("plugins"));
      //5.处理   对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      //用来装饰object的工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.处理反射工厂
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //把 settings  设置到COnfiguration对象中
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.处理  , 数据源就在这个元素里面
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //8.处理      类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //9 处理mappers 映射文件
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

这里我们看到,parseConfiguration方法对于mybatis配置文件中的所有项目都做了解析。我大致说一下每个步骤做了啥,具体的细节水友自己去断点看

  1. propertiesElement(root.evalNode(“properties”)) : 解析 ,加载db.properties中的配置项目,然后会把properties设置到Configuration的variables存储起来

  2. Properties settings = settingsAsProperties(root.evalNode(“settings”)); 处理 元素,转换为Properties ,然后通过 settingsElement(settings); 方法把settings中的配置保存到Configfiguration。见org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsElement

  3. typeAliasesElement(root.evalNode(“typeAliases”)); :处理别名配置,如果是通过方式配置别名,会把配置的实体类的class添加到Configuration对象的TypeAliasRegistry别名注册器中,TypeAliasRegistry内部维护了一个new HashMap, MapperProxyFactory>() ,key是mapper接口的class 类型,Value是一个MapperProxyFactory工厂,工厂中维护了mapper接口和接口中的方法如下

    public class MapperProxyFactory {
      //mapper接口
    private final Class mapperInterface;
    //接口中的方法
    private final Map methodCache = new ConcurrentHashMap();
    
源码解析-SqlSession的创建

接下来我们分析 sqlSessionFactory.openSession(); ,该方法是通过SqlSessionFactory创建SqlSession见:org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()

@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

这里重configuration中获取默认的executor类型默认是simple,事务的提交方式指定为手动提交,然后交给 openSessionFromDataSource 去处理

// execType :执行器的类型  ; autoCommit :事务提交方式为手动提交
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //1.从Configuration拿到配置对象,配置对象中包括 JdbcTranscationFactory 和 PooledDataSource
      final Environment environment = configuration.getEnvironment();
      //从environment总获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //2.通过事务工厂transactionFactory创建Transaction事务对象,默认实现是JdbcTransaction
      //JdbcTransaction中包括了Connection连接对象;DataSource 数据源;autoCommmit提交方式为false
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //3.根据execType创建执行器,默认是SimpleExecutor,
      //SimpleExecutor继承了BaseExecutor,其中中维护了Transcation对象和PerpetualCache一级缓存一级configuration
      //SimpleExecutor被包装到 CachingExecutor 总,这里使用了装饰者模式
      //然后会把Executor添加到Configuration的interceptorChain拦截器链中
      final Executor executor = configuration.newExecutor(tx, execType);
      //4.创建SqlSession ,默认实现是DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      //5.发生异常,关闭事务
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

总结一下上面的方法的核心业务

  1. 从Configuration拿到Environment对象,该对象中包括 JdbcTranscationFactory事务工厂 和 PooledDataSource 连接池

  2. 从Environment中获取事务工厂TransactionFactory ,通过事务工厂transactionFactory创建Transaction事务对象,默认实现是JdbcTransaction;JdbcTransaction中包括了Connection连接对象、DataSource 数据源、autoCommmit提交方式为false。JdbcTransaction中提供了事务的提交,回滚,关闭等方法。

  3. 然后根据execType创建执行器,默认是SimpleExecutor,SimpleExecutor继承了BaseExecutor,BaseExecutor维护了Transcation对象和PerpetualCache一级缓存、以及Configuration。 如果配置了 cacheEnabled=ture,会用装饰器模式对 executor 进行包装,SimpleExecutor被包装到 CachingExecutor 中,这里使用了装饰者模式然后会把Executor添加到Configuration的interceptorChain拦截器链中

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
          executorType = executorType == null ? defaultExecutorType : executorType;
          executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
          Executor executor;
          //1.根据类型创建Executor
          if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, transaction);
          } else if (ExecutorType.REUSE == executorType) {
            executor = new ReuseExecutor(this, transaction);
          } else {
            executor = new SimpleExecutor(this, transaction);
          }
         
          if (cacheEnabled) {
           //2.如果开启了一级缓存,默认是true,把SimpleExecutor交给Cachin gExecutor ,装饰者模式
            executor = new CachingExecutor(executor);
          }
          //3.把executor加入Configuration的interceptorChain拦截器链中
          executor = (Executor) interceptorChain.pluginAll(executor);
          return executor;
    }
    

    Executor的继承体系如下 在这里插入图片描述

  4. 创建SqlSession ,默认实现是DefaultSqlSession,其中维护了Configuration对象,Executor,autoCommit为false

  5. 在catch中关闭事务closeTransaction , 底层会调用connection.close关闭事务

这里要单独说一下三个Executor的区别

  1. SimpleExecutor:每执行一次 SQL就创建一个 Statement 对象,用 完立刻关闭 Statement 对象。
  2. ReuseExecutor:它能够实现Statement的复用,执行 SQL,会先把SQL 作为 key 查找 Statement 对象, 存在就使用,不存在就创建,用完后把Statement放置于 Map 内不关闭, 供下一次使用。
  3. BatchExecutor:执行 Update SQL(没有 select,不支持 select),将所 有 SQL都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存 了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理。与 JDBC 批处理相同。
总结

我们来总结一下Mybatis的初始化流程

  1. SqlSessionFactoryBuilder 加载 mybatis-config.xml配置文件,通过XMLConfigBuilder将配置解析为Configuration,并创建一个SqlSessionFactory。
  • 对于 会解析为Properties并存储到Configuration的variables

  • 对于 会解析为Properties并设置到Configuration,比如:延迟加载配置,二级缓存配置等

  • 对于 会解析解析类的class注册到Configuration的TypeAliasRegistry中,其中维护了一个new HashMap, MapperProxyFactory>() ,key是mapper接口的class 类型,Value是一个MapperProxyFactory工厂

  1. 根据SqlSessionFactory创建SqlSession,默认实现是DefaultSqlSession,其中会创建好 Transaction (默认JdbcTransaction)事务对象,和 Executor (默认SimpleExecutor)执行器。

接下来就是sqlSession.selectList 执行SQL语句的代码分析了,这个我们就留到下一章来把。

创作不易,如果文章对你有所帮助,请给个好评。

关注
打赏
1651329177
查看更多评论
0.0412s