MyBatis之配置文件解析_类型处理器的处理

类型处理器接口TypeHandler

JDBC执行SQL语句时,使用PreparedStatement设置参数值的方法是setXxx方法,Xxx表示一种类型,所以设置参数值需要根据参数的类型使用不同的方法,mybatis中用这个类型对应的TypeHandler对象的setParameter方法统一实现,查询数据时,使用ResultSet的getXxx方法获得数据,根据查询列的不同类型调用不同的方法,mybatis用这个类型对应的TypeHandler对象的getResult方法统一实现。

// 类型处理器接口,T:Java类型
public interface TypeHandler<T> {
  // 设置参数时调用,可以调用相应的ps中的方法
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  // 获得结果时调用	
  // 从rs中返回出给定的对象  
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

抽象类BaseTypeHandler

BaseTypeHandler是一个抽象类,实现了TypeHandler接口,mybatis提供了大量的常用类型处理器,都是基于BaseTypeHandler的,位于包org.apache.ibatis.type中,如IntegerTypeHandler,EnumTypeHandler等等。

// BaseTypeHandler是一个抽象类,实现了TypeHandler接口
// TypeReference是一个抽象类,可以获得范型T的原始类型
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
  // 实现了TypeHandler中的方法,在setParameter方法中处理了null的情型
  // 定义了新的抽象方法
  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  // 子类要实现的抽象方法
  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}

类型处理器注册器

TypeHandlerRegistry是类型处理器注册器,用来注册各种不同类型的注册器,使用多个Map存储各种不同的类型处理器,比较复杂的情形是一个Java类型,可能对应多个JDBC类型,每个JDBC类型又对应了不同的TypeHandler,这使用Map<Type, Map<JdbcType, TypeHandler<?>>>表示,这种情形下允许JdbcType为null。TypeHandlerRegistry的数据结构如下:

public final class TypeHandlerRegistry {
  // JdbcType为键,TypeHandler是值
  private final Map<JdbcType, TypeHandler<?>>  jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
  // 一个Java类型为键,值为一个Map,JdbcType为键,TypeHandler是值
  // 同一个Java类型,对应不同的JdbcType-TypeHandler  
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
  private final TypeHandler<Object> unknownTypeHandler;
  // Class类型,对应的TypeHandler  
  private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();

  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
  // 默认的枚举类型处理器为EnumTypeHandler
  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
}

TypeHandlerRegistry的register方法

TypeHandlerRegistry有多个重载的register方法来注册一个类型处理器


TypeHandlerRegistry主要方法包括注册类型处理器,及获得类型处理器的方法。
注册register方法的参数,有多个重载的register方法

// 多个重载的处理器的注册方法
// 基于Java类型的类型处理器的注册,3个参数:JavaType  JdbcType  handler
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      // javaType在typeHandlerMap是否已有值
      Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
      // 没有值,创建新的Map
      if (map == null || map == NULL_TYPE_HANDLER_MAP) {
        map = new HashMap<>();
      }
      // Z 
      map.put(jdbcType, handler);
      typeHandlerMap.put(javaType, map);
    }
    // allTypeHandlersMap中增加新值
    allTypeHandlersMap.put(handler.getClass(), handler);
}

// 首先看typeHandler上有无注解@MappedJdbcType,有则作为Jdbc类型,若没有则jdbcType作为null
// 2个参数:JavaType + TypeHandler
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler){}
    
// 一个参数: Class对象,表示一个TypeHandler类型,首先要找到这个类型处理器要处理的Java类型,
// 先看这个类型处理器类,有无注解@MappedTypes,若有则使用其定义的Java类型
// 否则使用反射获得TypeHandler<T>中的范型T的实际类作作为Java类型
public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    // 获得类型上的注解@MappedTypes
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      // 注解中属性value的值为javaTypeClass,进行注册  
      for (Class<?> javaTypeClass : mappedTypes.value()) {
        register(javaTypeClass, typeHandlerClass);
        mappedTypeFound = true;
      }
    }
    if (!mappedTypeFound) {
      // 如果没有注解@MappedTypes,使用反射获得TypeHandler<T>中的范型T的实际类作作为Java类型  
      register(getInstance(null, typeHandlerClass));
    }
}


多个重载的getTypeHandler方法:能根据Java类型、JDBC类型获得对应的类型注册器对象

自定义类型处理器及解析

先写一个类,实现接口TypeHandler,也可以基于BaseTypeHandler实现,定义TypeHandler类时,可以用注解MappedTypes,定义多个Java类型,如果没有则定义TypeHandler上的范型类型,用MappedJdbcTypes注解定义多个JdbcType,当然也可使用XML文件配置时的javaType与jdbcType

其次在mybatis配置文件中使用typeHandlers标签注册,如下所示:

<typeHandlers>
    <!-- 配置给定的类型处理器 -->
    <typeHandler handler="com.beck.mybatis.entity.ArrayTypeHandler" jdbcType="VARCHAR" ></typeHandler>
    <!-- 配置给定包中的所有类型处理器 -->
    <package name="com.beck.mybatis"></package>
</typeHanders>

XMLConfigBuilder对象解析配置文件时,解析typeHandlers标签的方法是typeHandlerElement,其基本逻辑为:

​ 若XML文件中定义了javaType属性,则使用其中的值作为javaType,其中的jdbcType属性有效

​ 若没有定义javaType属性,则其中的jdbcType属性无效,此时Java类型为注解MappedTypes定义上的类型,若没有使用这个注解,则使用范型作为Java类型,若使用了注解MappedJdbcTypes,则使用所定义的JdbcType类型,若没有使用这个注解,则JdbcType为null。代码如下:

// 注册自定义的类型处理器,处理子标签typeHandlers
private void typeHandlerElement(XNode parent) {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String typeHandlerPackage = child.getStringAttribute("name");
        // 注册包下的所有类型处理器  
        typeHandlerRegistry.register(typeHandlerPackage);
      } else { // 处理typeHandler子标签
        // 获得javaType,jdbcType及handler的属性值  
        String javaTypeName = child.getStringAttribute("javaType");
        String jdbcTypeName = child.getStringAttribute("jdbcType");
        String handlerTypeName = child.getStringAttribute("handler");
        Class<?> javaTypeClass = resolveClass(javaTypeName);
        JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
        Class<?> typeHandlerClass = resolveClass(handlerTypeName);
        // 若配置时没有定义javaType属性,则jdbcType的属性被忽略   
        if (javaTypeClass != null) {
          // 若配置时没有定义java类型,所配置的jdbc类型也被忽略  
          if (jdbcType == null) {
            //二个参数:java类型,handler,此时jdbcType为null  
            typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
          } else {
            // 三个参数: java类型,jdbc类型,handler  
            typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
          }
        } else {
          // 一个参数:handler, 此时查看typeHandlerClass上的注解MappedTypes作为Java Type,注解MappedJdbcTypes作为Jdbc Type
          // 若没有注解MappedTypes则用typeHandlerClass上的范型作为Java Type  
          typeHandlerRegistry.register(typeHandlerClass);
        }
      }
    }
  }
}

系统默认类型处理器的注册

​ mybatis初始化时,在SqlSessionFactoryBuilder的build方法中,会创建用来解析XML配置文件的对象XMLConfigBuilder对象,在创建这个对象时会首选创建Configuration对象,Configuration有一个TypeHandlerRegistry的引用,并创建了这个对象,TypeHandlerRegistry构造方法会注册多个mybatis默认的类型处理器。

public class Configuration {
    // 持有TypeHandlerRegistry,并创建了TypeHandlerRegistry对象
	protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);

}    

TypeHandlerRegistry的无参构造方法中,注册了系统默认的类型处理器。

public final class TypeHandlerRegistry {
    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());
        // 注册了系统自定义的类型处理器
        // ........
    }     
}

类型处理器的使用

当执行insert、update、delete SQL语句时,会调用参数类型对应的TypeHandler的setParameter方法,完成参数值的设置,使用逻辑在ParamHandler(默认实现类为DefaultParamHandler)的方法setParameters中

当执行查询语句时,会调用TypeHandler的getResult方法获得对应的结果,如果返回结果由ResultMap定义,则由DefaultResultSetHandler的getPropertyMappingValue方法由调用ResultMapping的TypeHandler的getResult方法获得结果。

若没有使用ResultMap,则使用DefaultResultSetHandler中的静态内部类UnMappedColumnAutoMapping的TypeHandler,调用其getResult方法获得要查询的值。DefaultResultSetHandlergetRow是获得查询数据的主控方法

posted @   beckwu  阅读(188)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示