TypeHandler 介绍
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型——mybatis.org
-
TypeHandler,类型处理器,即在实体类与数据库字段不能相适应时进行转换处理的工具类
如:数据库中使用01数值来表示布尔值,实体类中直接使用 Boolean 类型,在 MySQL 数据库中原生支持 true/false 关键字和数值的转换,可能不会报错,但在其他不支持 true/false 关键字的数据库中,则会抛出 SQL 错误
-
定制 MyBatis TypeHandler
MyBatis 默认已有的类型处理器
对于已有类型处理器不能满足需求时,MyBatis 提供了 TypeHandler 接口以及 BaseTypeHandler 抽象类以供实现来处理不支持的或非标准的类型,如:
@MappedJdbcTypes({JdbcType.VARCHAR}) public class MyStringTypeHandler extends BaseTypeHandler<String> { /** * 设置参数时转换实体属性 */ @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { // ... } /** * 映射结果集到实体属性 */ @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { // ... } // ... }MyBatis 会根据 TypeHandler 的 jdbcType 和 javaType 来匹配使用,上述的 TypeHandler 将会覆盖已有的处理 Java String 类型的属性以及 VARCHAR 类型的参数和结果的 TypeHandler
注意:MyBatis 不会通过检测数据库元信息来决定使用哪种类型,在定义 TypeHandler 和使用 TypeHandler 时最好指定 jdbcType 和 javaType 来帮助 MyBatis 准确定位要使用的处理器
-
注册 TypeHandler 到 Mybatis
在使用 mybatis-config.xml 配置的项目中,可如下配置
<typeHandlers> <!-- 配置 TypeHandler,可指定 javaTpe 和 jdbcType --> <typeHandler javaType="String" jdbcType="VARCHAR" handler="com.example.handler.MyBooleanTypeHandler"/> <!-- 自动扫描整个包下的 TypeHandler 实现 --> <package name="com.example.handler"/> </typeHandlers>在 SpringBoot 中,可直接通过 yml 配置
mybatis: # 加入 alias package,方便在 mapper xml 中使用 TypeHandler type-aliases-package: com.example.handler type-handlers-package: com.example.handlermybatis-config.xml 中 typeHandler 标签可以指定 javaType 和 jdbcType,而 package 标签的自动扫描和 SpringBoot 下的自动配置则需要使用 @MappedTypes 和 @MappedJdbcTypes 注解来指定,注意没有指定 javaType 时 MyBatis 会使用 TypeHandler 的泛型作为 javaType
-
使用 TypeHandler
虽然指定 javaType 和 jdbcType 可以让 MyBatis 自动匹配合适的 TypeHandler,但一般情况下直接指定 typeHandler 可能会更加简便直接
对参数类型进行处理
<insert id="insert"> insert into users (id, name, enableFlag) values ( #{id}, #{name}, #{enableFlag, typeHandler=MyBooleanTypeHandler} ) </insert>对结果集中类型进行处理
<resultMap type="com.example.bean.User" id="usermap"> <result column="id" property="id"/> <result column="name" property="name"/> <result column="enableFlag" property="enableFlag" typeHandler="MyBooleanTypeHandler"/> </resultMap>
TypeHandler 常用示例
布尔类型和数值转换
-
使用场景
一般在数据库中存储布尔类型会使用数值01来表示,若实体类中想要使用 Boolean 来定义字段,则需要借助布尔类型和数值的类型处理器
-
BoolToIntTypeHandler 类型处理器示例
@MappedTypes({Boolean.class}) @MappedJdbcTypes({JdbcType.INTEGER}) public class BoolToIntTypeHandler extends BaseTypeHandler<Boolean> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType) throws SQLException { if (parameter) { ps.setInt(i, 1); return; } ps.setInt(i, 0); } @Override public Boolean getNullableResult(ResultSet rs, String columnName) throws SQLException { int i = rs.getInt(columnName); return i != 0; } @Override public Boolean getNullableResult(ResultSet rs, int columnIndex) throws SQLException { int i = rs.getInt(columnIndex); return i != 0; } @Override public Boolean getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { int i = cs.getInt(columnIndex); return i != 0; } }
普通对象与 JSON 串转换
-
使用场景
实体类中的属性是复杂类型,在数据库中使用 JOSN 格式字符串来存储或者在查询 SQL 中使用了 JSON 聚合函数来生成 JSON 串
-
ObjToStrTypeHandler 类型处理器示例
@MappedTypes({Object.class, List.class}) @MappedJdbcTypes(JdbcType.VARCHAR) public class ObjToStrTypeHandler extends BaseTypeHandler<Object> { private final Class<?> type; public ObjToStrTypeHandler(Class<?> type) { this.type = type; } @Override public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, JacksonUtil.toJson(parameter)); } @Override public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { final String json = rs.getString(columnName); return JacksonUtil.toBean(json, type); } @Override public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException { final String json = rs.getString(columnIndex); return JacksonUtil.toBean(json, type); } @Override public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { final String json = cs.getString(columnIndex); return JacksonUtil.toBean(json, type); } }
枚举类型转换
-
MyBatis 提供了两种枚举类型处理器,EnumTypeHandler 和 EnumOrdinalTypeHandler
和一般类型有所区别的是,这两种枚举类型处理器可以处理所有继承了 Enum 的类
EnumTypeHandler 使用枚举的 name 来转换,数据库中使用字符串类型存储
EnumOrdinalTypeHandler 使用枚举的序号来转换,数据库中使用数值类型存储
MyBatis 默认使用 EnumTypeHandler 处理枚举,可通过配置 mybatis-config.xml 来显示指定某个枚举要使用的处理器
<typeHandlers> <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.example.enums.MyEnum"/> </typeHandlers>在 SpringBoot 中可以通过配置 default-enum-type-handler 来全局设定默认枚举处理器
mybatis: configuration: default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler当然,也可以在 mapper xml 的 SQL 和 resultMap 中直接指定 typeHandler
-
DefaultEnumTypeHandler 枚举类型处理器
示例参见SpringBoot 使用枚举-MyBatis 映射枚举
MyBatis Plus TypeHandler 支持
以下测试内容 MyBatis Plus 版本为 3.5.1
-
MyBatis Plus 提供了 @TableField 注解,可指定实体类字段要用的 TypeHandler
@Data @TableName(autoResultMap = true) public class User { private Long id; private String name; @TableField(typeHandler = BoolToIntTypeHandler.class) private Boolean enableFlag; } -
对结果集的类型处理
@TableName(autoResultMap = true)实体类上需要设置 @TableName 注解 autoResultMap 为 true,或者使用 resultMap 属性指定一个 xml 中配置好的 resultMap
-
对参数的类型处理
service 层方法 save、updateById、removeById 支持根据注解设置的 typeHandler 处理参数类型
注意使用带有 Wrapper 的方法时,条件最好通过实体类来设置,以保证参数被正确处理,如 eq 条件通过 setEntity 方法设置,更新 set 条件通过 update 方法传入实体
lambdaQuery
// 查询 user 表,id=0,enable_flag=1 // 若使用 eq,方法中的参数类型在当前版本可能无法被正确处理 userService.lambdaQuery() .setEntity(User.builder().id(0L).enableFlag(true).build()) .list();lambdaUpdate
// 更新 user 表,id=0,enable_flag=1 => enable_flag=0 userService.lambdaUpdate() .setEntity(User.builder().id(0L).enableFlag(true).build()) .update(User.builder().id(0L).enableFlag(false).build()); // 若使用 set 方法,支持传入 mapping 指定 typeHandler // 如 .set(User::getEnableFlag, false, "typeHandler=\"BoolToIntTypeHandler\"")remove
userService.remove(Wrappers.<User>lambdaQuery() .setEntity(User.builder().id(0L).enableFlag(true).build())); -
枚举类型类型处理器
配置枚举类型所在包
mybatis-plus: # 支持统配符 * 或者 ; 分割 type-enums-package: com.example.enumsMyBatis Plus 提供的 MybatisSqlSessionFactoryBean 会自动扫描包内合法的枚举类(使用了 @EnumValu 注解,或者实现了 IEnum 接口),分别为这些类注册使用 MybatisEnumTypeHandler
若需要全局指定枚举类型统一使用 MyBatis Plus 提供的处理器,可如下配置
mybatis-plus: # 修改 mybatis 的 DefaultEnumTypeHandler configuration: default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler配置完成后,枚举类型需要使用 @EnumValue 注解或者实现 IEnum 接口来指定转换的属性
@Getter public enum GradeEnum { PRIMARY(1, "小学"), SECONDORY(2, "中学"), HIGH(3, "高中"), ; GradeEnum(int code, String descp) { this.code = code; this.descp = descp; } // 数据库存的值是 code @EnumValue private final int code; // ... }