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

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

在这里插入图片描述
类型转换器还可以通过注解配置 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 extends Enum<T>> T valueOfEnum(Class<T> 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> 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<BaseEnum> {

    private Class<BaseEnum> type;

    public EnumTypeHandler() {
    }

    public EnumTypeHandler(Class<BaseEnum> 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中声明

<result column="enum1" jdbcType="INTEGER" property="enum1" typeHandler="com.xxx.handler.EnumTypeHandler"/>

2.在mybatis配置文件中设置

<typeHandlers>
        <typeHandler handler="com.xxx.handler.EnumTypeHandler"/>
    </typeHandlers>

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

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

源码分析

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

public <T> void register(TypeHandler<T> 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<T> typeReference = (TypeReference<T>) 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<T>) 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

  • 27
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
MyBatis中,你可以使用自定义类型处理器TypeHandler)来处理自定义的Map类型类型处理器用于在Java对象和数据库字段之间进行转换。 以下是一种实现自定义类型处理器处理自定义的Map的示例: 1. 定义一个自定义的Map类型,例如`CustomMap`: ```java public class CustomMap extends HashMap<String, Object> { // 添加自定义的方法或属性 } ``` 2. 实现一个自定义类型处理器,继承自`org.apache.ibatis.type.BaseTypeHandler`类,并实现对应的方法。 ```java import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class CustomMapTypeHandler extends BaseTypeHandler<CustomMap> { @Override public void setNonNullParameter(PreparedStatement ps, int i, CustomMap parameter, JdbcType jdbcType) throws SQLException { // 将CustomMap转换为需要的数据类型,并设置到PreparedStatement中 // ps.setXXX(i, convertedValue); } @Override public CustomMap getNullableResult(ResultSet rs, String columnName) throws SQLException { // 从ResultSet中获取指定列名的值,并将其转换为CustomMap类型 // Object columnValue = rs.getXXX(columnName); // CustomMap map = convertToCustomMap(columnValue); // return map; return null; } @Override public CustomMap getNullableResult(ResultSet rs, int columnIndex) throws SQLException { // 与上面类似,只是根据列索引获取值 return null; } @Override public CustomMap getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { // 与上面类似,只是在CallableStatement中获取值 return null; } } ``` 在上述示例中,我们继承了`BaseTypeHandler`类,并重写了父类的方法,在这些方法中进行了自定义类型的转换逻辑。 3. 在MyBatis的配置文件中,注册自定义类型处理器。 ```xml <typeHandlers> <typeHandler handler="com.example.CustomMapTypeHandler"/> </typeHandlers> ``` 通过以上步骤,你就可以使用自定义的Map类型,并通过自定义类型处理器来处理该类型的转换逻辑。在数据库操作时,MyBatis会自动调用类型处理器来进行转换。你可以根据实际需求,在类型处理器中编写相应的转换逻辑,将自定义的Map类型与数据库字段进行转换。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值