Mybatis的原理你真的理解吗?源码解析持续更新

Mybatis源码初探

之前都只是在用Mybatis,但是一直没有深入的去了解过它的源码机制,趁着最近空闲时间比较多,再加上日常生活中用这框架比较多,就来简单探究下它的源码

Mybatis的简介

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

JDBC核心步骤

Mybatis、Hibernate、DATA JPA 都是基于JDBC驱动来做的,那么我们来看看JDBC的几个核心步骤是怎么做的?

  1. 加载驱动
  2. 获取连接
  3. 创建sql语句
  4. 获取statement对象
  5. 执行sql语句
  6. 处理结果集
  7. 关闭连接

Mybatis核心步骤

Mybatis的基本操作如何做的?

  1. 创建xml配置文件
  2. 创建mapper接口
  3. 需要SqlSessionFactory对象
  4. 获取SqlSession对象

此时我们通过最简单的方法来模拟Mybatis是如何解析sql最后查询出数据的

先给定一个mapper接口 (此处不使用xml形式和@Param注解的解析工作先做简单处理)

java 复制代码
public interface EmpMapper {

  @Select("select * from emp where empno = #{empno} and ename = #{ename}")
  List<Emp> selectEmpList( Integer empno,  String ename);

}

大家都知道,此时想调用selectEmpList方法,需要具体的实例对象,但是接口不能实例化**(所以此时我们需要动态代理,因为是接口,则使用JDK的动态代理)**

Proxy.newProxyInstance()方法创建了一个代理对象 empMapper,该代理对象实现了 EmpMapper 接口,并在 InvocationHandlerinvoke() 方法中提供了代理对象方法的实现逻辑。

java 复制代码
EmpMapper  empMapper = (EmpMapper) Proxy.newProxyInstance(MyMybatis.class.getClassLoader(), new Class[]{EmpMapper.class}, new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//        System.out.println(method.getName());
//        System.out.println(Arrays.asList(args));
        return null;
      }
    });
    empMapper.selectEmpList(7369,"SMITH");
  }

这里的method就是接口方法的名称,args就是传入的参数

但是此时想要查询语句,得要先获取到@Select注解上的sql语句,然后进行参数的替换

java 复制代码
EmpMapper  empMapper = (EmpMapper) Proxy.newProxyInstance(MyMybatis.class.getClassLoader(), new Class[]{EmpMapper.class}, new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取方法上的注解
        Select select = method.getAnnotation(Select.class);
        if (select!=null){
          //核心逻辑处理(获得sql语句)
          String[] value = select.value();
          String sql = value[0];
          System.out.println(sql);
        }
        return null;
      }
    });
    empMapper.selectEmpList(7369,"SMITH");
  }

此时的sql语句是

我们已经得到了sql语句,现在要获取参数的替换,但是由于args是个数组,我们也不知道如何跟下面的参数进行匹配,可以用map来处理

java 复制代码
EmpMapper  empMapper = (EmpMapper) Proxy.newProxyInstance(MyMybatis.class.getClassLoader(), new Class[]{EmpMapper.class}, new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      	// 通过参数来完成替换功能,因此需要先去解析参数
        Map<String, Object> stringObjectMap = parseArgs(method, args);
        System.out.println(stringObjectMap);
        // 获取方法上的注解
        Select select = method.getAnnotation(Select.class);
        if (select!=null){
          //核心逻辑处理(获得sql语句)
          String[] value = select.value();
          String sql = value[0];
          System.out.println(sql);
        }
        return null;
      }
    });
    empMapper.selectEmpList(7369,"SMITH");
  }


public static Map<String,Object> parseArgs(Method method,Object[] args){
    Map<String,Object> map = new HashMap<String,Object>();
    // 获取方法的参数数组
    Parameter[] parameters = method.getParameters();
    int index[] = {0};
    // 遍历获取所有参数名称
    Arrays.asList(parameters).forEach(parameter -> {
      String name = parameter.getName();
      // 参数名和参数值做一一匹配
      map.put(name,args[index[0]]);
      // 修改下标
      index[0]++;
    });
    return map;
  }

此时得到的结果,已经可以获取到参数对应的值

但是此时还要完成参数的替换,可以按照下面逻辑去处理(可以用其它思路来实现)

java 复制代码
public static String parseSql(String sql,Map<String,Object> map){
    // 作为字符追加
    StringBuilder builder = new StringBuilder();
    for(int i = 0;i<sql.length();i++){
      // 转为字符
      char c = sql.charAt(i);
      // 边界判断
      if (c == '#'){
        // 找到# 下一个位置 {
        int index = i+1;
        // 取出 {
        char nextChar = sql.charAt(index);
        // 边界判断
        if (nextChar!='{'){
          throw new RuntimeException("sql语句有错误,不是以{开头的");
        }
        StringBuilder argBuilder = new StringBuilder();
        // 此方法为解析{}里面的内容
        i = parseSqlArgs(argBuilder,sql,index);
        // 第一次循环完成 此时argName 应该是 empno
        String argName = argBuilder.toString();
        // 从map中取出key为empno的值
        Object value = map.get(argName);
        // 追加操作
        builder.append(value.toString());
        continue;
      }
      // 不符合#的字符 做追加操作
      builder.append(c);
    }
    return builder.toString();
  }

public static int parseSqlArgs(StringBuilder argBuilder,String sql,int index){
    index++;
    for (;index<sql.length();index++){
      // 取出索引位置的字符 此时第一次循环应该为{empno}中的 e
      char c = sql.charAt(index);
      // 边界判断
      if (c!='}'){
        // 字符追加
        argBuilder.append(c);
        // 继续
        continue;
      }
      if(c=='}'){
        return index;
      }
    }
    throw new RuntimeException("sql语句错误,没有以}结尾");
  }

此时已经可以看到打印出来的sql语句

紧接着就可以做以下操作了 (这也是Mybatis的核心处理操作)

  1. 数据库的操作(jdbc的解析)
  2. 结果集的映射处理
  3. ResultSet一个值一个值封装到对象中去
相关推荐
考虑考虑44 分钟前
JDK9中的dropWhile
java·后端·java ee
新world1 小时前
mybatis-plus从入门到入土(二):单元测试
单元测试·log4j·mybatis
martinzh2 小时前
Spring AI 项目介绍
后端
前端付豪3 小时前
20、用 Python + API 打造终端天气预报工具(支持城市查询、天气图标、美化输出🧊
后端·python
爱学习的小学渣3 小时前
关系型数据库
后端
武子康3 小时前
大数据-33 HBase 整体架构 HMaster HRegion
大数据·后端·hbase
前端付豪3 小时前
19、用 Python + OpenAI 构建一个命令行 AI 问答助手
后端·python
凌览3 小时前
斩获 27k Star,一款开源的网站统计工具
前端·javascript·后端
全栈凯哥3 小时前
02.SpringBoot常用Utils工具类详解
java·spring boot·后端
狂师3 小时前
啥是AI Agent!2025年值得推荐入坑AI Agent的五大工具框架!(新手科普篇)
人工智能·后端·程序员