• mybatis执行过程,源码分析


    Mybatis认识

    我们知道,JDBC有四个核心对象:

    (1)DriverManager,用于注册数据库连接
    (2)Connection,与数据库连接对象
    (3)Statement/PrepareStatement,操作数据库SQL语句的对象
    (4)ResultSet,结果集或一张虚拟表

    而MyBatis也有四大核心对象:

    (1)SqlSession对象,该对象中包含了执行SQL语句的所有方法。类似于JDBC里面的Connection。
    (2)Executor接口,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。类似于JDBC里面的Statement/PrepareStatement。
    (3)MappedStatement对象,该对象是对映射SQL的封装,用于存储要映射的SQL语句的id、参数等信息。
    (4)ResultHandler对象,用于对返回的结果进行处理,最终得到自己想要的数据格式或类型。可以自定义返回类型。

    Mybatis执行原理跟踪

    在这里插入图片描述

    1.项目启动时,读取配置

    1. 通过jdbc连接信息注入bean:SqlSessionFactory
    2. 将扫描到的mapper.java接口信息,将接口中的方法都转换成MapperStatement对象,用于记录这个Mapper.java接口方法的方法参数、返回值和sql语句等等信息

    2.获取SqlSession

    SqlSession定义了事务提交回滚sql执行等能力,并且有一个ExecutorType的参数,用于控制Executor的类型

     // 批处理,并且不自动提交事务
    SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
    M mapper = sqlSession.getMapper(mapperClass);
    
    • 1
    • 2
    • 3

    Executor:

    • SimpleExecutor:默认类型,对于每一条执行的sql每执行一次都会重新预编译加载一次
    • ReuseExecutor:执行相同的sql语句,不会重复编译加载
    • BatchExecutor:批量执行策略,将批量的多条sql语句构成的Statement暂存到list中,flush的时候一次性提交到mysql服务器,避免重复的和mysql服务器建立连接交互
    • CachingExecutor:二级缓存策略,其中维护了一个map来保存查询结果,二级缓存在mapper文件层生效

    3.反向代理方法执行

    getMapper方法跟进去,最终可以看到实际上返回的Mapper实例其实是一个MapperProxy的反向代理出来的实例对象,每当执行任何方法,都会从MapperProxy的invoke中过,判断该方法是否是Object的方法,是否是接口default方法,如果都不是的话则直接进入mybatis的执行流程,通过invoke的反射信息拿到Method对象,通过Method对象拿到MapperStatement的id,即类名全限定名+方法名(这里不区分方法签名,所以在mybatis中不支持方法重载)

    MapperStatement中包含了我们要执行的Mapper.java接口的方法名,对应的sql,参数(参数最终都会注入到Map中,所以方法重载没有用),返回值,通过这些信息,生成StatementHandler对象用于构建sql,生成ParameterHandler用于处理参数,最终将参数和预编译的sql交由原生jdbc执行,最终拿到返回结果

    4.ResultHandler结果集处理

    在拿到返回数据后,通过ResultHandler对结果集进行封装,使用TypeHandler,使用反射中的set方法将属性注入POJO

    实际上查询的结果封装由ResultSetHandler来处理,结合mapper.xml中的resultMap标签对应的对象ResultMap,将字段名称做映射,配合TypeHandlerRegister中注册的TypeHandler,按照数据库字段与TypeHandler映射的字段,将ResultSet中的结果先交给TypeHandler的getNullableResult方法处理成我们的理想java类型字段,再通过反射,使用标准java bean的set方法将字段设置到POJO中

    MyBatis源码分析

    示例代码

    public void test () {
      UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
      User user = userMapper.getUserById(1L);
    }
    
    • 1
    • 2
    • 3
    • 4

    第一阶段

    通过SqlSession获取Mapper代理实例

    在上述的代码中,第一步,通过sqlSession获取UserMapper实例,因为UserMapper是我们定义的一个接口,所以真正使用时,使用的是mybatis为我们生成的UserMapper的代理实例,从这里入手看源码。

    通过断点发现SqlSession的实例是DefaultSqlSession

    通过如下调用栈跟踪源码

    DefaultSqlSession.getMapper(Class)

    Configuration.getMapper(Class, SqlSession)

    MapperRegistry.getMapper(Class, SqlSession)

     public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
       // knownMappers实际上是项目启动时,mybatis能扫描到的需要被代理的mapper.java接口
       // 这里拿到当前需要代理的接口的【代理mapper工厂类】
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
          throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
          // 从这里跟进去
          return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    从mapperProxyFactory.newInstance(SqlSession)跟进去

    mapperProxyFactory.newInstance(SqlSession)

    protected T newInstance(MapperProxy<T> mapperProxy) {
      return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
    
    public T newInstance(SqlSession sqlSession) {
      final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
      return newInstance(mapperProxy);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    通过这两个newInstance可以看出,最终生成的代理类是【MapperProxy】对象,

    综上所述,最终通过getMapper(Class type),获取到的结果就是MapperProxy的代理对象,那么我们通过该对象执行的所有方法其实最终都是通过MapperProxy的invoke方法,下面我们对invoke方法进行解析

    第二阶段

    MapperProxy对象的invoke方法

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      try {
        // 假如MapperProxy对象是UserMapper的代理类,则此处判断我们调用的该方法属于哪个类,比如在我们没有重写toString方法时,调用toString方法,则toString方法属于Object.class,此处就是在判断我们调用的方法是否属于Object提供的方法
        if (Object.class.equals(method.getDeclaringClass())) {
          return method.invoke(this, args);
        } else if (method.isDefault()) {
          // isDefault判断调用的方法是不是interface中的default默认实现的方法,此处不重要
          if (privateLookupInMethod == null) {
            return invokeDefaultMethodJava8(proxy, method, args);
          } else {
            return invokeDefaultMethodJava9(proxy, method, args);
          }
        }
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
      // 从缓存map中获取到MapperMethod对象
      final MapperMethod mapperMethod = cachedMapperMethod(method);
      // 执行MapperMethod对象的execute
      return mapperMethod.execute(sqlSession, args);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    MapperMethodexecute

    public Object execute(SqlSession sqlSession, Object[] args) {
      Object result;
      switch (command.getType()) {
        case INSERT: {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.insert(command.getName(), param));
          break;
        }
        case UPDATE: {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.update(command.getName(), param));
          break;
        }
        case DELETE: {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.delete(command.getName(), param));
          break;
        }
        case SELECT:
          if (method.returnsVoid() && method.hasResultHandler()) {
            executeWithResultHandler(sqlSession, args);
            result = null;
          } else if (method.returnsMany()) {
            result = executeForMany(sqlSession, args);
          } else if (method.returnsMap()) {
            result = executeForMap(sqlSession, args);
          } else if (method.returnsCursor()) {
            result = executeForCursor(sqlSession, args);
          } else {
            Object param = method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(command.getName(), param);
            if (method.returnsOptional()
                && (result == null || !method.getReturnType().equals(result.getClass()))) {
              result = Optional.ofNullable(result);
            }
          }
          break;
        case FLUSH:
          result = sqlSession.flushStatements();
          break;
        default:
          throw new BindingException("Unknown execution method for: " + command.getName());
      }
      if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
        throw new BindingException("Mapper method '" + command.getName()
                                   + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
      }
      return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    第三阶段

    有空再补充

  • 相关阅读:
    《Redis实战》笔记
    骑行耳机哪个品牌好,推荐五款最适合骑行佩戴的五款耳机
    数据库-----范式
    无人机光电识别跟踪算法!
    day4.python基础下
    工具篇 | Gradle入门与使用指南 - 附Github仓库地址
    美女放电还是整形-从《人月神话》误译谈状态机图
    网易低代码引擎Tango正式开源
    http客户端:Feign的使用 与 日志配置
    Windows用户真实最后登录报表
  • 原文地址:https://blog.csdn.net/qq1010830256/article/details/125403638