SpringBoot+MyBatis處理JSON字段的完整指南
前言
最近在開發(fā)一個活動發(fā)布功能時,遇到了一個讓人頭疼的問題:前端傳遞的聯系方式信息(contactInfo)是一個 JSON 對象,但后端始終無法正確接收和存儲到數據庫。經過一番折騰,終于完美解決了。本文將詳細記錄這個問題和解決方案,希望對遇到類似問題的朋友有所幫助。
問題場景
我需要實現一個活動發(fā)布接口,其中聯系方式字段 contactInfo 需要存儲多個信息(如微信、QQ、手機號等),設計如下:
數據庫表結構:
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 默認的日期格式是 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 默認不知道如何將 Java 對象轉換為數據庫的 JSON 字段。
坑4:自增 ID 返回 null
錯誤信息:
Column 'activity_id' cannot be null
原因: MyBatis insert 后沒有返回自增 ID,導致插入 activity_member 時 activity_id 為 null。
解決方案
經過多次嘗試,我選擇了最簡單直接的方案:手動轉換 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 接收,可以是字符串或對象
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 手動轉換
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);
// 關鍵:將 Object 轉換為 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">
<!-- 關鍵:添加 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;
}最終效果
數據庫成功存儲 JSON 數據:
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)缺點
優(yōu)點
- 簡單直接:不需要學習復雜的 TypeHandler
- 完全可控:自己決定如何轉換,易于調試
- 兼容性好:前端傳字符串或對象都能處理
- 代碼清晰:邏輯一目了然
缺點
- 查詢時需要手動解析 JSON
- 如果需要頻繁操作 JSON 內的字段,性能稍差
其他可選方案
方案2:使用 MyBatis 的 JacksonTypeHandler
在 Mapper XML 中配置:
<result column="contact_info" property="contactInfo"
typeHandler="org.apache.ibatis.type.JacksonTypeHandler"/>方案3:自定義 TypeHandler
實現 BaseTypeHandler 接口,自己處理 JSON 轉換。
總結
- JSON 字段接收:DTO 使用
Object或Map類型 - JSON 字段存儲:Entity 使用
String類型 - 轉換工具:使用 Jackson 的
ObjectMapper - 自增 ID:MyBatis insert 添加
useGeneratedKeys和keyProperty
遇到類似問題不要慌,一步步排查,總能找到解決方案。
到此這篇關于SpringBoot+MyBatis處理JSON字段的完整指南的文章就介紹到這了,更多相關SpringBoot+MyBatis處理JSON字段內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JAVA對象中使用?static?和?String?基礎探究
這篇文章主要介紹了JAVA對象中使用static和String基礎探究,文章圍繞主題展開詳細的內容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09
淺析SpringBoot中使用thymeleaf找不到.HTML文件的原因
這篇文章主要介紹了SpringBoot中使用thymeleaf找不到.HTML文件的原因分析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07

