Mybatis type conversion source code analysis

Mybatis type conversion source code analysis

TSMYK Java technology programming

This article will introduce from the following aspects

  1. Preface
  2. Type processor
  3. Type register
  4. Alias ​​registrar

    Preface

The data type provided by JDBC and the data type of Java are not completely corresponding. When Mybatis is parsing SQL and using PreparedStatement to set parameters for SQL, it needs to be converted from the Java type to the JDBC type. When obtaining the result from the ResultSet , The JDBC type needs to be converted to Java type; Mybatis's type conversion module is used to convert these two data types; for example, when writing Mapper files, you can write the following:


    <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>

You can specify the type corresponding to Java and the database, you can also specify a custom type processor, etc. When Mybatis parses SQL, it will perform the corresponding conversion through the type conversion processor

Source code analysis

The code related to Mybatis type conversion is mainly under the type package, as shown below:
Mybatis type conversion source code analysis

Of course, there are not only these classes under the type package, there are other built-in type conversion processors, such as ArrayTypeHandler, etc. There are also three types of registration classes TypeHandlerRegistry, SimpleTypeRegistry and TypeAliasRegistry, and some annotations are also defined under the package.

Type processor

TypeHandler interface

All type converters in Mybatis implement the TypeHandler interface. There are only four methods under this interface, which are divided into two categories. One is to convert JDBC type to Java type, and the other is to convert Java type to JDBC type. as follows:


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;
}

TypeReference abstract class

TypeReference is an abstract class, which means referencing a generic type:


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

In Mybatis, the only implementation of the TypeHandler interface, BaseTypeHandler, is provided, mainly for the convenience of users to customize the TypeHandler interface.

In the BaseTypeHandler abstract class, the setParameter() and getResult() methods of TypeHandler are implemented. Within these two methods, the processing of non-empty data is implemented by specific subclasses; the source code is as follows:


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

In the above BaseTypeHandler abstract class, set the parameters for SQL and obtain data in the result set, and the corresponding subclasses are implemented. It has about 31 implementation classes. Now take StringTypeHandler as an example, let’s see how it is implemented ;


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);
  }
}

As you can see, the type processor of the String type will call the setString method of PreparedStatement to bind the parameters, and call the getString of the ResultSet to get the result. This is probably the case for other implementation classes.

Type registrar TypeHandlerRegistry

When Mybatis is initialized, it will create objects for all known type handlers TypeHandler and register them in TypeHandlerRegistry. TypeHandlerRegistry will manage these objects. Next, let’s look at the source code of TypeHandlerRegistry:

Several Map collections are defined in TypeHandlerRegistry to store corresponding TypeHandler objects, as shown below:


// 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<?>>();

Register TypeHandler

When creating the object, these types of processors are registered:


  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());
    // ......... 注册其他类型........
}

Next, look at how these type handlers are registered, that is, store the corresponding type handlers in the several Maps defined above. In TypeHandlerRegistry, 12 overloaded register() methods are defined for registration, see below The following main methods are implemented:


 // 注册 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);
  }

After registering these type handler objects, how to find the corresponding type handlers? TypeHandlerRegistry also provides corresponding methods for searching. It provides 6 overloaded getTypeHandler methods to find the corresponding ones based on the Java type and Jdbc type. TypeHandler object:


  // 查找或初始化 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;
  }

The above is an implementation process of the type registrar TypeHandlerRegistry

TypeAliasRegistry

When writing Mapper SQL, you can use aliases, for example,


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

Then when parsing SQL, you can get the corresponding type, such as Java.util.Map, Java.lang.Integer, etc. Mybatis uses TypeAliasRegistry to complete the alias registration and management functions.

This method is relatively simple. It provides 5 overloaded registerAlias ​​methods to register aliases, provides a method resolveAlias ​​to resolve aliases, and finally registers aliases in the 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);
  }

Resolve 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);
    }
  }

Register the alias, the next step is to register the alias, register through the construction method


  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);
  }

The above is a main code logic of Mybatis for type conversion, which is quite easy to understand.

Guess you like

Origin blog.51cto.com/15077536/2608567