Mybatis之从配置到执行SQL的全过程

概述

本文我们来阐述Mybatis从启动到执行一条Sql的流程,我们以前文Mybats快速入门涉及到的代码为例,如下代码:

public static void main(String[] args) {
        // 创建MyBatis的SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        
        // 获取SqlSession对象
        try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
            // 获取UserMapper接口的实现类
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            
            // 查询id为1的用户数据
            User user = userMapper.getUserById(1);
            System.out.println(user);
        }
    }

大致步骤可以总结为下图:
在这里插入图片描述

分解

下面我们针对每个步骤结合源码来分析,本文只介绍大致的步骤流程,我们先有个大致总的流程,后续针对各个部分感兴趣的点再深挖~

加载全局文件

// 通过建造者模式创建SqlSessionFactoryBuilder
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream("mybatis-config.xml"));
// 进入build()
 public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
}
// 继续进入,这里有两行关键代码
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
...
}
// 先来看第一行,这里也使用建造者模式创建XMLConfigBuilder
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// ->创建XPathParser对象:XPathParser是一个XML解析,用于解析XML文件
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
 }
// 进入this(...)
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
	// 创建Configuration对象
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
// 到这里第一行代码就结束了,这里总结下:
1.创建了一个XMLConfigBuilder的对象
2.XMLConfigBuilder对象里构建了XPathParser对象(后续用于解析),
3.XMLConfigBuilder对象里构建Configuration对象,初始化了一些默认值(后续所有的配置都会放到这个对象里)

在这里插入图片描述

解析全局配置文件

// 我们接着看下一行代码
...
return build(parser.parse());
// 来看parser.parse()
public Configuration parse() {
    // 是否解析过
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 解析配置文件
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
// 这里的parser就是前面构建的XPathParser对象
// -> parseConfiguration(parser.evalNode("/configuration"));
private void parseConfiguration(XNode root) {
    try {
      // 解析并读取properties元素,加载属性配置
      propertiesElement(root.evalNode("properties"));
      
      // 解析并读取settings元素,加载全局配置属性
      Properties settings = settingsAsProperties(root.evalNode("settings"));

      // 加载自定义的VFS(Virtual File System)
      loadCustomVfs(settings);

      // 加载自定义的Log实现类
      loadCustomLogImpl(settings);

      // 解析并读取typeAliases元素,加载类型别名配置
      typeAliasesElement(root.evalNode("typeAliases"));

      // 解析并读取plugins元素,加载插件配置
      pluginElement(root.evalNode("plugins"));

      // 解析并读取objectFactory元素,加载对象工厂配置
      objectFactoryElement(root.evalNode("objectFactory"));

      // 解析并读取objectWrapperFactory元素,加载对象包装器工厂配置
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

      // 解析并读取reflectorFactory元素,加载反射器工厂配置
      reflectorFactoryElement(root.evalNode("reflectorFactory"));

      // 解析并设置全局配置属性
      settingsElement(settings);

      // 解析并读取environments元素,加载环境配置
      // issue #631: 先解析objectFactory和objectWrapperFactory后再加载environments
      environmentsElement(root.evalNode("environments"));

      // 解析并读取databaseIdProvider元素,加载数据库标识提供者配置
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));

      // 解析并读取typeHandlers元素,加载类型处理器配置
      typeHandlerElement(root.evalNode("typeHandlers"));

      // 解析并读取mappers元素,加载Mapper接口配置
      mapperElement(root.evalNode("mappers"));

    } catch (Exception e) {
      // 如果解析过程中发生异常,抛出BuilderException,并将原始异常包装进去
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}
// 大致总结上面代码
就是通过XPathParser对象将mybatis-config.xml里的配置信息全都解析为Java对象,然后封装到Configuration对象.

创建SqlSessionFactory对象

// 继续往后走,parser.parse()方法返回的结果就是Configuration对象
// ->
build(parser.parse());
// ->
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}
// 总结
这里通过Configuration构建了一个DefaultSqlSessionFactory,Configuration中包含前面解析到的所有配置信息.

创建SqlSession对象

// 来到下面代码,通过sqlSessionFactory创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// -> 
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 获取当前配置对象的环境配置信息
      final Environment environment = configuration.getEnvironment();
      
      // 根据环境配置获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      
      // 使用事务工厂创建一个新的事务对象tx,并指定数据源、事务隔离级别、是否自动提交事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      
      // 根据事务对象和执行器类型创建一个新的Executor对象,Executor是MyBatis的核心执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      
      // 使用Executor对象创建一个新的DefaultSqlSession对象,并返回
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      // 如果在打开Session过程中出现异常,则关闭事务并抛出异常
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      // 重置ErrorContext,清空错误信息
      ErrorContext.instance().reset();
    }
}

// 总结:MyBatis中的一个关键方法,用于打开一个新的SqlSession对象
// 主要步骤为:
1.获取当前配置对象的环境配置信息,包括数据源、事务管理器等
2.根据环境配置获取事务工厂,用于创建新的事务对象。
3.使用事务工厂创建一个新的事务对象tx,并指定数据源、事务隔离级别、是否自动提交事务。
4.根据事务对象和执行器类型创建一个新的Executor对象,ExecutorMyBatis的核心执行器,用于执行SQL语句
5.使用Executor对象创建一个新的DefaultSqlSession对象,并返回。DefaultSqlSessionMyBatis对外暴露的主要接口,用于进行数据库操作
6.如果在打开SqlSession过程中出现异常,则关闭事务并抛出异常。同时,通过ExceptionFactory.wrapException()方法将原始异常包装,使得异常信息更加明确和易于定位。
7.最后,重置ErrorContext,清空错误信息,以防止错误信息在不正确的地方被累积。

SqlSession获取Mapper代理对象

// 来到下列代码,获取Mapper对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// ->
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 从knownMappers中获取对应Mapper接口的MapperProxyFactory
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    
    // 如果MapperProxyFactory为空,说明该Mapper接口未在knownMappers中注册,抛出BindingException异常
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      // 使用mapperProxyFactory创建并返回一个新的Mapper接口的代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      // 如果在创建Mapper代理对象过程中出现异常,则抛出BindingException异常,同时将原始异常包装以提供更详细的错误信息
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}
// -> mapperProxyFactory.newInstance(sqlSession);
public T newInstance(SqlSession sqlSession) {
    // 创建一个MapperProxy对象,传入SqlSession、Mapper接口类型以及方法缓存对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    
    // 使用MapperProxy创建并返回一个新的Mapper接口的代理对象
    return newInstance(mapperProxy);
}

// 总结:创建Mapper接口的代理对象

代理对象执行sql

// 来到下列代码
User user = userMapper.selectById(1);
// 由于userMapper对象是个代理对象,所以它会先来到代理对象MapperProxy逻辑
// -> MapperProxy.invoke()
// -> mapperMethod.execute(sqlSession, args); 
// -> result = sqlSession.selectOne(command.getName(), param);
// -> 底层调用jdbc逻辑,返回结果集
// 这里后续会详细探究,这里暂时简单了解它的过程即可

总结

上述大致描述了Mybatis从启动到执行sql的流程, 后面我们再从细节来一一揭开它的面纱~

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值