SpringBoot+MyBatis处理JSON字段的完整指南
前言
最近在开发一个活动发布功能时,遇到了一个让人头疼的问题:前端传递的联系方式信息(contactInfo)是一个 JSON 对象,但后端始终无法正确接收和存储到数据库。经过一番折腾,终于完美解决了。本文将详细记录这个问题和解决方案,希望对遇到类似问题的朋友有所帮助。
问题场景
我需要实现一个活动发布接口,其中联系方式字段 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)方案优缺点
优点
- 简单直接:不需要学习复杂的 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字段内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Java使用FileOutputStream写Excel文件不落盘的解决方法
最近在写 Java 代码处理 Excel 文件的时候,遇到了一个挺头疼的问题:使用 Apache POI 的 XSSFWorkbook.write(FileOutputStream) 方法写文件,生成的 Excel 文件却打不开,所以本文就给大家介绍了Java使用FileOutputStream写Excel文件不落盘的解决方法2025-11-11
JavaScript 与 Java 区别介绍 学java怎么样
JavaScript 是一种嵌入式脚本文件,直接插入网页,有浏览器一边解释一边执行。而java 语言不一样,他必须在JAVA虚拟机上运行。而且事先需要进行编译。接下来脚本之家小编给大家揭晓js与java区别,感兴趣的朋友一起看看吧2016-09-09
IntelliJ IDEA 2020.2正式发布,两点多多总能助你提效
这篇文章主要介绍了IntelliJ IDEA 2020.2正式发布,诸多亮点总有几款能助你提效,本文通过图文实例代码相结合给大家介绍的非常详细,需要的朋友可以参考下2020-07-07


最新评论