MybatisPlus如何处理实体枚举类型转换的教程可以参考:点击查看教程,本文重点分析使用@EnumValue注解转换时遇到的一下错误原因,以及解决方案。

一、背景描述

在操作MybatisPlus的实体属性一般都是基本类型或者对应的引用类型或者String类型。但是MySQL8早已支持enum枚举类型,一直都没有实际操作过,于是想着有个枚举的场景打算实际项目里面用一下。当时在具体数据库表中设计了一个字段state来表示状态,这个状态有3个值:已入组已出组已移除。于是设计了这个字段为enum类型属性。但是在具体查询的时候出现如下的错误:
在这里插入图片描述


二、案例分析

当时具体场景比较简单就两个类:实体类枚举类

实体类(demo)

实体类的定义具体如下,重点关注属性:state

@Data
@EqualsAndHashCode
@TableName("demo")

public class Demo implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 身份证号
     */
    @TableField("id_card")
    private String idCard;

    /**
     * 受试者姓名
     */
    @TableField("`name`")
    private String name;

    /**
     * 联系电话
     */
    @TableField("phone")
    private String phone;

    /**
     * 入组时间
     */
    @TableField("join_time")
    private Date joinTime;

    /**
     * 状态:已入组、已出组、已移除
     */
    @TableField("state")
    private DemoStateEnum state;

    /**
     * 最近一次操作的用户
     */
    @TableField("last_user")
    private String lastUser;

    /**
     * 关联项目ID
     */
    @TableField("project_id")
    private Integer projectId;

    /**
     * 最后一次操作时间
     */
    @TableField("last_update")
    private Date lastUpdate;

    public static Demo builder(){
        return new Demo();
    }
}

枚举类(DemoStateEnum)

public enum DemoStateEnum {

    JOINED("已入组"),
    OUTED("已出组"),
    REMOVED("已移除");
    
    /**
     * 注意这个 @EnumValue注解一定要加上,否则SpringBoot启动都会报错,具体原因是因为MybatisePlus实现了根据这个注解表示实体属性自动枚举类型转换。如果找不到就会报错。
     */
    @EnumValue
    private String state;

    DemoStateEnum(String state){
       this.state = state;
    }
    
    /**
     * @JSONField这个注解解决alibaba.fastjson序列化是枚举显示的索引的值,而我们想要的显示是具体的值:已入组、已出组、已移除等。
     */
    @JSONField
    public String getState() {
        return state;
    }

    @Override
    public String toString() {
        return "DemoStateEnum{" +
                "state='" + state + '\'' +
                '}';
    }
}

表结构

CREATE TABLE `demo` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `id_card` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '身份证号',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '受试者姓名',
  `phone` varchar(13) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '联系电话',
  `join_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '入组时间',
  `state` enum('已入组','已出组','已移除') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '状态:已入组、已出组、已移除',
  `last_user` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '最近一次操作的用户',
  `project_id` int NOT NULL COMMENT '关联项目ID',
  `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后一次操作时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='枚举测试表';

三、单元测试

@SpringBootTest
public class DemoTest {

    @Autowired
    private DemoMapper deemoMapper;
    
    //查询所有demo数据
    @Test
    public void querySubject() throws JsonProcessingException {
        List<Demo> demos = deemoMapper.selectList(null);
        System.err.println(JSON.toJSONString(demos));
    }

}

执行以上单元测试会出现上面的错误,这里我就再贴一下图:
在这里插入图片描述

四、问题分析

# 根据上图细看这句话
Caused by: java.sql.SQLFeatureNotSupportedException
	at com.alibaba.druid.pool.DruidPooledResultSet.getObject(DruidPooledResultSet.java:1771)

找到DruidPooledResultSet.getObject()这个方法打上一个断点,然后根据idea的线程调用栈debug往上确定是由哪一步出现的异常。
在这里插入图片描述
如上图设置好debug点,开始运行上面的单元测试方法querySubject方法,监听断点:
在这里插入图片描述
那怎么知道上层是那个方法触发了这个异常呢,接着看左边的线程调用栈:可以看出黄色部分是上层真实异常的调用方。
在这里插入图片描述
基于以上我们继续往下分析,可以发现MybatisEnumTypeHandler#getNullableResult(java.sql.ResultSet, java.lang.String)这个方法里面的ResultSet的具体参数类型是一个DruidPooledResultSet,然后再调用DruidPooledResultSet类中的getObject(String columnLabel, Class type)方法。

public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
  throw new SQLFeatureNotSupportedException();
}

显然这个方法无论谁调用都是会抛出SQLFeatureNotSupportedException异常,那这样的话这个方法又有什么意义呢,于是猜想着是不是当前的druid是1.1.16(从下图可以看出版本依赖)版本太低导致无法适配当前查询场景,既然版本太低那就升级一下druid版本到1.1.21

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.21</version>
</dependency>

在这里插入图片描述
升级版本1.1.21继续尝试着运行,发现确实成功解决了。从下图可以看出druid版本成功升级从黄色区域可以看出来,那为什么可以解决当前问题呢?于是尝试进入同样的DruidPooledResultSet类中的getObject(String columnLabel, Class type)方法。发现1.1.16版本中的固定异常SQLFeatureNotSupportedException没有了,实现也完全不同了。如下:

public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
  try {
      // 正常调用JDBC中的ResultSet了,所以可以正常获取数据,不再出现那个异常了。
      return this.rs.getObject(columnLabel, type);
  } catch (Throwable var4) {
      throw this.checkException(var4);
  }
}

在这里插入图片描述
查询成功:
在这里插入图片描述

[{"id":4,"idCard":"666666","joinTime":"2023-08-09 15:21:13","lastUpdate":"2023-08-09 15:21:13","lastUser":"test user","name":"test name 6","phone":"123123123","projectId":2,"state":"已出组"},{"id":5,"idCard":"666666","joinTime":"2023-08-09 15:22:28","lastUpdate":"2023-08-09 15:22:28","lastUser":"test user","name":"test name 6","phone":"123123123","projectId":2,"state":"已出组"},{"id":6,"idCard":"666666","joinTime":"2023-08-09 15:24:00","lastUpdate":"2023-08-09 15:24:00","lastUser":"test user","name":"test name 6","phone":"123123123","projectId":2,"state":"已出组"}]

五、其他问题

alibaba.fastjson序列化枚举时候显示一个int值得问题,其实这个int值就是枚举中的ordinary也就是枚举列表中的索引。这个了解java枚举的应该都清楚枚举列表中的值都有自己对应的所有,而且底层也是存储在一个数组中的,也就是相当于数组的下表。但是不希望在序列化的时候显示这个下表,希望显示state的值。如何解决,在枚举的state属性的get方法加上如下@JSONField注解,这样就可以显示state的枚举值了。

    @JSONField
    public String getState() {
        return state;
    }
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐