MySQL JSON字段存储 + Java对应 + MyBatis Plus 增删改查全教程
·
MySQL JSON字段存储 + Java对应 + MyBatis Plus 增删改查全教程
本文完整覆盖:MySQL JSON字段创建、Java实体对应类型、MyBatis Plus 自动序列化/反序列化、增删改查实战样例(含JSON字段条件查询),代码可直接复制使用。
一、MySQL 如何存储 JSON 字段
1. 前提要求
MySQL 5.7及以上版本 原生支持 JSON 数据类型,相比字符串存储优势:
- 自动校验JSON格式合法性
- 支持高效JSON字段查询、索引
- 存储空间更优
2. 建表语句(JSON字段定义)
直接用 JSON 关键字声明字段即可,示例创建用户表,info 为JSON字段:
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(50) NOT NULL COMMENT '用户名',
`info` json DEFAULT NULL COMMENT '用户详情JSON',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
二、MySQL JSON字段 对应 Java 什么类型?
方案1:String 类型(简单但不推荐)
直接用 String 接收JSON字符串,需要手动序列化/反序列化,操作繁琐。
private String info; // 对应MySQL JSON字段
方案2:自定义对象 + MyBatis Plus 类型处理器(✅ 推荐)
MyBatis Plus 提供自动序列化/反序列化能力,将JSON字段直接映射为Java对象,面向对象操作,无需手动处理JSON。
- 对应Java类型:自定义实体类
- 依赖MP自带的
JacksonTypeHandler(基于Jackson自动转换)
三、环境准备(Maven依赖)
SpringBoot + MyBatis Plus 核心依赖:
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis Plus Boot Starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok 简化代码 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
四、MyBatis Plus 实战代码
1. 定义JSON对应的Java嵌套对象
对应MySQL info JSON字段的结构:
import lombok.Data;
/**
* JSON字段对应的Java实体
* 对应MySQL info字段: {"age":20,"address":"北京","phone":"13800138000"}
*/
@Data
public class UserInfo {
private Integer age;
private String address;
private String phone;
}
2. 主实体类(核心:JSON字段映射)
关键注解:
@TableName(autoResultMap = true):必须开启,否则类型处理器不生效@TableField(typeHandler = JacksonTypeHandler.class):指定JSON字段的类型处理器
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.Data;
import java.util.Date;
@Data
// 开启自动结果映射(必须)
@TableName(value = "user", autoResultMap = true)
public class User {
// 主键自增
@TableId(type = IdType.AUTO)
private Long id;
private String username;
// JSON字段:绑定类型处理器,自动转换Java对象 <-> MySQL JSON
@TableField(typeHandler = JacksonTypeHandler.class)
private UserInfo info;
private Date createTime;
}
3. Mapper & Service 层(MP原生接口)
Mapper接口
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
Service接口
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {
}
Service实现类
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
五、MyBatis Plus 增删改查 完整样例
直接在测试类/Controller中调用,JSON字段自动处理,无需手动转JSON字符串。
1. 新增(插入带JSON字段的数据)
@SpringBootTest
class MybatisPlusJsonTest {
@Resource
private UserService userService;
@Test
void addUser() {
// 1. 组装JSON对象
UserInfo info = new UserInfo();
info.setAge(20);
info.setAddress("北京市朝阳区");
info.setPhone("13800138000");
// 2. 组装主实体
User user = new User();
user.setUsername("张三");
user.setInfo(info); // 直接赋值Java对象
user.setCreateTime(new Date());
// 3. 新增(MP自动将info转为JSON存入MySQL)
boolean save = userService.save(user);
System.out.println("新增结果:" + save);
}
}
2. 修改(更新JSON字段)
@Test
void updateUser() {
// 1. 新的JSON数据
UserInfo newInfo = new UserInfo();
newInfo.setAge(21);
newInfo.setAddress("上海市浦东新区");
newInfo.setPhone("13900139000");
// 2. 更新实体
User user = new User();
user.setId(1L);
user.setInfo(newInfo); // 直接更新JSON对象
// 3. 修改(MP自动更新MySQL JSON字段)
boolean update = userService.updateById(user);
System.out.println("修改结果:" + update);
}
3. 查询(普通查询 + JSON字段条件查询)
3.1 普通查询(自动返回Java对象)
@Test
void selectUser() {
// 根据ID查询
User user = userService.getById(1L);
// 直接获取JSON对应的Java对象,无需反序列化
UserInfo info = user.getInfo();
System.out.println("用户信息:" + user);
System.out.println("JSON字段解析:年龄=" + info.getAge() + ",地址=" + info.getAddress());
}
3.2 条件查询(查询JSON字段内部值)
使用MySQL JSON函数查询:
字段->'$.属性':推荐写法JSON_EXTRACT(字段, '$.属性'):原生写法
示例:查询 info 中 age=21 的用户
@Test
void selectByJsonCondition() {
// 构造查询条件:info.age = 21
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
// 重点:MySQL JSON字段条件查询语法
wrapper.apply("info->'$.age' = {0}", 21);
// 执行查询
List<User> userList = userService.list(wrapper);
System.out.println("JSON条件查询结果:" + userList);
}
4. 删除
@Test
void deleteUser() {
// 根据ID删除
boolean remove = userService.removeById(1L);
System.out.println("删除结果:" + remove);
}
六、关键注意事项
-
必须开启
autoResultMap = true
实体类的@TableName注解必须加这个参数,否则类型处理器无法生效,会导致JSON字段映射失败。 -
类型处理器选择
JacksonTypeHandler:MP默认,依赖Jackson(无需额外配置)FastjsonTypeHandler:若用Fastjson,需替换依赖+注解
-
JSON字段空值处理
直接赋值null即可,MP会自动存储为NULL。 -
JSON字段索引
若需要频繁查询JSON内部字段,可给MySQL JSON字段创建函数索引,提升查询效率。
总结
- MySQL用**
JSON类型**存储JSON数据; - Java对应自定义对象,配合MP的
JacksonTypeHandler自动转换; - MyBatis Plus 增删改查直接操作Java对象,无需手动处理JSON字符串;
- JSON字段条件查询使用
字段->'$.属性'语法。
package com.example.demo.utils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.reflection.property.PropertyNamer;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.util.Collection;
/**
* 纯手写 JSON Lambda 工具类
* 【完全独立】不依赖 MyBatis Plus 内部 API,仅保留 MP 注解作为可选兼容
* 解决 3.5.x 版本 API 重构导致的报错
*/
public class JsonLambdaUtils {
/**
* 核心方法:双Lambda生成MySQL JSON查询语句
* @param entityLambda 主实体Lambda(如 User::getInfo)
* @param jsonLambda JSON对象Lambda(如 UserInfo::getAge)
* @return 示例:info->'$.age'
*/
public static <T, J> String json(SFunction<T, J> entityLambda, SFunction<J, ?> jsonLambda) {
// 1. 解析主实体 Lambda -> 数据库列名
String entityColumnName = getEntityColumnName(entityLambda);
// 2. 解析 JSON 对象 Lambda -> 属性名
String jsonFieldName = getLambdaFieldName(jsonLambda);
// 3. 拼接 MySQL JSON 语法
return String.format("%s->'$.%s'", entityColumnName, jsonFieldName);
}
/**
* 【核心独立实现】通过 SerializedLambda 解析实体字段名
*/
private static <T> String getEntityColumnName(SFunction<T, ?> func) {
try {
// 1. 强制序列化获取 SerializedLambda (核心黑魔法)
SerializedLambda lambda = getSerializedLambda(func);
// 2. 获取 Lambda 对应的方法名 (例如: getInfo)
String methodName = lambda.getImplMethodName();
// 3. 方法名转属性名 (getInfo -> info)
String fieldName = PropertyNamer.methodToProperty(methodName);
// 4. 获取实体类类型 (例如: com.example.demo.entity.User)
// 注意:这里直接用 getInstantiatedMethodType() 获取类描述符,或者强制转换
// 为了兼容不同版本,我们直接通过反射获取泛型类型
Class<?> entityClass = getFunctionalInterfaceClass(lambda);
// 5. 从 MP 表信息中获取最终的数据库列名 (保留 MP 注解适配,如需纯手写可替换逻辑)
TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
if (tableInfo == null) {
throw new RuntimeException("未找到实体类 [" + entityClass.getName() + "] 的表信息,请检查注解");
}
// 6. 根据属性名匹配数据库列名 (支持 @TableField 注解的列名映射)
Collection<TableFieldInfo> fieldList = tableInfo.getFieldList();
for (TableFieldInfo fieldInfo : fieldList) {
if (fieldInfo.getProperty().equals(fieldName)) {
return fieldInfo.getColumn();
}
}
// 如果找不到,直接返回属性名(默认情况)
return fieldName;
} catch (Exception e) {
throw new RuntimeException("解析实体 Lambda 失败", e);
}
}
/**
* 解析 JSON 对象的字段名
*/
private static <J> String getLambdaFieldName(SFunction<J, ?> func) {
try {
SerializedLambda lambda = getSerializedLambda(func);
String methodName = lambda.getImplMethodName();
return PropertyNamer.methodToProperty(methodName);
} catch (Exception e) {
throw new RuntimeException("解析 JSON Lambda 失败", e);
}
}
/**
* 【核心独立工具】获取 SerializedLambda
* 这是 Java 标准 API,不依赖任何第三方框架
*/
private static SerializedLambda getSerializedLambda(Serializable func) {
try {
// 通过反射调用 writeReplace 方法,获取序列化代理对象
java.lang.reflect.Method method = func.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(true);
return (SerializedLambda) method.invoke(func);
} catch (Exception e) {
throw new RuntimeException("获取 SerializedLambda 失败,请确保 SFunction 是可序列化的", e);
}
}
/**
* 【核心独立工具】通过 Lambda 获取实体类类型
*/
private static Class<?> getFunctionalInterfaceClass(SerializedLambda lambda) {
// 方案1: 尝试从 implMethodName 或其他字段推断 (较复杂)
// 方案2: 更简单可靠的方式 - 强制转换并通过泛型提取 (推荐)
// 这里我们直接通过类加载器获取实例化的类
String implClass = lambda.getImplClass().replace("/", ".");
try {
return Class.forName(implClass);
} catch (ClassNotFoundException e) {
throw new RuntimeException("加载实体类失败: " + implClass, e);
}
}
// ====================== 新增:JSON解引用表达式(用于字符串匹配) ======================
public static <T, J> String jsonUnquote(SFunction<T, J> entityLambda, SFunction<J, ?> jsonLambda) {
String column = getEntityColumnName(entityLambda);
String field = getLambdaFieldName(jsonLambda);
// ->> :MySQL解引用,去掉JSON字符串的引号,必须用于like/字符串eq
return String.format("%s->>'$.%s'", column, field);
}
// ====================== 新增:封装eq/like/in通用方法 ======================
/**
* JSON 等于查询
* @param wrapper 查询包装器
* @param entityLambda 实体JSON字段(如 User::getInfo)
* @param jsonLambda JSON内部字段(如 UserInfo::getAge)
* @param value 匹配值
*/
public static <T, J> void jsonEq(LambdaQueryWrapper<T> wrapper,
SFunction<T, J> entityLambda,
SFunction<J, ?> jsonLambda,
Object value) {
String expr = json(entityLambda, jsonLambda);
wrapper.apply(expr + " = ?", value);
}
/**
* JSON 模糊查询(字符串)
*/
public static <T, J> void jsonLike(LambdaQueryWrapper<T> wrapper,
SFunction<T, J> entityLambda,
SFunction<J, ?> jsonLambda,
String value) {
String expr = jsonUnquote(entityLambda, jsonLambda);
wrapper.apply(expr + " LIKE ?", "%" + value + "%");
}
/**
* JSON IN 查询
*/
public static <T, J> void jsonIn(LambdaQueryWrapper<T> wrapper,
SFunction<T, J> entityLambda,
SFunction<J, ?> jsonLambda,
Collection<?> values) {
String expr = json(entityLambda, jsonLambda);
wrapper.apply(expr + " IN ({0})", values);
}
/**
* 通用函数式接口,用于替代 MP 的 SFunction (如果不想引入 MP 依赖)
* 请将此接口放在独立的包下,例如 com.example.demo.function
*/
@FunctionalInterface
public interface SFunction<T, R> extends Serializable {
R apply(T t);
}
}
更多推荐


所有评论(0)