利用 Lombok 与 MyBatis 实现 List 类型字段的自动序列化与反序列化
在 Java 后端开发中,将 List 类型字段存入数据库时的序列化与反序列化一直是个繁琐的问题。本文介绍如何通过 Lombok 和 MyBatis-Plus 的配合,用两个注解优雅地解决这个痛点。
一个常见的烦恼
写 Java 后端的同学大概都遇到过这样的场景:业务上需要在某个实体里存一组标签、一组编码,或者一组配置项,用 List 来表示再自然不过了。但到了落库的时候,关系型数据库压根不认识 List 这种类型,你不得不手动把它序列化成 JSON 字符串存进去,读的时候再反序列化回来。
这段转换代码写一次还好,写多了就会发现到处都是重复的 JSON.toJSONString() 和 JSON.parseArray(),既啰嗦又容易出错——少写一个泛型参数,运行时就给你一个 ClassCastException。
有没有办法让框架帮我们把这件事自动做了?答案是有的,而且只需要两个注解。
解决方案:两个注解搞定一切
核心思路很简单:让 MyBatis-Plus 的 JacksonTypeHandler 来接管序列化和反序列化的工作,再配合 Lombok 消除样板代码。来看一个完整的实体类:
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.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
@Data // Lombok注解:自动生成getter、setter等方法
@Accessors(chain = true) // 启用链式调用风格
@TableName(value = "data", autoResultMap = true) // 指定表名并启用自动结果映射
public class Data {
@TableId(type = IdType.AUTO) // 指定主键生成策略为自增
private Integer id; // 主键ID
@TableField(typeHandler = JacksonTypeHandler.class) // 关键配置:使用Jackson处理器自动转换
private List<String> code; // 需要自动序列化/反序列化的List字段
}
就是这样,没有多余的转换代码,没有手写的 TypeHandler,干净利落。
背后发生了什么
看起来只加了几个注解,但背后的协作机制值得了解一下,这样遇到问题时才知道往哪里排查。
@TableName(autoResultMap = true) 是容易被忽略的一步。默认情况下 MyBatis-Plus 生成的 ResultMap 不会应用自定义的 TypeHandler,加上 autoResultMap = true 之后,框架才会在查询结果映射时使用你指定的处理器。漏掉这个配置,写入没问题,但读出来的字段会是 null 或者一个原始的 JSON 字符串——这是最常见的踩坑点。
@TableField(typeHandler = JacksonTypeHandler.class) 告诉框架这个字段需要特殊处理。在插入或更新时,JacksonTypeHandler 会调用 Jackson 把 List 序列化成 JSON 字符串写入数据库;在查询时,再把 JSON 字符串反序列化回 List 对象。整个过程对业务代码完全透明。
@Data 则是 Lombok 的经典注解,自动生成 getter、setter、toString()、equals() 和 hashCode(),让实体类保持极简。配合 @Accessors(chain = true),还能写出 new Data().setId(1).setCode(List.of("A", "B")) 这样的链式调用,代码读起来很流畅。
为什么选择这种方式
和手动转换相比,这种方案的优势不仅仅是少写几行代码:
- 不容易出错:手动转换时,序列化和反序列化的泛型类型必须严格匹配,稍有不慎就是运行时异常。交给 JacksonTypeHandler,类型推断由框架处理,出错概率大大降低。
- 改动集中:如果将来要换序列化方式(比如从 Jackson 换成 Fastjson),只需要替换 TypeHandler 的类名,不用满项目找手动转换的代码。
- 性能有保障:Jackson 是 Java 生态中最成熟的 JSON 库之一,内部做了大量优化,比自己手写的转换逻辑更可靠。
什么时候适合用
这种方案并不是万能的,它最适合以下场景:
- 字段存储的是简单列表数据(如标签、编码、配置项),不需要对列表元素做单独查询或索引。
- 数据结构相对稳定,不会频繁变更 List 内部元素的类型。
- 不想为了存几个字符串就多建一张关联表,一个 JSON 字段就能解决的事情,没必要过度设计。
但如果你需要对列表中的元素做复杂查询(比如”找出所有包含某个标签的记录”),那还是老老实实建关联表更合适,JSON 字段在这种场景下的查询性能和灵活性都不够理想。
小结
回过头来看,这个方案的本质就是把重复性的类型转换工作交给框架去做。两个注解的配合——autoResultMap = true 负责读,JacksonTypeHandler 负责读写转换——覆盖了完整的序列化生命周期。代码量少了,出错的机会也少了,这才是工具和框架应该带给我们的价值。