MyBatis自定义类型处理器 TypeHandler

在项目开发中经常会遇到一个问题:

当我们在javabean中自定义了枚举类型或者其它某个类型,但是在数据库中存储时往往需要转换成数据库对应的类型,并且在从数据库中取出来时也需要将数据库类型转换为javabean中的对应类型。比如:javabean中字段类型为Date,数据库中存储的是varchar类型;javabean中字段类型是Enum,数据库中存储的是String或者Integer。
因为有大量类似数据的转换,手动转换类型进行存储和查询已经过于麻烦。MyBatis为我们提供了解决办法:TypeHandler类型处理器。

类型处理器 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 接口实现的。这个接口定义了类型处理器的基本功能,接口定义如下所示。
MyBatis自定义类型处理器 TypeHandler_第1张图片
其中 setParameter 方法用于把 java 对象设置到 PreparedStatement 的参数中,getResult 方法用于从 ResultSet(根据列名或者索引位置获取) 或 CallableStatement(根据存储过程获取) 中取出数据转换为 java 对象。

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

MyBatis自定义类型处理器 TypeHandler_第2张图片
类型转换器还可以通过注解配置 java 类型和 jdbc 类型:

@MappedTypes:注解配置 java 类型
@MappedJdbcTypes:注解配置 jdbc 类型

自定义枚举类型处理器示例:

自定义一个枚举基类

import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;

/**
 * @author: liumengbing
 * @date: 2019/05/20 15:37
 **/
public interface BaseEnum {

    String DEFAULT_VALUE_NAME = "value";

    String DEFAULT_LABEL_NAME = "label";

    default Integer getValue() {
        Field field = ReflectionUtils.findField(this.getClass(), DEFAULT_VALUE_NAME);
        if (field == null)
            return null;
        try {
            field.setAccessible(true);
            return Integer.parseInt(field.get(this).toString());
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    default String getLabel() {
        Field field = ReflectionUtils.findField(this.getClass(), DEFAULT_LABEL_NAME);
        if (field == null)
            return null;
        try {
            field.setAccessible(true);
            return field.get(this).toString();
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    static > T valueOfEnum(Class enumClass, Integer value) {
        if (value == null)
            throw  new IllegalArgumentException("DisplayedEnum value should not be null");
        if (enumClass.isAssignableFrom(com.zfkr.qianyue.common.enums.BaseEnum.class))
            throw new IllegalArgumentException("illegal DisplayedEnum type");
        T[] enums = enumClass.getEnumConstants();
        for (T t: enums) {
            com.zfkr.qianyue.common.enums.BaseEnum displayedEnum = (com.zfkr.qianyue.common.enums.BaseEnum)t;
            if (displayedEnum.getValue().equals(value))
                return (T) displayedEnum;
        }
        throw new IllegalArgumentException("cannot parse integer: " + value + " to " + enumClass.getName());
    }

    static  T valueOfEnum1(T[] enums, Integer value) {

        for (T t: enums) {
            com.zfkr.qianyue.common.enums.BaseEnum displayedEnum = (com.zfkr.qianyue.common.enums.BaseEnum)t;
            if (displayedEnum.getValue().equals(value))
                return (T) displayedEnum;
        }
        throw new IllegalArgumentException("cannot parse integer: " + value + " to " );
    }
}

定义一个枚举类Enum1

/**
 * @author: liumengbing
 * @date: 2019/05/20 15:34
 **/
public enum Enum1 implements BaseEnum{

    UNAUDITED("未审核",0),AUDIT("待审核",1),AUDITED("已审核",2);

    private String label;

    private Integer value;

    Enum1(String label,Integer value){
        this.label = label;
        this.value = value;
    }

    public Integer getValue() {
        return value;
    }

    public String getLabel() {
        return label;
    }

    public static Enum1 getByValue(Integer value){
        for(Enum1 enum1 : values()){
            if (enum1.getValue() == value) {
                return enum1;
            }
        }
        return null;
    }

}

自定义枚举类型处理器

/**
 * @author: liumengbing
 * @date: 2019/05/20 15:34
 **/
@MappedTypes(value = { Enum1.class, Enum2.class})
public class EnumTypeHandler extends BaseTypeHandler {

    private Class type;

    public EnumTypeHandler() {
    }

    public EnumTypeHandler(Class type) {
        if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
        this.type = type;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, BaseEnum parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getValue());
    }

    @Override
    public BaseEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return convert(rs.getInt(columnName));
    }

    @Override
    public BaseEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return convert(rs.getInt(columnIndex));
    }

    @Override
    public BaseEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return convert(cs.getInt(columnIndex));
    }

    private BaseEnum convert(int status) {
        BaseEnum[] objs = type.getEnumConstants();
        for (BaseEnum em : objs) {
            if (em.getValue() == status) {
                return em;
            }
        }
        return null;
    }
}

Javabean,Dao层,mapper层处理忽略,将自定义的类型处理器配置到程序中即可。

把TypeHandler配置到程序中有三种方法:

1.在Mapper.xml中声明


2.在mybatis配置文件中设置


        
    

3.在springboot的yml配置文件中设置类型处理器所在的包名

mybatis:
  type-handlers-package: com.xxx.handler

源码分析

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);
    }
}

参考资料:
mybatis中几种typeHandler的定义使用
MyBatis 类型处理器 TypeHandler

你可能感兴趣的:(Java,Mybatis)