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一个值一个值封装到对象中去
相关推荐
FreeBuf_4 小时前
黄金旋律IAB组织利用暴露的ASP.NET机器密钥实施未授权访问
网络·后端·asp.net
张小洛5 小时前
Spring AOP 是如何生效的(入口源码级解析)?
java·后端·spring
军军君016 小时前
基于Springboot+UniApp+Ai实现模拟面试小工具四:后端项目基础框架搭建下
spring boot·spring·面试·elementui·typescript·uni-app·mybatis
why技术6 小时前
也是出息了,业务代码里面也用上算法了。
java·后端·算法
白仑色8 小时前
完整 Spring Boot + Vue 登录系统
vue.js·spring boot·后端
ZhangApple9 小时前
微信自动化工具:让自己的微信变成智能机器人!
前端·后端
Codebee9 小时前
OneCode 3.0: 注解驱动的Spring生态增强方案
后端·设计模式·架构
bobz9659 小时前
kubevirt virtinformers
后端
LuckyLay9 小时前
Django专家成长路线知识点——AI教你学Django
后端·python·django
Java微观世界9 小时前
征服Java三大特性:封装×继承×多态+this/super高阶指南
后端