Análisis de código fuente de conversión de tipo Mybatis

Análisis de código fuente de conversión de tipo Mybatis

Programación de tecnología Java TSMYK

Este artículo presentará los siguientes aspectos

  1. Prefacio
  2. Tipo de procesador
  3. Tipo de registro
  4. Registrador de alias

    Prefacio

El tipo de datos proporcionado por JDBC y el tipo de datos de Java no se corresponden completamente. Cuando Mybatis analiza SQL y utiliza PreparedStatement para establecer parámetros para SQL, debe convertirse del tipo Java al tipo JDBC. Al obtener el resultado de el ResultSet, el tipo JDBC debe convertirse al tipo Java; el módulo de conversión de tipos de Mybatis se utiliza para convertir estos dos tipos de datos; por ejemplo, al escribir archivos Mapper, puede escribir lo siguiente:


    <insert id="addUser" parameterType="User">
        INSERT INTO user(id, name, age, height) VALUES (
        #{id},
        #{name, javaType=String, jdbcType=varchar},
        #{age, javaType=int, jdbcType=NUMERIC, typeHandler=MyTypeHandler},
        #{height, javaType=double, jdbcType=NUMERIC, numericScale=2}
        )
    </insert>

Puede especificar el tipo correspondiente a Java y la base de datos, también puede especificar un procesador de tipo personalizado, etc. Cuando Mybatis analiza SQL, realizará la conversión correspondiente a través del procesador de conversión de tipo

Análisis de código fuente

El código relacionado con la conversión de tipos de Mybatis se encuentra principalmente en el paquete de tipos, como se muestra a continuación:
Análisis de código fuente de conversión de tipo Mybatis

Por supuesto, no solo hay estas clases en el paquete de tipos, hay otros procesadores de conversión de tipos integrados, como ArrayTypeHandler, etc. También hay tres tipos de clases de registro TypeHandlerRegistry, SimpleTypeRegistry y TypeAliasRegistry, y también se definen algunas anotaciones debajo del paquete.

Tipo de procesador

Interfaz TypeHandler

Todos los convertidores de tipo en Mybatis implementan la interfaz TypeHandler. Solo hay cuatro métodos en esta interfaz, que se dividen en dos categorías. Uno es convertir el tipo JDBC en tipo Java y el otro es convertir el tipo Java en tipo JDBC. De la siguiente manera :


public interface TypeHandler<T> {
  // 通过 PreparedStatement 绑定参数时,参数由 Jdbc 类型转换为 Java 类型
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  // 从 ResultSet 获取数据时,数据由 Java 类型转换为 Jdbc类型
  T getResult(ResultSet rs, String columnName) throws SQLException;

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

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

Clase abstracta TypeReference

TypeReference es una clase abstracta, lo que significa hacer referencia a un tipo genérico:


public abstract class TypeReference<T> {
  // 原始类型Type
  private final Type rawType;
  // 构造,获取原始类型
  protected TypeReference() {
    rawType = getSuperclassTypeParameter(getClass());
  }

  // 获取原始类
  Type getSuperclassTypeParameter(Class<?> clazz) {
    // 获得带有泛型的父类
    Type genericSuperclass = clazz.getGenericSuperclass();
    if (genericSuperclass instanceof Class) {
      if (TypeReference.class != genericSuperclass) {
        return getSuperclassTypeParameter(clazz.getSuperclass());
      }
     // 抛异常
    }
    // 获取到泛型中的原始类型
    // ParameterizedType是一个记录类型泛型的接口
    // getActualTypeArguments():回泛型类型数组
    Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
    if (rawType instanceof ParameterizedType) {
      rawType = ((ParameterizedType) rawType).getRawType();
    }
    // 返回原始类型
    return rawType;
  }
 // 返回原始类型
  public final Type getRawType() {
    return rawType;
  }
}

BaseTypeHandler

En Mybatis, se proporciona la única implementación de la interfaz TypeHandler, BaseTypeHandler, principalmente para que los usuarios puedan personalizar la interfaz TypeHandler.

En la clase abstracta BaseTypeHandler, se implementan los métodos setParameter () y getResult () de TypeHandler. Dentro de estos dos métodos, el procesamiento de datos no vacíos se implementa mediante subclases específicas; el código fuente es el siguiente:


public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
  // 表示一个配置文件类
  protected Configuration configuration;

  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }
  // 为 SQL 设置参数
  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      // 类型为空,则抛异常
      if (jdbcType == null) {
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
      }
      try {
        // 如果参数为空,则设置为 null
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException(e);
      }
    } else {
      try {
        // 如果参数不为空,则由子类实现,该方法是一个抽象方法
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        throw new TypeException(e);
      }
    }
  }
  // 从结果集中根据列名获取数据
  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    T result;
    try {
      // 获取结果,由子类实现
      result = getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException( e);
    }
    // 如果为空,则返回 null
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  // 从结果集中根据列索引获取数据
  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    //   同上
  }
  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    //   同上
  }  
  // 为 SQL 设置非空的参数,由各个子类自己实现
  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;
}

StringTypeHandler

En la clase abstracta BaseTypeHandler anterior, configure los parámetros para SQL y obtenga datos en el conjunto de resultados, y se implementarán las subclases correspondientes. Tiene alrededor de 31 clases de implementación. Ahora tome StringTypeHandler como ejemplo, veamos cómo se implementa ;


public class StringTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setString(i, parameter);
  }

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

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getString(columnIndex);
  }

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

Como puede ver, el procesador de tipos del tipo String llamará al método setString de PreparedStatement para vincular los parámetros, y llamará al getString del ResultSet para obtener el resultado, probablemente el caso de otras clases de implementación.

Tipo de registrador TypeHandlerRegistry

Cuando se inicializa Mybatis, creará objetos para todos los controladores de tipo conocidos TypeHandler y los registrará en TypeHandlerRegistry. TypeHandlerRegistry administrará estos objetos. A continuación, veamos el código fuente de TypeHandlerRegistry:

Varias colecciones de mapas se definen en TypeHandlerRegistry para almacenar los objetos TypeHandler correspondientes, como se muestra a continuación:


// Jdbc 类型和类型处理器 TypeHandler 的对应关系
// 该集合主要用于从结果集中读取数据时,从 Jdbc 类型转换为 Java 类型
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);

// Java类型和Jdbc类型的对应关系,当Java类型向指定的Jdbc类型转换时,需要使用的 TypeHandler 对象
// 一种Java类型可以对应多种Jdbc 类型,如 String 对应 char 和 varchar
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();

// 为知类型,当找不到对应类型时,使用该类型,也就是 ObjectTypeHandler
private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);

// 存放全部的 TypeHandler 类型以及该类型相应的 TypeHandler 对象
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

// 空类型
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = new HashMap<JdbcType, TypeHandler<?>>();

Registrar TypeHandler

Al crear el objeto, se registran estos tipos de procesadores:


  public TypeHandlerRegistry() {
    // 多种Java类型可以对应一种类型处理器
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    // ......... 注册其他类型........
    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());

    // 一种 Java 类型可以对应多种处理器
    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    // ......... 注册其他类型........
}

A continuación, observe cómo se registran estos manejadores de tipo, es decir, almacene los manejadores de tipo correspondientes en los varios mapas definidos anteriormente.En TypeHandlerRegistry, se definen 12 métodos register () sobrecargados para el registro, ver más abajo Se implementan los siguientes métodos principales:


 // 注册 Jdbc 类型和相应的处理器
  public void register(JdbcType jdbcType, TypeHandler<?> handler) {
    JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
  }

  // 注册 Java 类型和相应的处理器
  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);
    }
  }
  // 根据 Java 类型 Jdbc 类型和类型处理器进行相应的注册
  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      // 根据 Java 类型获取对应的 Jdbc 类型
      Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
      // 如果为空,则新建一个
      if (map == null) {
        map = new HashMap<JdbcType, TypeHandler<?>>();
        TYPE_HANDLER_MAP.put(javaType, map);
      }
      // 注册
      map.put(jdbcType, handler);
    }
    // 同时注册类型处理器类和对象的对应关系
    ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
  }

Después de registrar estos objetos de controlador de tipo, ¿cómo encontrar los controladores de tipo correspondientes? TypeHandlerRegistry también proporciona los métodos correspondientes para la búsqueda. Proporciona 6 métodos getTypeHandler sobrecargados para encontrar los correspondientes basados ​​en el tipo Java y el tipo Jdbc. Objeto TypeHandler:


  // 查找或初始化 Java 类型对应的 TypeHandler 集合
  private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    // 根据 Java 类型查找对应的 TypeHandler 集合
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
      // 根据 Jdbc 类型查找 TypeHandler  对象
      handler = jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        handler = jdbcHandlerMap.get(null);
      }
      if (handler == null) {
        // 如果 jdbcHandlerMap 只注册了一个 TypeHandler 对象,则使用此 TypeHandler 对象
        handler = pickSoleHandler(jdbcHandlerMap);
      }
    }
    return (TypeHandler<T>) handler;
  }

  // 根据 Java 类型查找对应的 TypeHandler 集合
  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
    // 去 TYPE_HANDLER_MAP 进行查找
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
    // 检测是否为空集合
    if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
      return null;
    }
    // 初始化指定 Java 类型的 TypeHandler 集合
    if (jdbcHandlerMap == null && type instanceof Class) {
      Class<?> clazz = (Class<?>) type;
      // 查找父类对应的 TypeHandler 集合,并作为初始集合
      jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
      if (jdbcHandlerMap != null) {
        TYPE_HANDLER_MAP.put(type, jdbcHandlerMap);
      } else if (clazz.isEnum()) {
        // 枚举类的处理,进行注册
        register(clazz, new EnumTypeHandler(clazz));
        return TYPE_HANDLER_MAP.get(clazz);
      }
    }
    if (jdbcHandlerMap == null) {
      TYPE_HANDLER_MAP.put(type, NULL_TYPE_HANDLER_MAP);
    }
    return jdbcHandlerMap;
  }

Lo anterior es un proceso de implementación del tipo registrador TypeHandlerRegistry

TipoAliasRegistro

Al escribir Mapper SQL, puede utilizar alias, por ejemplo,


<select id="findByName" resultType="map" parameterType="int">

Luego, al analizar SQL, puede obtener el tipo correspondiente, como Java.util.Map, Java.lang.Integer, etc. Mybatis usa TypeAliasRegistry para completar las funciones de registro y administración de alias.

Este método es relativamente simple: proporciona 5 métodos registerAlias ​​sobrecargados para registrar alias, proporciona un método resolveAlias ​​para resolver alias y finalmente registra alias en el constructor:


  private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>();

  // 注册别名
  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // 将名称转换为小写
    String key = alias.toLowerCase(Locale.ENGLISH);
    // 判断名称是否存在,如果别名已存在,且对应的类型不一致,则抛异常
    if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
    }
    // 注册,别名和类型的对应关系
    TYPE_ALIASES.put(key, value);
  }

  public void registerAlias(Class<?> type) {
    String alias = type.getSimpleName();
    // 处理 @Alias 注解的情况
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    } 
    registerAlias(alias, type);
  }

Resolver alias:


 // 解析别名
  public <T> Class<T> resolveAlias(String string) {
    try {
      if (string == null) {
        return null;
      }
      // 别名转换为小写,因为在注册的时候,转换过
      String key = string.toLowerCase(Locale.ENGLISH);
      Class<T> value;
      // 如果该别名已经注册,则获取对应的类型
      if (TYPE_ALIASES.containsKey(key)) {
        value = (Class<T>) TYPE_ALIASES.get(key);
      } else {
        // 尝试使用反射来获取类型
        value = (Class<T>) Resources.classForName(string);
      }
      // 返回对应的类型
      return value;
    } catch (ClassNotFoundException e) {
      throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
    }
  }

Registre el alias, el siguiente paso es registrar el alias, registrarse a través del método de construcción


  public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

Lo anterior es una lógica de código principal de Mybatis para la conversión de tipos, que es bastante fácil de entender.

Supongo que te gusta

Origin blog.51cto.com/15077536/2608567
Recomendado
Clasificación