如何正确实现Hibernate Specification按Tag筛选Article列表?
问题描述
我希望通过关联的Tag筛选Article列表,现有相关代码及环境如下:
Repository与Service代码
@Repository public interface ArticleRepository extends JpaRepository<Article, Long>, JpaSpecificationExecutor<Article> {} // Service调用逻辑 public List<Article> findAllArticlesWithTag(String tag) { return articleRepository .findAll(articleSpecificationFactory.allArticlesWithTag(tag)); }
问题出在Specification工厂方法allArticlesWithTag(String tag)的实现上:
public Specification<Article> allArticlesWithTag(String tag) { return new Specification<Article>() { @Override public Predicate toPredicate(Root<Article> root, CriteriaQuery<?> query, CriteriaBuilder builder) { Predicate predicate = builder.conjunction(); if (Objects.nonNull(tag) && !tag.isBlank()) { predicate.getExpressions().add( builder.in(builder.equal(root.get("tags"), tag))); // 此处写法错误 } return predicate; } }; }
数据库结构
CREATE TABLE IF NOT EXISTS articles ( id BIGSERIAL PRIMARY KEY, title VARCHAR(255) UNIQUE ); CREATE TABLE IF NOT EXISTS tags ( article_id BIGINT REFERENCES articles (id), tag VARCHAR(255) );
Article实体定义
Tag没有独立的Java实体,而是以@ElementCollection形式在Article实体中定义:
@Getter @Setter @Entity @ToString @NoArgsConstructor @AllArgsConstructor @Table(name = "articles") public class Article { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ElementCollection(targetClass = String.class, fetch = FetchType.EAGER) @CollectionTable(name = "tags", joinColumns = @JoinColumn(name = "article_id")) @Column(name = "tag") private List<String> tags = new ArrayList<>(); }
我知道需要通过组合CriteriaBuilder builder的调用实现正确筛选,但不清楚具体写法,请问如何修正toPredicate方法,实现按Tag筛选Article的功能?
解决方案
由于tags是@ElementCollection类型的集合属性,直接用root.get("tags")做等值判断是错误的,因为它指向的是整个集合而非单个元素。以下两种方法可以正确实现需求:
方法一:使用CriteriaBuilder.isMember(推荐)
CriteriaBuilder提供了isMember方法,专门用于判断某个元素是否存在于集合属性中,代码修改如下:
public Specification<Article> allArticlesWithTag(String tag) { return (root, query, builder) -> { Predicate predicate = builder.conjunction(); if (Objects.nonNull(tag) && !tag.isBlank()) { // 判断目标tag是否在当前Article的tags集合中 predicate.getExpressions().add( builder.isMember(tag, root.get("tags")) ); } return predicate; }; }
方法二:关联tags表查询
如果需要更复杂的查询逻辑,可以通过join关联tags表来实现匹配:
public Specification<Article> allArticlesWithTag(String tag) { return (root, query, builder) -> { if (Objects.isNull(tag) || tag.isBlank()) { return builder.conjunction(); // 返回空条件,查询全部Article } // 关联tags集合表 Join<Object, Object> tagsJoin = root.join("tags"); // 匹配tag字段值 query.distinct(true); // 开启去重,避免因多Tag关联导致的Article重复 return builder.equal(tagsJoin.get("tag"), tag); }; }
内容的提问来源于stack exchange,提问作者Pavel
相关产品推荐
相关产品推荐

