MySQL 与 Java 的 JSON 数据处理
系统梳理 JSON 在 MySQL 和 Java 中的增删改查,覆盖 Hutool JSON、Fastjson、Jackson 三种主流工具,全部用独立示例,方便理解。
目录
- [MySQL JSON 增删改查](#MySQL JSON 增删改查)
- [Hutool JSON 增删改查](#Hutool JSON 增删改查)
- [Fastjson 增删改查](#Fastjson 增删改查)
- [Jackson 增删改查](#Jackson 增删改查)
- [MyBatis TypeHandler](#MyBatis TypeHandler)
- 三种工具对比
1. MySQL JSON 增删改查
1.1 建表 + 插入
sql
-- 建表:定义JSON字段
CREATE TABLE sys_config (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
config_key VARCHAR(100) NOT NULL,
config_value JSON NOT NULL COMMENT 'JSON配置',
gmt_create DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 插入JSON数据
INSERT INTO sys_config (config_key, config_value) VALUES
('sms_config', '{"provider":"aliyun","signName":"小租","templateCode":"SMS_123"}');
-- 用JSON_OBJECT构造
INSERT INTO sys_config (config_key, config_value) VALUES
('pay_config', JSON_OBJECT('appId','xxx','mchId','123'));
-- 嵌套结构
INSERT INTO sys_config (config_key, config_value) VALUES
('auth', JSON_OBJECT(
'menu', JSON_ARRAY('用户管理','订单管理','财务'),
'button', JSON_OBJECT('add',true,'delete',false)
));
1.2 读取JSON
sql
-- 基本读取(->> 返回文本,去掉引号)
SELECT config_value->>'$.provider' FROM sys_config WHERE config_key='sms_config';
-- 结果:aliyun
-- -> 返回JSON类型(保留引号)
SELECT config_value->'$.provider' FROM sys_config WHERE config_key='sms_config';
-- 结果:"aliyun"
-- 嵌套字段
SELECT config_value->>'$.button.add' FROM sys_config WHERE config_key='auth';
-- 结果:true
-- 数组元素(下标从0开始)
SELECT JSON_EXTRACT(config_value, '$.menu[0]') FROM sys_config WHERE config_key='auth';
-- 结果:"用户管理"
1.3 修改JSON
sql
-- JSON_SET:更新已有字段 / 新增不存在的字段
UPDATE sys_config
SET config_value = JSON_SET(config_value, '$.provider', 'tencent')
WHERE config_key = 'sms_config';
-- JSON_INSERT:只新增,不更新已存在的字段
UPDATE sys_config
SET config_value = JSON_INSERT(config_value, '$.newField', 'hello')
WHERE config_key = 'sms_config';
-- JSON_REPLACE:只更新,不新增
UPDATE sys_config
SET config_value = JSON_REPLACE(config_value, '$.provider', 'new_provider')
WHERE config_key = 'sms_config';
-- 数组追加元素
UPDATE sys_config
SET config_value = JSON_ARRAY_APPEND(config_value, '$.menu', '新菜单')
WHERE config_key = 'auth';
1.4 删除JSON字段
sql
-- 删除对象字段
UPDATE sys_config
SET config_value = JSON_REMOVE(config_value, '$.email')
WHERE config_key = 'sms_config';
-- 删除数组元素(下标1的元素)
UPDATE sys_config
SET config_value = JSON_REMOVE(config_value, '$.menu[1]')
WHERE config_key = 'auth';
-- 同时删除多个字段
UPDATE sys_config
SET config_value = JSON_REMOVE(config_value, '$.phone', '$.email')
WHERE config_key = 'sms_config';
1.5 条件查询
sql
-- 判断数组是否包含某个值
SELECT * FROM sys_config
WHERE JSON_CONTAINS(config_value, '"用户管理"', '$.menu');
-- 数组长度
SELECT * FROM sys_config
WHERE JSON_LENGTH(config_value, '$.menu') > 2;
-- 按JSON字段值过滤
SELECT * FROM sys_config
WHERE config_value->>'$.provider' = 'aliyun';
1.6 函数速查
| 函数 | 作用 |
|---|---|
-> / ->> |
读取字段(->返回JSON,->>返回文本) |
JSON_EXTRACT |
提取值,等价于 -> |
JSON_SET |
更新/新增字段(都生效) |
JSON_INSERT |
只新增不更新 |
JSON_REPLACE |
只更新不新增 |
JSON_REMOVE |
删除字段/数组元素 |
JSON_ARRAY_APPEND |
数组末尾追加 |
JSON_ARRAY_INSERT |
数组指定位置插入 |
JSON_CONTAINS |
判断是否包含某个值 |
JSON_LENGTH |
获取数组长度 |
JSON_OBJECT |
构造JSON对象 |
JSON_ARRAY |
构造JSON数组 |
2. Hutool JSON 增删改查
Hutool 的
cn.hutool.json包,API简洁,链式调用方便。
2.1 创建
java
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
// 方式1:new JSONObject()
JSONObject obj = new JSONObject();
obj.set("name", "张三");
obj.set("age", 25);
// 方式2:链式调用(fluentPut)
JSONObject obj2 = new JSONObject()
.fluentPut("name", "张三")
.fluentPut("age", 25)
.fluentPut("address", new JSONObject()
.fluentPut("city", "北京"))
.fluentPut("hobbies", new JSONArray()
.fluentPut("编程")
.fluentPut("跑步"));
// {"name":"张三","age":25,"address":{"city":"北京"},"hobbies":["编程","跑步"]}
// 方式3:从字符串解析
JSONObject obj3 = JSONUtil.parseObj("{\"name\":\"张三\"}");
2.2 读取
java
JSONObject obj = JSONUtil.parseObj(
"{\"name\":\"张三\",\"age\":25,\"address\":{\"city\":\"北京\"}}"
);
// 基本字段
String name = obj.getStr("name"); // "张三"
int age = obj.getInt("age"); // 25
String phone = obj.getStr("phone", ""); // 不存在返回默认值
// 嵌套字段
String city = obj.getJSONObject("address").getStr("city"); // "北京"
// 判断字段是否存在
boolean has = obj.containsKey("name"); // true
2.3 增/改
java
JSONObject obj = JSONUtil.parseObj("{\"name\":\"张三\",\"age\":25}");
// set():存在则更新,不存在则新增
obj.set("age", 26); // 更新 → 26
obj.set("phone", "13800138000"); // 新增
// 链式操作
obj.fluentPut("city", "上海").fluentPut("dept", "技术部");
// {"name":"张三","age":26,"phone":"13800138000","city":"上海","dept":"技术部"}
// 修改嵌套字段
JSONObject nested = JSONUtil.parseObj("{\"user\":{\"name\":\"张三\"}}");
nested.getJSONObject("user").set("name", "李四");
// {"user":{"name":"李四"}}
2.4 删
java
JSONObject obj = JSONUtil.parseObj(
"{\"name\":\"张三\",\"age\":25,\"phone\":\"123\"}"
);
// 删除字段
obj.remove("phone");
// 删除多个
obj.remove("age", "name");
// {}
// 删除嵌套字段
JSONObject nested = JSONUtil.parseObj("{\"user\":{\"name\":\"张三\",\"phone\":\"123\"}}");
nested.getJSONObject("user").remove("phone");
// {"user":{"name":"张三"}}
2.5 数组操作
java
// 创建数组
JSONArray arr = new JSONArray();
arr.add("用户管理");
arr.add("订单管理");
arr.add("财务");
// ["用户管理","订单管理","财务"]
// 从字符串解析
JSONArray arr2 = JSONUtil.parseArray("[\"A\",\"B\",\"C\"]");
// 读取
String first = arr.getStr(0); // "用户管理"
// 追加
arr.add("人事");
// 指定位置插入
arr.add(1, "插入的菜单");
// 删除(按下标)
arr.remove(0);
// 删除(按值)
arr.remove("订单管理");
// 判断是否存在
boolean exists = arr.contains("用户管理"); // true
2.6 对象 ↔ Java互转
java
// JSON字符串 → Java对象
User user = JSONUtil.toBean("{\"name\":\"张三\",\"age\":25}", User.class);
// Java对象 → JSON字符串
String json = JSONUtil.toJsonStr(user);
// JSON字符串 → List
List<User> list = JSONUtil.toList("[{\"name\":\"张三\"}]", User.class);
3. Fastjson 增删改查
阿里的
com.alibaba.fastjson包,性能好,支持JSONPath点号读取。
3.1 创建
java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONArray;
// 方式1:new JSONObject()
JSONObject obj = new JSONObject();
obj.put("name", "张三");
obj.put("age", 25);
// 方式2:链式调用(fluentPut)
JSONObject obj2 = new JSONObject()
.fluentPut("name", "张三")
.fluentPut("age", 25)
.fluentPut("address", new JSONObject()
.fluentPut("city", "北京"))
.fluentPut("hobbies", new JSONArray()
.fluentAdd("编程")
.fluentAdd("跑步"));
// 方式3:从字符串解析
JSONObject obj3 = JSON.parseObject("{\"name\":\"张三\"}");
3.2 读取
java
JSONObject obj = JSON.parseObject(
"{\"name\":\"张三\",\"age\":25,\"address\":{\"city\":\"北京\"}}"
);
// 基本字段
String name = obj.getString("name"); // "张三"
int age = obj.getIntValue("age"); // 25
String phone = obj.getString("phone"); // 不存在返回null
// 嵌套字段(支持点号,比Hutool方便)
String city = obj.getString("address.city"); // "北京"
// 判断字段是否存在
boolean has = obj.containsKey("name"); // true
3.3 增/改
java
JSONObject obj = JSON.parseObject("{\"name\":\"张三\",\"age\":25}");
// put():存在则更新,不存在则新增
obj.put("age", 26);
obj.put("phone", "13800138000");
// 链式操作
obj.fluentPut("city", "上海").fluentPut("dept", "技术部");
// 修改嵌套字段
JSONObject nested = JSON.parseObject("{\"user\":{\"name\":\"张三\"}}");
nested.getJSONObject("user").put("name", "李四");
// {"user":{"name":"李四"}}
3.4 删
java
JSONObject obj = JSON.parseObject(
"{\"name\":\"张三\",\"age\":25,\"phone\":\"123\"}"
);
// 删除字段
obj.remove("phone");
// 删除嵌套字段
JSONObject nested = JSON.parseObject("{\"user\":{\"name\":\"张三\",\"phone\":\"123\"}}");
nested.getJSONObject("user").remove("phone");
// {"user":{"name":"张三"}}
3.5 数组操作
java
// 创建
JSONArray arr = new JSONArray();
arr.add("用户管理");
arr.add("订单管理");
arr.add("财务");
// 从字符串解析
JSONArray arr2 = JSON.parseArray("[\"A\",\"B\",\"C\"]");
// 读取
String first = arr.getString(0); // "用户管理"
// 追加
arr.add("人事");
// 删除
arr.remove(0); // 按下标
arr.remove("订单管理"); // 按值
// 遍历
for (Object item : arr) {
System.out.println(item);
}
3.6 JSONPath(Fastjson特色)
java
JSONObject obj = JSON.parseObject(
"{\"users\":[{\"name\":\"张三\"},{\"name\":\"李四\"}]}"
);
// 用点号读取嵌套字段
String name = (String) JSONPath.eval(obj, "$.users[0].name"); // "张三"
// 过滤
Object result = JSONPath.eval(obj, "$.users[?(@.name=='张三')]");
3.7 对象 ↔ Java互转
java
// JSON字符串 → Java对象
User user = JSON.parseObject("{\"name\":\"张三\"}", User.class);
// Java对象 → JSON字符串
String json = JSON.toJSONString(user);
// JSON字符串 → List
List<User> list = JSON.parseArray("[{\"name\":\"张三\"}]", User.class);
// JSONObject → Java对象
JSONObject obj = new JSONObject();
obj.put("name", "张三");
User user2 = obj.toJavaObject(User.class);
3.8 @JSONField注解
java
public class User {
// 指定字段名
@JSONField(name = "user_name")
private String name;
// 格式化日期
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
// 不序列化
@JSONField(serialize = false)
private String password;
// 指定序列化/反序列化顺序
@JSONField(ordinal = 1)
private Long id;
}
4. Jackson 增删改查
Spring Boot 默认集成,
spring-boot-starter-web自带,不需要额外引入。
4.1 创建
java
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
ObjectMapper mapper = new ObjectMapper();
// 创建空节点
ObjectNode node = mapper.createObjectNode();
node.put("name", "张三");
node.put("age", 25);
node.putObject("address") // 嵌套对象
.put("city", "北京");
node.putArray("hobbies") // 数组
.add("编程")
.add("跑步");
// 从Java对象转换
User user = new User("张三", 25);
ObjectNode node2 = mapper.valueToTree(user);
// 转为字符串
String json = mapper.writeValueAsString(node);
4.2 读取
java
import com.fasterxml.jackson.databind.JsonNode;
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(
"{\"name\":\"张三\",\"age\":25,\"address\":{\"city\":\"北京\"}}"
);
// 基本字段
String name = root.get("name").asText(); // "张三"
int age = root.get("age").asInt(); // 25
// 嵌套字段
String city = root.get("address").get("city").asText(); // "北京"
// 安全读取(不存在返回空串,不抛异常)
String phone = root.path("phone").asText(""); // ""
// 判断字段是否存在
boolean has = root.has("name"); // true
4.3 增/改
java
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = (ObjectNode) mapper.readTree(
"{\"name\":\"张三\",\"age\":25}"
);
// put():更新/新增
node.put("age", 26);
node.put("phone", "13800138000");
// set():更新(传入JsonNode)
node.set("name", mapper.valueToTree("李四"));
// putObject():创建嵌套对象
node.putObject("address").put("city", "上海");
// putArray():创建数组
node.putArray("hobbies").add("编程").add("跑步");
// replace():替换字段
node.replace("name", mapper.valueToTree("王五"));
4.4 删
java
ObjectMapper mapper = new ObjectMapper();
ObjectNode node = (ObjectNode) mapper.readTree(
"{\"name\":\"张三\",\"age\":25,\"phone\":\"123\"}"
);
// 删除字段
node.remove("phone");
// 删除多个
node.remove("age", "name");
// 删除嵌套字段
ObjectNode nested = (ObjectNode) mapper.readTree(
"{\"user\":{\"name\":\"张三\",\"phone\":\"123\"}}"
);
((ObjectNode) nested.get("user")).remove("phone");
// {"user":{"name":"张三"}}
4.5 数组操作
java
ObjectMapper mapper = new ObjectMapper();
ArrayNode arr = mapper.createArrayNode();
arr.add("用户管理");
arr.add("订单管理");
arr.add("财务");
// 读取
String first = arr.get(0).asText(); // "用户管理"
// 追加
arr.add("人事");
// 删除
arr.remove(0); // 按下标
// 遍历
for (JsonNode item : arr) {
System.out.println(item.asText());
}
4.6 对象 ↔ Java互转
java
ObjectMapper mapper = new ObjectMapper();
// JSON字符串 → Java对象
User user = mapper.readValue("{\"name\":\"张三\"}", User.class);
// Java对象 → JSON字符串
String json = mapper.writeValueAsString(user);
// JSON字符串 → Map
Map<String, Object> map = mapper.readValue(json, Map.class);
// JSON字符串 → List
List<User> list = mapper.readValue(
"[{\"name\":\"张三\"}]",
mapper.getTypeFactory().constructCollectionType(List.class, User.class)
);
4.7 常用注解
java
public class User {
@JsonProperty("user_name") // 指定JSON字段名
private String name;
@JsonIgnore // 序列化时忽略
private String password;
@JsonInclude(JsonInclude.Include.NON_NULL) // null不输出
private String remark;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") // 日期格式化
private Date createTime;
@JsonAlias({"userName", "user_name"}) // 反序列化接受多个名字
private String nickName;
}
5. MyBatis TypeHandler
数据库有 JSON 字段,MyBatis 默认不认识,需要自定义 TypeHandler 做转换。
5.1 Hutool版 TypeHandler
java
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.*;
public class HutoolJsonTypeHandler extends BaseTypeHandler<JSONObject> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
JSONObject parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.toString());
}
@Override
public JSONObject getNullableResult(ResultSet rs, String col) throws SQLException {
return parseJson(rs.getString(col));
}
@Override
public JSONObject getNullableResult(ResultSet rs, int col) throws SQLException {
return parseJson(rs.getString(col));
}
@Override
public JSONObject getNullableResult(CallableStatement cs, int col) throws SQLException {
return parseJson(cs.getString(col));
}
private JSONObject parseJson(String json) {
if (json == null || json.isEmpty()) return null;
return JSONUtil.parseObj(json);
}
}
5.2 Fastjson版 TypeHandler
java
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSON;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.*;
public class FastjsonTypeHandler extends BaseTypeHandler<JSONObject> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
JSONObject parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.toJSONString());
}
@Override
public JSONObject getNullableResult(ResultSet rs, String col) throws SQLException {
return parseJson(rs.getString(col));
}
@Override
public JSONObject getNullableResult(ResultSet rs, int col) throws SQLException {
return parseJson(rs.getString(col));
}
@Override
public JSONObject getNullableResult(CallableStatement cs, int col) throws SQLException {
return parseJson(cs.getString(col));
}
private JSONObject parseJson(String json) {
if (json == null || json.isEmpty()) return null;
return JSON.parseObject(json);
}
}
5.3 在实体类中使用
java
public class SysConfig {
private Long id;
private String configKey;
// Hutool版
@TableField(typeHandler = HutoolJsonTypeHandler.class)
private JSONObject configValue;
// 或 Fastjson版
// @TableField(typeHandler = FastjsonTypeHandler.class)
// private JSONObject configValue;
private Date gmtCreate;
}
5.4 在Mapper中使用
java
@Mapper
public interface SysConfigMapper {
// 查询(自动将JSON字符串转为JSONObject)
@Select("SELECT * FROM sys_config WHERE id = #{id}")
SysConfig selectById(@Param("id") Long id);
// 插入(自动将JSONObject转为JSON字符串)
@Insert("INSERT INTO sys_config (config_key, config_value) " +
"VALUES (#{configKey}, #{configValue, typeHandler=HutoolJsonTypeHandler})")
int insert(SysConfig config);
// 更新
@Update("UPDATE sys_config SET config_value = " +
"#{configValue, typeHandler=HutoolJsonTypeHandler} WHERE id = #{id}")
int update(SysConfig config);
// 查询JSON内部字段(不需要TypeHandler)
@Select("SELECT config_value->>'$.provider' FROM sys_config WHERE config_key = #{key}")
String getConfigProvider(@Param("key") String key);
}
6. 三种工具对比
6.1 选型建议
| 场景 | 推荐 |
|---|---|
| Spring Boot项目(默认集成) | Jackson |
| 非Spring项目,追求API简洁 | Hutool JSON |
| 需要JSONPath点号读取 | Fastjson |
| e签宝等第三方SDK | Fastjson(@JSONField注解) |
6.2 API对比
| 操作 | Hutool JSON | Fastjson | Jackson |
|---|---|---|---|
| 字符串→对象 | JSONUtil.parseObj(str) |
JSON.parseObject(str) |
mapper.readTree(str) |
| 字符串→Java | JSONUtil.toBean(str, Cls.class) |
JSON.parseObject(str, Cls.class) |
mapper.readValue(str, Cls.class) |
| 读取字段 | obj.getStr("key") |
obj.getString("key") |
node.get("key").asText() |
| 嵌套字段 | obj.getJSONObject("a").getStr("b") |
obj.getString("a.b") |
node.get("a").get("b").asText() |
| 默认值 | obj.getStr("key", "") |
obj.getString("key", "") |
node.path("key").asText("") |
| 修改字段 | obj.set("key", val) |
obj.put("key", val) |
node.put("key", val) |
| 删除字段 | obj.remove("key") |
obj.remove("key") |
node.remove("key") |
| 对象→字符串 | JSONUtil.toJsonStr(obj) |
JSON.toJSONString(obj) |
mapper.writeValueAsString(obj) |
6.3 特色差异
| 特性 | Hutool JSON | Fastjson | Jackson |
|---|---|---|---|
| 嵌套字段点号读取 | 需链式 getJSONObject().getStr() |
直接 getString("a.b") |
需链式 get().get().asText() |
| JSONPath | 不支持 | 支持 JSONPath.eval() |
不支持(需额外jar) |
| 注解 | 无 | @JSONField |
@JsonProperty/@JsonIgnore等 |
| 流式处理大文件 | 不支持 | 不支持 | 支持 Streaming API |
| Spring默认 | 否 | 否 | 是 |