美文网首页MyBatis源码剖析
[MyBatis源码分析 - 类型模块]

[MyBatis源码分析 - 类型模块]

作者: 小胡_鸭 | 来源:发表于2020-11-02 00:06 被阅读0次

一、背景介绍

  作为一个基于 JDBC 的 ORM 框架,MyBatis 在执行 SQL 绑定参数时,需要将数据从 Java 类型转换为 Jdbc 类型;在映射结果集时,需要将数据从 Jdbc 类型转换为 Java 类型,所以需要有一个公共的模块来处理这种类型的转换,这就是类型转换器模块。


  MyBatis 的 org.apache.ibatis.type 包中,实现了各种类型转换器,覆盖了 Java 常用的数据类型和 Jdbc 类型各种转换的场景,如下所示:

  组件相关介绍系列文章如下:

二、TypeHandler

  TypeHandler 为所有类型转换器都要实现的接口,并实现了各种类型映射处理的类型处理器,源码解析详见:[MyBatis源码分析 - 类型模块 - 组件一] TypeHandler

三、TypeReference

  TypeReference 是 BaseTypeHandler 的基类,提供了解析处理器中泛型的具体类型的功能,源码如下:

public abstract class TypeReference<T> {

  private final Type rawType;

  protected TypeReference() {
    rawType = getSuperclassTypeParameter(getClass());
  }

  // 以 StringTypeHandler 为例
  Type getSuperclassTypeParameter(Class<?> clazz) {
    // 泛型超类的类型为 BaseTypeHandler<String>,这是个 ParameterizedType
    Type genericSuperclass = clazz.getGenericSuperclass();
    // 用户自定义处理器的父类可能不带泛型
    if (genericSuperclass instanceof Class) {
      // try to climb up the hierarchy until meet something useful
      // 排除 TypeReference 类
      if (TypeReference.class != genericSuperclass) {
        // 递归调用,直到找到带泛型的父类,才能解析出其中的泛型的具体类型
        return getSuperclassTypeParameter(clazz.getSuperclass());
      }

      throw new TypeException("'" + getClass() + "' extends TypeReference but misses the type parameter. "
        + "Remove the extension or add a type parameter to it.");
    }
    // BaseTypeHandler<String> 得到的就是 String
    // 注意这里的 rawType 跟 ParameterizedType.getRawType() 不是一码事,随便命名一样
    // 这里的 raw 指 BaseTypeHandler<String> 中的 String
    // 而 ParameterizedType.getRawType() 指 BaseTypeHandler<String> 中的 BaseTypeHandler
    Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
    // TODO remove this when Reflector is fixed to return Types
    // 如果泛型类型是一个嵌套的 ParameterizedType,直接获得该类型的原始类型即可
    if (rawType instanceof ParameterizedType) {
      rawType = ((ParameterizedType) rawType).getRawType();
    }

    return rawType;
  }

  public final Type getRawType() {
    return rawType;
  }

  @Override
  public String toString() {
    return rawType.toString();
  }

}

四、相关注解

  默认情况下,如果是常规的类型,MyBatis 在做参数绑定和结果映射时会自动选择合适的类型处理器做转换,但有时候有特别的需求,用户可能会自定义处理器进行处理,需要在 mybatis-config.xml 配置自定义的处理器,有两种方式如下:

    <!-- 类型处理器 -->
    <typeHandlers>
        <!-- 配置自定义的typeHandler -->
        <typeHandler jdbcType="VARCHAR" javaType="string" handler="ssm.chapter4.typeHandler.MyTypeHandler"/>
        
        <!-- 使用扫描方式配置typeHandler -->        
        <package name="ssm.chapter4.typeHandler"/>
    </typeHandlers>

  第一种是指定处理器所要处理的 jdbcType 和 javaType,第二种是用包扫描的方式批量配置包范围内的所有自定义处理器。

  在用法上,一般用在结果映射上,同样有两种用法,如下:

    <resultMap type="role" id="roleMapper">
        <result property="id" column="id"/>
        <!-- 1、使用 jdbcType 和 javaType 指定 -->
        <result property="roleName" column="role_name" jdbcType="VARCHAR" javaType="string"/>
        <!-- 2、使用 typeHandler 属性指定 -->
        <result property="note" column="note" typeHandler="ssm.chapter4.typeHandler.MyTypeHandler"/>
    </resultMap>

  第一种是使用 jdbcType 和 javaType 指定;第二种是使用 typeHandler 属性指定。对于通过包扫描注册的处理器,无法直接使用第一种方式使用,还要加点配置,铺垫了这么多,没错就是这里要介绍的注解 @MappedJdbcTypes@MappedTypes

1、@MappedJdbcTypes

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)   // 注解使用目标是类
public @interface MappedJdbcTypes {
  JdbcType[] value();       // 匹配的 JdbcType 类型的数组
  boolean includeNullJdbcType() default false;  // 是否包含 java.sql.JDBCType#NULL
}

2、@MappedTypes

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MappedTypes {
  Class<?>[] value();   // 匹配的 JavaType 类型的数组
}

  注解的用法,就是给自定义的处理器的类加上标注要处理的 JdbcType 和 JavaType,跟注册处理器的第一种方式类似,示例如下:

@MappedTypes(SexEnum.class)
@MappedJdbcTypes(JdbcType.TINYINT)
public class SexEnumTypeHandler implements TypeHandler<SexEnum> {

    @Override
    public void setParameter(PreparedStatement ps, int i, SexEnum parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getId());
    }

    @Override
    public SexEnum getResult(ResultSet rs, String columnName) throws SQLException {
        int id = rs.getInt(columnName);
        return SexEnum.getSexById(id);
    }

    @Override
    public SexEnum getResult(ResultSet rs, int columnIndex) throws SQLException {
        int id = rs.getInt(columnIndex);
        return SexEnum.getSexById(id);
    }

    @Override
    public SexEnum getResult(CallableStatement cs, int columnIndex) throws SQLException {
        int id = cs.getInt(columnIndex);
        return SexEnum.getSexById(id);
    }   
}

五、JdbcType

  JdbcType 是个枚举类型,对 java.sql.Types 做了一下封装,并为根据类型码的数值查找对应的枚举提供了缓存,源码如下:

public enum JdbcType {
  /*
   * This is added to enable basic support for the
   * ARRAY data type - but a custom type handler is still required
   */
  ARRAY(Types.ARRAY),
  BIT(Types.BIT),
  TINYINT(Types.TINYINT),
  SMALLINT(Types.SMALLINT),
  INTEGER(Types.INTEGER),
  BIGINT(Types.BIGINT),
  FLOAT(Types.FLOAT),
  REAL(Types.REAL),
  DOUBLE(Types.DOUBLE),
  NUMERIC(Types.NUMERIC),
  DECIMAL(Types.DECIMAL),
  CHAR(Types.CHAR),
  VARCHAR(Types.VARCHAR),
  LONGVARCHAR(Types.LONGVARCHAR),
  DATE(Types.DATE),
  TIME(Types.TIME),
  TIMESTAMP(Types.TIMESTAMP),
  BINARY(Types.BINARY),
  VARBINARY(Types.VARBINARY),
  LONGVARBINARY(Types.LONGVARBINARY),
  NULL(Types.NULL),
  OTHER(Types.OTHER),
  BLOB(Types.BLOB),
  CLOB(Types.CLOB),
  BOOLEAN(Types.BOOLEAN),
  CURSOR(-10), // Oracle
  UNDEFINED(Integer.MIN_VALUE + 1000),
  NVARCHAR(Types.NVARCHAR), // JDK6
  NCHAR(Types.NCHAR), // JDK6
  NCLOB(Types.NCLOB), // JDK6
  STRUCT(Types.STRUCT),
  JAVA_OBJECT(Types.JAVA_OBJECT),
  DISTINCT(Types.DISTINCT),
  REF(Types.REF),
  DATALINK(Types.DATALINK),
  ROWID(Types.ROWID), // JDK6
  LONGNVARCHAR(Types.LONGNVARCHAR), // JDK6
  SQLXML(Types.SQLXML), // JDK6
  DATETIMEOFFSET(-155); // SQL Server 2008

  public final int TYPE_CODE;
  // 定义一个Map来保存类型值与枚举之间的映射关系
  private static Map<Integer,JdbcType> codeLookup = new HashMap<Integer,JdbcType>();

  // 静态初始化遍历枚举类型数组填充 codeLookup
  static {
    for (JdbcType type : JdbcType.values()) {
      codeLookup.put(type.TYPE_CODE, type);
    }
  }

  JdbcType(int code) {
    this.TYPE_CODE = code;
  }

  // 对外提供的将类型值转化为枚举的方法
  public static JdbcType forCode(int code)  {
    return codeLookup.get(code);
  }
}

六、TypeHandlerRegistry

  TypeHandlerRegistry 类型处理器注册表注册了类型转换时需要用到的各种处理器以及与Java类型和Jdbc类型的映射关系,源码解析详见:[MyBatis源码分析 - 类型模块 - 组件二] TypeHandlerRegistry

七、TypeAliasRegistry

  TypeAliasRegistry 类型别名注册表提供了别名注册机制,使得在使用 mapper 映射文件中指定类时,使用别名代替繁琐的权限定名,较少出错概率,源码解析详见:[MyBatis源码分析 - 类型模块 - 组件三] TypeAliasRegistry

相关文章

网友评论

    本文标题:[MyBatis源码分析 - 类型模块]

    本文链接:https://www.haomeiwen.com/subject/itaxvktx.html