Mybatis-Plus 解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!
Mybatis-Plus 通用枚举虽然解决了枚举类型与数据库存储值之间的映射关系,但对前后端数据交互过程的枚举类型处理说明很少,本文着重介绍枚举值在前后端数据交互过程中序列化和反序列化处理逻辑。
当然你可以可以采用另外一种方式处理,系统编码值的问题,在数据库中维护一张编码表,使用数据库关联查询或Java后端匹配,同时将下拉列表值通过后端接口返回给前端,不过这不是本文讲述的重点。
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.1version>
dependency>
mybatis-plus:
type-enums-package: com.example.demo.enumeration
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;
}
}
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;
}
后端返回结果给前端过程,视为序列化过程;
根据实际需要,使用
@JsonValue
标记字段或者@JsonFormat(shape= JsonFormat.Shape.OBJECT)
标记枚举类。二者的区别如下
说明:
1.若不指定
@JsonCreator
,jackson反序列化时则使用@JsonValue标记的字段做映射;2.若不指定
@JsonCreator
且未指定@JsonValue
,jackson反序列化时使用枚举实例name()
值做映射;
序列化结果:
"sex": "女"
序列化结果:
"sex": {
"code": "1",
"name": "女"
}
前端传参给后端视为反序列化过程;
不同的请求方式和传参方式,
反序列化
处理逻辑不一样,记住一点,application/json
请求方式(POST请求),走jackson反序列化逻辑,其余方式不走jackson反序列化逻辑(默认使用枚举实例name()
值做匹配 区分大小写)
=> 反序列化
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
逻辑
- 优先使用name匹配(若枚举类中配置了
@JsonValue
注解,则将配置注解的字段值作为可选的name值列表,否则使用枚举实例name作为name值列表 默认区分大小写)- 若使用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;
}
其它方式,包括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