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

如何用Spring Boot 3 + Hibernate 6实现Product数据可选过滤?

Spring Boot 3 + Hibernate 6 实现Product可选字段过滤的最佳实践

网传Hibernate 6无法使用Criteria API是错误的——它完全支持JPA Criteria API,同时还有多种更灵活的方案可选。下面针对你的Product实体,给出几种落地性强的可选字段过滤实践:

方案1:JPA Criteria API(原生支持,无额外依赖)

先定义一个过滤参数类,用来接收前端传来的可选过滤条件:

public class ProductFilter {
    private String title;
    private BigDecimal minPrice;
    private BigDecimal maxPrice;
    private Boolean featured;
    private Long brandId;
    private Long categoryId;
    private StatusZeroAndOne status;
    
    // 省略getter、setter
}

然后在Repository实现类中,用CriteriaBuilder动态构建查询:

@Repository
public class ProductCustomRepositoryImpl {

    @PersistenceContext
    private EntityManager entityManager;

    public List<Product> findProductsByFilter(ProductFilter filter) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<Product> cq = cb.createQuery(Product.class);
        Root<Product> productRoot = cq.from(Product.class);

        List<Predicate> predicates = new ArrayList<>();

        // 标题模糊匹配(忽略大小写)
        if (filter.getTitle() != null && !filter.getTitle().isBlank()) {
            predicates.add(cb.like(cb.lower(productRoot.get("title")), "%" + filter.getTitle().toLowerCase() + "%"));
        }

        // 价格区间过滤
        if (filter.getMinPrice() != null) {
            predicates.add(cb.greaterThanOrEqualTo(productRoot.get("price"), filter.getMinPrice()));
        }
        if (filter.getMaxPrice() != null) {
            predicates.add(cb.lessThanOrEqualTo(productRoot.get("price"), filter.getMaxPrice()));
        }

        // 精选商品过滤
        if (filter.getFeatured() != null) {
            predicates.add(cb.equal(productRoot.get("featured"), filter.getFeatured()));
        }

        // 品牌ID匹配
        if (filter.getBrandId() != null) {
            predicates.add(cb.equal(productRoot.get("brand").get("id"), filter.getBrandId()));
        }

        // 分类ID匹配
        if (filter.getCategoryId() != null) {
            predicates.add(cb.equal(productRoot.get("category").get("id"), filter.getCategoryId()));
        }

        // 状态匹配
        if (filter.getStatus() != null) {
            predicates.add(cb.equal(productRoot.get("status"), filter.getStatus()));
        }

        cq.where(predicates.toArray(new Predicate[0]));
        return entityManager.createQuery(cq).getResultList();
    }
}

方案2:Spring Data JPA Specification(优雅的动态查询封装)

让你的ProductRepository继承JpaSpecificationExecutor:

public interface ProductRepository extends JpaRepository<Product, Long>, JpaSpecificationExecutor<Product> {
}

编写Specification工具类,封装过滤逻辑:

public class ProductSpecifications {

    public static Specification<Product> withFilter(ProductFilter filter) {
        return (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();

            if (filter.getTitle() != null && !filter.getTitle().isBlank()) {
                predicates.add(cb.like(cb.lower(root.get("title")), "%" + filter.getTitle().toLowerCase() + "%"));
            }

            if (filter.getMinPrice() != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("price"), filter.getMinPrice()));
            }
            if (filter.getMaxPrice() != null) {
                predicates.add(cb.lessThanOrEqualTo(root.get("price"), filter.getMaxPrice()));
            }

            if (filter.getFeatured() != null) {
                predicates.add(cb.equal(root.get("featured"), filter.getFeatured()));
            }

            if (filter.getBrandId() != null) {
                predicates.add(cb.equal(root.get("brand").get("id"), filter.getBrandId()));
            }

            if (filter.getCategoryId() != null) {
                predicates.add(cb.equal(root.get("category").get("id"), filter.getCategoryId()));
            }

            if (filter.getStatus() != null) {
                predicates.add(cb.equal(root.get("status"), filter.getStatus()));
            }

            return cb.and(predicates.toArray(new Predicate[0]));
        };
    }
}

在Service中直接调用:

// 注入ProductRepository
@Autowired
private ProductRepository productRepository;

public List<Product> getProducts(ProductFilter filter) {
    return productRepository.findAll(ProductSpecifications.withFilter(filter));
}

方案3:Querydsl(类型安全的动态查询)

Querydsl能避免硬编码字段名带来的错误,提供编译期类型检查。

第一步:添加依赖(Maven)

<dependencies>
    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
        <version>5.0.0</version>
    </dependency>
    <dependency>
        <groupId>com.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <version>5.0.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

第二步:配置APT插件生成Q类

在pom.xml的build节点下添加插件:

<build>
    <plugins>
        <plugin>
            <groupId>com.mysema.maven</groupId>
            <artifactId>apt-maven-plugin</artifactId>
            <version>1.1.3</version>
            <executions>
                <execution>
                    <goals>
                        <goal>process</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>target/generated-sources/java</outputDirectory>
                        <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

执行mvn compile后,会自动生成QProduct类。

第三步:编写查询逻辑

让ProductRepository继承QuerydslJpaRepository:

public interface ProductRepository extends JpaRepository<Product, Long>, QuerydslJpaRepository<Product, Long> {
}

然后在Service中实现过滤:

@Autowired
private ProductRepository productRepository;

public List<Product> getProducts(ProductFilter filter) {
    QProduct product = QProduct.product;
    BooleanBuilder builder = new BooleanBuilder();

    if (filter.getTitle() != null && !filter.getTitle().isBlank()) {
        builder.and(product.title.toLowerCase().contains(filter.getTitle().toLowerCase()));
    }

    if (filter.getMinPrice() != null) {
        builder.and(product.price.goe(filter.getMinPrice()));
    }
    if (filter.getMaxPrice() != null) {
        builder.and(product.price.loe(filter.getMaxPrice()));
    }

    if (filter.getFeatured() != null) {
        builder.and(product.featured.eq(filter.getFeatured()));
    }

    if (filter.getBrandId() != null) {
        builder.and(product.brand.id.eq(filter.getBrandId()));
    }

    if (filter.getCategoryId() != null) {
        builder.and(product.category.id.eq(filter.getCategoryId()));
    }

    if (filter.getStatus() != null) {
        builder.and(product.status.eq(filter.getStatus()));
    }

    return productRepository.findAll(builder);
}

方案4:动态JPQL拼接(适合熟悉JPQL的场景)

直接拼接JPQL语句,但必须用参数绑定防止SQL注入:

@Repository
public class ProductCustomRepositoryImpl {

    @PersistenceContext
    private EntityManager entityManager;

    public List<Product> findProductsByFilter(ProductFilter filter) {
        StringBuilder jpql = new StringBuilder("SELECT p FROM Product p WHERE 1=1");
        Map<String, Object> params = new HashMap<>();

        if (filter.getTitle() != null && !filter.getTitle().isBlank()) {
            jpql.append(" AND LOWER(p.title) LIKE :title");
            params.put("title", "%" + filter.getTitle().toLowerCase() + "%");
        }

        if (filter.getMinPrice() != null) {
            jpql.append(" AND p.price >= :minPrice");
            params.put("minPrice", filter.getMinPrice());
        }
        if (filter.getMaxPrice() != null) {
            jpql.append(" AND p.price <= :maxPrice");
            params.put("maxPrice", filter.getMaxPrice());
        }

        if (filter.getFeatured() != null) {
            jpql.append(" AND p.featured = :featured");
            params.put("featured", filter.getFeatured());
        }

        if (filter.getBrandId() != null) {
            jpql.append(" AND p.brand.id = :brandId");
            params.put("brandId", filter.getBrandId());
        }

        if (filter.getCategoryId() != null) {
            jpql.append(" AND p.category.id = :categoryId");
            params.put("categoryId", filter.getCategoryId());
        }

        if (filter.getStatus() != null) {
            jpql.append(" AND p.status = :status");
            params.put("status", filter.getStatus());
        }

        TypedQuery<Product> query = entityManager.createQuery(jpql.toString(), Product.class);
        params.forEach(query::setParameter);
        return query.getResultList();
    }
}

方案选择建议

  • 追求标准JPA兼容性,无额外依赖:选JPA Criteria API或Spring Data Specification
  • 看重类型安全,避免字段名硬编码错误:选Querydsl
  • 熟悉JPQL语法,喜欢手写查询语句:选动态JPQL(务必用参数绑定)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 10:27:07