MyBatis 类型处理器 TypeHandler

今天开发 APP 后台程序的时候,升级了一下数据类型,把很多原本 String 类型的变量改成了集合类型。这里涉及到 MyBatis 中的自定义类型处理,记录在这里以作备忘。本文的讨论基于 MyBatis 3.4.0 版本。

类型处理器 TypeHandler

MyBatis 中的 TypeHandler 类型处理器用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值。MyBatis 内置了大部分基本类型的类型处理器,所以对于基本类型可以直接处理,当我们需要处理其他类型的时候就需要自定义类型处理器。

MyBatis 内置的 TypeHandler

在 MyBatis 的 TypeHandlerRegistry 类型中,可以看到内置的类型处理器。内置处理器比较多,这里整理常见的一些。

BooleanTypeHandler:用于 java 类型 boolean,jdbc 类型 bit、boolean

ByteTypeHandler:用于 java 类型 byte,jdbc 类型 TINYINT

ShortTypeHandler:用于 java 类型 short,jdbc 类型 SMALLINT

IntegerTypeHandler:用于 INTEGER 类型

LongTypeHandler:用于 long 类型 

FloatTypeHandler:用于 FLOAT 类型

DoubleTypeHandler:用于 double 类型

StringTypeHandler:用于 java 类型 string,jdbc 类型 CHAR、VARCHAR

ArrayTypeHandler:用于 jdbc 类型 ARRAY

BigDecimalTypeHandler:用于 java 类型 BigDecimal,jdbc 类型 REAL、DECIMAL、NUMERIC

DateTypeHandler:用于 java 类型 Date,jdbc 类型 TIMESTAMP

DateOnlyTypeHandler:用于 java 类型 Date,jdbc 类型 DATE

TimeOnlyTypeHandler:用于 java 类型 Date,jdbc 类型 TIME

对于常见的 Enum 类型,内置了 EnumTypeHandler 进行 Enum 名称的转换和 EnumOrdinalTypeHandler 进行 Enum 序数的转换。这两个类型处理器没有在 TypeHandlerRegistry 中注册,如果需要使用必须手动配置。

自定义 TypeHandler

自定义类型处理器是通过实现 org.apache.ibatis.type.TypeHandler 接口实现的。这个接口定义了类型处理器的基本功能,接口定义如下所示。

public interface TypeHandler {
  void setParameter(PreparedStatement ps, int i, T parameter
, JdbcType jdbcType) throws SQLException; T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}

其中 setParameter 方法用于把 java 对象设置到 PreparedStatement 的参数中,getResult 方法用于从 ResultSet 或 CallableStatement 中取出数据转换为 java 对象。

实际开发中,我们可以继承 org.apache.ibatis.type.BaseTypeHandler 类型来实现自定义类型处理器。这个类型是抽象类型,实现了 TypeHandler 的方法进行通用流程的封装,做了异常处理,并定义了几个类似的抽象方法,如下所示。继承 BaseTypeHandler 类型可以极大地降低开发难度。

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;

类型转换器还可以通过注解配置 java 类型和 jdbc 类型: 

@MappedTypes:注解配置 java 类型 

@MappedJdbcTypes:注解配置 jdbc 类型

源码分析

MyBatis 启动后首先把 typeHandler 注册进去。首先尝试读取 MappedTypes 注解,如果有这个注解定义了 java 类型,则把这个类型处理器注册到相应的 java 类型的处理器中。如果没有使用注解,但是继承了 TypeReference 类型,比如前面提到的 BaseTypeHandler,则通过 TypeReference 的接口获取原始类型注册到相应的 java 类型的处理器中。如果实在是获取不到 java 类型,则按照无类型处理。

public  void register(TypeHandler typeHandler) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandler.getClass()
       .getAnnotation(MappedTypes.class); if (mappedTypes != null) { for (Class handledType : mappedTypes.value()) { register(handledType, typeHandler); mappedTypeFound = true; } } // @since 3.1.0 - try to auto-discover the mapped type if (!mappedTypeFound && typeHandler instanceof TypeReference) { try { TypeReference typeReference = (TypeReference) typeHandler; register(typeReference.getRawType(), typeHandler); mappedTypeFound = true; } catch (Throwable t) { // maybe users define the TypeReference with a different type and
are not assignable, so just ignore it } } if (!mappedTypeFound) { register((Class) null, typeHandler); } }

MyBatis 在预处理语句设置参数时调用 TypeHandler 进行 java 对象到 jdbc 的 PreparedStatement 参数值的转换。以下为其中一个调用片段。

TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull();
}
try { typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) { throw new TypeException("Could not set parameters for mapping: "
   + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) { throw new TypeException("Could not set parameters for mapping: "
   + parameterMapping + ". Cause: " + e, e);
}

MyBatis 查询数据库完成后,调用 TypeHandler 的方法读取数据转换成 java 对象。以下为其中一个调用片段。

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, 
 ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { if (propertyMapping.getNestedQueryId() != null) { return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping,
           lazyLoader, columnPrefix); } else if (propertyMapping.getResultSet() != null) { addPendingChildRelation(rs, metaResultObject, propertyMapping);
       // TODO is that OK? return DEFERED; } else { final TypeHandler typeHandler = propertyMapping.getTypeHandler(); final String column = prependPrefix(propertyMapping.getColumn(),
           columnPrefix); return typeHandler.getResult(rs, column); }
}

关于 TypeHandler,您有什么理解和不解呢,欢迎留言共同讨论。

分享学习笔记和技术总结,内容涉及 Java 技术、软件架构、前沿技术、开源框架、数据结构与算法、编程感悟等多个领域,欢迎关注。本文首发于微信公众号“后端开发那点事儿” 。

你可能感兴趣的:(MyBatis 类型处理器 TypeHandler)