Mybatis源码分析(ParameterHandler参数读取)

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

Mybatis参数读取源码分析

  • Mybatis框架最底层需要访问数据库,本质是到数据库去执行sql语句,那么给sql语句传递参数是必不可少的一个环节,sql语句在预编译阶段会用占位符替换参数的位置,在需要执行的时候需要将参数放到对应的位置上,这个将参数替换占位符的工作就是ParameterHandler来完成的,本文我们来看看这个对象处理的相关知识。

一、ParameterHandler

  • ParameterHandler在Mybatis四大对象中负责将sql中的占位符替换为真正的参数,它是一个接口,有且只有一个实现类DefaultParameterHandler
    public interface ParameterHandler {
    
      Object getParameterObject();
    
      void setParameters(PreparedStatement ps)
          throws SQLException;
    
    }
  • setParameters是处理参数最核心的方法。

二、DefaultParameterHandler

  • DefaultParameterHandler是接口的唯一实现类,代码不多,主要是setParameters方法,通过注释我们可以看到其大体的功能主流程
    public class DefaultParameterHandler implements ParameterHandler {
    
      //1.类型处理器注册中心
      private final TypeHandlerRegistry typeHandlerRegistry;
    
      //2.MappedStatement是保存sql语句的数据结构
      private final MappedStatement mappedStatement;
      //3.参数对象
      private final Object parameterObject;
      //4.BoundSql对象是sql语句和相关信息的封装
      private BoundSql boundSql;
      //5.全局配置对象
      private Configuration configuration;
    
      //构造方法
      public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        this.mappedStatement = mappedStatement;
        this.configuration = mappedStatement.getConfiguration();
        this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
        this.parameterObject = parameterObject;
        this.boundSql = boundSql;
      }
    
      @Override
      public Object getParameterObject() {
        return parameterObject;
      }
    
      /**
       * 将占位符替换为参数值
       * */
      @Override
      public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
        //1.获取sql语句的参数,ParameterMapping里面包含参数的名称类型等详细信息,还包括类型处理器
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
          //2.遍历依次处理
          for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            //3.OUT类型参数不处理
            if (parameterMapping.getMode() != ParameterMode.OUT) {
              Object value;
              //4.获取参数名称
              String propertyName = parameterMapping.getProperty();
              //5.如果propertyName是动态参数,就会从动态参数中取值。(当使用<foreach>的时候,MyBatis会自动生成额外的动态参数)
              if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
                value = boundSql.getAdditionalParameter(propertyName);
              } else if (parameterObject == null) {
                  //6.如果参数是null,不管属性名是什么,都会返回null。
                value = null;
              } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                  //7.判断类型处理器是否有参数类型,如果参数是一个简单类型,或者是一个注册了typeHandler的对象类型,就会直接使用该参数作为返回值,和属性名无关。
                value = parameterObject;
              } else {
                //8.这种情况下是复杂对象或者Map类型,通过反射方便的取值。通过MetaObject操作
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                value = metaObject.getValue(propertyName);
              }
              TypeHandler typeHandler = parameterMapping.getTypeHandler();
              //9.获取对应的数据库类型
              JdbcType jdbcType = parameterMapping.getJdbcType();
              //空类型
              if (value == null && jdbcType == null) {
                jdbcType = configuration.getJdbcTypeForNull();
              }
              //10.对PreparedStatement的占位符设置值(类型处理器可以给PreparedStatement设值)
              try {
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
              } catch (TypeException e) {
                throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
              } catch (SQLException e) {
                throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
              }
            }
          }
        }
      }
    }
  • 核心流程
    1.sql语句中的占位符?都对应了BoundSql#parameterMappings集合中的一个元素,在该对象中记录了对应的参数名称和参数的相关属性。
    在SimpleExecutor的doQuery方法中调用prepareStatement方法处理参数占位符,方法里面调用的就是PreparedStatementHandler的parameterize
    方法,最终调用的是DefaultParameterHandler#setParameters,在PreparedStatementHandler内部持有ParameterHandler对象。
    
    PS:简单来说就是SimpleExecutor#doQuery方法内部通过配置对象创建StatementHandler -> 调用PreparedStatementHandler#parameterize方
    法(内部持有ParameterHandler) ->DefaultParameterHandler#setParameters来完成sql语句执行之前的参数替换占位符

三、流程调试

  • 本节我们通过调试来看看前面说的核心流程,看看ParameterHandler参数处理在整个查询的哪一个步骤完成。断点在:SimpleExecutor#doQuery方法的prepareStatement(handler, ms.getStatementLog()),如下,
    然后执行一次查询操作:
    mapper.findMemberById(1);

3.1 SimpleExecutor#doQuery

  • SimpleExecutor#doQuery方法是一次查询的入口,查询处理和返回结果集都在这里。在第2步调用prepareStatement方法获取Statement,里面就包含参数的预处理。(这里还可以参考 14-Mybatis源码和设计模式-5(Executor组件与模板模式,装饰器模式))
    /**
       * 查询的实现
       * */
      @Override
      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();
          //1.创建StatementHandler
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          //2.用StatementHandler对象创建stmt,并使用StatementHandler对占位符进行处理
          stmt = prepareStatement(handler, ms.getStatementLog());
          //3.通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
          return handler.<E>query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }

3.2 SimpleExecutor#prepareStatement

  • prepareStatement方法是获取Statement,默认是PrepareStatement。(其实是一个增强了的代理对象,具备日志能力的代理对象),其中第三步就是进行参数处理。
      /**
       * 创建Statement
       * */
      private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        //1.获取connection对象的动态代理,添加日志能力;(这里参考日志模块的代理模式)
        Connection connection = getConnection(statementLog);
        //2.使用StatementHandler,利用connection创建(prepare)Statement
        stmt = handler.prepare(connection, transaction.getTimeout());
        //3.使用StatementHandler处理占位符
        handler.parameterize(stmt);
        return stmt;
      }

3.3 PreparedStatementHandler#parameterize

  • SimpleExecutor#prepareStatement 的方法首先会走到RoutingStatementHandler#parameterize,但是RoutingStatementHandler其实是一个静态代理,实际上走的是其内部的PreparedStatementHandler(参考:17-Mybatis源码分析(StatementHandler数据库访问))。
    后面就到了PreparedStatementHandler#parameterize,方法很简单,直接调用ParameterHandler的setParameters方法,直接走到DefaultParameterHandler#setParameters
      @Override
      public void parameterize(Statement statement) throws SQLException {
        parameterHandler.setParameters((PreparedStatement) statement);
      }

3.4 DefaultParameterHandler#setParameters

  • 参考前面的注释和下面的调试信息,这里会直接使用typeHandler进行设值操作,调用之前实际上已经通过参数类型获取到了对应的typeHandler。(parameterMapping.getTypeHandler())

3.5 BaseTypeHandler#setParameter

  • 这里实际上走的是BaseTypeHandler的子类IntegerTypeHandler,不过setParameter是BaseTypeHandler的一个模板方法,里面的一个流程方法setNonNullParameter会调用子类IntegerTypeHandler的,在IntegerTypeHandler的setNonNullParameter里面直接调用PreparedStatement的setInt方法。到这里其实就走到了PreparedStatement的代理类的invoke方法(我们前面提到过,其实四大对象运行时都是一个代理对象,是一个具备日志能力的代理对象,这里参考:11-Mybatis源码和设计模式-2(日志模块和适配器模式,代理模式)),然后就到了PreparedStatementLogger类。(下面文字写错了,因为参数是1,所以走下面一个逻辑)
    
      //BaseTypeHandler
      @Override
      public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
         //省略...
          try {
            setNonNullParameter(ps, i, parameter, jdbcType);
          //省略...
          }
      }
    
     //IntegerTypeHandler
      @Override
      public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
          throws SQLException {
        ps.setInt(i, parameter);
      }

3.6 PreparedStatementLogger#invoke

  • 最后通过method.invoke(statement, params),终于到了真实的PreparedStatement对象,调用的是PreparedStatement的setInt方法。我们发现绕了一圈已经到了JDBC的包下了,是的,Mybatis封装了很多最后底层参数设值还是JDBC那一套。

四、小结

4.1 调试流程图

4.2 小结

  • Executor是sql语句的执行器,Executor通过配置对象创建StatementHandler,继而得到了StatementHandler,StatementHandler是整个数据库访问过程的控制关键,它的内部持有ParameterHandler,因此StatementHandler可以通过后者来处理参数。在StatementHandler处理参数的过程中会通过参数类型来找到对应的typeHandler来处理参数,整个过程中Statement对象都作为参数在传递,到了typeHandler他会调用Statement的setInt来设置值,其实整个过程中Statement对象都在传递,Mybatis通过封装,但是还是在使用JDBC的API。
  • 关于Mybatis中数据访问的整体流程,可以阅读参考文章[3]

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值