You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebFlux+R2DBC操作PostgreSQL JSONB的动态查询与格式转换问题

解决Spring WebFlux + R2DBC操作PostgreSQL JSONB字段的两个问题

1. 实现@Query注解的动态参数查询

基础动态参数绑定

直接用命名参数绑定替代硬编码,Spring Data R2DBC支持通过@Param注解将方法参数映射到SQL中的占位符,彻底避免硬编码值或字段名:

public interface ProductRepository extends ReactiveCrudRepository<Product, Long> {
    // 动态指定JSONB嵌套字段名和匹配值
    @Query("SELECT * FROM products WHERE pdata->>:nestedKey = :targetValue")
    Flux<Product> findByNestedProperty(@Param("nestedKey") String nestedKey, @Param("targetValue") String targetValue);
}

复杂动态多条件查询

如果需要根据可选参数动态拼接SQL,推荐使用Spring Data R2DBC的Criteria API,无需手动拼接SQL,同时规避SQL注入风险:

public interface ProductRepository extends ReactiveCrudRepository<Product, Long>, ReactiveQueryByExampleExecutor<Product> {
    default Flux<Product> findByDynamicFilters(String category, Integer minPrice) {
        Criteria criteria = Criteria.empty();
        
        if (category != null) {
            // 匹配JSONB中的category字段
            criteria = criteria.and("pdata.category").is(category);
        }
        if (minPrice != null) {
            // 匹配JSONB中的price字段大于等于指定值
            criteria = criteria.and("pdata.price").greaterThanOrEqualTo(minPrice);
        }
        
        return findAll(criteria);
    }
}

2. 解决JSONB字段返回带转义字符串的问题

默认情况下R2DBC会将JSONB字段映射为String类型,导致返回结果出现转义字符。可以通过以下两种方式解决:

方式1:使用JsonObject类型映射

直接将实体类中的pdata字段声明为org.springframework.data.relational.core.sql.JsonObject,Spring Data R2DBC会自动处理JSON的序列化/反序列化:

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.sql.JsonObject;

public class Product {
    @Id
    private Long id;
    
    // 标注字段类型为jsonb,自动映射为JsonObject
    @Column(columnDefinition = "jsonb")
    private JsonObject pdata;

    // Getter & Setter
}

方式2:映射为自定义POJO

如果需要将JSONB字段直接映射为业务POJO,需要添加自定义转换器并注册到R2DBC配置中:

步骤1:定义业务POJO

public class ProductData {
    private String category;
    private Double price;
    private String brand;

    // Getter & Setter
}

步骤2:实现序列化/反序列化转换器

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import java.io.IOException;

// 从数据库JSON字符串转为POJO
@ReadingConverter
public class JsonbToProductDataConverter implements Converter<String, ProductData> {
    private final ObjectMapper objectMapper;

    public JsonbToProductDataConverter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public ProductData convert(String source) {
        try {
            return objectMapper.readValue(source, ProductData.class);
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to parse JSON to ProductData", e);
        }
    }
}

// 从POJO转为数据库JSON字符串
@WritingConverter
public class ProductDataToJsonbConverter implements Converter<ProductData, String> {
    private final ObjectMapper objectMapper;

    public ProductDataToJsonbConverter(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public String convert(ProductData source) {
        try {
            return objectMapper.writeValueAsString(source);
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to serialize ProductData to JSON", e);
        }
    }
}

步骤3:注册转换器到R2DBC配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.r2dbc.convert.R2dbcCustomConversions;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;

@Configuration
public class R2dbcConfig {
    @Bean
    public R2dbcCustomConversions r2dbcCustomConversions(ObjectMapper objectMapper) {
        return R2dbcCustomConversions.of(PostgresDialect.INSTANCE,
                List.of(new JsonbToProductDataConverter(objectMapper), new ProductDataToJsonbConverter(objectMapper)));
    }
}

步骤4:修改实体类字段类型

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;

public class Product {
    @Id
    private Long id;
    
    @Column(columnDefinition = "jsonb")
    private ProductData pdata;

    // Getter & Setter
}

内容的提问来源于stack exchange,提问作者Abdullah Imran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 17:45:28