SpringBoot+MyBatis處理JSON字段的完整指南
前言
最近在開發(fā)一個活動發(fā)布功能時,遇到了一個讓人頭疼的問題:前端傳遞的聯(lián)系方式信息(contactInfo)是一個 JSON 對象,但后端始終無法正確接收和存儲到數(shù)據(jù)庫。經(jīng)過一番折騰,終于完美解決了。本文將詳細(xì)記錄這個問題和解決方案,希望對遇到類似問題的朋友有所幫助。
問題場景
我需要實(shí)現(xiàn)一個活動發(fā)布接口,其中聯(lián)系方式字段 contactInfo 需要存儲多個信息(如微信、QQ、手機(jī)號等),設(shè)計如下:
數(shù)據(jù)庫表結(jié)構(gòu):
CREATE TABLE activity (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
title VARCHAR(50),
category VARCHAR(20),
activity_type VARCHAR(50),
activity_time DATETIME,
city VARCHAR(50),
people_limit INT,
current_people INT,
contact_info JSON, -- JSON 類型字段
contact_visibility VARCHAR(20),
create_time DATETIME,
status TINYINT
);前端傳參格式:
{
"title": "今晚王者三排",
"category": "GAME",
"activityType": "王者榮耀",
"activityTime": "2026-03-23 20:00:00",
"city": "南京",
"peopleLimit": 3,
"contactInfo": {
"wechat": "test123",
"qq": "123456789",
"phone": "13800138000"
},
"contactVisibility": "PUBLIC"
}遇到的坑
坑1:LocalDateTime 反序列化失敗
錯誤信息:
Cannot deserialize value of type `java.time.LocalDateTime` from String "2026-03-23 20:00:00"
原因: Jackson 默認(rèn)的日期格式是 yyyy-MM-ddTHH:mm:ss,但前端傳的是 yyyy-MM-dd HH:mm:ss(空格分隔)。
解決方案: 在 DTO 中添加 @JsonFormat 注解
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime activityTime;
坑2:contactInfo 類型不匹配
錯誤信息:
Cannot deserialize value of type `java.lang.String` from Object value
原因: 前端傳的是對象 {"wechat":"test123"},但后端 DTO 定義的是 String 類型。
坑3:MyBatis 無法處理 JSON 字段
錯誤信息:
Type handler was null on parameter mapping for property 'contactInfo'
原因: MyBatis 默認(rèn)不知道如何將 Java 對象轉(zhuǎn)換為數(shù)據(jù)庫的 JSON 字段。
坑4:自增 ID 返回 null
錯誤信息:
Column 'activity_id' cannot be null
原因: MyBatis insert 后沒有返回自增 ID,導(dǎo)致插入 activity_member 時 activity_id 為 null。
解決方案
經(jīng)過多次嘗試,我選擇了最簡單直接的方案:手動轉(zhuǎn)換 JSON
1. DTO 使用 Object 類型接收
package com.jade.partnermatch.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ActivityCreateDTO {
private String title;
private String category;
private String activityType;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime activityTime;
private String city;
private Integer peopleLimit;
private Object contactInfo; // 使用 Object 接收,可以是字符串或?qū)ο?
private String contactVisibility;
}2. Entity 使用 String 存儲
package com.jade.partnermatch.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Activity {
private Long id;
private Long userId;
private String title;
private String category;
private String activityType;
private LocalDateTime activityTime;
private String city;
private Integer peopleLimit;
private Integer currentPeople;
private String contactInfo; // 存 JSON 字符串
private String contactVisibility;
private LocalDateTime createTime;
private Integer status;
}3. Service 手動轉(zhuǎn)換
package com.jade.partnermatch.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jade.partnermatch.dto.ActivityCreateDTO;
import com.jade.partnermatch.entity.Activity;
import com.jade.partnermatch.mapper.ActivityMapper;
import com.jade.partnermatch.mapper.ActivityMemberMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Service
public class ActivityService {
@Autowired
private ActivityMapper activityMapper;
@Autowired
private ActivityMemberMapper activityMemberMapper;
@Autowired
private ObjectMapper objectMapper; // 注入 Jackson
@Transactional
public void create(ActivityCreateDTO dto) {
Activity activity = new Activity();
activity.setUserId(1L);
activity.setTitle(dto.getTitle());
activity.setCategory(dto.getCategory());
activity.setActivityType(dto.getActivityType());
activity.setActivityTime(dto.getActivityTime());
activity.setCity(dto.getCity());
activity.setPeopleLimit(dto.getPeopleLimit());
activity.setCurrentPeople(1);
// 關(guān)鍵:將 Object 轉(zhuǎn)換為 JSON 字符串
String contactInfoJson = convertToJson(dto.getContactInfo());
activity.setContactInfo(contactInfoJson);
activity.setContactVisibility(dto.getContactVisibility());
activity.setCreateTime(LocalDateTime.now());
activity.setStatus(0);
// 先插入活動,獲取自增 ID
activityMapper.insert(activity);
// 再插入活動成員
activityMemberMapper.insert(activity.getId(), activity.getUserId());
}
private String convertToJson(Object obj) {
if (obj == null) {
return null;
}
try {
return objectMapper.writeValueAsString(obj);
} catch (Exception e) {
e.printStackTrace();
return "{}";
}
}
}4. Mapper XML 配置自增 ID
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jade.partnermatch.mapper.ActivityMapper">
<!-- 關(guān)鍵:添加 useGeneratedKeys 和 keyProperty -->
<insert id="insert" parameterType="Activity" useGeneratedKeys="true" keyProperty="id">
INSERT INTO activity
(user_id, title, category, activity_type, activity_time, city,
people_limit, current_people, contact_info, contact_visibility,
create_time, status)
VALUES
(#{userId}, #{title}, #{category}, #{activityType}, #{activityTime}, #{city},
#{peopleLimit}, #{currentPeople}, #{contactInfo}, #{contactVisibility},
#{createTime}, #{status})
</insert>
<select id="list" resultType="Activity">
SELECT * FROM activity
WHERE status = 0
ORDER BY create_time DESC
</select>
</mapper>5. 如果需要查詢時解析 JSON
// 在查詢后解析 JSON 字符串為 Map
public List<Activity> list() {
List<Activity> activities = activityMapper.list();
for (Activity activity : activities) {
if (activity.getContactInfo() != null) {
try {
Map<String, Object> contactInfoMap = objectMapper.readValue(
activity.getContactInfo(),
Map.class
);
// 使用 contactInfoMap...
} catch (Exception e) {
e.printStackTrace();
}
}
}
return activities;
}最終效果
數(shù)據(jù)庫成功存儲 JSON 數(shù)據(jù):
mysql> select * from activity;
+----+---------+--------------+----------+---------------+---------------------+------+--------------+----------------+-----------------------+--------------------+---------------------+--------+
| id | user_id | title | category | activity_type | activity_time | city | people_limit | current_people | contact_info | contact_visibility | create_time | status |
+----+---------+--------------+----------+---------------+---------------------+------+--------------+----------------+-----------------------+--------------------+---------------------+--------+
| 1 | 1 | 今晚王者三排 | GAME | 王者榮耀 | 2026-03-23 20:00:00 | 南京 | 3 | 2 | {"wechat": "test123"} | PUBLIC | 2026-03-24 20:22:59 | 0 |
| 2 | 1 | 今晚王者三排 | GAME | 王者榮耀 | 2026-03-23 20:00:00 | 南京 | 3 | 1 | {"wechat": "test123"} | PUBLIC | 2026-03-26 13:50:05 | 0 |
| 3 | 1 | 今晚王者三排 | GAME | 王者榮耀 | 2026-03-23 20:00:00 | 南京 | 3 | 1 | {"wechat": "test123"} | PUBLIC | 2026-03-26 13:51:45 | 0 |
| 4 | 1 | 今晚王者三排 | GAME | 王者榮耀 | 2026-03-23 20:00:00 | 南京 | 3 | 1 | {"wechat": "test123"} | PUBLIC | 2026-03-26 13:53:44 | 0 |
+----+---------+--------------+----------+---------------+---------------------+------+--------------+----------------+-----------------------+--------------------+---------------------+--------+
4 rows in set (0.00 sec)方案優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 簡單直接:不需要學(xué)習(xí)復(fù)雜的 TypeHandler
- 完全可控:自己決定如何轉(zhuǎn)換,易于調(diào)試
- 兼容性好:前端傳字符串或?qū)ο蠖寄芴幚?/li>
- 代碼清晰:邏輯一目了然
缺點(diǎn)
- 查詢時需要手動解析 JSON
- 如果需要頻繁操作 JSON 內(nèi)的字段,性能稍差
其他可選方案
方案2:使用 MyBatis 的 JacksonTypeHandler
在 Mapper XML 中配置:
<result column="contact_info" property="contactInfo"
typeHandler="org.apache.ibatis.type.JacksonTypeHandler"/>方案3:自定義 TypeHandler
實(shí)現(xiàn) BaseTypeHandler 接口,自己處理 JSON 轉(zhuǎn)換。
總結(jié)
- JSON 字段接收:DTO 使用
Object或Map類型 - JSON 字段存儲:Entity 使用
String類型 - 轉(zhuǎn)換工具:使用 Jackson 的
ObjectMapper - 自增 ID:MyBatis insert 添加
useGeneratedKeys和keyProperty
遇到類似問題不要慌,一步步排查,總能找到解決方案。
到此這篇關(guān)于SpringBoot+MyBatis處理JSON字段的完整指南的文章就介紹到這了,更多相關(guān)SpringBoot+MyBatis處理JSON字段內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Lombok同時使?@Data和@Builder踩坑總結(jié)
這篇文章主要介紹了Lombok同時使?@Data和@Builder踩坑總結(jié),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值需要的小伙伴可以參考一下,希望對你的學(xué)習(xí)有所幫助2022-05-05
從零實(shí)現(xiàn)一個簡單的Spring Bean容器的代碼案例
Spring是一個非常流行的Java?Web開發(fā)框架,它提供了強(qiáng)大的依賴注入、面向切面編程、聲明式事務(wù)管理等功能,為開發(fā)者提供了高效、快速地構(gòu)建Web應(yīng)用程序的工具,在這篇文章中,咱們將一步一步地構(gòu)建一個簡單的SpringBean容器,需要的朋友可以參考下2023-06-06
JAVA對象中使用?static?和?String?基礎(chǔ)探究
這篇文章主要介紹了JAVA對象中使用static和String基礎(chǔ)探究,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09
淺析SpringBoot中使用thymeleaf找不到.HTML文件的原因
這篇文章主要介紹了SpringBoot中使用thymeleaf找不到.HTML文件的原因分析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07
mybatis的insert語句插入數(shù)據(jù)時的返回值的實(shí)現(xiàn)
這篇文章主要介紹了mybatis的insert語句插入數(shù)據(jù)時的返回值的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10

