一、背景介绍
作为一个基于 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。
网友评论