【问题标题】:JPA 2 Criteria ManyToMany SubselectJPA 2 标准多对多子选择
【发布时间】:2017-04-10 14:17:45
【问题描述】:

我有以下场景:

Person.class:

@Entity
@Table(name = "person")
@SequenceGenerator(name = "seq_person", sequenceName = "seq_person", allocationSize = 1, initialValue = 1)
public class Person implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_pessoa")
    @Column(name = "id")
    private int id;

    @Column(name = "name", nullable = false)
    private String name;

    @ManyToMany(fetch = FetchType.LAZY, cascade = { javax.persistence.CascadeType.REFRESH, javax.persistence.CascadeType.MERGE })
    @JoinTable(name = "person_category", joinColumns = { @JoinColumn(name = "person_id") }, inverseJoinColumns = { @JoinColumn(name = "category_id") })
    protected List<Category> categories = new ArrayList<>();

    ...
}

Category.class:

@Entity
@Table(name = "category")
@SequenceGenerator(name = "seq_category", sequenceName = "seq_category", allocationSize = 1, initialValue = 1)
public class Category implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_category")
    @Column(name = "id")
    private int id;

    @Column(name = "name", nullable = false)
    private String name;

    ...
}

在这种情况下,我需要使用 JPA2 Criteria 进行查询,以替换此 HQL 查询:

SELECT obj FROM Person AS obj
WHERE EXISTS (
    SELECT category.id FROM obj.categories AS category
    WHERE category.name = 'Staff'
)

我尝试与类别进行连接,但它会将结果乘以每个人的类别数量:

CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Person> cq = cb.createQuery(Person.class);

Root<Person> root = cq.from(Person);

cq.select(root);

Subquery<Category> subquery = cq.subquery(Category.class);
Root<Category> subqueryFrom = subquery.from(Category.class);
subquery.select(subquery.from(clazz).get("id"));

Predicate predicateJoin = cb.equal(root.join("categories", JoinType.LEFT), subqueryFrom);
Predicate predicateName = cb.like(subqueryFrom.get("name"), "%" + content + "%");

subquery.where(cb.and(predicateJoin, predicateName));
cb.where(cb.exists(subquery));

TypedQuery<Person> typedQuery = this.entityManager.createQuery(cq);

List<Person> resultList = typedQuery.getResultList();

这个条件导致这个 SQL:

select
    person0_.id as id1_50_,
    person0_.name as name29_50_
from
    person person0_ 
left outer join
    person_category categories2_ 
        on person0_.id=categories2_.person_id 
left outer join
    category categoryp3_ 
        on categories2_.category_id=categoryp3_.id 
where
    exists (
        select
            categoryp5_.id 
        from
            person_category categoryp4_ cross 
        join
            person_category categoryp5_ 
        where
            categoryp3_.id=categoryp4_.id 
            and (
                categoryp4_.name like ?
            )
    )

生成的 SQL 使用 person_category 进行 CROSS JOIN 并在子选择之外进行 LEFT JOIN。

使用 HQL Hibernate 生成以下 SQL:

select
    person0_.id as id1_50_,
    person0_.name as name29_50_
from
    person person0_ 
where
    exists (
        select
            categoryp2_.id 
        from
            person_category categories1_,
            category categoryp2_ 
        where
            person0_.id=categories1_.person_id 
            and categories1_.category_id=categoryp2_.id 
            and categoryp2_.name=?
    )

如何在 Criteria 中进行此查询?

【问题讨论】:

    标签: java database hibernate jpa


    【解决方案1】:

    您需要correlate() 外部查询与子查询。尝试以下方式(没有真实模型很难测试,但应该足够接近):

        CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
        CriteriaQuery<Person> cq = cb.createQuery(Person.class);
    
        Root<Person> root = cq.from(Person.class);
    
        cq.select(root);
    
        Subquery<Category> subquery = cq.subquery(Category.class);
        Root<Person> subqueryRoot = subquery.correlate(root);
        Join<Person,Category> personCategories = subqueryRoot.join("categories");
        subquery
            .select(personCategories)
            .where(cb.equal(personCategories.get("name"), "Staff"));
    
        cq.where(cb.exists(subquery));
    
        TypedQuery<Person> typedQuery = this.entityManager.createQuery(cq);
    
        List<Person> resultList = typedQuery.getResultList();
    

    应该导致:

    SELECT obj FROM Person AS obj
    WHERE EXISTS (
        SELECT category FROM obj.categories AS category
        WHERE category.name = 'Staff'
    )
    

    【讨论】:

    • 有效!我所做的唯一更改是在 subquery.select(personCategories) 中更改为 subquery.select(subqueryRoot.get("id")),现在可以完美运行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-26
    • 2011-12-29
    • 2019-12-07
    • 1970-01-01
    • 2016-01-15
    • 2018-09-06
    • 1970-01-01
    相关资源
    最近更新 更多