Mybatis-Plus 通用枚举及前后端交互实战经验

一、前言

Mybatis-Plus 解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!
Mybatis-Plus 通用枚举虽然解决了枚举类型与数据库存储值之间的映射关系,但对前后端数据交互过程的枚举类型处理说明很少,本文着重介绍枚举值在前后端数据交互过程中序列化和反序列化处理逻辑。
当然你可以可以采用另外一种方式处理,系统编码值的问题,在数据库中维护一张编码表,使用数据库关联查询或Java后端匹配,同时将下拉列表值通过后端接口返回给前端,不过这不是本文讲述的重点。

二、使用示例

1.全局配置

  • pom.xml
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
  • application.yml
mybatis-plus:
  type-enums-package: com.example.demo.enumeration

2.枚举类

package com.example.demo.enumeration;

import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.JsonNode;

// @JsonFormat(shape= JsonFormat.Shape.OBJECT)
public enum SexEnum {
    WOMAN("1", "女"), MAN("0", "男");

    SexEnum(String code, String name) {
        this.code = code;
        this.name = name;
    }

    // mybatis枚举类型与数据存储值映射
    @EnumValue
    private String code;
    // 序列化结果值
    @JsonValue
    private String name;

    public String getCode() {
        return code;
    }

    public String getName() {
        return name;
    }

    // 若不配置@JsonCreator,jackson反序列化时则使用@JsonValue标记的字段做映射
    @JsonCreator
    public static SexEnum jacksonInstance(final JsonNode jsonNode) {
        String code = jsonNode.asText();
        SexEnum[] values = SexEnum.values();
        for (SexEnum sexEnum : values) {
            if (sexEnum.getCode().equals(code)) {
                return sexEnum;
            }
        }
        return null;
    }
}

3.实体类

package com.example.demo.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.example.demo.enumeration.SexEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * 用户信息表(省略无关属性)
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "sys_user")
public class SysUser {
    /**
     * 用户ID
     */
    @TableId(value = "user_id", type = IdType.AUTO)
    private Long userId;

    /**
     * 用户性别(0男 1女 2未知)
     */
    @TableField(value = "sex")
    private SexEnum sex;

}

三、后端返回结果给前端(以jackson为例)

后端返回结果给前端过程,视为序列化过程;

根据实际需要,使用@JsonValue标记字段或者@JsonFormat(shape= JsonFormat.Shape.OBJECT)标记枚举类。二者的区别如下

1.@JsonValue

说明:

1.若不指定@JsonCreator,jackson反序列化时则使用@JsonValue标记的字段做映射;

2.若不指定@JsonCreator且未指定@JsonValue,jackson反序列化时使用枚举实例name()值做映射;

序列化结果:

 "sex": "女"

2.@JsonFormat(shape= JsonFormat.Shape.OBJECT)

序列化结果:

    "sex": {
      "code": "1",
      "name": "女"
    }

四、前端传参给后端

前端传参给后端视为反序列化过程;

不同的请求方式和传参方式,反序列化处理逻辑不一样,记住一点,application/json请求方式(POST请求),走jackson反序列化逻辑,其余方式不走jackson反序列化逻辑(默认使用枚举实例name()值做匹配 区分大小写

枚举类型反序列化过程

1.POST请求-application/json方式

=> 反序列化

com.fasterxml.jackson.databind.deser.BeanDeserializer#deserializeFromObject

=> 反序列化并调用setter

com.fasterxml.jackson.databind.deser.impl.MethodProperty#deserializeAndSet

=> 若配置了@JsonCreator,则调用@JsonCreator注解的方法com.example.demo.enumeration.SexEnum#jacksonInstance,否则调用com.fasterxml.jackson.databind.deser.std.EnumDeserializer#deserialize

com.example.demo.enumeration.SexEnum#jacksonInstance

=> 枚举反序列化 (重点)核心代码详见附①

com.fasterxml.jackson.databind.deser.std.EnumDeserializer#deserialize

=> 反序列化逻辑


  • 若前端传值枚举类型为String则走_fromString逻辑

    1. 优先使用name匹配(若枚举类中配置了@JsonValue注解,则将配置注解的字段值作为可选的name值列表,否则使用枚举实例name作为name值列表 默认区分大小写
    2. 若使用name匹配不到,则进行强制为Integer,作为枚举values下标匹配

    附② com.fasterxml.jackson.databind.deser.std.EnumDeserializer#_fromString

  • 若前端传值枚举类型为int则走_fromInteger逻辑

    直接使用枚举values下标匹配

    附③ com.fasterxml.jackson.databind.deser.std.EnumDeserializer#_fromInteger


附①

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
    {
        // Usually should just get string value:
        // 04-Sep-2020, tatu: for 2.11.3 / 2.12.0, removed "FIELD_NAME" as allowed;
        //   did not work and gave odd error message.
        if (p.hasToken(JsonToken.VALUE_STRING)) {
            return _fromString(p, ctxt, p.getText());
        }

        // But let's consider int acceptable as well (if within ordinal range)
        if (p.hasToken(JsonToken.VALUE_NUMBER_INT)) {
            // 26-Sep-2021, tatu: [databind#1850] Special case where we get "true" integer
            //    enumeration and should avoid use of {@code Enum.index()}
            if (_isFromIntValue) {
                // ... whether to rely on "getText()" returning String, or get number, convert?
                // For now assume all format backends can produce String:
                return _fromString(p, ctxt, p.getText());
            }
            return _fromInteger(p, ctxt, p.getIntValue());
        }

        // 29-Jun-2020, tatu: New! "Scalar from Object" (mostly for XML)
        if (p.isExpectedStartObjectToken()) {
            return _fromString(p, ctxt,
                    ctxt.extractScalarFromObject(p, this, _valueClass));
        }
        return _deserializeOther(p, ctxt);
    }

附②

    protected Object _fromString(JsonParser p, DeserializationContext ctxt,
            String text)
        throws IOException
    {
        CompactStringObjectMap lookup = ctxt.isEnabled(DeserializationFeature.READ_ENUMS_USING_TO_STRING)
                ? _getToStringLookup(ctxt) : _lookupByName;
        // 优先使用name匹配
        Object result = lookup.find(text);
        if (result == null) {
            String trimmed = text.trim();
            if ((trimmed == text) || (result = lookup.find(trimmed)) == null) {
                // 使用name匹配时,强转为Integer作为values数组下标匹配
                return _deserializeAltString(p, ctxt, lookup, trimmed);
            }
        }
        return result;
    }

附③

    protected Object _fromInteger(JsonParser p, DeserializationContext ctxt,
            int index)
        throws IOException
    {
        final CoercionAction act = ctxt.findCoercionAction(logicalType(), handledType(),
                CoercionInputShape.Integer);

        // First, check legacy setting for slightly different message
        if (act == CoercionAction.Fail) {
            if (ctxt.isEnabled(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)) {
                return ctxt.handleWeirdNumberValue(_enumClass(), index,
                        "not allowed to deserialize Enum value out of number: disable DeserializationConfig.DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS to allow"
                        );
            }
            // otherwise this will force failure with new setting
            _checkCoercionFail(ctxt, act, handledType(), index,
                    "Integer value ("+index+")");
        }
        switch (act) {
        case AsNull:
            return null;
        case AsEmpty:
            return getEmptyValue(ctxt);
        case TryConvert:
        default:
        }
        if (index >= 0 && index < _enumsByIndex.length) {
            // values数组下标匹配
            return _enumsByIndex[index];
        }
        if ((_enumDefaultValue != null)
                && ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE)) {
            return _enumDefaultValue;
        }
        if (!ctxt.isEnabled(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL)) {
            return ctxt.handleWeirdNumberValue(_enumClass(), index,
                    "index value outside legal index range [0..%s]",
                    _enumsByIndex.length-1);
        }
        return null;
    }

2.其它方式

其它方式,包括GET请求、POST请求-application/x-www-form-urlencoded方式、POST请求-multipart/form-data方式,均不走jackson反序列化逻辑;

默认使用枚举实例name()值做匹配 (区分大小写)

参考:

Mybatis-Plus 通用枚举

【Mybatis】mybatis-plus 通用枚举 @JsonValue 接收参数报错 No enum constant

Jackson – Deserialization from json to Java enums

  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬山境KL攻城狮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值