MyBatis 类型处理器(TypeHandler)注册与映射机制:JsonListTypeHandler和JsonListTypeHandler注册时机

下面几种机制会让你的 List<String>/Map<String,?> 能正确读写成 JSON 数组/对象文本:

  1. MyBatis-Plus 自动注册

    最新版本的 MyBatis-Plus starter 会把类路径下所有带 @MappedTypes({List.class})@MappedJdbcTypes(JdbcType.VARCHAR) 这类注解的 TypeHandler 自动注册进 TypeHandlerRegistry,所以即使你不在 ConfigurationCustomizer 里再手动 registry.register(...),MyBatis-Plus 启动时也会把它们扫描进来。

  2. 字段注解强制指定

    如果你的实体里写了

    @TableField(typeHandler = JsonListTypeHandler.class) private List<String> skills;

    那 MyBatis 在构建映射的时候会直接 new 这个 JsonListTypeHandler 来处理该字段,不会再走默认的 StringTypeHandler

  3. JDBC 驱动的容错

    就算真没有任何自定义 Handler,JDBC 驱动也会把你传进去的字符串(比如你在 Mapper XML 里写 #{skills},skills.toString() 恰好是 ["a","b"])原封不动地当 VARCHAR 存到 DB,当你再查回 String 字段时,就能看到 JSON 文本。


所以你看到"即使把那两行全局注册删掉,也能正常上传/查询",并不奇怪:

  • MyBatis-Plus Starter 在后台已经给你注册好了 List/Map 的 JSON 处理器;

  • 你要么在实体里用注解显式选了 Handler,要么 DB 和驱动自己把字符串存下来了。

如果你真想验证"到底哪条路径在生效",可以:

  1. 断点调试 JsonListTypeHandler#setNonNullParameter(...)

    直接看看在执行插入/更新时,MyBatis 到底走的是哪个 Handler 的方法。

  2. 临时写个 ConfigurationCustomizer 打日志

    @Bean public ConfigurationCustomizer customizer() { return config -> { config.getTypeHandlerRegistry() .getTypeHandler(List.class) .ifPresent(h -> System.out.println("List 类型处理器是:" + h.getClass())); }; }

启动后控制台能看到到底用的哪个 Handler。

核心原理回顾

  1. @MappedTypes 注解

    在类上打了 @MappedTypes(List.class)@MappedTypes(Map.class),MyBatis-Plus Starter 在启动时会 扫描自动注册 这些类型处理器。

    • 如果你的版本里这个自动扫描没开,就算有 @MappedTypes,MyBatis 也不会加载它,你就必须在 ConfigurationCustomizer 里手动 registry.register(...)
  2. 字段级别指定

    如果你在实体类字段上写了

    @TableField(typeHandler = JsonListTypeHandler.class) private List<String> skills;

    那就算全局没注册,MyBatis 也会给该字段硬绑定到这个 handler 上。

  3. MyBatis-Plus 内置 JSON 处理器

    新版本里,MP 自带了基于 Jackson 的 JacksonTypeHandler,它也会给带 @MappedTypes 的类自动装上。

    所以你如果单纯删掉全局注册,MP 可能已经自己把 JsonListTypeHandlerJsonMapTypeHandler 扫进去了,看起来就"好像不用注册也行"了。


为什么你"以前删掉会报错"?

  • 旧版本 的 MP Starter 并不自动扫描你自己写的 JsonListTypeHandler,也没给 List.classMap.class 任何默认 handler。

  • 这时,插入或查询带 List<String> 字段,就会抛错:

    Type handler was null for parameter List

  • 你就逼不得已在 ConfigurationCustomizer 里手动注册,或者在字段上加 @TableField(typeHandler=...),才跑通。


现在为啥又"删了也能正常"?

  • 升级后 ,MP 已经把所有带 @MappedTypes 注解的 handler 自动扫描并注册 了;

  • 或者你给字段加了 @TableField(typeHandler=...),绕过了全局注册需求;

  • 又或者你的表里该列其实是 VARCHARTEXT,你直接在 service 里手动 JSON.toString(list) 存,插入成功------但是反查时就只能拿到 String。


建议

  1. 保留你的 Handler 类 ,毕竟它能让你自动 在实体里写 List<String>Map<String,?>

  2. 如果想最简化,全局注册可以删,但要确保你的 MP Starter 版本 ≥ 3.5 且内置扫描打开:

    mybatis-plus: type-handlers-package: com.example.chat2.handler

  3. 或者在每个 JSON 字段上写:

    @TableField(typeHandler = JsonListTypeHandler.class) private List<String> skills;

    这样就算全局没扫到,也能按字段绑定。


小结

  • JsonListTypeHandler/JsonMapTypeHandler 本身是你项目必需的,只是注册方式可以有三条路:

    1. 全局 ConfigurationCustomizer 手动注册

    2. @MappedTypes + MP 自动扫描

    3. 字段上 @TableField(typeHandler=...)

  • 你"以前没写注册就报错"是因为那会儿自动扫描没开;现在自动扫描打开了,注册看似"多余"了,但在老版本或没有字段注解的情况下,它依然是必不可少的。

希望理清了为什么「看着好像多余」又「删掉又会报错」的来龙去脉!

而我是在xml写了这个

<result property="skills" column="skills" typeHandler="com.example.chat2.handler.JsonListTypeHandler"/>

<result property="tools" column="tools" typeHandler="com.example.chat2.handler.JsonListTypeHandler"/>

MyBatis 会直接按照你指定的 typeHandler 来做序列化/反序列化,这种方式和在 Java 里用 @MappedTypes 或者在全局注册处理器是等价的------它们都是最终把 JSON 文本和 List<String> 对象挂钩。

是XML 显式覆盖

在 <resultMap> 或 <select> 的 <result> 里写 typeHandler="...",MyBatis 在映射这个列的时候,跳过默认的类型推断,直接 new 指定的 JsonListTypeHandler。

不再依赖自动扫描

无需再借助 @MappedTypes、type-handlers-package 或者 ConfigurationCustomizer 全局注册,都能保证该字段走你想要的 Handler。

清晰直观

只要看 XML 就知道哪些列要走 JSON 处理,不会被其他配置"隐式"影响。

何时用哪种方式?

方式 优点 缺点
XML 中 typeHandler 最直观,按字段精确控制;不依赖额外扫描 每个字段都得在 XML 定义一次,比较啰嗦
字段注解 @TableField(typeHandler=...) 配置集中在实体类;配合 MP 自动生成也能生效 如果你写 XML,而是用 MP 的 Wrapper/注解方式,则需要这样,XML 与注解混用时可能有重复
全局自动扫描(@MappedTypes + Starter 或者 ConfigurationCustomizer 一次注册,全表所有 List/Map 列自动生效 控制粒度粗,所有同类型字段都会走同一个 Handler

小贴士

如果你只在少数几个字段用 JSON,XML 显式 是最简单可靠的方式;

如果全项目大量用到,建议用 全局扫描 或者 字段注解,免得 XML 太长;

切勿同时对同一个字段在 XML、注解和全局注册里都写不同的 Handler,否则会有优先级混乱的问题。

相关推荐
天“码”行空16 分钟前
java面向对象的三大特性之一多态
java·开发语言·jvm
毕设源码-郭学长20 分钟前
【开题答辩全过程】以 基于SpringBoot框架的民俗文化交流与交易平台的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
好大哥呀1 小时前
Java Web的学习路径
java·前端·学习
f***14771 小时前
SpringBoot实战:高效实现API限流策略
java·spring boot·后端
on the way 1231 小时前
day06-SpringDI 依赖注入
java·spring
odoo中国1 小时前
Odoo 19 模块结构概述
开发语言·python·module·odoo·核心组件·py文件按
C***11501 小时前
Spring aop 五种通知类型
java·前端·spring
BD_Marathon2 小时前
SpringBoot——多环境开发配置
java·spring boot·后端
代码N年归来仍是新手村成员2 小时前
【Java转Go】即时通信系统代码分析(一)基础Server 构建
java·开发语言·golang
Z1Jxxx2 小时前
01序列01序列
开发语言·c++·算法