Kevin's blog Kevin's blog
首页
  • AI基础
  • RAG技术
  • 提示词工程
  • Wireshark抓包
  • 常见问题
  • 数据库
  • 代码技巧
  • 浏览器
  • 手册教程
  • 技术应用
  • 流程规范
  • github技巧
  • git笔记
  • vpn笔记
  • 知识概念
  • 学习笔记
  • 环境搭建
  • linux&运维
  • 微服务
  • 经验技巧
  • 实用手册
  • arthas常用
  • spring应用
  • javaAgent技术
  • 网站
友情链接
  • 分类
  • 标签
  • 归档

Kevin

你可以迷茫,但不可以虚度
首页
  • AI基础
  • RAG技术
  • 提示词工程
  • Wireshark抓包
  • 常见问题
  • 数据库
  • 代码技巧
  • 浏览器
  • 手册教程
  • 技术应用
  • 流程规范
  • github技巧
  • git笔记
  • vpn笔记
  • 知识概念
  • 学习笔记
  • 环境搭建
  • linux&运维
  • 微服务
  • 经验技巧
  • 实用手册
  • arthas常用
  • spring应用
  • javaAgent技术
  • 网站
友情链接
  • 分类
  • 标签
  • 归档
  • 常见问题

  • 数据库

  • 代码技巧

    • mybatis

      • mybatis技巧
    • 几分钟处理完30亿个数据
    • AOP实现请求日志
    • JSON处理
    • 枚举类型的全面处理
      • 引言
      • 枚举处理的常见挑战
      • 实现自定义序列化器与反序列化器 (处理出参入参中的枚举)
        • IEnumToJson 接口
        • 自定义 JSON 序列化器 OcloudJsonEnumSerializer
        • 自定义 JSON 反序列化器 OcloudJsonEnumDeserializer
        • 简单例子解释
      • 数据库交互中枚举处理
        • IEnum 接口
        • 泛型的定义
        • 自定义 MyBatis-Plus 类型处理器 OcloudEnumTypeHandler
        • OcloudEnumTypeHandler 类的工作原理
        • 配置 MyBatis-Plus 类型处理器
      • 枚举支持出参入参、数据库处理
      • 总结
      • 更多思考
    • httpClientUtil
    • 如何优雅的写 Controller 层代码
    • 看源码技巧
    • http-curl例子
    • 动态的添加注解信息
    • 手动安装jar到maven仓库
  • 浏览器

  • spring应用

  • 使用Java Agent字节码技术扩展
  • 什么是AP,什么是CP,什么是CAP
  • RabbitMq相关
  • ELK查询技巧
  • 性能优化手段
  • 经验技巧
  • 代码技巧
kevin
2024-08-24
目录

枚举类型的全面处理

# 枚举类型的全面处理:序列化、反序列化与数据库交互

# 引言

在企业级应用开发中,枚举类型广泛用于代表固定集合的数据,如状态码、配置选项等。然而,处理枚举类型的序列化、反序列化及其与数据库的交互常常给开发者带来挑战。本文将详细解释如何在 Java 应用中使用 MyBatis-Plus 和 Jackson 来优雅地处理枚举类型。

# 枚举处理的常见挑战

  1. 前后端数据交换:前端需要可读的描述,后端需存储为枚举。
  2. 数据持久化:数据库通常不直接支持枚举类型,需要转换为字符串或整数。
  3. 代码维护:枚举类型的频繁变动需要灵活的代码设计来适应这些变更。

# 实现自定义序列化器与反序列化器 (处理出参入参中的枚举)

# IEnumToJson 接口

IEnumToJson 接口用于支持枚举类型的自定义 JSON 序列化。

@JsonDeserialize(using = OcloudJsonEnumDeserializer.class)
public interface IEnumToJson extends Serializable {

    /**
     * 将枚举转换成map
     * @return
     */
    @JsonValue
    default Map<String,Object> toMap() {
        return EnumUtils.getFieldValueMap(this.getClass(),this);
    }

    /**
     * 根据枚举name获取枚举值
     * @param enumName
     * @param clazz
     * @param <E>
     * @return
     */
    static <E extends Enum<E> & IEnumToJson> E toEnum(String enumName, Class<E> clazz) {
        if (StringUtils.isEmpty(enumName)) {
            return null;
        }
        return Enum.valueOf(clazz, enumName);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

用途解释:

这个接口扩展了Serializable,意味着实现此接口的枚举可以被序列化。接口提供了两个主要的方法:

  • toMap(): 默认方法,返回枚举的属性映射到一个Map对象。这个方法依赖于一个辅助类EnumUtils,用于获取枚举类中的字段及其值。
  • toEnum(String enumName, Class<E> clazz): 静态方法,根据提供的枚举名称和枚举类,返回相应的枚举实例。这是一个通用方法,用于在反序列化时将JSON字符串转换成相应的枚举类型。

# 自定义 JSON 序列化器 OcloudJsonEnumSerializer

public class OcloudJsonEnumSerializer extends JsonSerializer<IEnumToJson> {
    @Override
    public void serialize(IEnumToJson value, JsonGenerator gen, SerializerProvider serializers) throws IOException { ;
        gen.writeStartObject();
        value.toMap().entrySet().stream().forEach(entry -> {
            try {
                gen.writeObjectField(entry.getKey(),entry.getValue());
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        gen.writeEndObject();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

用途解释:

这是一个自定义的JSON序列化器,继承自JsonSerializer<IEnumToJson>。它的主要职责是:

  • 在序列化过程中,将枚举实例转换为JSON对象。通过调用枚举的toMap()方法获取枚举的字段和值,然后将这些键值对写入JSON。
  • 用于处理当枚举作为返回参数时,如何将枚举序列化成JSON。比如,如果一个API返回一个枚举类型的状态字段,这个序列化器将枚举转换成一个具体的JSON对象,可能包含枚举的名称、代码、描述等。

# 自定义 JSON 反序列化器 OcloudJsonEnumDeserializer

@Slf4j
public class OcloudJsonEnumDeserializer extends JsonDeserializer<IEnumToJson> {

    private final String ENUM_NAME = "name";

    @Override
    @SuppressWarnings("unchecked")
    public IEnumToJson deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        try {
            JsonNode node = jp.getCodec().readTree(jp);
            String currentName = jp.currentName();
            Object currentValue = jp.getCurrentValue();
            @SuppressWarnings("rawtypes")
            Class findPropertyType = BeanUtils.findPropertyType(currentName, currentValue.getClass());
            IEnumToJson valueOf;
            JsonNode jsonNode = node.get(ENUM_NAME);
            if(JsonNodeType.ARRAY.equals(node.getNodeType())){
                jsonNode = node.get(1).get(ENUM_NAME);
            }
            if (jsonNode == null) {
                valueOf = (IEnumToJson) IEnumToJson.toEnum(node.asText(), findPropertyType);
            } else {
                valueOf = (IEnumToJson) IEnumToJson.toEnum(jsonNode.asText(), findPropertyType);
            }
            return valueOf;
        } catch (Exception e) {
            log.error("Error deserializing enum field: {}", jp.currentName(), e);
            log.error("JsonNode content: {}", jp.getCodec().readTree(jp).toString());
            return null;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

用途解释:

这是一个自定义的JSON反序列化器,继承自JsonDeserializer<IEnumToJson>。其主要功能包括:

  • 在反序列化过程中,从JSON数据中读取并转换为对应的枚举实例。通过解析JSON节点,尝试获取"name"字段,然后使用toEnum方法将其转换为枚举实例。
  • 错误处理和日志记录:在反序列化过程中出现错误时,记录错误信息,方便问题定位和调试。
  • 用于处理接收到的JSON中含有枚举类型数据时,如何将这些JSON数据反序列化成具体的枚举类型。这是在解析JSON请求体时非常有用,尤其是当API需要根据接收到的枚举值来执行特定操作时。

# 简单例子解释

假设有一个API /api/updateStatus,其接收一个JSON对象作为请求体,其中包含一个名为 status 的字段,该字段的值是一个枚举类型 StatusEnum 的名称。

  1. 枚举定义:

    public enum StatusEnum implements IEnumToJson {
        ACTIVE("Active"),
        INACTIVE("Inactive");
    
        private String description;
    
        StatusEnum(String description) {
            this.description = description;
        }
    
        @JsonValue
        public Map<String, Object> toMap() {
            Map<String, Object> map = new HashMap<>();
            map.put("name", this.name());
            map.put("description", this.description);
            return map;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
  2. 使用 OcloudJsonEnumDeserializer 反序列化: 当接收到如下JSON请求体时:

    {
        "status": "ACTIVE"
    }
    
    1
    2
    3

    反序列化器将根据 "status" 字段的值 "ACTIVE",查找 StatusEnum 枚举中对应的枚举常量 StatusEnum.ACTIVE。

  3. 使用 OcloudJsonEnumSerializer 序列化: 当API处理完请求后,假设需要返回这个状态,序列化器将 StatusEnum.ACTIVE 枚举常量转换成如下JSON格式:

    {
        "status": {
            "name": "ACTIVE",
            "description": "Active"
        }
    }
    
    1
    2
    3
    4
    5
    6

​ 这个处理方式使得枚举值在前后端之间的传递更为清晰明确,且能够携带更多的信息,比如描述等。

# 数据库交互中枚举处理

# IEnum 接口

IEnum 接口允许枚举类附带一个可以存储到数据库的值。

public interface IEnum<T extends Serializable> {
    T getValue();
}
1
2
3

用途解释:

  • getValue: 提供一个方法来获取枚举关联的数据库值(如整数或字符串)。这个值是用于数据库存储和读取的,确保枚举与数据库之间的类型兼容。

IEnum 接口是否需要取决于具体需求。如果您的枚举处理不需要与特定的数据类型(如整数、字符串等)关联,或者您不需要在枚举上实现通用的接口方法,那么您可能不需要使用 IEnum 接口。在许多情况下,直接使用 IEnumToJson 可以满足需求,尤其是在涉及到JSON序列化和反序列化时。

# 泛型的定义

泛型 <T> 在 IEnum 接口中通常用来指定与枚举相关联的数据类型。这种类型可以是任何类,例如 Integer、String 等,用于定义枚举类型所关联的数据(如数据库中的存储值)。用于定义获取枚举实例关联值的方法,这里它有一个 getValue() 方法用于返回枚举的业务值(例如,数据库中存储的具体值)。枚举类通过实现这个接口,可以明确告诉 OcloudEnumTypeHandler 如何从枚举类型中提取这个业务值。

# 自定义 MyBatis-Plus 类型处理器 OcloudEnumTypeHandler

public class OcloudEnumTypeHandler<E extends IEnum<T>,T extends Serializable> extends BaseTypeHandler<E> {

    private final Class<E> type;

    private final E[] enums;

    public OcloudEnumTypeHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
        this.enums = type.getEnumConstants();
        if (this.enums == null) {
            //throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
        }
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setObject(i,parameter.getValue());
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Object value = rs.getObject(columnName);
        if (value != null && rs.wasNull()) {
            return null;
        }
        return getValue(value);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Object value = rs.getObject(columnIndex);
        if (value != null && rs.wasNull()) {
            return null;
        }
        return getValue(value);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Object value = cs.getObject(columnIndex);
        if (value != null && cs.wasNull()) {
            return null;
        }
        return getValue(value);
    }


    private E getValue(Object value) {
        try {
            return Arrays.stream(enums).filter(e -> Objects.equals(value,e.getValue())).findFirst().orElse(null);
        } catch (Exception ex) {
            throw new IllegalArgumentException("Cannot convert " + value + " to " + type.getSimpleName() + " by value["+value+"]", ex);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# OcloudEnumTypeHandler 类的工作原理

  1. 构造函数:
    • 接受一个枚举类型 Class<E> 作为参数。
    • 检查传入的类型是否为 null,如果是,则抛出异常。
    • 获取这个枚举类型的所有常量(即枚举值),存储在 enums 数组中。
  2. 设置参数(写入数据库):
    • 当需要将枚举类型的数据写入数据库时,会调用 setNonNullParameter 方法。
    • 这个方法使用枚举的 getValue() 方法来获取对应的业务值,然后将这个值作为SQL命令的参数。
  3. 获取结果(从数据库读取):
    • 当从数据库读取数据转换成枚举类型时,会使用 getNullableResult 方法。
    • 这些方法从结果集中获取指定列的值。
    • 如果该值非空,将会调用 getValue 方法来将这个值转换回对应的枚举类型。
    • getValue 方法遍历所有枚举常量,寻找其业务值与从数据库获取的值匹配的枚举项。

# 配置 MyBatis-Plus 类型处理器

在 application.yml 中添加配置:

mybatis-plus:
  configuration:
    default-enum-type-handler: com.olight.cloud.mybatis.type.OcloudEnumTypeHandler
1
2
3

# 枚举支持出参入参、数据库处理

  • 只需要实现IEnum与IEnumToJson接口即可
@Getter
@AllArgsConstructor
public enum LogTypeEnum implements IEnum<Integer>, IEnumToJson {
    /**
     * 登录日志
     */
    LOGIN(0),
    /**
     * 操作日志
     */
    OPERATION(1),
    /**
     * 异常日志
     */
    ERROR(2);

    private final int code;

    @Override
    public Integer getValue() {
        return code;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 总结

通过上述设计,我们可以全面地解决枚举类型在 Java 应用中的序列化、反序列化及数据库交互问题。这不仅提高了代码的可维护性,也提高了开发效率。

# 更多思考

除了使用 MyBatis-Plus 和 Jackson,其他技术如 JPA Attribute Converter 也可以实现类似功能,提供不同的优势,如更紧密的集成到 JPA 生命周期等。开发者可以根据项目具体需求选择最合适的技术路径。

希望本文能帮助您更好地理解和应用枚举类型的处理策略,更高效地开发和维护您的 Java 应用。

上次更新: 2025/01/13, 10:51:06
JSON处理
httpClientUtil

← JSON处理 httpClientUtil→

最近更新
01
AI是如何学习的
06-05
02
chatGpt提示原则
06-05
03
提示词工程实践指南
06-05
更多文章>
| Copyright © 2022-2025 Kevin | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式