下面幾種機制會讓你的 List<String>
/Map<String,?>
能正確讀寫成 JSON 數組/對象文本:
-
MyBatis-Plus 自動注冊
最新版本的 MyBatis-Plus starter 會把類路徑下所有帶@MappedTypes({List.class})
、@MappedJdbcTypes(JdbcType.VARCHAR)
這類注解的TypeHandler
自動注冊進TypeHandlerRegistry
,所以即使你不在ConfigurationCustomizer
里再手動registry.register(...)
,MyBatis-Plus 啟動時也會把它們掃描進來。 -
字段注解強制指定
如果你的實體里寫了@TableField(typeHandler = JsonListTypeHandler.class) private List<String> skills;
那 MyBatis 在構建映射的時候會直接 new 這個
JsonListTypeHandler
來處理該字段,不會再走默認的StringTypeHandler
。 -
JDBC 驅動的容錯
就算真沒有任何自定義 Handler,JDBC 驅動也會把你傳進去的字符串(比如你在 Mapper XML 里寫#{skills}
,skills.toString() 恰好是["a","b"]
)原封不動地當VARCHAR
存到 DB,當你再查回String
字段時,就能看到 JSON 文本。
所以你看到“即使把那兩行全局注冊刪掉,也能正常上傳/查詢”,并不奇怪:
-
MyBatis-Plus Starter 在后臺已經給你注冊好了 List/Map 的 JSON 處理器;
-
你要么在實體里用注解顯式選了 Handler,要么 DB 和驅動自己把字符串存下來了。
如果你真想驗證“到底哪條路徑在生效”,可以:
-
斷點調試
JsonListTypeHandler#setNonNullParameter(...)
直接看看在執行插入/更新時,MyBatis 到底走的是哪個 Handler 的方法。 -
臨時寫個
ConfigurationCustomizer
打日志@Bean public ConfigurationCustomizer customizer() { return config -> { config.getTypeHandlerRegistry() .getTypeHandler(List.class) .ifPresent(h -> System.out.println("List 類型處理器是:" + h.getClass())); }; }
啟動后控制臺能看到到底用的哪個 Handler。
核心原理回顧
-
@MappedTypes
注解
在類上打了@MappedTypes(List.class)
或@MappedTypes(Map.class)
,MyBatis-Plus Starter 在啟動時會 掃描 并 自動注冊 這些類型處理器。-
如果你的版本里這個自動掃描沒開,就算有
@MappedTypes
,MyBatis 也不會加載它,你就必須在ConfigurationCustomizer
里手動registry.register(...)
。
-
-
字段級別指定
如果你在實體類字段上寫了@TableField(typeHandler = JsonListTypeHandler.class) private List<String> skills;
那就算全局沒注冊,MyBatis 也會給該字段硬綁定到這個 handler 上。
-
MyBatis-Plus 內置 JSON 處理器
新版本里,MP 自帶了基于 Jackson 的JacksonTypeHandler
,它也會給帶@MappedTypes
的類自動裝上。
所以你如果單純刪掉全局注冊,MP 可能已經自己把JsonListTypeHandler
/JsonMapTypeHandler
掃進去了,看起來就“好像不用注冊也行”了。
為什么你“以前刪掉會報錯”?
-
舊版本 的 MP Starter 并不自動掃描你自己寫的
JsonListTypeHandler
,也沒給List.class
或Map.class
任何默認 handler。 -
這時,插入或查詢帶
List<String>
字段,就會拋錯:Type handler was null for parameter List
-
你就逼不得已在
ConfigurationCustomizer
里手動注冊,或者在字段上加@TableField(typeHandler=…)
,才跑通。
現在為啥又“刪了也能正常”?
-
升級后,MP 已經把所有帶
@MappedTypes
注解的 handler 自動掃描并注冊 了; -
或者你給字段加了
@TableField(typeHandler=…)
,繞過了全局注冊需求; -
又或者你的表里該列其實是
VARCHAR
/TEXT
,你直接在 service 里手動JSON.toString(list)
存,插入成功——但是反查時就只能拿到 String。
建議
-
保留你的 Handler 類,畢竟它能讓你自動在實體里寫
List<String>
/Map<String,?>
。 -
如果想最簡化,全局注冊可以刪,但要確保你的 MP Starter 版本 ≥ 3.5 且內置掃描打開:
mybatis-plus: type-handlers-package: com.example.chat2.handler
-
或者在每個 JSON 字段上寫:
@TableField(typeHandler = JsonListTypeHandler.class) private List<String> skills;
這樣就算全局沒掃到,也能按字段綁定。
小結
-
JsonListTypeHandler/JsonMapTypeHandler 本身是你項目必需的,只是注冊方式可以有三條路:
-
全局
ConfigurationCustomizer
手動注冊 -
@MappedTypes
+ MP 自動掃描 -
字段上
@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,否則會有優先級混亂的問題。