如何处理枚举类型(上)

news2024/12/23 12:44:59
作者简介:大家好,我是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

进群,大家一起学习,一起进步,一起对抗互联网寒冬

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1251802.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HCIA-RS基础-RIP路由协议

前言&#xff1a; RIP路由协议是一种常用的距离矢量路由协议&#xff0c;广泛应用于小规模网络中。本文将详细介绍RIP路由协议的两个版本&#xff1a;RIPv1和RIPv2&#xff0c;并介绍RIP的常用配置命令。通过学习本文&#xff0c;您将能够掌握RIP协议的基本原理、RIPv1和RIPv2的…

福州大学《嵌入式系统综合设计》实验七:图像灰度直方图

一、实验目的 直方图是一种统计特征&#xff0c;在图像中广为使用&#xff0c;因为具有计算简便、不受平移、旋转的影响&#xff0c;因此可以作为图像的一种有效的局部/全局特征来表示图像&#xff0c;是图像的重要特征之一。直方图在SIFT算法、HOG算法、直方图均衡等图像特征…

【阿里云】图像识别 智能分类识别 增加网络控制功能点(三)

一、增加网络控制功能 实现需求TCP 心跳机制解决Soket异常断开问题 二、Linux内核提供了通过sysctl命令查看和配置TCP KeepAlive参数的方法。 查看当前系统的TCP KeepAlive参数修改TCP KeepAlive参数 三、C语言实现TCP KeepAlive功能 四、setsockopt用于设置套接字选项的系…

为什么别人能做好CSGO游戏搬砖,而你不能?

CSGO搬砖日常出货更新 做Steam游戏搬砖的门槛很低&#xff0c;以至于这两年不断有小白涌入市场&#xff0c;想要在饰品市场中分一杯羹。这个项目是很简单&#xff0c;但想要站稳脚跟&#xff0c;有稳定收入的第一步就得搞清楚项目逻辑。 首先你得搞清楚&#xff0c;steam搬砖盈…

【MySQL系列】PolarDB入门使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

基于孔雀算法优化概率神经网络PNN的分类预测 - 附代码

基于孔雀算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于孔雀算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于孔雀优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

巧妙之中见真章:深入解析常用的创建型设计模式

设计模式之创建型设计模式详解 一、设计模式是什么&#xff1f;二、模板方法2.1、代码结构2.2、符合的设计原则2.3、如何扩展代码2.4、小结 三、观察者模式3.1、代码结构3.2、符合的设计原则3.3、如何扩展代码3.4、小结 四、策略模式4.1、代码结构4.2、符合的设计原则4.3、如何…

计算机视觉面试题-01

计算机视觉面试通常涉及广泛的主题&#xff0c;包括图像处理、深度学习、目标检测、特征提取、图像分类等。以下是一些可能在计算机视觉面试中遇到的常见问题&#xff1a; 图像处理和计算机视觉基础 图像是如何表示的&#xff1f; 图像在计算机中可以通过不同的表示方法&…

Leetcode—28.找出字符串中第一个匹配项的下标【简单】

2023每日刷题&#xff08;四十&#xff09; Leetcode—28.找出字符串中第一个匹配项的下标 实现代码 int strStr(char* haystack, char* needle) {int len1 strlen(haystack);int len2 strlen(needle);int idx -1;int i 0;while(i < len1 - len2) {if(strncmp(haystac…

尺度为什么是sigma?

我们先看中值滤波和均值滤波。 以前&#xff0c;我认为是一样的&#xff0c;没有区分过。 他们说&#xff0c;均值滤波有使图像模糊的效果。 中值滤波有使图像去椒盐的效果。为什么不同呢&#xff1f;试了一下&#xff0c;果然不同&#xff0c;然后追踪了一下定义。 12345&…

从程序员到解决方案工程师:一次跨界的职场冒险

在科技行业里&#xff0c;程序员和解决方案工程师是两个非常常见的职业。虽然这两个职业都需要一定的行业理解和问题解决能力&#xff0c;但它们的工作内容和职责却有很大的不同。 那么&#xff0c;如果一名程序员决定转行做一名解决方案工程师&#xff0c;他会经历怎样的体验…

QXDM Filter使用指南

QXDM Filter使用指南 1. QXDM简介2 如何制作和导入Filter2.1 制作Filter2.1.1 制作Windows环境下Filter2.1.2 制作Linux环境下Filter 2.2 Windows环境下导入Filter 3 Filter配置3.1 注册拨号问题3.1.1 LOG Packets(OTA)3.1.2 LOG Packets3.1.3 Event Reports3.1.4 Message Pack…

Java网络爬虫实战

List item 文章目录 ⭐️写在前面的话⭐️&#x1f4cc;What is it?分类网络爬虫按照系统结构和实现技术&#xff0c;大致可以分为以下几种类型&#xff1a;通用网络爬虫&#xff08;General Purpose Web Crawler&#xff09;、聚焦网络爬虫&#xff08;Focused Web Crawler&a…

关于python中的nonlocal关键字

如果在函数的子函数中需要调用外部变量&#xff0c;一般会看见一个nonlocal声明&#xff0c;类似下面这种&#xff1a; def outer_function():x 10def inner_function():nonlocal xx 1print(x)inner_function()outer_function()在这个例子中&#xff0c;inner_function 引用…

AR眼镜双目光波导/主板硬件方案

AR(增强现实)技术的发展离不开光学元件&#xff0c;而在其中&#xff0c;光波导和Micro OLED被视为AR眼镜光学方案的黄金搭档。光学元件在AR行业中扮演着核心角色&#xff0c;其成本高昂且直接影响用户体验的亮度、清晰度和大小等因素。AR眼镜的硬件成本中&#xff0c;光机部分…

Postman如何使用(二):Postman Collection的创建/使用/导出分享等

一、什么是Postman Collection&#xff1f; Postman Collection是可让您将各个请求分组在一起。 您可以将这些请求组织到文件夹中。中文经常将collection翻译成收藏夹。如果再下文中看到这样的翻译不要觉得意外。Postman Collection会使你的工作效率更上一层楼。Postman Colle…

浅谈现代化城市建设中智慧消防的研究与应用

安科瑞 华楠 【摘要】随着城市现代化发展&#xff0c;城市居住密度愈来愈大&#xff0c;城市建筑结构复杂多样化&#xff0c;高层建筑火灾发生率在不断地升高。对现代化城市面临的消防问题展开讨论&#xff0c;针对智慧消防在现代化城市建设中的现状进行了分析&#xff0c;并提…

肾合胶囊 | 修行人追求的“长生不老”,其实就是一个“增阳消阴”的过程!

关于生命的问题&#xff0c;在人们面前有两条路任你选择。 人的生命活动靠精气来维持&#xff0c;善于保养精气者长生&#xff0c;否则就是短寿。 长生或短寿&#xff0c;只能由你自己选择。 其实要想长生并不是很难的事情&#xff0c;长生药就在你自己身上&#xff0c;只不…

YOLO目标检测——背包检测数据集下载分享【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;各种背包检测数据集说明&#xff1a;背包检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富标签说明&#xff1a;使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(xml)、coco(json)和yolo(txt)三种格式标签…

SpringBoot 2 系列停止维护,Java8 党何去何从?

SpringBoot 2.x 版本正式停止更新维护&#xff0c;官方将不再提供对 JDK8 版本的支持 SpringBoot Logo 版本的新特性 3.2 版本正式发布&#xff0c;亮点包括&#xff1a; 支持 JDK17、JDK21 版本 对虚拟线程的完整支持 JVM Checkpoint Restore&#xff08;Project CRaC&…