MySQL JSON字段存储 + Java对应 + MyBatis Plus 增删改查全教程
MySQL JSON字段存储 Java对应 MyBatis Plus 增删改查全教程本文完整覆盖MySQL JSON字段创建、Java实体对应类型、MyBatis Plus 自动序列化/反序列化、增删改查实战样例含JSON字段条件查询代码可直接复制使用。一、MySQL 如何存储 JSON 字段1. 前提要求MySQL5.7及以上版本原生支持JSON数据类型相比字符串存储优势自动校验JSON格式合法性支持高效JSON字段查询、索引存储空间更优2. 建表语句JSON字段定义直接用JSON关键字声明字段即可示例创建用户表info为JSON字段CREATETABLEuser(idbigintNOTNULLAUTO_INCREMENTCOMMENT主键,usernamevarchar(50)NOTNULLCOMMENT用户名,infojsonDEFAULTNULLCOMMENT用户详情JSON,create_timedatetimeDEFAULTCURRENT_TIMESTAMPCOMMENT创建时间,PRIMARYKEY(id))ENGINEInnoDBDEFAULTCHARSETutf8mb4COMMENT用户表;二、MySQL JSON字段 对应 Java 什么类型方案1String 类型简单但不推荐直接用String接收JSON字符串需要手动序列化/反序列化操作繁琐。privateStringinfo;// 对应MySQL JSON字段方案2自定义对象 MyBatis Plus 类型处理器✅ 推荐MyBatis Plus 提供自动序列化/反序列化能力将JSON字段直接映射为Java对象面向对象操作无需手动处理JSON。对应Java类型自定义实体类依赖MP自带的JacksonTypeHandler基于Jackson自动转换三、环境准备Maven依赖SpringBoot MyBatis Plus 核心依赖!-- SpringBoot Web --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!-- MyBatis Plus Boot Starter --dependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.5.3.1/version/dependency!-- MySQL驱动 --dependencygroupIdcom.mysql/groupIdartifactIdmysql-connector-java/artifactIdscoperuntime/scope/dependency!-- Lombok 简化代码 --dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependency四、MyBatis Plus 实战代码1. 定义JSON对应的Java嵌套对象对应MySQLinfoJSON字段的结构importlombok.Data;/** * JSON字段对应的Java实体 * 对应MySQL info字段: {age:20,address:北京,phone:13800138000} */DatapublicclassUserInfo{privateIntegerage;privateStringaddress;privateStringphone;}2. 主实体类核心JSON字段映射关键注解TableName(autoResultMap true)必须开启否则类型处理器不生效TableField(typeHandler JacksonTypeHandler.class)指定JSON字段的类型处理器importcom.baomidou.mybatisplus.annotation.IdType;importcom.baomidou.mybatisplus.annotation.TableField;importcom.baomidou.mybatisplus.annotation.TableId;importcom.baomidou.mybatisplus.annotation.TableName;importcom.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;importlombok.Data;importjava.util.Date;Data// 开启自动结果映射必须TableName(valueuser,autoResultMaptrue)publicclassUser{// 主键自增TableId(typeIdType.AUTO)privateLongid;privateStringusername;// JSON字段绑定类型处理器自动转换Java对象 - MySQL JSONTableField(typeHandlerJacksonTypeHandler.class)privateUserInfoinfo;privateDatecreateTime;}3. Mapper Service 层MP原生接口Mapper接口importcom.baomidou.mybatisplus.core.mapper.BaseMapper;importorg.apache.ibatis.annotations.Mapper;MapperpublicinterfaceUserMapperextendsBaseMapperUser{}Service接口importcom.baomidou.mybatisplus.extension.service.IService;publicinterfaceUserServiceextendsIServiceUser{}Service实现类importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importorg.springframework.stereotype.Service;ServicepublicclassUserServiceImplextendsServiceImplUserMapper,UserimplementsUserService{}五、MyBatis Plus 增删改查 完整样例直接在测试类/Controller中调用JSON字段自动处理无需手动转JSON字符串。1. 新增插入带JSON字段的数据SpringBootTestclassMybatisPlusJsonTest{ResourceprivateUserServiceuserService;TestvoidaddUser(){// 1. 组装JSON对象UserInfoinfonewUserInfo();info.setAge(20);info.setAddress(北京市朝阳区);info.setPhone(13800138000);// 2. 组装主实体UserusernewUser();user.setUsername(张三);user.setInfo(info);// 直接赋值Java对象user.setCreateTime(newDate());// 3. 新增MP自动将info转为JSON存入MySQLbooleansaveuserService.save(user);System.out.println(新增结果save);}}2. 修改更新JSON字段TestvoidupdateUser(){// 1. 新的JSON数据UserInfonewInfonewUserInfo();newInfo.setAge(21);newInfo.setAddress(上海市浦东新区);newInfo.setPhone(13900139000);// 2. 更新实体UserusernewUser();user.setId(1L);user.setInfo(newInfo);// 直接更新JSON对象// 3. 修改MP自动更新MySQL JSON字段booleanupdateuserService.updateById(user);System.out.println(修改结果update);}3. 查询普通查询 JSON字段条件查询3.1 普通查询自动返回Java对象TestvoidselectUser(){// 根据ID查询UseruseruserService.getById(1L);// 直接获取JSON对应的Java对象无需反序列化UserInfoinfouser.getInfo();System.out.println(用户信息user);System.out.println(JSON字段解析年龄info.getAge()地址info.getAddress());}3.2 条件查询查询JSON字段内部值使用MySQL JSON函数查询字段-$.属性推荐写法JSON_EXTRACT(字段, $.属性)原生写法示例查询info中age21的用户TestvoidselectByJsonCondition(){// 构造查询条件info.age 21LambdaQueryWrapperUserwrappernewLambdaQueryWrapper();// 重点MySQL JSON字段条件查询语法wrapper.apply(info-$.age {0},21);// 执行查询ListUseruserListuserService.list(wrapper);System.out.println(JSON条件查询结果userList);}4. 删除TestvoiddeleteUser(){// 根据ID删除booleanremoveuserService.removeById(1L);System.out.println(删除结果remove);}六、关键注意事项必须开启autoResultMap true实体类的TableName注解必须加这个参数否则类型处理器无法生效会导致JSON字段映射失败。类型处理器选择JacksonTypeHandlerMP默认依赖Jackson无需额外配置FastjsonTypeHandler若用Fastjson需替换依赖注解JSON字段空值处理直接赋值null即可MP会自动存储为NULL。JSON字段索引若需要频繁查询JSON内部字段可给MySQL JSON字段创建函数索引提升查询效率。总结MySQL用**JSON类型**存储JSON数据Java对应自定义对象配合MP的JacksonTypeHandler自动转换MyBatis Plus 增删改查直接操作Java对象无需手动处理JSON字符串JSON字段条件查询使用字段-$.属性语法。packagecom.example.demo.utils;importcom.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;importcom.baomidou.mybatisplus.core.metadata.TableFieldInfo;importcom.baomidou.mybatisplus.core.metadata.TableInfo;importcom.baomidou.mybatisplus.core.metadata.TableInfoHelper;importorg.apache.ibatis.reflection.property.PropertyNamer;importjava.io.Serializable;importjava.lang.invoke.SerializedLambda;importjava.util.Collection;/** * 纯手写 JSON Lambda 工具类 * 【完全独立】不依赖 MyBatis Plus 内部 API仅保留 MP 注解作为可选兼容 * 解决 3.5.x 版本 API 重构导致的报错 */publicclassJsonLambdaUtils{/** * 核心方法双Lambda生成MySQL JSON查询语句 * param entityLambda 主实体Lambda如 User::getInfo * param jsonLambda JSON对象Lambda如 UserInfo::getAge * return 示例info-$.age */publicstaticT,JStringjson(SFunctionT,JentityLambda,SFunctionJ,?jsonLambda){// 1. 解析主实体 Lambda - 数据库列名StringentityColumnNamegetEntityColumnName(entityLambda);// 2. 解析 JSON 对象 Lambda - 属性名StringjsonFieldNamegetLambdaFieldName(jsonLambda);// 3. 拼接 MySQL JSON 语法returnString.format(%s-$.%s,entityColumnName,jsonFieldName);}/** * 【核心独立实现】通过 SerializedLambda 解析实体字段名 */privatestaticTStringgetEntityColumnName(SFunctionT,?func){try{// 1. 强制序列化获取 SerializedLambda (核心黑魔法)SerializedLambdalambdagetSerializedLambda(func);// 2. 获取 Lambda 对应的方法名 (例如: getInfo)StringmethodNamelambda.getImplMethodName();// 3. 方法名转属性名 (getInfo - info)StringfieldNamePropertyNamer.methodToProperty(methodName);// 4. 获取实体类类型 (例如: com.example.demo.entity.User)// 注意这里直接用 getInstantiatedMethodType() 获取类描述符或者强制转换// 为了兼容不同版本我们直接通过反射获取泛型类型Class?entityClassgetFunctionalInterfaceClass(lambda);// 5. 从 MP 表信息中获取最终的数据库列名 (保留 MP 注解适配如需纯手写可替换逻辑)TableInfotableInfoTableInfoHelper.getTableInfo(entityClass);if(tableInfonull){thrownewRuntimeException(未找到实体类 [entityClass.getName()] 的表信息请检查注解);}// 6. 根据属性名匹配数据库列名 (支持 TableField 注解的列名映射)CollectionTableFieldInfofieldListtableInfo.getFieldList();for(TableFieldInfofieldInfo:fieldList){if(fieldInfo.getProperty().equals(fieldName)){returnfieldInfo.getColumn();}}// 如果找不到直接返回属性名默认情况returnfieldName;}catch(Exceptione){thrownewRuntimeException(解析实体 Lambda 失败,e);}}/** * 解析 JSON 对象的字段名 */privatestaticJStringgetLambdaFieldName(SFunctionJ,?func){try{SerializedLambdalambdagetSerializedLambda(func);StringmethodNamelambda.getImplMethodName();returnPropertyNamer.methodToProperty(methodName);}catch(Exceptione){thrownewRuntimeException(解析 JSON Lambda 失败,e);}}/** * 【核心独立工具】获取 SerializedLambda * 这是 Java 标准 API不依赖任何第三方框架 */privatestaticSerializedLambdagetSerializedLambda(Serializablefunc){try{// 通过反射调用 writeReplace 方法获取序列化代理对象java.lang.reflect.Methodmethodfunc.getClass().getDeclaredMethod(writeReplace);method.setAccessible(true);return(SerializedLambda)method.invoke(func);}catch(Exceptione){thrownewRuntimeException(获取 SerializedLambda 失败请确保 SFunction 是可序列化的,e);}}/** * 【核心独立工具】通过 Lambda 获取实体类类型 */privatestaticClass?getFunctionalInterfaceClass(SerializedLambdalambda){// 方案1: 尝试从 implMethodName 或其他字段推断 (较复杂)// 方案2: 更简单可靠的方式 - 强制转换并通过泛型提取 (推荐)// 这里我们直接通过类加载器获取实例化的类StringimplClasslambda.getImplClass().replace(/,.);try{returnClass.forName(implClass);}catch(ClassNotFoundExceptione){thrownewRuntimeException(加载实体类失败: implClass,e);}}// 新增JSON解引用表达式用于字符串匹配 publicstaticT,JStringjsonUnquote(SFunctionT,JentityLambda,SFunctionJ,?jsonLambda){StringcolumngetEntityColumnName(entityLambda);StringfieldgetLambdaFieldName(jsonLambda);// - MySQL解引用去掉JSON字符串的引号必须用于like/字符串eqreturnString.format(%s-$.%s,column,field);}// 新增封装eq/like/in通用方法 /** * JSON 等于查询 * param wrapper 查询包装器 * param entityLambda 实体JSON字段如 User::getInfo * param jsonLambda JSON内部字段如 UserInfo::getAge * param value 匹配值 */publicstaticT,JvoidjsonEq(LambdaQueryWrapperTwrapper,SFunctionT,JentityLambda,SFunctionJ,?jsonLambda,Objectvalue){Stringexprjson(entityLambda,jsonLambda);wrapper.apply(expr ?,value);}/** * JSON 模糊查询字符串 */publicstaticT,JvoidjsonLike(LambdaQueryWrapperTwrapper,SFunctionT,JentityLambda,SFunctionJ,?jsonLambda,Stringvalue){StringexprjsonUnquote(entityLambda,jsonLambda);wrapper.apply(expr LIKE ?,%value%);}/** * JSON IN 查询 */publicstaticT,JvoidjsonIn(LambdaQueryWrapperTwrapper,SFunctionT,JentityLambda,SFunctionJ,?jsonLambda,Collection?values){Stringexprjson(entityLambda,jsonLambda);wrapper.apply(expr IN ({0}),values);}/** * 通用函数式接口用于替代 MP 的 SFunction (如果不想引入 MP 依赖) * 请将此接口放在独立的包下例如 com.example.demo.function */FunctionalInterfacepublicinterfaceSFunctionT,RextendsSerializable{Rapply(Tt);}}