小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
MyBatis自定义TypeHandler
1 什么是TypeHandler
TypeHandler根据字面意思即为类型处理器
引用官方文档的描述: MyBatis在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型
MyBatis存在一些默认的类型处理器, 可参考官方文档
2 为什么要使用TypeHandler
在开发过程中, 当默认的TypeHandler无法满足需求时, 例如遇到MyBatis不支持的数据类型或需要特殊处理的类型转换, 便需要自己定制对应的TypeHandler
笔者会在下面的代码实现中完成如下几种情况的TypeHandler:
- 逗号分隔保存在数据库中的数据, 在对应的Java类中为数组
- 自定义枚举
3 如何自定义TypeHandler
MyBatis提供了接口org.apache.ibatis.type.TypeHandler
和类org.apache.ibatis.type.BaseTypeHandler
官方文档给出的示例为继承BaseTypeHandler
, 笔者在这里也使用这种方式
先来观察一下官方的StringTypeHandler
:
1 | java复制代码public class StringTypeHandler extends BaseTypeHandler<String> { |
方法名称和代码都简洁明了, 观察可知, 只需要完成四个方法的覆盖, 即可实现自定义TypeHandler
3.1 逗号分隔字符串转数组
假设用户表t_user设计如下:
id | username | tags |
---|---|---|
1 | admin | admin, user |
对应的Java类为:
1 | java复制代码@Data |
tags属性在数据库中用逗号分隔的字符串保存, 但User类对应的属性为String数组
可以创建StringArrayTypeHandler
来解决类型转换的问题:
1 | java复制代码public class StringArrayTypeHandler extends BaseTypeHandler<String[]> { |
3.2 自定义枚举
如何创建包含中文名称的枚举, 可以参考MyBatis中使用Java类与枚举
先创建工具类用于根据code获取枚举实体:
1 | java复制代码public class ValueNameEnumUtils { |
和3.1中的情况不同, 枚举的具体类型是不确定, 所以我们要使用泛型的方式处理TypeHandler
创建ValueNameEnumTypeHandler
:
1 | java复制代码public class ValueNameEnumTypeHandler<E extends ValueNameEnum> extends BaseTypeHandler<ValueNameEnum> { |
泛型虽然名之为泛, 但在编译过程中实际会发生类型擦除
关于类型擦除可以阅读面试官:说说什么是泛型的类型擦除?
总之, 对于泛型TypeHandler, 我们需要声明一个用来标识具体类型的属性private final Class<E> type
和创建对应的构造函数public ValueNameEnumTypeHandler(Class<E> type)
接下来和3.1中的一致, 重写四个方法:
1 | java复制代码public class ValueNameEnumTypeHandler<E extends ValueNameEnum> extends BaseTypeHandler<ValueNameEnum> { |
完成上述代码直接启动, 会抛出异常: Unable to find a usable constructor for class cn.houtaroy.springboot.common.mybatis.handler.ValueNameEnumTypeHandler
产生异常的源码如下:
1 | java复制代码public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) { |
报错的原因直白, 没有找到ValueNameEnumTypeHandler的构造函数
首先我们要了解下Java类构造函数的机制: 如果定义了构造函数, 则使用定义, 否则默认生成空构造函数
在3.1中的StringArrayTypeHandler
, 我们没有定义构造函数, 自动生成空构造函数, typeHandlerClass.getConstructor()
不会抛出异常
但ValueNameEnumTypeHandler
定义了一个构造函数ValueNameEnumTypeHandler(Class<E> type)
, 且没有指定JavaType, typeHandlerClass.getConstructor()
自然抛出异常
解决方法有两种:
- 创造空的构造函数
- 指定JavaType
笔者推荐第二种, 因为第一种方式枚举类属性type
会产生NPE(空指针异常), MyBatis官方也我们提供了注解@MappedTypes
用于指定JavaType:
1 | java复制代码@MappedTypes(ValueNameEnum.class) |
4 如何使用TypeHandler
在上一章节中, 我们完成了编码实现自定义TypeHandler, 但完成的TypeHandler还没办法进行使用, 需要手动进行配置
有两种配置方式: 局部使用和全局使用
以StringArrayTypeHandler
举例:
4.1 局部使用
在ResultMap中使用:
1 | xml复制代码<resultMap id="UserResultMap" type="cn.houtaroy.springboot.common.system.model.User"> |
在语句中使用:
1 | sql复制代码update t_user set tags = #{tags, typeHandler=cn.houtaroy.springboot.extension.mybatis.handler.StringArrayTypeHandler} |
4.2 全局使用
使用配置文件指定handler包名:
1 | yaml复制代码mybatis: |
注意, 此配置类型为String, 只能配置一个包, 推荐使用下面的方式
手写配置类:
1 | java复制代码@Configuration |
StringArrayTypeHandler
不适合全局配置, 它会在全部JavaType为String[]的属性上使用
5 拓展阅读
- MyBatis3官方文档中TypeHandler内容: mybatis – MyBatis 3 | 配置
- 网上搜索的在Spring Bean声明周期中进行全局配置: Mybatis自定义全局TypeHander_chuobenggu7592的博客-CSDN博客
本文转载自: 掘金