User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   SQL   ›   MyBatis   ›   正文
MyBatis

Mybatis原理—Mapper接口的动态代理Sql执行过程

2022-03-05 15:17:56
680  0 1
参考目录 隐藏
1) Mapper动态代理
2) 简单总结
3) 重要类
4) 调试主要关注点
5) 数据库执行过程
6) 二级缓存原理

阅读完需:约 23 分钟

前面介绍了SqlSessionFactory和SqlSession的构建过程,然后就可以通过SqlSession来获取Mapper对象了,所以这章来学习Mapper接口是如何通过动态代理来创建对象的。

Mybatis原理—SqlSession的构建过程

Mapper动态代理

①、程序入口:

②、SqlSession的实现类为DefaultSqlSession,所以进入DeaultSqlSession类找到重写的getMapper()方法。

可以发现它明显是调用了Configuration对象中的getMapper()方法来获取对应的接口对象。

③、所以点击进入Configuration类找到对应的getMapper()方法。

可以发现这里也是将工作继续交到MapperRegistry的getMapper()的方法中,所以我们继续向下进行。

④、点击进入MapperRegistry类找到getMapper()方法。

上面的代码中MapperProxyFactory对象是通过knownMappers来获取的,它是一个HashMap,这个knownMapper的定义:

⑤、通过上面可以发现是使用MapperProxyFactory来生成代理类,所以进入MapperProxyFactory的newInstance()方法中。

注意:这里调用了两个newInstance()方法,上面的代码中进入后先调用第二个newInstance方法并创建MapperProxy代理对象,所以这里的重点是关注这个类。然后再去调用第一个newInstance方法并将创建好的MapperProxy代理对象传入进去,根据该对象创建代理类并返回。所以到这里已经得到需要的代理类了,但是我们的代理类所做的工作还得继续向下看MapperProxy类。


获取Mapper的流程总结如下:


⑥、找到MapperProxy类,这个类非常重要。可以发现这个类实现了InvocationHandler接口,因为JDK的动态代理必须实现这个接口,只要类实现了InvocationHandler接口的类最终都会执行invoke()方法,所以我们重点关注重写的invoke()方法。

MyBatis(3.5.3)版本不一样,但是最后都是调用mapperMethod.execute(sqlSession, args)

  /**
   * 方法实现说明:我们的Mapper接口调用我们的目标对象
   * @author:xsls
   * @param proxy 代理对象
   * @param method:目标方法
   * @param args :目标对象参数
   * @return:Object
   * @exception:
   * @date:2019/8/27 19:15
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      /**
       * 判断我们的方法是不是我们的Object类定义的方法,若是直接通过反射调用
       */
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (method.isDefault()) {   //是否接口的默认方法
        /**
         * 调用我们的接口中的默认方法
         */
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    /**
     * 真正的进行调用,做了二个事情
     * 第一步:把我们的方法对象封装成一个MapperMethod对象(带有缓存作用的)
     */
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    /**
     *通过sqlSessionTemplate来调用我们的目标方法
     * 那么我们就需要去研究下sqlSessionTemplate是什么初始化的
     * 我们知道spring 跟mybatis整合的时候,进行了偷天换日
     * 把我们mapper接口包下的所有接口类型都变为了MapperFactoryBean
     * 然后我们发现实现了SqlSessionDaoSupport,我们还记得在整合的时候,
     * 把我们EmployeeMapper(案例class类型属性为MapperFactoryBean)
     * 的注入模型给改了,改成了by_type,所以会调用SqlSessionDaoSupport
     * 的setXXX方法进行赋值,从而创建了我们的sqlSessionTemplate
     * 而在实例化我们的sqlSessionTemplate对象的时候,为我们创建了sqlSessionTemplate的代理对象
     *     this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
            new Class[] { SqlSession.class }, new SqlSessionInterceptor());
     */
    return mapperMethod.execute(sqlSession, args);
  }

invoke()方法中首先判断代理对象是不是一个类,这里的Mapper代理对象是一个接口,不是一个类,所有会调用cachedInvoker(method).invoke()方法来生成MapperMethod对象,它是用来描述Mapper接口里面一个方法的内容的。有点类似于Spring中的BeanDefinition类。(MapperMethod对象下面会有介绍)。

下面我们来看看这个MapperMethod是如何生成的。它是调用了cachedInvoker(method).invoke()方法来生成的(注意:这里是一个方法链,先是调用了cachedInvoker(method),然后再调用的invoke()),cachedInvoker(method)这个方法就不仔细看了,它的返回值为MapperMethodInvoker对象,这个类是MapperProxy类中定义的一个内部接口,并且定义了内部实现类,我们重点要看的是调用invoke()方法,如下。

可以发现这里定义了MapperMethod对象,并且通过内部实现类重写的invoke()方法调用了mapperMethod.execute()方法,这个execute()方法是具体执行操作的方法,然后将结果返回。

⑦、所以我们来具体看看MapperMethod类。注意:我们首先需要知道这个MapperMethod类是干什么的?它用来描述Mapper接口里面一个方法的内容的,有点像Spring中的BeanDefinition。MapperMethod类是整个代理机制的核心类,它对SqlSession中的操作进行了封装使用,主要的功能是执行SQL的相关操作,例如执行sqlSession.selectList操作。该类里面定义了两个内部类分别为:SqlCommand(Sql命令)和MethodSignature(方法签名),其中SqlCommand听名字就知道是用来封装SQL命令的,是的,它是用来封装CRUD操作,也就是我们在xml中配置的操作的节点,每个节点都会生成一个MappedStatement类。另一个MethodSignature用来封装方法的参数以及返回类型。MapperMethod在初始化时会实例化两个组件SqlCommand(Sql命令)和MethodSignature(方法签名),必须同时给这两个组件提供参数:Mapper的接口路径(mapperInterface),待执行的方法(method),配置的Configuration。然后通过获取SqlCommand中的执行类型,MapperMethod才知道该Mapper接口将要执行什么样的操作。

 /**
   * 方法实现说明:执行我们的目标方法
   * @author:sqlSession:我们的sqlSessionTemplate
   * @param args:方法参数
   * @return:Object
   * @exception:
   * @date:2019/9/8 15:43
   */
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    /**
     * 判断我们执行sql命令的类型
     */
    switch (command.getType()) {
      //insert操作
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      //update操作
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      //delete操作
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      //select操作
      case SELECT:
        //返回值为空
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          //返回值是一个List
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          //返回值是一个map
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          //返回游标
          result = executeForCursor(sqlSession, args);
        } else {
          //查询返回单个

          /**
           * 解析我们的参数
           */
          Object param = method.convertArgsToSqlCommandParam(args);
          /**
           * 通过调用sqlSessionTemplate来执行我们的sql
           * 第一步:获取我们的statmentName(com.tuling.mapper.EmployeeMapper.findOne)
           * 然后我们就需要重点研究下SqlSessionTemplate是怎么来的?
           * 在mybatis和spring整合的时候,我们偷天换日了我们mapper接口包下的所有的
           * beandefinition改成了MapperFactoryBean类型的
           * MapperFactoryBean<T> extends SqlSessionDaoSupport的类实现了SqlSessionDaoSupport
           * 那么就会调用他的setXXX方法为我们的sqlSessionTemplate赋值
           *
           */
          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;
  }

⑧、我们重点来看一下execute()方法中属性为SELECT时调用的executeForMany(SqlSession sqlSession, Object[] args)方法,这个方法表示查询多条数据。

这是查询多条记录的一个方法,从上面的方法可以看到,虽然在SELECT操作中,是调用了MapperMethod中的方法,但本质上仍是通过Sqlsession下的selectList()方法实现的,而其它增删改查都是类似的。最后经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。

  /**
   * 方法实现说明
   * @author:xsls
   * @param statement: statementId
   * @param parameter:参数对象
   * @param rowBounds :mybiats的逻辑分页对象
   * @return:
   * @exception:
   * @date:2019/9/9 20:33
   */
  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      /**
       * 第一步:通过我们的statement去我们的全局配置类中获取MappedStatement
       */
      MappedStatement ms = configuration.getMappedStatement(statement);
      /**
       * 通过执行器去执行我们的sql对象
       * 第一步:包装我们的集合类参数
       * 第二步:一般情况下是executor为cacheExetory对象
       */
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

然后,通过一层一层的调用,最终会来到doQuery方法, 这儿咱们就随便找个Excutor看看doQuery方法的实现吧,我这儿选择了SimpleExecutor:

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //内部封装了ParameterHandler和ResultSetHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      //StatementHandler封装了Statement, 让 StatementHandler 去处理
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

接下来,咱们看看StatementHandler 的一个实现类 PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement), 看看它使怎么去处理的:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
     //到此,原形毕露, PreparedStatement, 这个大家都已经滚瓜烂熟了吧
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //结果交给了ResultSetHandler 去处理,处理完之后返回给客户端
    return resultSetHandler.<E> handleResultSets(ps);
  }

到此,整个调用流程结束


回到selectList(),我们可以看到执行的参数是通过command.getName()来获取的,所以继续来跟踪command.getName()是怎么来的,这里是SQLCommand的内容。

⑨、MapperMethod的内部类——SqlCommand,它封装了具体执行的动作。

  /**
   * 用户保存我们Mapper接口方法信息
   */
  public static class SqlCommand {
    /**
     * 接口的方法名全路径比如:com.tuling.mapper.DeptMapper.findDepts
     */
    private final String name;
    /**
     * 对应接口方法操作的sql类型(是insert|update|delte|select)
     */
    private final SqlCommandType type;

    /**
     * 方法实现说明:创建我们的SqlCommand
     * @author:xsls
     * @param configuration:mybatis的全局配置
     * @param mapperInterface:我们Mapper接口的class类型
     * @param method:方法对象
     * @return:
     * @exception:
     * @date:2019/9/6 21:51
     */
    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      //获取我们的方法的名称
      final String methodName = method.getName();
      //方法所在接口的类型
      final Class<?> declaringClass = method.getDeclaringClass();
      /**
       * 根据接口,方法名称解析出我们对应的mapperStatment对象
       */
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
        //把我们的mappedStatmentID(com.tuling.mapper.EmpMapper.findEmp)
        name = ms.getId();
        //sql操作的类型(比如insert|delete|update|select)
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

    public String getName() {
      return name;
    }

    public SqlCommandType getType() {
      return type;
    }

    /**
     * 方法实现说明:解析我们的mappedStatment对象
     * @author:xsls
     * @param mapperInterface:我们mapper接口的class类型
     * @param methodName :方法名称
     * @param declaringClass:方法所在类的接口
     * @param configuration:mybatis的全局配置
     * @return: MappedStatement
     * @exception:
     * @date:2019/9/8 13:29
     */
    private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
      //获取我们的sql对应的statmentId(com.tuling.mapper.DeptMapper.findDepts)
      String statementId = mapperInterface.getName() + "." + methodName;
      //根据我们的statmentId判断我们的主配置类是否包含 了我们的mapperStatment对象
      if (configuration.hasStatement(statementId)) {
        //存在通过key获取对应的mapperStatment对象返回
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
        return null;
      }
      /**
       * 获取我们mapper接口的父类接口
       */
      for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        //判断方法所在的类是否实现了superInterface
        if (declaringClass.isAssignableFrom(superInterface)) {
          //解析我们父类的MappedStatment对象
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }
  }

首先是获取了方法名和类名,然后调用了resolveMappedStatement()方法来解析生成MappedStatement对象。所以我们重点来看看这个解析的方法,进入方法后可以看到首先就是构建了statementId,注意到这个id是由接口名字+方法名字组成的。接着往下走,发现configuration.getMappedStatement(statementId);这句话,参数传入的是statementId,也就是说要找的MappedStatement并不是new出来的,而是通过statementId从Configuration类对象中get出来的。也就是说很早之前MappedStatement在很早之前就已经被初始化,并且放到Configuration对象里面,是的,我们的MappedStatement在SqlSessionFactory构建的时候就已经封装完成了,它包含了一条SQL语句的所有信息,MappedStatement本身是一个Map,它的可以为接口名字+方法名字组成的,所以这里我们可以根据statementId来获取MappedStatement对象。最后根据获取的MappedStatement对象来初始化name和type的值。

⑩、MapperMethod的内部类——MethodSignature,它主要封装了Mapper接口中方法的参数类型、返回值类型等信息。

 public static class MethodSignature {

    private final boolean returnsMany;
    private final boolean returnsMap;
    private final boolean returnsVoid;
    private final boolean returnsCursor;
    private final boolean returnsOptional;
    private final Class<?> returnType;
    private final String mapKey;
    private final Integer resultHandlerIndex;
    private final Integer rowBoundsIndex;
    private final ParamNameResolver paramNameResolver;

    /**
     * 方法实现说明:方法签名对象
     * @author:xsls
     * @param configuration:mybaits的全局配置类
     * @param mapperInterface:我们mapper接口的class
     * @param method:接口方法调用对象
     * @return:
     * @exception:
     * @date:2019/9/8 13:45
     */
    public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
      /**
       * 解析方法的返回值类型
       */
      Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
      //判断返回值是不是class类型的
      if (resolvedReturnType instanceof Class<?>) {
        this.returnType = (Class<?>) resolvedReturnType;
      } else if (resolvedReturnType instanceof ParameterizedType) {
        //是不是参数泛型的
        this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
      } else {
        //普通的
        this.returnType = method.getReturnType();
      }
      //返回值是不是为空
      this.returnsVoid = void.class.equals(this.returnType);
      //返回是是不是集合类型
      this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
      //返回值是不是游标
      this.returnsCursor = Cursor.class.equals(this.returnType);
      //返回值是不是optionnal类型的
      this.returnsOptional = Optional.class.equals(this.returnType);
      this.mapKey = getMapKey(method);
      this.returnsMap = this.mapKey != null;
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      /**
       * 初始化我们参数解析器对象
       */
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }

    /**
     * 方法实现说明:通过我们的参数解析器解析方法的参数
     * @author:xsls
     * @param args:参数数组
     * @return: Object处理后的参数
     * @exception:
     * @date:2019/9/8 17:15
     */
    public Object convertArgsToSqlCommandParam(Object[] args) {
      return paramNameResolver.getNamedParams(args);
    }

    public boolean hasRowBounds() {
      return rowBoundsIndex != null;
    }

    public RowBounds extractRowBounds(Object[] args) {
      return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
    }

    public boolean hasResultHandler() {
      return resultHandlerIndex != null;
    }

    public ResultHandler extractResultHandler(Object[] args) {
      return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
    }

    public String getMapKey() {
      return mapKey;
    }

    public Class<?> getReturnType() {
      return returnType;
    }

    public boolean returnsMany() {
      return returnsMany;
    }

    public boolean returnsMap() {
      return returnsMap;
    }

    public boolean returnsVoid() {
      return returnsVoid;
    }

    public boolean returnsCursor() {
      return returnsCursor;
    }

    /**
     * return whether return type is {@code java.util.Optional}.
     * @return return {@code true}, if return type is {@code java.util.Optional}
     * @since 3.5.0
     */
    public boolean returnsOptional() {
      return returnsOptional;
    }

    private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
      Integer index = null;
      final Class<?>[] argTypes = method.getParameterTypes();
      for (int i = 0; i < argTypes.length; i++) {
        if (paramType.isAssignableFrom(argTypes[i])) {
          if (index == null) {
            index = i;
          } else {
            throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
          }
        }
      }
      return index;
    }

    private String getMapKey(Method method) {
      String mapKey = null;
      if (Map.class.isAssignableFrom(method.getReturnType())) {
        final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
        if (mapKeyAnnotation != null) {
          mapKey = mapKeyAnnotation.value();
        }
      }
      return mapKey;
    }
  }

这里主要是对Mapper接口中的方法进行解析处理并且封装,它是通过参数解析器ParamNameResolver来完成的。

上面用到反射类Method中的很多方法,所以我们来简单掌握Method类常用的方法:

  • getName():获取方法名
  • getDeclaringClass:获取全限定类名
  • getModifiers():获取权限修饰符
  • getReturnType():获取返回类型
  • getExceptionTypes():获取所有抛出的异常类型
  • getParameterTypes():获取所有参数的类型
  • getParameterAnnotations():获取方法中的所有注解
  • getAnnotations():获取方法级别的注解

至此mapper的动态代理介绍完了。下面还介绍了Mapper接口中的方法参数是怎么来获取的,它是通过参数解析器(ParamNameResolver)来完成的,有兴趣可以了解一下。

⑪、在上述代码中经常能看见这样一句代码 method.convertArgsToSqlCommandParam(args); 这个方法是MethodSignature内部类中的,该方法主要的功能是获取@Param注解上的参数值。而实现方式便是通过参数解析器(ParamNameResolver),convertArgsToSqlCommandParam(args)方法实际上调用的是ParamNameResolver下的getNamedParams(args)方法。在分析该方法之前,先看看构造器都做了些什么操作。

构造器同样需要两个入参,配置类和方法名,ParamNameResolver类下包含了两个属性字段GENERIC_NAME_PREFIX属性前缀和参数集合names,构造器会将Method中所有参数级别的注解全部解析出来方法有序参数集中,names中存储形式为<参数下标,参数名>,如果在注解上设置了参数名,则会直接获取注解的value值,如果没有使用@Param注解,则使用真实的参数名,注意:真实参数名其实是arg0,arg1….的形式展现的,在判断真实参数名时,Mybatis会检查JDK版本是否包含java.lang.reflect.Parameter类,不存在该类的化会抛出ClassNotFoundException异常。完成初始化后,就可以调用getNamedParams(args)方法了,如下代码所示,该方法使用了类中的names属性,从有序集合中取出所有的<参数索引,参数名>键值对>,随后填充到另外一个集合中,以<参数名,参数下标索引,>形式呈现,同时会保留一份<param+下标索引,参数下标>的键值对。

  public Object getNamedParams(Object[] args) {
    //获取参数的个数
    /**
     * names的数据结构为map
     * ({key="0",value="id"},{key="1",value="name"})
     */
    final int paramCount = names.size();
    //若参数的个数为空或者个数为0直接返回
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      /**
       * 若有且只有一个参数 而且没有标注了@Param指定方法方法名称
       *
       */
      return args[names.firstKey()];
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      /**
       * 循坏我们所有的参数的个数
       */
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        //把key为id,value为1加入到param中
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        //加入通用的参数:名称为param+0,1,2,3......
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          //把key为param+0,1,2,3.....,value值加入到param中
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

简单总结

这边结合获取SqlSession的流程,做下简单的总结:

获得SqlSession的流程:

  • SqlSessionFactoryBuilder解析配置文件,包括属性配置、别名配置、拦截器配置、环境(数据源和事务管理器)、Mapper配置等;解析完这些配置后会生成一个Configration对象,这个对象中包含了MyBatis需要的所有配置,然后会用这个Configration对象创建一个SqlSessionFactory对象,这个对象中包含了Configration对象;
  • 拿到SqlSessionFactory对象后,会调用SqlSessionFactory的openSesison方法,这个方法会创建一个Sql执行器(Executor组件中包含了Transaction对象),这个Sql执行器会代理你配置的拦截器方法。
  • 获得上面的Sql执行器后,会创建一个SqlSession(默认使用DefaultSqlSession),这个SqlSession中也包含了Configration对象和上面创建的Executor对象,所以通过SqlSession也能拿到全局配置;
  • 获得SqlSession对象后就能执行各种CRUD方法了。

Sql的执行流程:

  • 调用SqlSession的getMapper方法,获得Mapper接口的动态代理对象MapperProxy,调用Mapper接口的所有方法都会调用到MapperProxy的invoke方法(动态代理机制);
  • MapperProxy的invoke方法中唯一做的就是创建一个MapperMethod对象,然后调用这个对象的execute方法,sqlSession会作为execute方法的入参;
  • 往下,层层调下来会进入Executor组件(如果配置插件会对Executor进行动态代理)的query方法,这个方法中会创建一个StatementHandler对象,这个对象中同时会封装ParameterHandler和ResultSetHandler对象。调用StatementHandler预编译参数以及设置参数值,使用ParameterHandler来给sql设置参数。

Executor组件有两个直接实现类,分别是BaseExecutor和CachingExecutor。CachingExecutor静态代理了BaseExecutor。Executor组件封装了Transction组件,Transction组件中又分装了Datasource组件。

  • 调用StatementHandler的增删改查方法获得结果,ResultSetHandler对结果进行封装转换,请求结束。

Executor、StatementHandler 、ParameterHandler、ResultSetHandler,Mybatis的插件会对上面的四个组件进行动态代理。


重要类

  • MapperRegistry:本质上是一个Map,其中的key是Mapper接口的全限定名,value的MapperProxyFactory;
  • MapperProxyFactory:这个类是MapperRegistry中存的value值,在通过sqlSession获取Mapper时,其实先获取到的是这个工厂,然后通过这个工厂创建Mapper的动态代理类;
  • MapperProxy:实现了InvocationHandler接口,Mapper的动态代理接口方法的调用都会到达这个类的invoke方法;
  • MapperMethod:判断你当前执行的方式是增删改查哪一种,并通过SqlSession执行相应的操作;
  • SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;
  • Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;
    • StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
    • ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数。
    • ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
    • TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换
    • MappedStatement:MappedStatement维护了一条节点的封装
    • SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
    • BoundSql:表示动态生成的SQL语句以及相应的参数信息
    • Configuration:MyBatis所有的配置信息都维持在Configuration对象之中。

调试主要关注点

  • MapperProxy.invoke方法:MyBatis的所有Mapper对象都是通过动态代理生成的,任何方法的调用都会调到invoke方法,这个方法的主要功能就是创建MapperMethod对象,并放进缓存。所以调试时我们可以在这个位置打个断点,看下是否成功拿到了MapperMethod对象,并执行了execute方法。
  • MapperMethod.execute方法:这个方法会判断你当前执行的方式是增删改查哪一种,并通过SqlSession执行相应的操作。Debug时也建议在此打个断点看下。
  • DefaultSqlSession.selectList方法:这个方法获取了获取了MappedStatement对象,并最终调用了Executor的query方法;

数据库执行过程


二级缓存原理

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

1 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 594004浏览

随机文章
Kotlin-函数进阶—高阶函数(十五)
4年前
SpringMVC—HandlerMethodArgumentResolver参数处理器
3年前
SpringSecurity—OAuth 2(二)授权码模式
5年前
Java—并发编程(二)synchronized关键字
4年前
Java—并发编程(七)JUC集合 – (4) ConcurrentHashMap
4年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 594004 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付