从零开始 Spring Boot 16:枚举

从零开始 Spring Boot 16:枚举

spring boot

图源:简书 (jianshu.com)

在开发Web应用时,无法避免的是会定义一些“离散值”,比如书籍类型,包含艺术、小说、工程书籍等。在数据库中,我们一般会使用整数来表示这些值,比如1代表小说,2代表艺术,3代表工程相关书籍等。但在代码中使用整数来表示类型可读性就很差了,以前一般使用类常量来表示这些值,但更好的做法是使用枚举。

关于Java中枚举的基本知识,可以阅读Java编程笔记19:枚举 - 魔芋红茶’s blog (icexmoon.cn)

下面通过在我们的图书应用中引入枚举类型来说明如何在Spring Boot项目中使用枚举,以及相关的注意事项。

下面的示例代码都将由从零开始 Spring Boot 15:Http Client - 魔芋红茶’s blog (icexmoon.cn)中的最终代码修改而来,相关完整代码见learn_spring_boot/ch15 (github.com)

为了演示,我们首先需要在项目相关的数据库中添加一个表示书籍类型的字段:

CREATE TABLE `book` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `description` text NOT NULL,
  `user_id` int NOT NULL,
  `type` tinyint NOT NULL DEFAULT '5' COMMENT '书籍类型 1艺术 2小说 3科幻 4历史 5其它',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb3

book表的type字段表示书籍类型。

IEnum

然后我们就需要考虑如何将数据库中整数读取为枚举。

因为我们项目使用了MyBatis Plus,所以可以很容易地通过其定义的通用枚举接口IEnum<T>来实现这一点。只要让自定义枚举实现这个接口,MyBatis Plus在执行SQL后就会自动完成相关的类型转换,Entity相关数据实体就会有相应的枚举常量。

但实际使用中我发现往往需要给实现了IEnum的枚举实现一个额外的getDesc方法用于返回枚举的说明文字,但因为该方法不在IEnum定义中,有时候只有IEnum引用就很难调用该方法。因此我会定义一个扩展自IEnum的自定义接口作为从数据库加载的枚举类型的实现接口:

public interface IDescEnum<T extends Serializable> extends IEnum<T> {
   
    String getDesc();
}

然后就需要创建一个实现了IDescEnum接口的枚举:

public enum BookType implements IDescEnum<Integer> {
   
    ART(1, "艺术"), NOVEL(2, "小说"), SF(3, "科幻"), HISTORY(4, "历史"), OTHER(5, "其它");

    BookType(Integer value, String desc) {
   
        this.value = value;
        this.desc = desc;
    }

    private Integer value;
    private String desc;

    @Override
    public String getDesc() {
   
        return desc;
    }

    @Override
    public Integer getValue() {
   
        return value;
    }
}

当然还要在数据实体(DAO)中添加枚举类型:

public class Book implements Serializable {
   
	...
    private BookType type;
}

很简单,这样就可以使用MyBatis Plus提供的相关API查询数据库,查询后的Book实体会以枚举的形式保存type属性。当然添加和更新时也会将枚举常量转换成对应的整数来保存到数据库。

输入输出

@JsonValue

现在我们已经处理好了应用和数据库之间的枚举转换,但还需要处理HTTP请求和应用之间的枚举转换。

正常情况下我们都希望通过接口传入的枚举相关参数是整数形式。比如在添加图书的时候,需要添加图书类型,传入的参数可能是这样:

{
   
    "name": "自由与和平",
    "desc": "自由与和平",
    "type": 1
}

但实际如果你将DTO中的相关属性定义为枚举,默认情况下接受的是常量的字面量,比如:

{
   
    "name": "自由与和平",
    "desc": "自由与和平",
    "type": "ART"
}

当然也可以使用整数,但枚举常量对应的整数并不是我们定义的value,而是其在enum中的定义顺序,该值可以通过Enum.ordinal方法获取。这个值从0开始,在上边的示例中,枚举常量ART的值是0,NOVEL是1,以此类推。

我认为这样存在一些问题,一来不直观,从enum定义中很难确认具体的枚举常量对应的值,除非数一遍。二来这个值依赖在枚举中定义的顺序,也就是说定义后就不能随便移动位置了,否则就会将入参解析为另一个枚举常量。

实际上这些默认的将入参解析为枚举常量的行为都是依赖于Spring Boot中的JSON解析器的,Spring Boot默认的JSON解析器是Jackson,而Jackson默认对JSON中的枚举类型的解析行为就是上面介绍的那样。

所以要想改变这种默认行为,让传入参数中的枚举类型按照我们定义的IDescEnumvalue属性进行解析,就需要调整Jackson。

幸运的是Jackson提供一个注解@JsonValue可以很容易地做到这点:

public enum BookType implements IDescEnum<Integer> {
   
	...
    @JsonValue
    private Integer value;
    private String desc;
	...
}

就像上面展示的,只要给枚举中的value属性添加@JsonValue,传入参数中的枚举类型就会自动按照其value值转换为对应的枚举常量,此外,返回值中的枚举常量也会被转换为相应的value值。

可以很容易编写一个简单示例来验证这一点:

    @Data
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值