Mybatis源码解析--ParameterHandler

Mybatis源码解析--ParameterHandler

背景

Mybatis源码解析--StatementHandler中我们知道StatementHandler是对JDBC中Statement功能的封装,而针对3种Statement提供了对应的StatementHandler,而StatementHandlerStatement的参数设置和结果集的解析逻辑分别交给ParameterHandlerResultSetHandler进行处理,本篇就来研究ParameterHandler的内部实现。

ParameterHandler的创建

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

ParameterHandler与JDBC

JDBC方式实现参数设置

首先使用JDBC提供的接口实现与PraneterHandler相同的功能,伪代码如下:

public class StudentMapper {
   public int insertStudent(Student student) {
        //构造sql语句
        String sql = "insert into student (n_id, c_name, n_age, c_school) values (?, ?, ?, ?)";
        //获取PrepareStatement对象
        PrepareStatement statement = connection.prepareStatement(sql);
        //设置参数
        statement.setInt(1, student.getId());
        statement.setString(2, student.getName());
        statement.setInt(3, student.getAge());
        statement.setString(4, student.getStudent());
        //省略执行语句、结果集的解析、资源释放等操作
    }
}

首先需要一个SQL语句模板,接着需要创建PrepareStatement对象对sql进行预编译,最后根据sql语句的占位符位置设置对应参数。可以分析出有以下几个必须参数:

  • SQL语句模板:要执行的sql语句模板,需要将占位符替换为真实参数才能执行

  • PrepareStatement:对sql进行预编译以及提供了替换SQL占位符的API

  • Student:提供真正参数的对象

  • statement.setInt(1, student.getAge()):根据占位符的下标替换真实参数(占位符与真实参数的映射关系)

由于SQL的预编译是在StatementHandler类中完成的,请参考Mybatis源码解析--StatementHandler,本篇只关注对PrepareStatement的赋值(也就是后3个参数)

ParameterHandler方式实现参数设置

ParameterHandler接口定义

ParameterHandler的职责就是对ParameterStatement设置参数,我们就先来看一下它对这些操作的抽象

public interface ParameterHandler {
  //用来获取封装真实入参的对象
  Object getParameterObject();
  //PreParedStatement参数替换的逻辑实现
  void setParameters(PreparedStatement ps) throws SQLException;
}

与JDBC实现对比,已经有2个重要参数出现,还差占位符与真实参数的映射关系,通过ParameterHandler提供的方法名称可以猜测核心逻辑肯定是在setParameters(PreparedStatement ps)方法中,Mybatis框架提供了一个默认实现类DefaultParameterHandler,那就来看一下DefaultParameterHandler#setParameters的具体实现

public void setParameters(PreparedStatement ps) {
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
          // 对每个ParameterMapping进行处理,此处先省略内部具体实现
      }
    }
  }

可以看到先从boundSql对象中获取List<ParameterMapping>,然后对其进行遍历,逐个处理,这个和JDBC中追个替换占位符逻辑类似。那ParameterMapping该类提供了什么功能呢?我们跟进去研究研究

ParameterMapping类

public class ParameterMapping {
​
  private Configuration configuration;
  // java对象的属性名
  private String property;
  private ParameterMode mode;
  // java对象该属性的类型
  private Class<?> javaType = Object.class;
  // 数据库类型
  private JdbcType jdbcType;
  private Integer numericScale;
  // 用于java对象和数据库表记录之间的转换
  private TypeHandler<?> typeHandler;
  private String resultMapId;
  private String jdbcTypeName;
  private String expression;
  // 建造器和getter方法省略  
}

ParameterMapping类中定义了一堆属性,除此之外使用建造者模式提供了一个建造器(作用:简化ParameterMapping的创建和关键属性的校验)以及获取这些属性的getter方法。可以将以上的属性抽象成下面这张图:

首先Mybatis是一个ORM框架,ORM框架的出现是因为JAVA和数据库各自都有一套类型的定义,它两之间要进行交互,就需要对所有类型建立一种映射关系。而ParameterMapping类就是对这种映射关系的封装。将转换的逻辑交给TypeHandler进行处理,Mybatis在type子包下定义了很多TypeHandler的实现类用来实现JAVA类型与数据库类型的相互转换。说了这么多好像和上面JDBC的关系映射没有多大关系,使用JDBC时直接在SQL语句中使用?占位符来代替真实属性进行预编译,然后根据占位符的下标替换为对应的真实属性,最后执行获取结果;而MyBatis中我们是如何定义SQL的呢?

PrepareHandler设置参数原理

下图是对上图分析的印证

由此可见BoundSql封装了一次SQL执行所需的所有数据,通过上面分析可以得知Mybatis通过自定义占位符#{}里面的字符串可以与真实对象的属性建立某种映射,并将这些映射关系保存到List<ParameterMapping>中,然后再将#{}替换成JDBC的?进行预编译,此时?List<ParameterHandler>是一一对应的;Mybaits通过List<ParameterHandler>的对应顺序就知道对应?应该获取真实对象的哪个属性。到此上面的所有疑问都解决了,下面我们就看具体对PrepareStatement的赋值操作

ParameterHandler参数设置

    for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { 
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            // 入参为null,则value赋值为null
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            // 如果该参数有对应的TypeHandler,直接交给TypeHandler转换
            value = parameterObject;
          } else {
            // 将真实入参封装成MetaObject
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          // 处理null
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // 调用TypeHandler对PrepareStatement进行参数设置
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }

核心逻辑就是获取属性的值,最后交给TypeHandler对象来对PrepareStatement进行参数设置。获取属性值有以下几个分支:

  • BoundSql指定了该属性的值,则value = boundSql.getAdditionalParameter(propertyName)

  • 没有入参(parameterObject == null),则value = null

  • TypeHandlerRegistry中有处理该入参的TypeHandler,则value = 入参

  • 其他情况,则需要将入参包装成MetaObject对象,MetaObject提供了工具类辅助获取属性值

根据不同情况获取到属性的值,最终交给TypeHandler进行真正的参数设置

ParameterHandler参数设置流程图如下

对于前3种情况很好理解,不再展开分析,其实大多数情况都是走的最后一种,所以下面我们详细分析MetaObject方式是如何获取属性值的。

对于TypeHandler是如何进行具体参数设置的,请查看Mybatis源码分析--TypeHandler

属性值的获取

MetaObject类

public class MetaObject {
​
  // 该对象是SQL所需的数据,可以是map,集合,bean实体对象
  private final Object originalObject;
  // 根据originalObject的真实类型,将其交给对应的Wrapper来处理
  private final ObjectWrapper objectWrapper;
  private final ObjectFactory objectFactory;
  private final ObjectWrapperFactory objectWrapperFactory;
  private final ReflectorFactory reflectorFactory;
​
  /**
   * 私有构造方法
   */
  private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;
​
    /**
     * 根据object的真实类型  创建对应的包装类
     * 此处的object是SqlSession中相关API传入的object参数,是最终替换SQL的入参
     * {@link org.apache.ibatis.session.SqlSession#insert(String, Object)}
     * {@link org.apache.ibatis.session.SqlSession#update(String, Object)}
     * {@link org.apache.ibatis.session.SqlSession#delete(String, Object)}
     * {@link org.apache.ibatis.session.SqlSession#select(String, Object, RowBounds, ResultHandler)}}
     */
    if (object instanceof ObjectWrapper) {
      this.objectWrapper = (ObjectWrapper) object;
    } else if (objectWrapperFactory.hasWrapperFor(object)) {
      this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } else if (object instanceof Map) {
      this.objectWrapper = new MapWrapper(this, (Map) object);
    } else if (object instanceof Collection) {
      this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } else {
      this.objectWrapper = new BeanWrapper(this, object);
    }
  }
}  

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值