MyBatis 在预处理语句(PreparedStatement)中设置一个参数或者从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。Mybatis默认为我们实现了许多TypeHandler, 当我们没有配置指定TypeHandler时,Mybatis会根据参数或者返回结果的不同,默认为我们选择合适的TypeHandler处理。
在MyBatis中,StatementHandler负责对需要执行的SQL语句进行预编译处理,主要完成以下两项工作:
1.调用参数处理器(ParameterHandler)来设置需要传入SQL的参数;
2.调用结果集处理器(ResultSetHandler)来处理查询到的结果数据;
而不管要完成其中的哪一项工作都需要使用类型处理器(TypeHandler)来进行数据类型处理,无论是ParameterHandler为预处理语句(PreparedStatement)设置一个参数,还是ResultSetHandler将结果集中的一条记录转换为合适的Java类型。在整个过程中,TypeHandler负责完成数据库类型与JavaBean类型的转换工作。
<typeHandlers>
<typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
<typeHandler handler="" javaType=""/>
<typeHandler jdbcType="" handler=""/>
<typeHandler javaType="" jdbcType="" handler=""/>
<package name="org.mybatis.example"/>
typeHandlers>
Map<Class<?>, TypeHandler< ? >> ALL_TYPE_HANDLERS_MAP:所有 TypeHandler 的类名和类的映射关系
Map<Type, Map<JdbcType, TypeHandler < ? >>> TYPE_HANDLER_MAP:所有 Type 管理的映射关系
Map<JdbcType, TypeHandler< ? >> JDBC_TYPE_HANDLER_MAP:
配置解析
private void typeHandlerElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//如果是 package,找到 name 对应的包名。扫描包下所有非接口、非匿名、非抽象类。
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
//当 javaTypeName 为类的全路径或者是已经注册的类的别名的时候不为空
Class<?> javaTypeClass = resolveClass(javaTypeName);
//jdbcTypeName 必须为 JdbcType 支持的类型
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
//当 handlerTypeName 为类的全路径或者是已经注册的类的别名的时候不为空
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
//
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
//扫描 typeHandlerClass 的 MapperType 注解
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
查找某个类的 TypeHandler
在实际配置文件中,会根据 jdbcType 和 javaType 找对应的 TypeHandler,查询过程如下:
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
if (ParamMap.class.equals(type)) {
return null;
}
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null) {
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null) {
handler = jdbcHandlerMap.get(null);
}
if (handler == null) {
// #591
handler = pickSoleHandler(jdbcHandlerMap);
}
}
// type drives generics here
return (TypeHandler<T>) handler;
}
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
return null;
}
if (jdbcHandlerMap == null && type instanceof Class) {
Class<?> clazz = (Class<?>) type;
//Enum 类型特殊处理
if (clazz.isEnum()) {
jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(clazz, clazz);
if (jdbcHandlerMap == null) {
register(clazz, getInstance(clazz, defaultEnumTypeHandler));
return TYPE_HANDLER_MAP.get(clazz);
}
} else { //非 Enum 类型,会从父类一直查找。
jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
}
}
TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
return jdbcHandlerMap;
}
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClazz) {
for (Class<?> iface : clazz.getInterfaces()) {
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(iface);
if (jdbcHandlerMap == null) {
//深度优先遍历查找
jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(iface, enumClazz);
}
if (jdbcHandlerMap != null) {
// Found a type handler regsiterd to a super interface
HashMap<JdbcType, TypeHandler<?>> newMap = new HashMap<>();
for (Entry<JdbcType, TypeHandler<?>> entry : jdbcHandlerMap.entrySet()) {
// Create a type handler instance with enum type as a constructor arg
newMap.put(entry.getKey(), getInstance(enumClazz, entry.getValue().getClass()));
}
return newMap;
}
}
return null;
}
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
Class<?> superclass = clazz.getSuperclass();
if (superclass == null || Object.class.equals(superclass)) {
return null;
}
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(superclass);
if (jdbcHandlerMap != null) {
return jdbcHandlerMap;
} else {
//递归查找
return getJdbcHandlerMapForSuperclass(superclass);
}
}
private TypeHandler<?> pickSoleHandler(Map<JdbcType, TypeHandler<?>> jdbcHandlerMap) {
TypeHandler<?> soleHandler = null;
for (TypeHandler<?> handler : jdbcHandlerMap.values()) {
if (soleHandler == null) {
soleHandler = handler;
} else if (!handler.getClass().equals(soleHandler.getClass())) {
// More than one type handlers registered.
return null;
}
}
return soleHandler;
}
备注:
(1)创建一个继承自 BaseType 的自定义typeHandler的类;
(2)在Mybatis配置文件或注解中中配置类型处理器
(3)在引射器的XML配置中标识需要用自定义typeHandler处理的参数或者结果。
在自定义 TypeHandler 的时候,简单得继承 BaseTypeHandler 即可。
TypeHandler
public interface TypeHandler<T> {
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;
}
所有自定义 TypeHandler 都必须实现接口 TypeHandler
BaseTypeHandler
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
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;
}
mybatis 默认的 BaseTypeHandler 统一了异常处理,实现者自定义 TypHanlder 应该继承BaseTypeHandler 而不是TypeHandler
StringTypeHandler
@MappedJdbcType(String.class)
public class StringTypeHandler extends BaseTypeHandler<String> {
//预处理的的时候,将 Java 类型 parameter 转换为 JDBC 类型
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);
}
//将 JDBC 类型转换为 Java 类型
@Override
public String getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getString(columnName);
}
//将 JDBC 类型转换为 Java 类型
@Override
public String getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getString(columnIndex);
}
//将 JDBC 类型转换为 Java 类型
@Override
public String getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getString(columnIndex);
}
}
这里 Jdbc 类型为 VARCHAR,Java 类型为 String。
问题:如何自定义 TypeHandler 将 Enum 类转换为 int 类型。 答案参考 EnumOrdinalTypeHandler
<resultMap type="cn.don.pojo.Student" id="queryStudentByNumResult">
<result property="name" column="name" typeHandler="StringTypeHandler"/>
resultMap>
简单的例子:https://blog.csdn.net/u012525096/article/details/82459455
这篇文章的例子不错:https://elim.iteye.com/blog/1847854
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());
register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
register(Float.class, new FloatTypeHandler());
register(float.class, new FloatTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler());
register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler());
register(Reader.class, new ClobReaderTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler());
register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
register(JdbcType.ARRAY, new ArrayTypeHandler());
register(BigInteger.class, new BigIntegerTypeHandler());
register(JdbcType.BIGINT, new LongTypeHandler());
register(BigDecimal.class, new BigDecimalTypeHandler());
register(JdbcType.REAL, new BigDecimalTypeHandler());
register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
register(InputStream.class, new BlobInputStreamTypeHandler());
register(Byte[].class, new ByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
register(byte[].class, new ByteArrayTypeHandler());
register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.BLOB, new BlobTypeHandler());
register(Object.class, UNKNOWN_TYPE_HANDLER);
register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(Date.class, new DateTypeHandler());
register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
register(JdbcType.TIMESTAMP, new DateTypeHandler());
register(JdbcType.DATE, new DateOnlyTypeHandler());
register(JdbcType.TIME, new TimeOnlyTypeHandler());
register(java.sql.Date.class, new SqlDateTypeHandler());
register(java.sql.Time.class, new SqlTimeTypeHandler());
register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());
register(Instant.class, InstantTypeHandler.class);
register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
register(LocalDate.class, LocalDateTypeHandler.class);
register(LocalTime.class, LocalTimeTypeHandler.class);
register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
register(OffsetTime.class, OffsetTimeTypeHandler.class);
register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
register(Month.class, MonthTypeHandler.class);
register(Year.class, YearTypeHandler.class);
register(YearMonth.class, YearMonthTypeHandler.class);
register(JapaneseDate.class, JapaneseDateTypeHandler.class);
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
已经实现但是默认没有注册的handler
ArrayTypeHandler
EnumOrdinalTypeHandler
EnumTypeHandler
ObjectTypeHandler