【MyBatis 源码拆解系列】MyBatis 运行原理 - 数据库操作最终由哪些类负责?

欢迎关注公众号 【11来了】 ,持续 MyBatis 源码系列内容!

在我后台回复 「资料」 可领取编程高频电子书
在我后台回复「面试」可领取硬核面试笔记

文章导读地址:点击查看文章导读!

感谢你的关注!

MyBatis 源码系列文章:
(一)【MyBatis 源码拆解系列】MyBatis 源码如何学习?
(二)【MyBatis 源码拆解系列】MyBatis 运行原理 - 读取 xml 配置文件
(三)MyBatis 运行原理 - MyBatis 的核心类 SqlSessionFactory 和 SqlSession
(四)MyBatis 运行原理 - MyBatis 中的代理模式

四、MyBatis 的数据库操作最终由哪些类负责?

本节主要介绍内容如下:

image-20240922143137174

这里就是最后一个步骤了,调用 UserMapper 接口的方法进行查询,底层是如何去执行对应的 SQL 查询数据库呢?

// 5、调用接口展开数据库操作
List<User> userList =  userMapper.queryUserBySchoolName(userParam);

在通过代理模式获取了 UserMapper 接口的代理对象之后,那么这里调用 userMapper 其实调用的就是 MyBatis 内部创建的代理对象了,然后就会走到拦截器 MapperProxy 的 invoke() 方法中,那么这里直接看 invoke() 方法:

// MyBatis 源码 binding 包下的 MapperProxy 类
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    // 如果调用的 Object 的方法:equals()、toString()、hashCode() 等,则不进行代理
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else if (method.isDefault()) {
      return invokeDefaultMethod(proxy, method, args);
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
  // 核心方法
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
}

这里直接看核心方法,先将 method -> MapperMethod 的映射缓存起来,然后将方法的执行都交给 MapperMethod 即可,接下来看 MapperMethod 的 execute() 如何执行:

// MyBatis 源码 binding 包下的 MapperMethod 类
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: {
      // ... 
    }
    case UPDATE: {
      // ...
    }
    case DELETE: {
      // ...
    }
    case SELECT:
      // ...
      } else if (method.returnsMany()) {
        // 这里查询的是 List 集合,因此会走到下边这行  
        result = executeForMany(sqlSession, args);
      }
      // ...
  }
  return result;
}

这里为了清晰,将其他的代码给省略掉,也就是根据方法的类型以及返回结果的类型判断走哪一个方法,这里会走到 executeForMany() 方法内部,如下:

// MyBatis 源码 binding 包下的 MapperMethod 类
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
  // 1、查询参数的转换
  Object param = method.convertArgsToSqlCommandParam(args);
  if (method.hasRowBounds()) {
    // ...
  } else {
    // 2、执行 SQL 查询
    result = sqlSession.selectList(command.getName(), param);
  }
  // ...
  return result;
}

这里先进行查询参数的转换,再进行 SQL 查询,最终 SQL 查询还是由 SqlSession 来完成,之前看了 SqlSession 内部都是 selectList()、selectOne() 等数据库查询方法,进入内部看细节:

// MyBatis 源码 session 包下的 DefaultSqlSession 类
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    // 1、先拿到 MappedStatement  
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 2、通过执行器去完成查询  
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

这里先通过 statement 去获取对应的 MappedStatement,statement 其实就是执行的 UserMapper 方法的全限定类名 + 方法名:

image-20240922135210746

通过 statement 作为唯一标识,去获取对应的 MappedStatement,他肯定会存储一些执行 SQL 中需要的信息,具体存储什么信息这里先不关心,重要的是单独抽取出来一个 MappedStatement 来存储对应信息这个思想,部分信息如下:

image-20240924221020051

最终将真正的执行动作交给执行器 Executor,这里会走到 CachingExecutor,涉及 装饰器模式 ,对其他的几种 Executor 进行包装增强,提供了二级缓存的功能,这里暂时先不讲二级缓存实现细节,先从整体对 MyBatis 整个执行流程有一个大致的了解,在看源码的初始阶段, 先清除哪个功能大致对应哪个模块,再根据功能点进行细看即可

至此,MyBatis 整体的运行流程大致介绍完成了,为了可以对整体有一个直观的认识,还有很多细节性的内容并没有介绍,比如如何解析 UserMapper.xml 文件中的 SQL、Executor 内部具体如何去执行 SQL 语句、SQL 语句返回的结果如何进行封装等等,这些会放在后边,通过单个功能点进行详细拆解,透视内部执行原理

总结:

MyBatis 内部源码有个特点,每个方法都很短,在查看源码的时候会在不同的类之间不断跳转,为什么呢?为了保持类的单一职责,将功能不断地进行抽取,比如:

  • UserMapper 被 MapperProxy 代理,代理对象的创建交给 MapperProxyFactory
  • UserMapper 中的每个方法都有对应的 MapperMethod 来管理
  • 具体的 SQL 执行都是交给 MapperMethod,通过判断查询类型以及返回类型,进行不同类型的处理之后再走到 SqlSession 中,在这里获取 MappedStatement 之后,将具体 SQL 的执行又交给执行器 Executor,基础的执行器 Executor 负责 SQL 的执行,CachingExecutor 对基础的执行器 Executor 进行包装增强,提供了二级缓存的功能

可以感受到,通过将类的职责范围不断缩小,通过类名可以很清晰地描述整个执行流程,这样我们在以后开发中,也就不需要一直去写大量的 Service 服务来完成需求了,也可以针对一些功能创建不同的类来完成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

11来了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值