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一个值一个值封装到对象中去
相关推荐
集成显卡43 分钟前
Rust实战七 |基于带 colored 颜色文字控制台的批量文件删除工具
开发语言·后端·rust
jeffer_liu2 小时前
Spring AI 生产级实战:工具调用
java·人工智能·后端·spring·ai编程
Cosolar2 小时前
AutoGen 精通教程:从零到企业级多 Agent 系统架构师
人工智能·后端·面试
狂炫冰美式4 小时前
你还在古法PPT吗,试试HTML呢?免费编辑导出工具给 xdm 放这了
前端·后端·github
万少5 小时前
未来组织的分水岭不是员工数量,而是人才密度
前端·后端·面试
Honmaple5 小时前
终端 AI 编程的两条路:Pi 极简哲学 vs Oh-My-Pi 全能主义深度对决
后端
我是一颗柠檬5 小时前
【Redis】发布订阅与消息队列Day8(2026年)
数据库·redis·后端·缓存
道友可好5 小时前
OpenSpec:轻到起飞的 AI 编程规范层
前端·人工智能·后端
IT_陈寒6 小时前
React状态管理这个坑,我爬了整整三天才出来
前端·人工智能·后端