SpringBoot+MyBatis处理JSON字段的完整指南

 更新时间:2026年03月30日 09:23:27   作者:荒古前  
本文主要介绍了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字段内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Springboot实现XSS漏洞过滤的示例代码

    Springboot实现XSS漏洞过滤的示例代码

    这篇文章主要介绍了Springboot实现XSS漏洞过滤的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • Java使用FileOutputStream写Excel文件不落盘的解决方法

    Java使用FileOutputStream写Excel文件不落盘的解决方法

    最近在写 Java 代码处理 Excel 文件的时候,遇到了一个挺头疼的问题:使用 Apache POI 的 XSSFWorkbook.write(FileOutputStream) 方法写文件,生成的 Excel 文件却打不开,所以本文就给大家介绍了Java使用FileOutputStream写Excel文件不落盘的解决方法
    2025-11-11
  • JavaScript 与 Java 区别介绍  学java怎么样

    JavaScript 与 Java 区别介绍 学java怎么样

    JavaScript 是一种嵌入式脚本文件,直接插入网页,有浏览器一边解释一边执行。而java 语言不一样,他必须在JAVA虚拟机上运行。而且事先需要进行编译。接下来脚本之家小编给大家揭晓js与java区别,感兴趣的朋友一起看看吧
    2016-09-09
  • 浅谈Java中生产者与消费者问题的演变

    浅谈Java中生产者与消费者问题的演变

    这篇文章主要介绍了浅谈Java中生产者与消费者问题的演变,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-10-10
  • Java使用Flyway实现数据库版本控制的技术指南

    Java使用Flyway实现数据库版本控制的技术指南

    在现代应用开发中,数据库结构经常随着业务需求不断演变,使用手动SQL脚本管理数据库版本,不仅容易出现错误,还难以跟踪和回滚,Flyway是一个强大的数据库迁移工具,能够帮助开发者高效管理和自动化数据库的版本控制,本文将介绍Flyway的基本功能及其在SpringBoot项目中的实践
    2025-02-02
  • IntelliJ IDEA 2020.2正式发布,两点多多总能助你提效

    IntelliJ IDEA 2020.2正式发布,两点多多总能助你提效

    这篇文章主要介绍了IntelliJ IDEA 2020.2正式发布,诸多亮点总有几款能助你提效,本文通过图文实例代码相结合给大家介绍的非常详细,需要的朋友可以参考下
    2020-07-07
  • java文件上传至ftp服务器的方法

    java文件上传至ftp服务器的方法

    这篇文章主要为大家详细介绍了java文件上传至ftp服务器的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01
  • Java并发之ReentrantLock类源码解析

    Java并发之ReentrantLock类源码解析

    这篇文章主要为大家详细介绍了Java并发系列之ReentrantLock源码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • springboot关于容器启动事件总结

    springboot关于容器启动事件总结

    在本篇文章里小编给大家整理的是一篇关于springboot容器启动事件相关知识点,需要的朋友们学习下。
    2019-10-10
  • java类中serialVersionUID的作用及其使用

    java类中serialVersionUID的作用及其使用

    这篇文章主要介绍了java类中serialVersionUID的作用及其使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12

最新评论