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

当我们在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;}@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, BaseEnum parameter, JdbcType jdbcType) throws SQLException {ps.setInt(i, parameter.getValue());}@Overridepublic BaseEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {return convert(rs.getInt(columnName));}@Overridepublic BaseEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return convert(rs.getInt(columnIndex));}@Overridepublic 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 typeif (!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

MyBatis自定义类型处理器 TypeHandler相关推荐

  1. MyBatis自定义类型处理器(typeHandler)

    MyBatis自定义类型处理器(typeHandler) 我们执行sql语句通过PreparedStatement语句实现,PreparedStatement会设置?值,类型处理器帮PreparedS ...

  2. 【Mybatis】类型处理器TypeHandler的作用与自定义

    一.什么是类型处理器 1.类型处理器(TypeHandler) MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的值以合 ...

  3. Mybatis——类型处理器TypeHandler

    在日常开发中使用mybatis时,mybatis的mapper.xml.mapper接口.entity实体一般会由mybatis-generator自动生成,其中实体的每个属性与数据库表的列一一对应, ...

  4. MyBatis核心源码剖析(SqlSession XML解析 Mapper executor SQL执行过程 自定义类型处理器 缓存 日志)

    MyBatis核心源码剖析 MyBatis核心源码剖析 1 MyBatis源码概述 1.1 为什么要看MyBatis框架的源码 1.2 如何深入学习MyBatis源码 1.3 源码分析的5大原则 2 ...

  5. MyBatis(九):MyBatis类型处理器(TypeHandler)详解

    TypeHandler简介 TypeHandler,顾名思义类型转换器,就是将数据库中的类型与Java中的类型进行相互转换的处理器. MyBatis 在设置预处理语句(PreparedStatemen ...

  6. 在mybatis里如何自定义类型处理器

    类型处理器(typeHandlers) MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Jav ...

  7. 自定义类型处理器的应用

    问题描述: 一个JSON字符串在转对象的时候报JSON解析异常的错误,我仔细看了一下错误堆栈,是枚举导致的数组越界问题. [{"fee":0,"amount": ...

  8. MyBatis之类型处理器typeHandlers

    typeHandlers类型处理器 实现java类型与数据库类型之间的转换,可以使用系统提供的类型处理器,也可以自定义类型处理器,这里介绍的是实现自定义类型处理器处理数据库类型与java类型的转换 比 ...

  9. 类型处理器TypeHandler

    作用:用于JavaType和JdbcType之间的转换 解析:当Mybatis将一个Java对象写入数据库时,它会创建一个PreparedStatument对象,并且使用setValue()方法对占位 ...

最新文章

  1. JSON.stringify报cyclic object value错误
  2. [NSDI 17] TuX2: 面向机器学习的分布式图计算系统 学习总结
  3. 20321关系数据库理论基础
  4. AAAI 2021 | 基于动态混合关系网络的对话式语义解析方法
  5. android bitmap xml,XML Bitmap
  6. cocos2d-x的Android工程开启c++0x特性
  7. Web前端笔记-解决Vue编写的输入框(input、textarea等)使用JS设置value时提交表单无效的问题
  8. Dirichlet Distribution(狄利克雷分布)与Dirichlet Process(狄利克雷过程)
  9. leetcode51. N皇后
  10. aws redshift_AWS Redshift入门
  11. python键盘监听
  12. C++设计模式实现--职责链(Chain of Responsibility)模式
  13. 自学-Linux-老男孩Linux77期-day4
  14. 生信可视化(part2)--箱线图
  15. oracle的dos登陆口令,ORACLE的DOS操作方式
  16. 苹果手机照片误删如何找回
  17. Node开发实践总结-定时脚本的设计与实现
  18. 计算机专业的女孩穿搭,大学里女生一般喜欢男生怎样穿搭
  19. 5分钟了解赴港上市公司CEO薪酬
  20. 【物联网中间件平台-02】YFIOs技术白皮书(V1.1)

热门文章

  1. 从Zygote孵化frameworks进程,分析StartActivity流程中intent传递数据的最大值。
  2. 在Android Studio中打开DDMS
  3. android 跳转到小米手机神隐模式
  4. ASCII+Unicode+UTF8(字符编码方式-字节) Base64(字节编码方式-64字符)
  5. Android system.img 打包解包
  6. IBM Java垃圾回收
  7. MySql—索引原理
  8. SDNU 1416.一元三次方程求解(数学)
  9. HDU - 4847 Wow! Such Doge!
  10. ant+jmeter