如何为给定的具有多对多关系实体的 SQL 查询构建 JPA 规范?

问题描述 投票:0回答:1

我有以下实体:

@Entity
@Table(name = "advertisement")
public class AdEntity {
   @Id
   @GeneratedValue(strategy = GenerationType.UUID)
   private String id;
   
   @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
   @JoinTable(name = "ad_tags", joinColumns = {@JoinColumn(name = "ad", foreignKey = @ForeignKey(name = "fk_at_ad")) }, inverseJoinColumns = {@JoinColumn(name = "tag", foreignKey = @ForeignKey(name = "fk_at_tag")) })
   private Set<TagEntity> tags;
.
.
.
}

@Entity
@Table(name = "tag")
public class TagEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private String id;

    @ManyToMany(mappedBy = "tags")
    private Set<AdEntity> ads;
.
.
.
}

这个想法是创建一个过滤器搜索来检索包含所提供的所有标签的所有广告,因此 IN 子句不起作用,因为它将检索至少具有一个标签的所有广告。我能够创建一个 sql 查询来检索我需要的内容,如下所示:

select ad.id from advertisement ad
where ad.id in (select at.ad x
from ad_tags at
join tag t on t.id = at.tag
where t.name in ('tag1', 'tag2', 'tag3')
group by x
having count(tag) = 3)

所以我现在遇到的问题是我想为此查询创建一个 JPA 规范

我找到了一些文档来尝试构建部分查询,但问题是我有一个多对多关系,所以我不完全确定如何构建规范,这是我所拥有的,但它没有工作:

public static Specification<AdEntity> filterTags(List<TagEntity> tags) {
    
  return (root, query, criteriaBuilder) -> {
            
    query.distinct(true);

            
    Subquery<TagEntity> tagSubquery = query.subquery(TagEntity.class);
    Root<TagEntity> tagRoot = tagSubquery.from(TagEntity.class);
            
    Expression<Collection<AdEntity>> adTags = tagRoot.get("ads");
            
    Expression<Long> countExpression = criteriaBuilder.count(root);
            
    query.multiselect(root.get("tags"), countExpression);
            
    query.groupBy(root.get("tags"));
            
    Predicate havingPredicate = criteriaBuilder.equal(countExpression, tags.size());
            
    query.having(havingPredicate);      
    tagSubquery.select(tagRoot);        
    tagSubquery.where(tagRoot.in(tags), criteriaBuilder.isMember(root, adTags));
    
            
    return criteriaBuilder.exists(tagSubquery);
  };
            
}

正确的方法应该是什么?

java mysql spring jpa many-to-many
1个回答
0
投票

维克多。

以下规格:

public static Specification<AdEntity> byTagsAllIn(Set<String> tags) {
    return (root, query, builder) -> {
        query.groupBy(root.get(AdEntity_.ID));
        query.having(builder.equal(builder.count(root), tags.size()));
        return root.joinSet(AdEntity_.TAGS, JoinType.INNER).get(TagEntity_.TAG_NAME).in(tags);
    };
}

会生成这样的伪SQL:

Hibernate: 
    select
        a1_0.id,
        a1_0.advertisement_name 
    from
        advertisements a1_0 
    join
        (ad_tags t1_0 
    join
        tags t1_1 
            on t1_1.id=t1_0.tag) 
                on a1_0.id=t1_0.ad 
        where
            t1_1.tag_name in (?,?,?) 
        group by
            a1_0.id 
        having
            count(a1_0.id)=?

考虑使用 hibernate-jpamodelgen 在项目构建时生成静态元模型

AdEntity_
TagEntity_

© www.soinside.com 2019 - 2024. All rights reserved.