五维思考

学习要加,骄傲要减,机会要乘,懒惰要除。 http://www.5dthink.cn

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

1、基本操作

要在MyBatis-Plus中使用MySQL的JSON类型,可以按照以下步骤操作:

数据库表设计

在MySQL表中定义JSON类型的字段,例如:

CREATE TABLE your_table (
  id INT PRIMARY KEY AUTO_INCREMENT,
  json_data JSON
);

实体类映射

在Java实体类中,使用相应的注解映射JSON字段。

  • 添加依赖

    确保项目中已添加MyBatis-Plus和Jackson相关的依赖。

  • 实体类注解

    @Data
    @TableName(autoResultMap = true) // 开启自动结果映射
    public class YourEntity {
        // ...
        @TableField(typeHandler = JacksonTypeHandler.class) // 指定JSON类型处理器
        private YourJsonObject jsonData;
        // ...
    }
    
    • @TableName(autoResultMap = true):启用自动结果映射,确保MyBatis-Plus能够正确映射JSON字段。
    • @TableField(typeHandler = JacksonTypeHandler.class):指定使用JacksonTypeHandler处理JSON类型与Java对象之间的转换。

CRUD操作

使用MyBatis-Plus提供的CRUD方法,可以直接操作JSON类型字段。

  • 插入数据

    YourEntity entity = new YourEntity();
    entity.setJsonData(yourJsonObject); // 设置JSON对象
    yourEntityMapper.insert(entity);
    
  • 查询数据

    YourEntity entity = yourEntityMapper.selectById(id);
    YourJsonObject jsonData = entity.getJsonData(); // 获取JSON对象
    

自定义TypeHandler

如果需要使用自定义的JSON类型处理器,可以实现org.apache.ibatis.type.TypeHandler接口。

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(Object.class)
public class CustomJsonTypeHandler<T> extends BaseTypeHandler<T> {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    // ... 实现setNonNullParameter、getNullableResult等方法
}

然后在实体类中使用@TableField(typeHandler = CustomJsonTypeHandler.class)指定自定义的TypeHandler。

注意事项

  • MySQL版本:确保MySQL版本在5.7以上,才支持JSON类型。
  • Jackson依赖:如果使用JacksonTypeHandler,需要确保项目中已添加Jackson相关的依赖。

2、MySQL查询JSON字段

在MySQL中可以直接查询JSON字段中的值。MySQL提供了多个函数来操作和查询JSON数据类型,例如JSON_EXTRACT()->操作符等。

假设你有一个表your_table,其中包含一个JSON类型的字段json_data。如果你想查询这个JSON字段中某个键对应的值,可以使用如下方法:

使用 JSON_EXTRACT()

SELECT JSON_EXTRACT(json_data, '$.key_name') AS key_value FROM your_table;

这里,$.key_name是你要查询的键路径。如果json_data的内容是{"key_name":"value"},那么上述查询将返回"value"

使用 -> 操作符

MySQL还提供了一个更简洁的语法,即使用->操作符来代替JSON_EXTRACT()函数:

SELECT json_data->'$.key_name' AS key_value FROM your_table;

这个查询与前面提到的JSON_EXTRACT()例子具有相同的效果。

查询条件

你还可以在WHERE子句中使用这些函数或操作符来过滤结果。例如,查找所有key_name值为"specific_value"的记录:

SELECT * FROM your_table WHERE json_data->>'$.key_name' = 'specific_value';

注意这里的->>操作符,它不仅提取了JSON值,还取消了可能存在的引号(适用于字符串值),这使得它可以与普通字符串直接比较。

通过这些方法,你可以直接从MySQL的JSON字段中查询特定的值,并根据需要进行处理。结合MyBatis-Plus使用时,只需在XML映射文件或者注解中编写相应的SQL语句即可实现对JSON字段的复杂查询。


3、Lambda 表达式

在 MyBatis-Plus 中,原生的 Lambda 表达式(QueryWrapper.lambda())无法直接支持 MySQL 的 JSON 字段路径查询,因为 Lambda 表达式是基于 Java 实体类的字段进行编译时检查的,而 JSON 字段中的路径(如 $.name)并不是实体类的直接属性。

但是,你仍然可以通过以下几种方式,在使用 QueryWrapper 的同时,结合 Lambda 风格或增强可读性来实现对 JSON 字段的查询:

✅ 方法一:在 QueryWrapper 中使用 apply 方法(推荐)

虽然不能直接用 Lambda 操作 JSON 路径,但你可以使用 QueryWrapperapply 方法插入原生 SQL 片段,并保持链式调用风格。

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

// 查询 json_data 中 name 字段等于 'Alice' 的记录
QueryWrapper<YourEntity> wrapper = new QueryWrapper<>();
wrapper.apply("json_data->>'$.name' = {0}", "Alice");

List<YourEntity> result = yourEntityMapper.selectList(wrapper);
  • ->> 会提取并去掉引号(适用于字符串)。
  • {0} 是 MyBatis-Plus 的占位符,防止 SQL 注入。
  • 这种方式可以很好地融入 QueryWrapper 的链式语法,接近“类 Lambda”的体验。

✅ 方法二:结合 eqlike 使用 apply

如果你还想使用 eqlike 等语义化方法,可以配合 apply

QueryWrapper<YourEntity> wrapper = new QueryWrapper<>();
wrapper.apply("json_data->>'$.email' = {0}", "alice@example.com");

或者模糊查询:

wrapper.apply("json_data->>'$.name' LIKE {0}", "%li%");

✅ 方法三:封装成工具方法(提升复用性)

为了更接近“Lambda 风格”,你可以封装一个工具类或扩展方法:

public class JsonQueryWrapper<T> extends QueryWrapper<T> {

    public JsonQueryWrapper<T> jsonEq(String jsonField, String jsonPath, Object value) {
        return (JsonQueryWrapper<T>) this.apply(jsonField + "->>'$" + jsonPath + "' = {0}", value);
    }

    public JsonQueryWrapper<T> jsonLike(String jsonField, String jsonPath, String value) {
        return (JsonQueryWrapper<T>) this.apply(jsonField + "->>'$" + jsonPath + "' LIKE {0}", "%" + value + "%");
    }
}

使用方式:

JsonQueryWrapper<YourEntity> wrapper = new JsonQueryWrapper<>();
wrapper.jsonEq("json_data", ".name", "Alice")
       .jsonLike("json_data", ".email", "gmail");

List<YourEntity> result = yourEntityMapper.selectList(wrapper);

这样既保持了链式调用,又提升了代码可读性。

❌ 不能这样做(错误示例)

// 错误!实体类没有 getName() 对应 JSON 内部字段
wrapper.lambda().eq(YourEntity::getName, "Alice"); // 不会映射到 json_data->name

🔔 总结

方法 是否支持 Lambda 推荐程度 说明
apply() + 原生 JSON 路径 ⭐⭐⭐⭐☆ 最常用,安全且灵活
自定义 QueryWrapper 扩展 ❌(但风格类似) ⭐⭐⭐⭐ 提高可读性和复用性
实体类映射 JSON 子字段 当前不支持,不推荐

最佳实践:使用 QueryWrapper.apply() 插入 json_field->>'$.path' = ? 的条件,是目前最稳定、清晰且安全的方式。

🧩 补充:JSON 索引建议

如果 JSON 查询频繁,建议为常用字段创建 虚拟列 + 索引

ALTER TABLE your_table 
ADD COLUMN name_virtual VARCHAR(100) 
GENERATED ALWAYS AS (json_data->>'$.name');

CREATE INDEX idx_name_virtual ON your_table(name_virtual);

这样可以大幅提升查询性能。


4、实体类映射类型(String、POJO、Map)

在大多数情况下,将 MySQL 的 JSON 类型字段在 Java 实体类中映射为 String 类型,进行 CRUD 操作是可行的,并且 MyBatis-Plus 可以正常工作,但有一些重要的细节和潜在问题需要注意。

可以这样做的原因

  1. MySQL 的 JSON 字段本质上是文本存储

    • MySQL 的 JSON 类型在底层是以优化的二进制格式存储的,但在 SQL 层面,它表现为一个 JSON 格式的字符串。
    • 当你 SELECT 时,返回的是一个 JSON 字符串(如 {"name": "Alice", "age": 30})。
  2. MyBatis/MyBatis-Plus 的默认行为

    • 如果你没有为该字段指定 typeHandler,MyBatis 会尝试将数据库中的 JSON 值当作字符串处理。
    • 因此,将实体类字段定义为 String,MyBatis 会直接把查询结果赋值给这个 String 字段。
  3. 插入和更新

    • 只要你传入的 String合法的 JSON 格式,MySQL 能够接受并正确解析它。
    • 例如:entity.setJsonData("{\"name\": \"Bob\"}"); 插入时不会报错。

⚠️ 潜在问题和注意事项

1. 没有类型安全,容易出错

  • 你可以在代码中给 String 字段赋任何字符串,比如 "not a json",MyBatis 不会检查,但 MySQL 在插入时会抛出异常:

    Invalid JSON text: "Invalid value."

  • 缺少编译时或运行时的 JSON 格式校验。

2. 操作不便,需要手动序列化/反序列化

  • 每次使用 JSON 内容时,你都需要手动用 ObjectMapperJSON.toJSONString() 进行转换:
    // 查询后
    String jsonData = entity.getJsonData();
    YourObject obj = objectMapper.readValue(jsonData, YourObject.class);
    
    // 更新前
    YourObject obj = new YourObject();
    entity.setJsonData(objectMapper.writeValueAsString(obj));
    
  • 容易出错,代码冗余。

3. 无法利用 MyBatis-Plus 的 JacksonTypeHandler 自动映射

  • 如果你用 String 类型,即使加了 @TableField(typeHandler = JacksonTypeHandler.class),它也会尝试把 Java 对象序列化成 JSON 字符串,但目标字段是 String,可能导致类型不匹配或异常。

4. JSON 函数查询可能受影响

  • 虽然 SQL 层面不影响(你查的是数据库字段),但如果你在应用层对 String 字段做逻辑判断,可能因为格式问题(如空格、转义)导致错误。

推荐做法(最佳实践)

✅ 方案一:使用 POJO 对象 + JacksonTypeHandler

@Data
public class YourEntity {
    private Long id;

    @TableField(typeHandler = JacksonTypeHandler.class)
    private UserInfo jsonData; // UserInfo 是一个 Java 对象
}
@Data
public class UserInfo {
    private String name;
    private Integer age;
}
  • 优点:类型安全、自动序列化/反序列化、代码清晰。
  • 要求:确保引入 jackson-databind 依赖。

✅ 方案二:使用 Map<String, Object> + JacksonTypeHandler

@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> jsonData;
  • 适合结构不固定的 JSON 数据。

✅ 方案三:继续用 String,但做好校验

如果你坚持用 String,建议:

  • setJsonData() 方法中加入 JSON 格式校验:
    public void setJsonData(String jsonData) {
        if (jsonData != null && !jsonData.trim().isEmpty()) {
            try {
                objectMapper.readTree(jsonData); // 验证是否为合法 JSON
            } catch (IOException e) {
                throw new IllegalArgumentException("Invalid JSON format", e);
            }
        }
        this.jsonData = jsonData;
    }
    
  • 在插入前确保数据是合法 JSON。

✅ 总结

项目 说明
是否影响 CRUD ❌ 不影响(只要 JSON 格式正确)
是否推荐 ⚠️ 不推荐长期使用,仅适合临时或简单场景
推荐替代方案 使用 POJOMap + JacksonTypeHandler
关键点 确保 String 中的内容是合法 JSON,否则插入会失败

💡 结论:为了代码健壮性和可维护性,建议使用对象映射 + JacksonTypeHandler

posted on 2025-08-02 09:35  五维思考  阅读(717)  评论(0)    收藏  举报

QQ群:1. 全栈码农【346906288】2. VBA/VSTO【2660245】