作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬
我们会分为上、下两篇分别介绍DAO及Controller层如何处理枚举。重点不是枚举本身,而是希望帮大家开阔眼界,实际开发手动转换枚举也未尝不可。
不了解枚举的同学请先去阅读小册中与枚举相关的其他章节。另外,本文会用到反射及注解相关知识,不熟悉的同学请戳:
反射
注解
这一篇先介绍DAO中枚举相关的处理。
强调一下,这里我直接使用原生MyBatis,而不是通用Mapper或MyBatis-Plus,意在说明MyBatis本身有注意到枚举转换的问题并预留了接口。
环境准备
SQL(注意,这里rest_day是故意使用varchar的,后面会解释)
CREATE TABLE `t_user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(50) DEFAULT '' COMMENT '姓名',
`age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄',
`rest_day` varchar(20) DEFAULT '' COMMENT '休息日',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
pom.xml
<dependencies>
<!--SpringBoot Web,下篇会用到-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--MyBatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--Lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql:///test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: on
logging:
level:
com.example.dao: debug
启动类
/**
* @author mx
*/
@MapperScan("com.example.dao")
@SpringBootApplication
public class MybatisEnumDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisEnumDemoApplication.class, args);
}
}
DO
/**
* @author mx
* @date 2023-11-25 09:56
*/
@Data
public class UserDO {
/**
* 主键id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
/**
* 休息日,实际数据库字段是tinyint或varchar
*/
private WeekDayEnum restDay;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改时间
*/
private Date updateTime;
/**
* 是否删除
*/
private Boolean deleted;
}
WeekDayEnum
/**
* @author mx
*/
@Getter
public enum WeekDayEnum {
MONDAY(1,"星期一"),
TUESDAY(2,"星期二"),
WEDNESDAY(3,"星期三"),
THURSDAY(4,"星期四"),
FRIDAY(5,"星期五"),
SATURDAY(6,"星期六"),
SUNDAY(7,"星期日");
WeekDayEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
private final Integer code;
private final String desc;
}
UserMapper.java
/**
* @author mx
*/
public interface UserMapper {
/**
* 插入用户
*
* @param userDO
*/
void insertUser(UserDO userDO);
/**
* 根据id查询
* @param id
* @return
*/
UserDO selectUserById(@Param("id") Long id);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.dao.UserMapper">
<resultMap id="BaseResultMap" type="com.example.entity.UserDO">
<!--
WARNING - @mbg.generated
-->
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="name" jdbcType="VARCHAR" property="name"/>
<result column="age" jdbcType="TINYINT" property="age"/>
<result column="rest_day" jdbcType="VARCHAR" property="restDay"/>
<result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
<result column="deleted" jdbcType="BIT" property="deleted"/>
</resultMap>
<insert id="insertUser">
INSERT INTO t_user (`name`, age, rest_day)
VALUES(#{name}, #{age}, #{restDay})
</insert>
<!-- VALUES(#{name, jdbcType=VARCHAR}, #{age, jdbcType=INTEGER}, #{restDay, jdbcType=VARCHAR})-->
<select id="selectUserById" resultType="com.example.entity.UserDO">
SELECT * FROM t_user WHERE id=#{id}
</select>
</mapper>
@SpringBootTest
class MybatisEnumTest {
@Autowired
private UserMapper userMapper;
@Test
public void testInsert() {
UserDO userDO = new UserDO();
userDO.setName("MyBatis枚举测试");
userDO.setAge(18);
userDO.setRestDay(WeekDayEnum.FRIDAY);
userMapper.insertUser(userDO);
}
@Test
public void testSelect() {
UserDO userDO = userMapper.selectUserById(1L);
System.out.println(userDO);
}
}
插入测试:
查询测试:
至此,我们完成了最简单的环境搭建。
但你们应该会发现一个神奇的现象:
- 存入时:private WeekDayEnum restDay(内存) --> MyBatis --> "FRIDAY"(数据库)
- 查询时:private WeekDayEnum restDay(内存) <-- MyBatis <-- "FRIDAY"(数据库)
在Java和数据库之间,MyBatis承担了中间人的角色,存入时会自动将枚举对象转为字符串,而取出时又把字符串转为枚举对象。
怎么做到的呢?
枚举的存入
我们发现,MyBatis默认对枚举的处理是将枚举的名称插入数据库,而枚举的名称其实就是Enum.name,定义在父类Enum中:
那么MyBatis是在哪里调用WeekDayEnum的name()方法进行转换的呢?
注意截图中这个类的名字:EnumTypeHandler。
MyBatis提供了两个枚举转换器,EnumTypeHandler是其中之一,另一个是EnumOrdinalTypeHandler:
通过源码很容易看出两者的区别
- EnumTypeHandler:取枚举的name作为值插入数据库
- EnumOrdinalTypeHandler:取枚举的ordinal作为值插入数据库
name和ordinal被定义在Enum抽象类中,而所有枚举类实际上都会继承Enum,所以每一个枚举对象都有name和ordinal。
MyBatis默认的枚举转换器是EnumTypeHandler,所以开头的SQL我故意把rest_day设置为varchar类型,刚好接收被EnumTypeHandler转换后的枚举字符串。
如果我们改为tinyint,就会报错:
DROP TABLE IF EXISTS t_user;
CREATE TABLE `t_user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(50) DEFAULT '' COMMENT '姓名',
`age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄',
`rest_day` tinyint(1) DEFAULT 1 COMMENT '休息日',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
重新执行测试案例:
如果数据库rest_day使用的是tinyint类型,需要将MyBatis的默认枚举转换器切换为EnumOrdinalTypeHandler:
mybatis:
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: on
# 显式声明Mybatis枚举转换器,默认是EnumTypeHandler
default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
给EnumOrdinalTypeHandler打上断点,再次测试插入:
注意,数据库存的是ordinal,而不是code,Enum的ordinal从0开始,所以4代表FRIDAY
查询:
为什么会打印"FRIDAY"呢?
此时UserDO中的restDay确实是WeekDayEnum对象,打印一个对象通常会调用它的toString(),而Enum父类重写了toString(),实际返回Enum.name,所以打印一个枚举对象最终输出的是Enum.name。
小结:
- MyBatis默认存入时会使用EnumTypeHandler枚举类型进行转换,调用Enum.name()存入枚举名称
- 如果希望存入ordinal,可以切换默认枚举转换器为EnumOrdinalTypeHandler
枚举的取出
接着,我们来观察一下数据库中rest_day字段的"FRIDAY"和4被查出来以后如何变成枚举对象FRIDAY的。
同样的,肯定还是EnumTypeHandler和EnumOrdinalTypeHandler帮我们转换的。由于刚才数据库的rest_day已经被我们改为tinyint,所以我们通过EnumOrdinalTypeHandler观察取出时的操作:
好了好了,我知道了,你别说了。我就想知道enums哪来的?
往上看:
在项目启动时MyBatis会初始化EnumTypeHandler,调用构造器时会对private final E[] enums属性赋值。然而神奇的是,type此时enumConstants为null,但从最终结果来看enumConstants是有值的,所以getEnumConstants()内部必然发生了什么。
跟踪进去会发现:
我们之前在设计山寨枚举及反编译枚举时介绍过values()方法和VALUES数组了:
OK,至此EnumOrdinalTypeHandler介绍完了。我们顺便看看EnumTypeHandler:
哦?底层调用了父类Enum的valueOf()方法,根据枚举名称获取枚举实例:
又会跳到Class类的方法中,而且这个方法上面见过了:
T[] universe就是T[] values,而且准备了一个名为m的Map,把枚举的名称作为key,枚举对象本身作为value,把枚举存了起来(你看,又是实用小算法)。
最终Enum.valueOf()其实就是传入枚举名称,然后从Map中得到对应的枚举实例:
所以数据库的"FRIDAY"会被转为FRIDAY对象。
对MyBatis默认提供的枚举转换器的介绍就到这里了。
但不论EnumTypeHandler还是EnumOrdinalTypeHandler,其实都不好用。实际开发中,我们往往使用的不是ordinal或name,而是自己定义的枚举字段,比如code、desc。
默认的两个枚举转换器,一个针对name,另一个针对ordinal,这两个字段属于抽象父类Enum,会在初始化时赋值,而子类特有的code和desc却没用到。
简单版枚举转换器
核心思想是,照着EnumOrdinalTypeHandler抄,搞一个山寨的,然后让MyBatis用我们的Handler转换。
自定义枚举转换器分3步:
- 编写枚举转换类,实现MyBatis提供的TypeHandler接口
- 指定type-handlers-package,告诉MyBatis在哪里可以找到我们自定义的转换器
- 在转换器上用@MappedTypes({WeekDayEnum.class})指定用来处理哪个枚举
TypeHandler是个接口,啥都没有,白手起家太难了:
MyBatis另外提供了BaseTypeHandler让我们继承,EnumOrdinalTypeHandler也是这么干的:
要想自定义枚举转换器,最快的办法是“抄袭”EnumOrdinalTypeHandler:
再把里面的内容全部拷过来:
为了方便确认最终起作用的是我们自定义的MyEnumTypeHandler,稍作修改:
如果最终插入的值会在原来的基础上加100,就说明走了我们自定义的转换器。
然后加上@MappedTypes({WeekDayEnum.class})注解指定处理WeekDayEnum:
最后告诉MyBatis我们自定义的转换器包路径:
mybatis:
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: on
# 显式声明Mybatis默认枚举转换器(默认EnumTypeHandler)
default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler
# 指定自定义的枚举转换器路径
type-handlers-package: com.example.handlers
传入的是4,经过我们的Handler后实际插入104,说明自定义转换器成功了!但别高兴得太早,上面仅仅是拷贝EnumOrdinalTypeHandler,最终插入的还是ordinal,并不是自定义的枚举字段code。
什么是JdbcType
在正式改代码之前,我们先来解决一个疑惑:JdbcType是什么?
不论是EnumTypeHandler还是EnumOrdinalTypeHandler,setNonNullParameter()的参数列表都有JdbcType:
而且EnumTypeHandler的setNonNullParameter()内部还对JdbcTye做了判断。
所以,什么是JdbcType呢?
MyBatis在org.apache.ibatis.type包下定义了一个JdbcType枚举,用来定义数据库的字段类型,与JdbcType对应的还有JavaType。
如果你跟着上面的代码做了实验,会发现不论怎么修改UserDO,type始终是null:
JdbcType是在SQL中规定的,而不是UserDO中。
其实,只要大家仔细回想,就会发现以前在写SQL语句时好像会指定JdbcType:
<insert id="insertUser">
INSERT INTO t_user (`name`, age, rest_day)
VALUES(#{name, jdbcType=VARCHAR}, #{age, jdbcType=INTEGER}, #{restDay, jdbcType=INTEGER})
</insert>
如果不指定,MyBatis会自己判断。
现在我把restDay设为jdbcType=INTEGER,再次启动程序就会发现:
注意,这里的4可不是FRIDAY,而是INTEGER:
意思是把Object类型转为INTEGER插入。特别注意,由于上面ps.setObject()方法中传入的是parameter.name(),也就是Enum.name(),所以实际传入的String类型的"FRIDAY"。
当然,此时会报错(实际参数是String,你偏要转为Integer存入):
Cause: java.sql.SQLException: Cannot convert class java.lang.String to SQL type requested due to java.lang.NumberFormatException - For input string: "FRIDAY"
在追踪源码的过程中,有一点很不解:枚举类型最终被归为DECIMAL_UNSIGNED...
但本文不是研究MyBatis源码的,就此打住。总之,必须在MyEnumTypeHandler中对WeekDayEnum进行转换。
注解+反射实现枚举自动类型转换
由于这是demo,且平常我都不写JdbcType,所以我们不考虑type!=null的情况:
/**
* 标记需要转换的枚举字段
*
* @author mx
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumValue {
}
@Getter
public enum WeekDayEnum {
MONDAY(1,"星期一"),
TUESDAY(2,"星期二"),
WEDNESDAY(3,"星期三"),
THURSDAY(4,"星期四"),
FRIDAY(5,"星期五"),
SATURDAY(6,"星期六"),
SUNDAY(7,"星期日");
WeekDayEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
// 标记最终把code作为枚举的值插入数据库
@EnumValue
private final Integer code;
private final String desc;
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
if (jdbcType == null) {
// 获取WeekDayEnum的所有字段并循环,找到带有@EnumValue注解的字段
Field[] declaredFields = type.getDeclaredFields();
for (Field declaredField : declaredFields) {
// 是否有@EnumValue注解
EnumValue enumValue = declaredField.getAnnotation(EnumValue.class);
if (enumValue != null) {
Object fieldValue = null;
try {
// 反射获取标记了@EnumValue注解的字段的value
declaredField.setAccessible(true);
fieldValue = declaredField.get(parameter);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// 设置值
ps.setObject(i, fieldValue);
return;
}
}
} else {
// 不考虑jdbcType!=null的情况
ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);
}
}
大家自己测试,一般来说是没问题的:
==> Preparing: INSERT INTO t_user (`name`, age, rest_day) VALUES(?, ?, ?)
==> Parameters: MyBatis枚举测试(String), 18(Integer), 5(Integer)
<== Updates: 1
注意,存入的是WeekDayEnum.FRIDAY,而数据库显示5,说明这次不是ordinal,而是code。
当然,你也可以把数据库rest_day字段改回VARCHAR,然后把@EnumValue注解加在private String desc上。
还是不要高兴得太早,我们来测一下查询:
是的,MyEnumTypeHandler在取出时会把5当做ordinal解析,所以最终得到的是SATURDAY。
为什么呢?因为当初照抄EnumOrdinalTypeHandler,我们只改了存入的逻辑。
取出的逻辑就不详细说了,大家复制过去看看即可:
@MappedTypes({WeekDayEnum.class})
public class MyEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
private final Class<E> type;
private final E[] enums;
public MyEnumTypeHandler(Class<E> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.type = type;
this.enums = type.getEnumConstants();
if (this.enums == null) {
throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
if (jdbcType == null) {
// 获取WeekDayEnum的所有字段并循环,找到带有@EnumValue注解的字段
Field[] declaredFields = type.getDeclaredFields();
for (Field declaredField : declaredFields) {
// 是否有@EnumValue注解
EnumValue enumValue = declaredField.getAnnotation(EnumValue.class);
if (enumValue != null) {
Object fieldValue = null;
try {
// 反射获取标记了@EnumValue注解的字段的value
declaredField.setAccessible(true);
fieldValue = declaredField.get(parameter);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// 设置值
ps.setObject(i, fieldValue);
return;
}
}
} else {
// 不考虑jdbcType!=null的情况
ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);
}
}
@Override
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 定义一个变量,接收从数据库查出的rest_day
Object valueFromDB = null;
// 确定当初存入时指定了哪个字段
Field enumValueField = null;
Field[] declaredFields = type.getDeclaredFields();
for (Field field : declaredFields) {
// 是否有@EnumValue注解
EnumValue enumValue = field.getAnnotation(EnumValue.class);
if (enumValue != null) {
// 找到带有@EnumValue的字段
enumValueField = field;
// 数据库返回了ResultSet,也即是查询结果集,我们可以从中获取restDay的值
valueFromDB = rs.getObject(columnName, enumValueField.getType());
break;
}
}
if (enumValueField == null) {
// 如果没有标注@EnumValue,还是按默认的解析返回
return getResultByOrdinal(rs, columnName);
}
// 遍历WeekDayEnum的所有实例,反射获取每个实例中标注了@EnumValue的字段值并比较
enumValueField.setAccessible(true);
for (E weekday : enums) {
Object value = null;
try {
value = enumValueField.get(weekday);
if (valueFromDB.equals(value)) {
// 值相等,返回对于的枚举对象
return weekday;
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return null;
}
private E getResultByOrdinal(ResultSet rs, String columnName) throws SQLException {
int ordinal = rs.getInt(columnName);
if (ordinal == 0 && rs.wasNull()) {
return null;
}
return toOrdinalEnum(ordinal);
}
private E toOrdinalEnum(int ordinal) {
try {
return enums[ordinal];
} catch (Exception ex) {
throw new IllegalArgumentException("Cannot convert " + ordinal + " to " + type.getSimpleName() + " by ordinal value.", ex);
}
}
@Override
public E getNullableResult(ResultSet rs, int columnIndex) {
return null;
}
@Override
public E getNullableResult(CallableStatement cs, int columnIndex) {
return null;
}
}
MyBatis-Plus对枚举的处理
有时候就是这么巧,万万没想到MyBatis-Plus的处理方式和我们惊人地相似。
自3.1.0
开始,如果你无需使用原生枚举,可配置默认枚举来省略扫描通用枚举配置
- 升级说明:
3.1.0
以下版本改变了原生默认行为,升级时请将默认枚举设置为EnumOrdinalTypeHandler
- 影响用户:
实体中使用原生枚举 - 其他说明:
配置枚举包扫描的时候能提前注册使用注解枚举的缓存
声明通用枚举属性
方式一: 使用 @EnumValue 注解枚举属性
public enum GradeEnum {
PRIMARY(1, "小学"), SECONDORY(2, "中学"), HIGH(3, "高中");
GradeEnum(int code, String descp) {
this.code = code;
this.descp = descp;
}
@EnumValue//标记数据库存的值是code
private final int code;
//。。。
}
方式二: 枚举属性,实现 IEnum 接口如下:
public enum AgeEnum implements IEnum<Integer> {
ONE(1, "一岁"),
TWO(2, "二岁"),
THREE(3, "三岁");
private int value;
private String desc;
@Override
public Integer getValue() {
return this.value;
}
}
实体属性使用枚举类型
public class User {
/**
* 名字
* 数据库字段: name varchar(20)
*/
private String name;
/**
* 年龄,IEnum接口的枚举处理
* 数据库字段:age INT(3)
*/
private AgeEnum age;
/**
* 年级,原生枚举(带{@link com.baomidou.mybatisplus.annotation.EnumValue}):
* 数据库字段:grade INT(2)
*/
private GradeEnum grade;
}
配置扫描通用枚举
mybatis-plus:
# 支持统配符 * 或者 ; 分割
typeEnumsPackage: com.baomidou.springboot.entity.enums
....
没想到,我的构思被MyBatis-Plus抄袭了。
一些说明
其实数据库有一种枚举字段类型,大家可以了解下,本文并没有介绍,个人不建议使用。
另外,MyEnumTypeHandler还有很多不足,最大的不足就是仍然不够通用。
假设现在系统新增InvitationStatusEnum:
DROP TABLE IF EXISTS t_user;
CREATE TABLE `t_user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(50) DEFAULT '' COMMENT '姓名',
`age` tinyint(3) unsigned DEFAULT NULL COMMENT '年龄',
`rest_day` tinyint(1) DEFAULT 1 COMMENT '休息日',
`invitation_status` varchar(50) DEFAULT '' COMMENT '面试状态',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`deleted` tinyint(1) unsigned DEFAULT '0' COMMENT '是否删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
@Getter
public enum InvitationStatusEnum {
WAIT_FOR_DEAL("wait_for_deal", "等待处理"),
SUITABLE("suitable", "合适"),
;
@EnumValue
private final String value;
private final String desc;
InvitationStatusEnum(String value, String desc) {
this.value = value;
this.desc = desc;
}
}
你会发现又不行了。除非在@MappedTypes的属性中另外指定InvitationStatusEnum.class:
也就是说,作为通用组件的MyEnumTypeHandler还是无法避免被反复修改,不如MyBatis-Plus来得优雅。有兴趣的同学可以自行研究(意义不大)。
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
进群,大家一起学习,一起进步,一起对抗互联网寒冬