首先mybatis的自定义TypeHandler可以通过构造方法传入一个Class Type.这个Class Type 对于枚举和普通类在某些场景上会有区别
区别
1.枚举只要如下所示注册一个父接口BaseEnum的EnumTypeHandler,之后在绑定实体类枚举字段(实现了BaseEnum的字段)时就可以在EnumTypeHandler拿到实体类字段ClassType.
//BaseEnum是TypeEnum,StatusEnum,CategoryEnum都有实现的接口(Interface)
@MappedTypes(value = BaseEnum.class)
public class EnumTypeHandler<T extends BaseEnum> extends BaseTypeHandler<BaseEnum> {
//因为MappedTypes只需要配置BaseEnum
//当实体类字段是TypeEnum则Type是TypeEnum.class
//实体类字段是StatusEnum则Type是StatusEnum.class
//实体类字段是CategoryEnum则Type是CategoryEnum.class
private Class<T> type;
public EnumTypeHandler(Class<T> type) {
this.type = type;
}
..............................
}
2.对于普通类则需要如下通过MappedTypes为Animal及其所有子类都注册一个AnimalTypeHandler,才可以在绑定实体类字段的时候在AnimalTypeHandler拿到实体类字段对应的ClassType(当然也可以自己写代码scan包主动注册所有Animal子类的AnimalTypeHandler).
//Animal是Dog,Cat的父类,
@MappedTypes(value = {Animal.class,Dog.class})
public class AnimalTypeHandler<T extends Animal> extends BaseTypeHandler<Animal> {
//因为MappedTypes只配置了Animal和Dog
//所以当实体类字段是Animal则Type是Animal.class
//实体类字段是Dog则Type是Dog.class
//实体类字段是Cat则Type是Animal.class(因为MappedTypes没有Cat.class)
private Class<T> type;
public EnumTypeHandler(Class<T> type) {
this.type = type;
}
..............................
}
为什么
1.对于枚举:如果当前实体的枚举字段Class没有对应的TypeHandler,mybaits会主动寻找其Parent Interface的TypeHandler,并用这个TypeHandler重新注册一个新的TypeHander包含当前实体枚举字典的Class作为当前实体枚举字段的TypeHandler.
即在mybatis中自己只需要注册一个Parent Interface的TypeHandler,mybatis会主动为实现了Parent Interface的枚举都创建一个TypeHandler包含枚举的ClassType.
public final class TypeHandlerRegistry {
.................................
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
//先从缓存中获取当前枚举的Class对应的TypeHandler,存在则直接返回
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
return null;
}
if (jdbcHandlerMap == null && type instanceof Class) {
//如果缓存在不存在对应的TypeHandler执行的逻辑.
Class<?> clazz = (Class<?>) type;
if (Enum.class.isAssignableFrom(clazz)) {
Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;
//如果当前是枚举则从调用获getJdbcHandlerMapForEnumInterfaces取枚举TypeHandler方法
jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass);
if (jdbcHandlerMap == null) {
//如果获取不到TypeHander,则创建默认的枚举TypeHandler
register(enumClass, getInstance(enumClass, defaultEnumTypeHandler));
return typeHandlerMap.get(enumClass);
}
} else {
jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
}
}
//把拿到的TypeHandler和当初枚举的Class对应放入缓存
typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
return jdbcHandlerMap;
}
/**
* 获取枚举TypeHandler的方法
* 递归获取所有父接口的TypeHandler,一旦有某个接口存在对应的TypeHandler,
* 会用这个TypeHandler用反射创建一个新的是实例,构造函数传入当前枚举的class
*/
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForEnumInterfaces(Class<?> clazz, Class<?> enumClazz) {
//首先先遍历枚举的所有父接口
for (Class<?> iface : clazz.getInterfaces()) {
//获取当前父接口的TypeHandler
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(iface);
if (jdbcHandlerMap == null) {
//如果当前父接口没有TypeHandler,递归调用获取当前接口的父接口的TypeHandler
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()) {
//如果当前父接口存在对应的TypeHandler,
//则调用getInstance用当前TypeHandler创建一个新的TypeHandler并传入当前枚举的Class
//所以每个枚举类都有单独一个对应的TypeHandler,并且TypeHandler里包含当前的枚举的Class
newMap.put(entry.getKey(), getInstance(enumClazz, entry.getValue().getClass()));
}
return newMap;
}
}
return null;
}
@SuppressWarnings("unchecked")
public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
if (javaTypeClass != null) {
try {
Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
//用传入的typeHandlerClass创建一个新的TypeHandler并传入对应的Type进构造函数
return (TypeHandler<T>) c.newInstance(javaTypeClass);
} catch (NoSuchMethodException ignored) {
// ignored
} catch (Exception e) {
throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
}
}
try {
//如果没有有参构造函数则用无参构造函数创建实例
Constructor<?> c = typeHandlerClass.getConstructor();
return (TypeHandler<T>) c.newInstance();
} catch (Exception e) {
throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
}
}
}
2.对于普通Class,如果当前实体字段的Class没有对应的TypeHandler,Mybatis只会递归获取父类的TypeHandler,用父类的TypeHandler作为当前实体字段的TypeHandler,不会为当前实体字段特别创建一个TypeHandler包含对应的Class信息,而当前父类的TypeHandler包含的是父类的Class Type.
即如果自己没有主动注册Parent Class的所有子类的TypeHandler,那么子类用TypeHandler绑定实体字段时只能拿到父类的ClassType信息
public final class TypeHandlerRegistry {
.................................
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
//先从缓存中获取当前枚举的Class对应的TypeHandler,存在则直接返回
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) {
return null;
}
if (jdbcHandlerMap == null && type instanceof Class) {
Class<?> clazz = (Class<?>) type;
if (Enum.class.isAssignableFrom(clazz)) {
Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;
jdbcHandlerMap = getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass);
if (jdbcHandlerMap == null) {
register(enumClass, getInstance(enumClass, defaultEnumTypeHandler));
return typeHandlerMap.get(enumClass);
}
} else {
//如果不是枚举调用getJdbcHandlerMapForSuperclass获取TypeHandler;
jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
}
}
//把拿到的TypeHandler和当初枚举的Class对应放入缓存
typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
return jdbcHandlerMap;
}
/**
* 获取非枚举的TypeHandler
* 就是递归调用获取所有父类的TypeHandler(不包括接口)
*/
private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
//获取父类
Class<?> superclass = clazz.getSuperclass();
if (superclass == null || Object.class.equals(superclass)) {
return null;
}
//获取父类的TypeHandler
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(superclass);
if (jdbcHandlerMap != null) {
//如果当前父类存在TypeHandler则返回
//因为没有用TypeHandler重新创建一个新的TypeHandler传入当前类的Class,
//所以返回的TypeHandler不包含当前类的Class,只包含对应父类的Class
return jdbcHandlerMap;
} else {
//如果父类的没有对应的TypeHandler则继续获取当前父类的父类的TypeHandler返回
return getJdbcHandlerMapForSuperclass(superclass);
}
}
}
改如下源码几行代码就可以用一个针对Animal.class的TypeHandler就可以获取到所有子类实体字段对应的Type(不会影响mybatis源码原来的逻辑)
public class DefaultResultSetHandler implements ResultSetHandler {
...........................................................................................
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
//----------------旧的代码start
//final Object value = mapping.typeHandler.getResult(rsw.getResultSet(),mapping.column);
//----------------旧的代码end
//----------------新的代码start
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column, mapping.propertyType);
//----------------新的代码end
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
final String mapKey = resultMap.getId() + ":" + columnPrefix;
List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
if (autoMapping == null) {
autoMapping = new ArrayList<>();
final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
for (String columnName : unmappedColumnNames) {
String propertyName = columnName;
if (columnPrefix != null && !columnPrefix.isEmpty()) {
// When columnPrefix is specified,
// ignore columns without the prefix.
if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
propertyName = columnName.substring(columnPrefix.length());
} else {
continue;
}
}
final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
if (property != null && metaObject.hasSetter(property)) {
if (resultMap.getMappedProperties().contains(property)) {
continue;
}
final Class<?> propertyType = metaObject.getSetterType(property);
if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
//----------------旧的代码start
//autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
//----------------旧的代码end
//----------------新的代码start
autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive(), propertyType));
//----------------新的代码end
} else {
configuration.getAutoMappingUnknownColumnBehavior()
.doAction(mappedStatement, columnName, property, propertyType);
}
} else {
configuration.getAutoMappingUnknownColumnBehavior()
.doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);
}
}
autoMappingsCache.put(mapKey, autoMapping);
}
return autoMapping;
}
private static class UnMappedColumnAutoMapping {
private final String column;
private final String property;
//----------------新增的代码start
private final Class<?> propertyType;
//----------------新增的代码end
private final TypeHandler<?> typeHandler;
private final boolean primitive;
//----------------旧的代码start
//public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
//----------------旧的代码start
//----------------新的代码start
public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive, Class<?> propertyType) {
//----------------新的代码end
this.column = column;
this.property = property;
this.typeHandler = typeHandler;
this.primitive = primitive;
//----------------新增的代码start
this.propertyType = propertyType;
//----------------新增的代码end
}
}
}
public interface TypeHandler<T> {
..........................................
//----------------新的代码start
/**
* Gets the result.
*
* @param rs the rs
* @param columnName Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
* @param propertyType 实体类字段对应的Class
* @return
* @throws SQLException
*/
default T getResult(ResultSet rs, String columnName, Class<?> propertyType) throws SQLException {
return getResult(rs, columnName);
}
//----------------新的代码end
/*目前提供的代码不包含对getResult(ResultSet rs, int columnIndex, Class<?> propertyType)相关源码的修改,可以自行模仿上面修改,大概也是3-4个地方简单改下
default T getResult(ResultSet rs, int columnIndex, Class<?> propertyType) throws SQLException {
return getResult(rs, columnIndex);
}
*/
}
经过以上修改实现TypeHandler时只需要重写 T getResult(ResultSet rs, String columnName, Class<?> propertyType) 方法即可拿到实体字段类型propertyType.而不重写这个方法就和原来mybatis的逻辑一模一样.