mybatis 参数赋值及类型解析

基本类型处理器

configuration对象初始化的时候会创建TypeHandlerRegistry,构造方法里指定了默认类型处理。基本类型常见的数据库类型都又对应的解析器。

TypeHandlerRegistry类typeHandlerMap属性存储了javaType和类型TypeHandler之间的映射关系。这里的mapkey值就是javaType对应的Class,然后value呢?我们知道一个类型可以对应多个类型,如varchar和char都可以解析成String类型。所以这里的value又是一个map,key是jdbcType,value是TypeHandler。所以这个结构就是{

   javaTypeClass1->map{

	jdbcType1->TypeHandler1,

	jdbcType1->TypeHandler2...

},

   javaTypeClass2->map{

	jdbcType1->TypeHandler1,

	jdbcType1->TypeHandler2...

}

    ...

},

来看下String类型对应的TypeHandler具体结构:

image

注意看这里内层map有个null的key。这里先不说,后面会说到。

类型处理器注册过程

在构造函数里会注册基本处理类型,然后从类型解析器的MappedJdbcTypes注解,拿到jdbcType。最后存储到上面说的typeHandlerMap中。

public TypeHandlerRegistry(Configuration configuration) {
  this.unknownTypeHandler = new UnknownTypeHandler(configuration);

  register(Boolean.class, new BooleanTypeHandler());
  register(boolean.class, new BooleanTypeHandler());
  register(JdbcType.BOOLEAN, new BooleanTypeHandler());
  register(JdbcType.BIT, new BooleanTypeHandler());
  ...
}
//解析typeHandler上的MappedJdbcTypes注解,拿到jdbcType
  private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
       //实际注册方法
        register(javaType, handledJdbcType, typeHandler);
      }
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else {
      register(javaType, null, typeHandler);
    }
  }
  /**
   最后的注册方法,存储到上面所的typeHandlerMap结构中
  **/
  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
  if (javaType != null) {
    //拿出javaType对应的所有类型解析器
    Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
    if (map == null || map == NULL_TYPE_HANDLER_MAP) {
      map = new HashMap<>();
    }
    map.put(jdbcType, handler);
    typeHandlerMap.put(javaType, map);
  }
  allTypeHandlersMap.put(handler.getClass(), handler);
}

语句解析

XMLStatementBuilder.parseStatementNode解析statment语句。也就是对应配置的select|insert|update|delete标签。

public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");

  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);

  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  ...
  //解析sql语句
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  String resultType = context.getStringAttribute("resultType");
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");
  //解析的语句构造一个MappedStatement对象进行存储
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

上面的parseStatementNode方法会将语句标签上配置的属性解析出来,然后sql解析是下面调用链。

XMLStatementBuilder.parseStatementNode

XMLLanguageDriver.createSqlSource

	XMLScriptBuilder.parseScriptNode

		new RawSqlSource(SqlSourceBuilder.parse())

			GenericTokenParser.parse()

			//最后返回一个StaticSqlSource类型的sqlSource,handler是ParameterMappingTokenHandler,解析的参数都在sqlSource里

			return new StaticSqlSource(configuration, sql, handler.getParameterMappings())

具体sql语句解析交给GenericTokenParser.parse()。具体的就是把#{}配置的参数按顺序解析成参数集,然后sql变成带?的预编译sql语句。parse()方法里就是对sql进行字符串处理,两个变量openToken = "#{",closeToken = "}"用来找到变量的位置,然后将#{expression}中的expression交给ParameterMappingTokenHandler.handleToken()方法进行解析参数映射。最后返回 如语句

select * from sys_user where userNo = #{userNo,jdbcType=VARCHAR}

最后语句会转成

select * from sys_user where userNo = ?

然后调用ParameterMappingTokenHandler.handleToken(”userNo,jdbcType=VARCHAR“)处理参数。

SqlSourceBuilder.handleToken 处理sql中的#{}变量,会放到parameterMappings中。parameterMappings是一个list,里面元素是ParameterMapping对象。ParameterMapping对象有以下主要属性,都可以通过#{expression}中的expression进行解析。在sql参数中最简单的配置方式是 #{name}这样配置,其实还可以指定以下参数javaType,jdbcType,typeHandler,mode 等。

#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}

这也是看到上面buildParameterMapping最后有对propertiesMap这些属性的处理。

public class ParameterMapping {
  //属性名  
  private String property;
  //参数类型 IN/OUT
  private ParameterMode mode;
  //java类型
  private Class<?> javaType = Object.class;
  //jdbc类型
  private JdbcType jdbcType;
  private Integer numericScale;
  //类型处理器
  private TypeHandler<?> typeHandler;
  private String resultMapId;
  private String jdbcTypeName;
  //原始配置表达式
  private String expression;
}

下面是ParameterMapping具体解析。SqlSourceBuilder.handleToken方法

public String handleToken(String content) {
//将变量放到参数映射map里
  parameterMappings.add(buildParameterMapping(content));
  //返回?变量占位符
  return "?";
}
  private ParameterMapping buildParameterMapping(String content) {
      /**将表达式(#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler})转成map
        变量名的key会转成property
      */
      Map<String, String> propertiesMap = parseParameterMapping(content);
      String property = propertiesMap.get("property");
      Class<?> propertyType;
      //推断变量的类型
      if (metaParameters.hasGetter(property)) { //入参有该参数的get方法,是一个对象的属性
        propertyType = metaParameters.getGetterType(property);
      } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {//有该类型的类型解析器
        propertyType = parameterType;
      } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
        propertyType = java.sql.ResultSet.class;
      } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
        propertyType = Object.class;
      } else {
        MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
        if (metaClass.hasGetter(property)) {
          propertyType = metaClass.getGetterType(property);
        } else {
          propertyType = Object.class;
        }
      }
      //设置其它属性
      ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
      Class<?> javaType = propertyType;
      String typeHandlerAlias = null;
      //下面处理#{}中明确配置的选项进行赋值
      for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
        String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {
          javaType = resolveClass(value);
          builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {
          builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {
          builder.mode(resolveParameterMode(value));
        } else if ("numericScale".equals(name)) {
          builder.numericScale(Integer.valueOf(value));
        } else if ("resultMap".equals(name)) {
          builder.resultMapId(value);
        } else if ("typeHandler".equals(name)) {
          typeHandlerAlias = value;
        } else if ("jdbcTypeName".equals(name)) {
          builder.jdbcTypeName(value);
        } else if ("property".equals(name)) {
          // Do Nothing
        } else if ("expression".equals(name)) {
          throw new BuilderException("Expression based parameters are not supported yet");
        } else {
          throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + PARAMETER_PROPERTIES);
        }
      }
      if (typeHandlerAlias != null) {
        builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
      }
      return builder.build();
    }

接口参数解析

接口参数解析主要通过反射根据Method方法获取接口参数信息,ParamNameResolver.getNamedParams方法主要解析

//入参args是通过反射拿到的接口实参
public Object getNamedParams(Object[] args) {
  final int paramCount = names.size();
  if (args == null || paramCount == 0) {
    return null;
  } else if (!hasParamAnnotation && paramCount == 1) {//没有Param注解,参数只有一个
    Object value = args[names.firstKey()];
    return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
  } else {//多个参数返回ParamMap
    final Map<String, Object> param = new ParamMap<>();
    int i = 0;
    //names是通过反射解析的形参名列表
    for (Map.Entry<Integer, String> entry : names.entrySet()) {
      param.put(entry.getValue(), args[entry.getKey()]);
      // add generic param names (param1, param2, ...)
      //这里GENERIC_NAME_PREFIX值是param,
      final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
      // ensure not to overwrite parameter named with @Param
      if (!names.containsValue(genericParamName)) {
        param.put(genericParamName, args[entry.getKey()]);
      }
      i++;
    }
    return param;
  }
}

形参的解析默认情况下都是arg0,arg1,...这种样式。从jdk1.8开始可以指定java参数 -parameters保留形参

入参赋值

主要在DefaultParameterHandler进行完成。看了上面的sql语句解析的过程这里就很好理解了。就是拿出上面解析的参数映射集parameterMappings,然后依次通过PreparedStatement对预编译sql进行参数赋值。最后会调用typeHandler.setParamenter进行赋值。

//parameterObject是接口实参
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);
      if (parameterMapping.getMode() != ParameterMode.OUT) {//参数类型是入参
        Object value;
        String propertyName = parameterMapping.getProperty();//拿出参数名
        if (boundSql.hasAdditionalParameter(propertyName)) { 
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
        //A实参有对应的类型处理handler,进行参数赋值
          value = parameterObject;
        } else {//B使用反射获取参数值
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        JdbcType jdbcType = parameterMapping.getJdbcType();
        
        //参数赋值,最后会调用setNonNullParameter方法
        typeHandler.setParameter(ps, i + 1, value, jdbcType);
        
      }
    }
  }
}

结合上面的例子,来看以下几个例子:

1、接口:User findUserByNo(String userNo);

   sql: select * from sys_user where userNo = #{a1} and userName = #{b1}

 最后会发现该方法能正常执行,然后a1和b1都会赋值成userNo的值。这是因为a1和b1的赋值都会走上面代码A处逻辑。所以当入参是基本类型(有对应的类型处理器),sql参数可以随便写。

2、接口:User findUserByNo1(String userNo);

   sql: select * from sys_user where userNo = #{a1} and userName = #{b1}

直接报异常:org.apache.ibatis.binding.BindingException: Parameter 'a1' not found. Available parameters are [arg1, arg0, param1, param2]

在前面说缓存的时候,同一个接口且入参相等,所以在构造缓存key的时候会根据sql的参数映射取一次入参变量。executor.createCacheKey

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  ...
  for (ParameterMapping parameterMapping : parameterMappings) {
    if (parameterMapping.getMode() != ParameterMode.OUT) {
      Object value;
      String propertyName = parameterMapping.getProperty();
      if (boundSql.hasAdditionalParameter(propertyName)) {
        value = boundSql.getAdditionalParameter(propertyName);
      } else if (parameterObject == null) {
        value = null;
      } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
        value = parameterObject;
      } else {
        MetaObject metaObject = configuration.newMetaObject(parameterObject);
        value = metaObject.getValue(propertyName);
      }
      cacheKey.update(value);
    }
  }
 
  return cacheKey;
}

这里parameterObject是多个参数会返回一个paramMap,会走最后的else通过反射获取属性值,a1在对象中获取不到就抛异常了。

3、接口:User findUserByNo2(User user);

   sql: select * from sys_user where userNo = #{userNo} and userName = #{userName}

正常执行,一个参数,会通过反射从入参对应get方法直接取值

4、接口:User findUserByNo3(User user,User1 user1);//User和User1相同

   sql: select * from sys_user where userNo = #{userNo} and userName = #{userName}

这里执行又报2的错误了,来分析下多个参数parameterObject还是paramMap,最后还是反射获取值

从metaObject.getValue开始

//入参是参数名
public Object getValue(String name) {
  //A 解析sql变量名
  PropertyTokenizer prop = new PropertyTokenizer(name);
  if (prop.hasNext()) {//B 多级属性
  //hasNext就是判断有没有children,看下面的PropertyTokenizer方法就能够指定,参数包含.
    MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());//indexName是.的前半部分
    if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
      return null;
    } else {//C这里又调用自己,下次在来的时候就会走上层的else方法了。
      return metaValue.getValue(prop.getChildren());
    }
  } else {//D 这里objectWrapper实例是MapWrapper,因为参数是paramsMap类型
    return objectWrapper.get(prop);
  }
}

A处代码, PropertyTokenizer构造函数,解析参数名

public PropertyTokenizer(String fullname) {
  int delim = fullname.indexOf('.');//参数是否包含 .
  if (delim > -1) {//包含,将fullname拆成两部分 name.children
    name = fullname.substring(0, delim); 
    children = fullname.substring(delim + 1);
  } else {
    name = fullname;
    children = null;
  }
  indexedName = name;
  delim = name.indexOf('[');
  if (delim > -1) {
    index = name.substring(delim + 1, name.length() - 1);
    name = name.substring(0, delim);
  }
}

D处代码 解析方法在MapWrapper.get方法

public Object get(PropertyTokenizer prop) {
  if (prop.getIndex() != null) {
    Object collection = resolveCollection(prop, map);
    return getCollectionValue(prop, collection);
  } else {
    return map.get(prop.getName());
  }
}

所以参数userNo走的过程是,A解析变量名,然后不含子属性,直接走D。D里又走的else直接取值,最后从参数集里[arg0,arg1,param1,param2]获取不到属性。抛出异常。

多个对象参数变量要指定具体sql参数输入哪个形参,然后使用 形参.属性名 的形式设置。这里sql的条件就需要改成where userNo = #{param1.userNo} and userName = #{param1.userName}

我们来分析下param1.userNo的解析过程。首先走步骤A解析变量名,含有'.'有children,被分成name.children两部分,进到B。然后根据前半部分param1调用getValue方法,又从A开始,这次会直接走到D,这个时候在MapWrapper.get方法里也就是D能直接获取到param1,然后在B里又根据children从上面获取的param1里反射获取userNo,这里就能获取到值了。

小结

在语句解析的时候会根据#{}占位解析出sql中所有变量到parameterMappings,并且生成带?的预编译sql语句。最后在DefaultParameterHandler.setParameters中根据parameterMappings十二猴子PrepareStatment所有参数。取值是根据sql中变量从mapper接口实参取值。当mapper接口只有一个参数时,且typeHandlerRegistry有该参数类型的解析器,则sql变量取值直接取接口实参,如果没有类型解析器则会尝试通过反射找该变量在入参中的get方法取值。当有多个接口参数时,形参会封装成paramMap, sql中语句变量需要以 形参名形式明确指定,如果时简单基本属性直接使用形参名,如果时对象属性,需要使用 形参名.属性名 的形式mybatis才能解析找到其对应的get方法。

总结一句话入参赋值,第一步需要根据#{}占位表达式变量从接口的入参根据反射获取变量值,第二部根据参数类型找到对应typeHandler对PrepareStatment语句赋值。

resultMap解析

resultMap配置属性字段映射。如果result节点里不配置javaType,则会从返回实体类中去寻找该property对应的get方法,通过入参解析对应类型javaType,拿到javaType后利用上面的typeHandlerMap就可以获取到TypeHandler了。因为jdbcType是可以不配置的,所以也有了上面的null key值。

<resultMap id="userResultMap1" type="com.test.bean.User">
    <result property="userId" column="userId" javaType="java.lang.Long" jdbcType="BIGINT"/>
    <result property="loginTime" column="loginTime" typeHandler="com.test.mybatis.MyDateTypeHandler" />
</resultMap>

解析开始位置在XMLMapperBuilder.resultMapElement()方法

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
  //获取返回类型
  String type = resultMapNode.getStringAttribute("type",
      resultMapNode.getStringAttribute("ofType",
          resultMapNode.getStringAttribute("resultType",
              resultMapNode.getStringAttribute("javaType"))));
  Class<?> typeClass = resolveClass(type);
  if (typeClass == null) {
    typeClass = inheritEnclosingType(resultMapNode, enclosingType);
  }
  Discriminator discriminator = null;
  List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
  //获取所有的子节点
  List<XNode> resultChildren = resultMapNode.getChildren();
  for (XNode resultChild : resultChildren) {
    if ("constructor".equals(resultChild.getName())) {//constructor节点类型处理
      processConstructorElement(resultChild, typeClass, resultMappings);
    } else if ("discriminator".equals(resultChild.getName())) {//discriminator类型节点处理
      discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
    } else {
      List<ResultFlag> flags = new ArrayList<>();
      if ("id".equals(resultChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      //解析result、collection 、association类型配置
      resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
    }
  }
  ...
}
//解析result节点
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
    //解析xml节点所有配置属性
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
      property = context.getStringAttribute("property");
    }
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    String nestedResultMap = context.getStringAttribute("resultMap", () ->
        processNestedResultMappings(context, Collections.emptyList(), resultType));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    //构建ResultMapping对象
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }

MapperBuilderAssistant.buildResultMapping()方法

public ResultMapping buildResultMapping(
    Class<?> resultType,
    String property,
    String column,
    Class<?> javaType,
    JdbcType jdbcType,
    String nestedSelect,
    String nestedResultMap,
    String notNullColumn,
    String columnPrefix,
    Class<? extends TypeHandler<?>> typeHandler,
    List<ResultFlag> flags,
    String resultSet,
    String foreignColumn,
    boolean lazy) {
  //获取java类型 
  Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
  //根据指定的typeHandler获取handler实例,如果未指定返回null
  TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
  List<ResultMapping> composites;
  if ((nestedSelect == null || nestedSelect.isEmpty()) && (foreignColumn == null || foreignColumn.isEmpty())) {
    composites = Collections.emptyList();
  } else {
    composites = parseCompositeColumnName(column);
  }
  //构造ResultMapping对象
  return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
      .jdbcType(jdbcType)
      .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
      .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
      .resultSet(resultSet)
      .typeHandler(typeHandlerInstance)
      .flags(flags == null ? new ArrayList<>() : flags)
      .composites(composites)
      .notNullColumns(parseMultipleColumnNames(notNullColumn))
      .columnPrefix(columnPrefix)
      .foreignColumn(foreignColumn)
      .lazy(lazy)
      .build();
}

ResultMapping对象build方法会调用resolveTypeHandler()方法获取具体的typeHandler

private void resolveTypeHandler() {
  //配置未指定类型解析器并且javaType不为空
  if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
    Configuration configuration = resultMapping.configuration;
    TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    //获取typeHandler,从上面说的typeHandlerMap里获取对应的TypeHandler
    resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, resultMapping.jdbcType);
  }
}

出参赋值

返回值前面的文章说过是从PrepareStatment里先拿到列的jdbcType,然后根据映射获取property的javaType。这个时候jdbcType和javaType都是有值的。从TypeHandlerRegistry.typeHandlerMap里能获取到handler类型。然后调用handler的getResult()方法最后会调getNullableResult方法获取值。然后赋值给返回对象实例。

DefaultResultSetHandler的getPropertyMappingValue方法

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
    throws SQLException {
  if (propertyMapping.getNestedQueryId() != null) {
    return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
  } else if (propertyMapping.getResultSet() != null) {
    addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
    return DEFERRED;
  } else {//获取类型解析器
    final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
    final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    //获取列值
    return typeHandler.getResult(rs, column);
  }
}

自定义类型处理器

看了上面的入参出参的出来过程,都可以显示指定类型处理器

入参 :#{name,typeHandler=MyTypeHandler}

出参在resultMap中

<result property="name" column="name" typeHandler="com.test.mybatis.MyTypeHandler" />

如果我们想使用自己的类型解析器,首先要定义自己的TypeHandler然后继承BaseTypeHandler实现其中4个抽象方法。使用@MappedJdbcTypes和@MappedTypes注解标明处理的类型。如下面的自定义Date类型解析器,将数据库bigint类型时间转为java的Date类型。

@MappedJdbcTypes(JdbcType.BIGINT)
@MappedTypes(Date.class)
public class MyDateTypeHandler extends BaseTypeHandler<Date> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
        ps.setLong(i,parameter.getTime());
    }

    @Override
    public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return new Date(rs.getLong(columnName));
    }

    @Override
    public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return new Date(rs.getLong(columnIndex));
    }

    @Override
    public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return cs.getDate(columnIndex);
    }
}

最后在配置中注册我们的类型处理器

<typeHandlers>
    <typeHandler  handler="com.test.mybatis.MyDateTypeHandler"/>
</typeHandlers>

总结

mybatis在初始化的时候会加载所有的类型解析器,存放到TypeHandlerRegistry.typeHandlerMap中。 mapper文件sql语句可以解析出对应的变量参数列表放到Configuration对象mappedStatements集合里,mapper接口方法通过反射也会拿到参数列表。在代理调用的时候可以拿到接口实参列表和形参。在sql参数的具体赋值的时候,根据sql变量从代理实参根据反射推断取值,最后PrepareStatment参数赋值时使用类型解析器对占位变量进行赋值。可以在sql文件里明确的指定javaType,mybatis也可以通过反射自动推断出javaType。 在执行sql和构造缓存key的时候会根据sql参数从接口入参取值。在查询返回的时候jdbcType可以从resultset获取,javaType可以通过反射根据property从entity总获取,可以根据typeHandlerMap获取对应类型解析器进行赋值。

posted @ 2023-08-17 10:13  朋羽  阅读(109)  评论(0编辑  收藏  举报