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注解的解析作业先做简单处理)

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() 办法中供给了署理目标办法的完结逻辑。

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就是传入的参数

Mybatis的原理你真的了解吗?源码解析继续更新
可是此刻想要查询句子,得要先获取到@Select注解上的sql句子,然后进行参数的替换

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句子是

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

咱们现已得到了sql句子,现在要获取参数的替换,可是因为args是个数组,咱们也不知道怎么跟下面的参数进行匹配,能够用map来处理

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;
  }

此刻得到的成果,现已能够获取到参数对应的值

Mybatis的原理你真的了解吗?源码解析继续更新
可是此刻还要完结参数的替换,能够按照下面逻辑去处理(能够用其它思路来完结)

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的原理你真的了解吗?源码解析继续更新
紧接着就能够做以下操作了 (这也是Mybatis的中心处理操作)

  1. 数据库的操作(jdbc的解析)
  2. 成果集的映射处理
  3. ResultSet一个值一个值封装到目标中去