枚举在项目中的实际使用——数据持久层、序列化处理

747 阅读5分钟

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

上篇文章《Java反射之枚举类型》,讲解了枚举的基本用法。今天介绍枚举在项目中的实际使用,包括数据持久层、序列化的使用。

枚举普通用法

先有数据库表order

CREATE TABLE `order` (
  `id` int NOT NULL AUTO_INCREMENT,
  `pay_status` int DEFAULT NULL COMMENT '支付状态,1:未付款;2:已付款;-1:已取消',
  `pay_type` int DEFAULT NULL COMMENT '支付方式,1:微信支付;2:支付宝支付',
  # 其他字段忽略
  PRIMARY KEY (`id`)
)

对应实体类

public class Order {

    private Long id;

    /**
     * 支付状态,1:未付款;2:已付款;-1:已取消
     *
     */
    private Integer payStatus;

    /**
     * 支付方式,1:微信支付;2:支付宝支付
     *
     */
    private Integer payType;
    
    // 其他字段忽略
  

这两个字段:pay_status、pay_type的值可以用枚举表示,如下:

public enum PayStatus {
    WAITING_APY(1, "待支付"),
    FINISH_PAY(2, "支付完成"),
    CANCEL(-1, "已取消"),
    ;

    @EnumValue
    private Integer code;
    private String msg;

    PayStatus(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}
public enum PayStatus {
    WAITING_PAY(1, "待支付"),
    FINISH_PAY(2, "支付完成"),
    RETURN(3, "已退回"),
    ;

    @EnumValue
    private Integer code;
    private String msg;

    PayStatus(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
    
    public static PayStatus getByCode(Integer code) {
        for (PayStatus payStatus : PayStatus.values()) {
            if (Objects.equals(payStatus.getCode(), code)) {
                return payStatus;
            }
        }
        return null;
    }
}

使用枚举

public void createOrder() {
    Order newOrder = new Order();
    newOrder.setPayStatus(PayStatus.WAITING_PAY.getCode());
    newOrder.setPayType(PayType.ALI_PAY.getCode());
}


public void handleOrder() {
    Order order  = orderSerivce.getById(1);
    Integer payStatusCode = order.getPayStatus();
    PayStatus payStatus = PayStatus.getByCode(payStatusCode);
    if (payStatus == PayStatus.RETURN) {
        //业务枚举
    }
}

数据持久层直接使用枚举

像上面的使用枚举方式,有个麻烦的点:需要手动获取枚举值的code,在set到对象中;需要获取枚举,需要调用getByCode()方法。

在Mybatis Plus框架中,可以直接将对象属性定义为枚举类型。

public class Order {

    private Long id;

    /**
     * 支付状态,1:未付款;2:已付款;-1:已取消
     *
     */
    private PayStatus payStatus;

    /**
     * 支付方式,1:微信支付;2:支付宝支付
     *
     */
    private PayType payType;
    
    // 其他字段忽略
  

只需要在与数据表字段对应的枚举属性加上@EnumValue

public enum PayStatus {
    WAITING_PAY(1, "待支付"),
    FINISH_PAY(2, "支付完成"),
    RETURN(3, "已退回"),
    ;

    @EnumValue
    private Integer code;
    private String msg;

    PayStatus(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public static PayStatus getByCode(Integer code) {
        for (PayStatus payStatus : PayStatus.values()) {
            if (Objects.equals(payStatus.getCode(), code)) {
                return payStatus;
            }
        }
        return null;
    }
}

详细可参考官方文档:mp.baomidou.com/guide/enum.…

这样,我们就可以使用枚举进行业务逻辑处理

public void createOrder() {
    Order newOrder = new Order();
    newOrder.setPayStatus(PayStatus.WAITING_PAY);
    newOrder.setPayType(PayType.ALI_PAY);
}

public void handleOrder() {
    Order order  = orderSerivce.getById(1);
    PayStatus payStatus = order.getPayStatus();
    if (payStatus == PayStatus.RETURN) {
        //...
    }
}

前后端交互序列化和反序列化

默认序列化方式

在Spring Boot的Web中,请求的参数和响应数据默认通过com.fasterxml.jackson序列化和反序列化。

枚举序列化成枚举的名称name(即定义的名称)。演示:

@GetMapping("order/get")
public Order getOrder() {
    Order order = new Order();
    order.setId(1);
    order.setPayStatus(PayStatus.WAITING_PAY);
    order.setPayType(PayType.WX_PAY);
    return order;
}

image.png

枚举的反序列化有两种形式:枚举名称和顺序,即java.lang.Enum 类的两个属性nameordinal

演示:

@PostMapping("/order/search")
public List<Order> search(@RequestBody Order order) {

    return null;
}

请求参数: {"id":1,"payType":0,"payStatus":"WAITING_PAY"}

从断点可以看出,payType=0反序列化为WX_PAY枚举,payStatus="WAITING_PAY"反序列化为WAITING_PAY枚举。

image.png

特别注意的一点,反序列化的值为整型时,其表示的是枚举的顺序,而不是枚举属性里的值。比如某个枚举只有2个枚举值(不管属性值是什么),反序列化的取值范围只有0,1,其他都会报错

自定义序列化方式

有时前端只需要我们返回枚举里的属性值,比如payType返回的1,2用来表示微信支付、支付宝支付。我们只需要在该属性值加上@JsonValue注解

public enum PayType {
    WX_PAY(1, "微信支付", 20),
    ALI_PAY(2, "支付宝支付", 10);

    @JsonValue
    private Integer code;
    
    private String msg;
    private int sort;

    PayType(Integer code, String msg, int sort) {
        this.code = code;
        this.msg = msg;
        this.sort = sort;
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public int getSort() {
        return sort;
    }
}

还可以通过实现抽象类JsonSerializerJsonDeserializer序列化和反序列化。具体流程:

  1. 实现抽象类JsonSerializerJsonDeserializer
  2. 在对应的字段加上注解@JsonSerialize@JsonDeserialize

完整代码

序列化

package com.carrywei.breadspring.serializer;

import com.carrywei.breadspring.enums.PayStatus;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * 
 * @version 1.0
 * @date 2021/11/2
 */
@Component
public class PayStatusSerializer extends JsonSerializer {
    @Override
    public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        PayStatus payStatus = (PayStatus) o;
        jsonGenerator.writeString(payStatus.getMsg());
    }
}

反序列化

package com.carrywei.breadspring.serializer;

import com.carrywei.breadspring.enums.PayStatus;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * 
 * @version 1.0
 * @date 2021/11/2
 */
@Component
public class PayStatusDeserializer extends JsonDeserializer<PayStatus> {
    @Override
    public PayStatus deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        int intValue = jsonParser.getIntValue();
        return PayStatus.getByCode(intValue);
    }
}

加注解

/**
 * 支付状态
 */
@JsonSerialize(using = PayStatusSerializer.class)
@JsonDeserialize(using = PayStatusDeserializer.class)
private PayStatus payStatus;
通用反序列化

对于枚举的反序列化,基本上所有的枚举都是一样的逻辑:将枚举属性值反序列化成枚举。为了确定枚举哪个属性值为反序列化用到的,我们定义接口BaseEnum,其中getCode()方法返回该属性值。

public interface BaseEnum<T> {
    /**
     * 获取枚举值
     * @return
     */
    T getCode();

    /**
     * 获取枚举值
     * @return
     */
    String getMsg();
}

此外,所有枚举实现该接口

public enum PayType implements BaseEnum<Integer> {}
public enum PayStatus implements BaseEnum<Integer> {}

在具体反序列化过程中,借助反射技术,实现枚举通用的反序列化方法。完整代码如下:

@Component
@Slf4j
public class BaseEnumJsonDeserializer  extends JsonDeserializer {
    private BucLogger logger = new BucLogger(BaseEnumJsonDeserializer.class);
    @Override
    public BaseEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        BaseEnum result = null;
        try {
            // 获取当前待反序列化的字段名称
            String currentName = jsonParser.getCurrentName();
            // 通过反射获取当前字段
            Field declaredField = jsonParser.getCurrentValue().getClass().getDeclaredField(currentName);
            // 当对象类型是BaseEnum类型的子类
            if (BaseEnum.class.isAssignableFrom(declaredField.getType())) {
                // 枚举值
                Object[] enumConstants = declaredField.getType().getEnumConstants();
                for (Object obj : enumConstants) {
                    if (String.valueOf(((BaseEnum) obj).getCode()).equals(jsonParser.getText())) {
                        result = (BaseEnum) obj;
                        break;
                    }
                }
            }
        } catch (NoSuchFieldException e) {
            logger.info("BaseEnumJsonDeserializer反序列化异常, obj = {}, currentName = {}", jsonParser.getCurrentValue(),
                    jsonParser.getCurrentName());
            throw new BusinessException(CommonCodeMsg.SYSTEM_ERROR);
        }
        return result;
    }
}