mybaits TypeHandler构造函数传入的type对于枚举和普通类子类的区别分析以及如何获取具体子类的Type的方法

首先mybatis的自定义TypeHandler可以通过构造方法传入一个Class Type.这个Class Type 对于枚举和普通类在某些场景上会有区别

区别

 1.枚举只要如下所示注册一个父接口BaseEnum的EnumTypeHandler,之后在绑定实体类枚举字段(实现了BaseEnum的字段)时就可以在EnumTypeHandler拿到实体类字段ClassType.

//BaseEnum是TypeEnum,StatusEnum,CategoryEnum都有实现的接口(Interface)
@MappedTypes(value = BaseEnum.class)
public class EnumTypeHandler<T extends BaseEnum> extends BaseTypeHandler<BaseEnum> {
     //因为MappedTypes只需要配置BaseEnum
    //当实体类字段是TypeEnum则Type是TypeEnum.class
    //实体类字段是StatusEnum则Type是StatusEnum.class
    //实体类字段是CategoryEnum则Type是CategoryEnum.class
    private Class<T> type;

    public EnumTypeHandler(Class<T> type) {
        this.type = type;
    }
    ..............................
}

2.对于普通类则需要如下通过MappedTypes为Animal及其所有子类都注册一个AnimalTypeHandler,才可以在绑定实体类字段的时候在AnimalTypeHandler拿到实体类字段对应的ClassType(当然也可以自己写代码scan包主动注册所有Animal子类的AnimalTypeHandler).

//Animal是Dog,Cat的父类,
@MappedTypes(value = {Animal.class,Dog.class})
public class AnimalTypeHandler<T extends Animal> extends BaseTypeHandler<Animal> {
    //因为MappedTypes只配置了Animal和Dog
    //所以当实体类字段是Animal则Type是Animal.class
    //实体类字段是Dog则Type是Dog.class
    //实体类字段是Cat则Type是Animal.class(因为MappedTypes没有Cat.class)
    private Class<T> type;

    public EnumTypeHandler(Class<T> type) {
        this.type = type;
    }
    ..............................
}

为什么

1.对于枚举:如果当前实体的枚举字段Class没有对应的TypeHandler,mybaits会主动寻找其Parent Interface的TypeHandler,并用这个TypeHandler重新注册一个新的TypeHander包含当前实体枚举字典的Class作为当前实体枚举字段的TypeHandler.

即在mybatis中自己只需要注册一个Parent Interface的TypeHandler,mybatis会主动为实现了Parent Interface的枚举都创建一个TypeHandler包含枚举的ClassType.

public final class TypeHandlerRegistry {
 .................................

 private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
        //先从缓存中获取当前枚举的Class对应的TypeHandler,存在则直接返回
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
    if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
      return null;
    }
    if (jdbcHandlerMap == null && type instanceof Class) {
      //如果缓存在不存在对应的TypeHandler执行的逻辑.
      Class<?> clazz = (Class<?>) type;
      if (Enum.class.isAssignableFrom(clazz)) {
       
        Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;
        //如果当前是枚举则从调用获getJdbcHandlerMapForEnumInterfaces取枚举TypeHandler方法
        jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass);
        if (jdbcHandlerMap == null) {
           //如果获取不到TypeHander,则创建默认的枚举TypeHandler
          register(enumClass, getInstance(enumClass, defaultEnumTypeHandler));
          return typeHandlerMap.get(enumClass);
        }
      } else {
        jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
      }
    }
    //把拿到的TypeHandler和当初枚举的Class对应放入缓存
    typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
    return jdbcHandlerMap;
  }

  /**
  * 获取枚举TypeHandler的方法
  * 递归获取所有父接口的TypeHandler,一旦有某个接口存在对应的TypeHandler,
  * 会用这个TypeHandler用反射创建一个新的是实例,构造函数传入当前枚举的class
  */
 private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClazz) {
    //首先先遍历枚举的所有父接口
    for (Class<?> iface : clazz.getInterfaces()) {
        //获取当前父接口的TypeHandler
      Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(iface);
      if (jdbcHandlerMap == null) {
        //如果当前父接口没有TypeHandler,递归调用获取当前接口的父接口的TypeHandler
        jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(iface, enumClazz);
      }
      if (jdbcHandlerMap != null) {
        // Found a type handler regsiterd to a super interface
        HashMap<JdbcType, TypeHandler<?>> newMap = new HashMap<>();
        for (Entry<JdbcType, TypeHandler<?>> entry : jdbcHandlerMap.entrySet()) {
          //如果当前父接口存在对应的TypeHandler,
          //则调用getInstance用当前TypeHandler创建一个新的TypeHandler并传入当前枚举的Class
          //所以每个枚举类都有单独一个对应的TypeHandler,并且TypeHandler里包含当前的枚举的Class
          newMap.put(entry.getKey(), getInstance(enumClazz, entry.getValue().getClass()));
        }
        return newMap;
      }
    }
    return null;
  }

@SuppressWarnings("unchecked")
  public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    if (javaTypeClass != null) {
      try {
        Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
        //用传入的typeHandlerClass创建一个新的TypeHandler并传入对应的Type进构造函数
        return (TypeHandler<T>) c.newInstance(javaTypeClass);
      } catch (NoSuchMethodException ignored) {
        // ignored
      } catch (Exception e) {
        throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
      }
    }
    try {
        //如果没有有参构造函数则用无参构造函数创建实例
      Constructor<?> c = typeHandlerClass.getConstructor();
      return (TypeHandler<T>) c.newInstance();
    } catch (Exception e) {
      throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
    }
  }
}

2.对于普通Class,如果当前实体字段的Class没有对应的TypeHandler,Mybatis只会递归获取父类的TypeHandler,用父类的TypeHandler作为当前实体字段的TypeHandler,不会为当前实体字段特别创建一个TypeHandler包含对应的Class信息,而当前父类的TypeHandler包含的是父类的Class Type.

即如果自己没有主动注册Parent Class的所有子类的TypeHandler,那么子类用TypeHandler绑定实体字段时只能拿到父类的ClassType信息

public final class TypeHandlerRegistry {
    .................................

    private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
        //先从缓存中获取当前枚举的Class对应的TypeHandler,存在则直接返回
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
    if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
      return null;
    }
    if (jdbcHandlerMap == null && type instanceof Class) {
      Class<?> clazz = (Class<?>) type;
      if (Enum.class.isAssignableFrom(clazz)) {
       
        Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;
        jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass);
        if (jdbcHandlerMap == null) {
          register(enumClass, getInstance(enumClass, defaultEnumTypeHandler));
          return typeHandlerMap.get(enumClass);
        }
      } else {
        //如果不是枚举调用getJdbcHandlerMapForSuperclass获取TypeHandler;
        jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
      }
    }
    //把拿到的TypeHandler和当初枚举的Class对应放入缓存
    typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
    return jdbcHandlerMap;
  }

      
   /**
    * 获取非枚举的TypeHandler
    * 就是递归调用获取所有父类的TypeHandler(不包括接口)
    */
  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
    //获取父类
    Class<?> superclass =  clazz.getSuperclass();
    if (superclass == null || Object.class.equals(superclass)) {
      return null;
    }
    //获取父类的TypeHandler
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(superclass);
    if (jdbcHandlerMap != null) {
      //如果当前父类存在TypeHandler则返回
      //因为没有用TypeHandler重新创建一个新的TypeHandler传入当前类的Class,
      //所以返回的TypeHandler不包含当前类的Class,只包含对应父类的Class
      return jdbcHandlerMap;
    } else {
      //如果父类的没有对应的TypeHandler则继续获取当前父类的父类的TypeHandler返回
      return getJdbcHandlerMapForSuperclass(superclass);
    }
  }
}

 改如下源码几行代码就可以用一个针对Animal.class的​​​​​​TypeHandler就可以获取到所有子类实体字段对应的Type(不会影响mybatis源码原来的逻辑)

public class DefaultResultSetHandler implements ResultSetHandler {
  ...........................................................................................
  
  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
        //----------------旧的代码start
        //final Object value = mapping.typeHandler.getResult(rsw.getResultSet(),mapping.column);
        //----------------旧的代码end
        //----------------新的代码start
		final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column, mapping.propertyType);
        //----------------新的代码end
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }
  
   private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    final String mapKey = resultMap.getId() + ":" + columnPrefix;
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
    if (autoMapping == null) {
      autoMapping = new ArrayList<>();
      final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
      for (String columnName : unmappedColumnNames) {
        String propertyName = columnName;
        if (columnPrefix != null && !columnPrefix.isEmpty()) {
          // When columnPrefix is specified,
          // ignore columns without the prefix.
          if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
            propertyName = columnName.substring(columnPrefix.length());
          } else {
            continue;
          }
        }
        final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
        if (property != null && metaObject.hasSetter(property)) {
          if (resultMap.getMappedProperties().contains(property)) {
            continue;
          }
          final Class<?> propertyType = metaObject.getSetterType(property);
          if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
            final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
            //----------------旧的代码start
            //autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
            //----------------旧的代码end
            //----------------新的代码start
			autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive(), propertyType));
            //----------------新的代码end
          } else {
            configuration.getAutoMappingUnknownColumnBehavior()
                .doAction(mappedStatement, columnName, property, propertyType);
          }
        } else {
          configuration.getAutoMappingUnknownColumnBehavior()
              .doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
        }
      }
      autoMappingsCache.put(mapKey, autoMapping);
    }
    return autoMapping;
  }
  
  private static class UnMappedColumnAutoMapping {
    private final String column;
    private final String property;
    //----------------新增的代码start
	private final Class<?> propertyType;
    //----------------新增的代码end
    private final TypeHandler<?> typeHandler;
    private final boolean primitive;

    //----------------旧的代码start
    //public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
    //----------------旧的代码start
    //----------------新的代码start
    public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive, Class<?> propertyType) {
    //----------------新的代码end
   
      this.column = column;
      this.property = property;
      this.typeHandler = typeHandler;
      this.primitive = primitive;
      //----------------新增的代码start
	  this.propertyType = propertyType;
       //----------------新增的代码end
    }
  }
}
public interface TypeHandler<T> {
  ..........................................
  
 //----------------新的代码start
  /**
   * Gets the result.
   *
   * @param rs           the rs
   * @param columnName   Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
   * @param propertyType 实体类字段对应的Class
   * @return
   * @throws SQLException
   */
  default T getResult(ResultSet rs, String columnName, Class<?> propertyType) throws SQLException {
    return getResult(rs, columnName);
  }
 //----------------新的代码end
  
  /*目前提供的代码不包含对getResult(ResultSet rs, int columnIndex, Class<?> propertyType)相关源码的修改,可以自行模仿上面修改,大概也是3-4个地方简单改下
  default T getResult(ResultSet rs, int columnIndex, Class<?> propertyType) throws SQLException {
    return getResult(rs, columnIndex);
  }
  */
}

经过以上修改实现TypeHandler时只需要重写 T getResult(ResultSet rs, String columnName, Class<?> propertyType) 方法即可拿到实体字段类型propertyType.而不重写这个方法就和原来mybatis的逻辑一模一样.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值