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,否则会有优先级混乱的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/79014.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

专题二十一:无线局域网——WLAN

一、WLAN简介 WLAN&#xff08;Wireless Local Area Network &#xff09;无线局域网&#xff0c;使用的是 IEEE 802.11 标准系列。 标准版本发布年份最大传输速率频段Wi-Fi代数特点/描述IEEE 802.1119971–2 Mbps2.4 GHzWi-Fi 0最早的无线局域网标准&#xff0c;传输速率低&…

python多进程的使用

多进程编程全面指南&#xff1a;从入门到实践 摘要&#xff1a;本文是为初学者设计的Python多进程编程全攻略&#xff0c;涵盖基础概念、核心函数详解、系统特性分析&#xff0c;并附带流程图、测试用例、开源项目推荐和经典书籍清单。通过8个实战代码示例和3个性能对比实验&am…

数据库管理与安全:从用户权限到备份恢复的全面指南

引言 在数字化时代&#xff0c;数据已成为组织最宝贵的资产之一。数据库作为存储和管理这些数据的核心系统&#xff0c;其安全性和可靠性直接关系到企业的运营和发展。无论是金融交易记录、医疗健康信息&#xff0c;还是电子商务平台的用户数据&#xff0c;都需要通过完善的数…

Electron Forge【实战】带图片的 AI 聊天

改用支持图片的 AI 模型 qwen-turbo 仅支持文字&#xff0c;要想体验图片聊天&#xff0c;需改用 qwen-vl-plus src/initData.ts {id: 2,name: "aliyun",title: "阿里 -- 通义千问",desc: "阿里百炼 -- 通义千问",// https://help.aliyun.com/z…

在 Elastic 中使用 JOIN 进行威胁狩猎!

作者&#xff1a;来自 Elastic Paul Ewing, Jonhnathan Ribeiro Elastic 的管道查询语言 ES | QL 为查询带来了 join 功能。 威胁狩猎者欢呼吧&#xff01;你是否一直在寻找一种通过 Elastic 的速度和强大功能来连接数据的方法&#xff1f;好消息&#xff01;Elastic 现在可以通…

从实列中学习linux shell5: 利用shell 脚本 检测硬盘空间容量,当使用量达到80%的时候 发送邮件

下面是用于检测硬盘空间并在使用量达到80%时发送邮件的Shell脚本 第一步 编写脚本 #!/bin/bash# 邮件配置 recipient"zhaoqingyou99qhzt.com" subject"磁盘空间警报" mail_cmd"/usr/bin/mail" # 根据实际邮件命令路径修改# 检查是否安装邮件工…

Ethan独立开发产品日报 | 2025-04-30

1. Daytona 安全且灵活的基础设施&#xff0c;用于运行你的人工智能生成代码。 Daytona Cloud重新定义了AI代理的基础设施&#xff0c;具备低于90毫秒的启动时间、原生性能和有状态执行能力&#xff0c;这些是传统云服务无法比拟的。您可以以前所未有的速度和灵活性来创建、管…

Unity SpriteMask(精灵遮罩)

&#x1f3c6; 个人愚见&#xff0c;没事写写笔记 &#x1f3c6;《博客内容》&#xff1a;Unity3D开发内容 &#x1f3c6;&#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f50e;SpriteMask&#xff1a;精灵遮罩 &#x1f4a1;作用就是对精灵图片产生遮罩&#xff0c…

OpenHarmony全局资源调度管控子系统之内存管理部件

OpenHarmony之内存管理部件 内存管理部件 简介目录框架 进程回收优先级列表 补充 回收策略/查杀策略 使用说明参数配置说明 availbufferSizeZswapdParamkillConfignandlife 相关仓 简介 内存管理部件位于全局资源调度管控子系统中&#xff0c;基于应用的生命周期状态&#…

姜老师的MBTI课程笔记小结(1)ENFJ人格

课程文稿&#xff1a; 好&#xff0c;今天我们的重点其实并不在ENTJ&#xff0c;而是在于如果一个人其他都很像&#xff0c;只是在思考和感受这两端选择的时候&#xff0c;他缺了思考而更尊重感受&#xff0c;它会是什么样的一个人格特质呢&#xff1f;这就是ENFG在16人格的学派…

Node.js 应用场景

Node.js 应用场景 引言 Node.js 是一个基于 Chrome V8 JavaScript 引擎的开源、跨平台 JavaScript 运行环境。它主要用于服务器端开发&#xff0c;通过非阻塞 I/O 模型实现了高并发处理能力。本文将详细介绍 Node.js 的应用场景&#xff0c;帮助你了解其在实际项目中的应用。…

Qt/C++面试【速通笔记六】—Qt 中的线程同步

在多线程编程中&#xff0c;多个线程同时访问共享资源时&#xff0c;可能会出现数据不一致或者错误的情况。这时&#xff0c;我们需要线程同步机制来保证程序的正确性。Qt 提供了多种线程同步方式&#xff0c;每种方式适用于不同的场景。 1. 互斥锁&#xff08;QMutex&#xff…

JDK-17 保姆级安装教程(附安装包)

文章目录 一、下载二、安装三、验证是否安装成功1、看 java 和 javac 是否可用2、看 java 和 javac 的版本号是否无问题 一、下载 JDK-17_windows-x64_bin.exe 二、安装 三、验证是否安装成功 java&#xff1a;执行工具 javac&#xff1a;编译工具 1、看 java 和 javac 是否…

【LeetCode Hot100】回溯篇

前言 本文用于整理LeetCode Hot100中题目解答&#xff0c;因题目比较简单且更多是为了面试快速写出正确思路&#xff0c;只做简单题意解读和一句话题解方便记忆。但代码会全部给出&#xff0c;方便大家整理代码思路。 46. 全排列 一句话题意 给定一个无重复数字的序列&#xf…

pytest-前后置及fixture运用

1.pytest中的xunit风格前后置处理 pytest中用例的前后置可以直接使用类似于unittest中的前后置处理&#xff0c;但是pytest中的前后置处理方式更 加丰富&#xff0c;分为模块级、类级、方法级、函数级等不同等级的前后置处理&#xff0c;具体见下面的代码&#xff1a; test_…

使用scipy求解优化问题

一、求解二次规划问题 min(X.T * P * X C.T * X) s.t. Xi > 0 ∑Xi 1 1.定义目标函数 def objective(x):return 0.5 * np.dot(x, np.dot(P, x)) np.dot(c, x)2. 定义等式约束 def equality_constraint(x):return np.sum(x) - 1 3.定义边界约束&#xff1a;x # …

C++初阶-STL简介

目录 1.什么是STL 2.STL的版本 3.STL的六大组件 4.STL的重要性 4.1在笔试中 4.2在面试中 4.3.在公司中 5.如何学习STL 6.总结和之后的规划 1.什么是STL STL&#xff08;standard template library-标准模板库&#xff09;&#xff1b;是C标准库的重要组成部分&#xf…

kivy android打包buildozer.spec GUI配置

这个适合刚刚学习kivyd的道友使用&#xff0c;后面看情况更新 代码 import tkinter as tk from tkinter import ttk, filedialog, messagebox, simpledialog import configparser import os import json # 新增导入class BuildozerConfigTool:def __init__(self, master):se…

MOOS-ivp使用(一)——水下机器人系统的入门与使用

MOOS-ivp使用&#xff08;一&#xff09;——水下机器人系统的入门与使用 MOOS-ivp&#xff08;Marine Operational Oceanographic System for Intelligent Vehicle Planning&#xff09;是专为水下机器人&#xff08;如AUV&#xff09;设计的开源框架。类似于ROS&#xff0c;…