JPA Criteria API 过滤子类的属性

JPA Criteria API filtering on subclasses' attribute

我有以下实体域。

Parent class

@Entity
public final class SupportModuleInteraction implements Serializable {

    @Id
    private Long id;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "supportModuleInteraction")
    @OrderColumn
    private List<Event> events;

    // ... getters/setters/ other attributes ommitted
}

Child class 层次结构

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "event_type")
public abstract class Event implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ManyToOne(fetch = FetchType.EAGER)
    private SupportModuleInteraction supportModuleInteraction;
}


@Entity
@DiscriminatorValue("SEARCH")
public final class SearchEvent extends Event {

    @Lob
    private String searchTerm;
}

我正在尝试检索任何包含包含关键字的 SearchEvent 的 SupportModuleInteractions。

这是我用来尝试检索那些 SupportModuleInteractions 的规范:

public static Specification<SupportModuleInteraction> interactionWithSearchEventContaingKeyword(String keyword) {
        return new Specification<SupportModuleInteraction>() {
            @Override
            public Predicate toPredicate(Root<SupportModuleInteraction> smi, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Join<SupportModuleInteraction, SearchEvent> events = smi.join("events");
                return criteriaBuilder.and(
                        criteriaBuilder.equal(events.type(), SearchEvent.class),
                        criteriaBuilder.like(events.get("searchTerm"), "%" + keyword + "%"));
            }
        };
    }

规范没有说明事件不包含称为 "searchTerm" 的属性,这是有道理的,因为它只存在于 SearchEvent 子 class.

我在这里 JPA2 Criteria queries on entity hierarchy 查看了这个答案,它没有解决我的问题,因为查询中涉及的实体位于 class 层次结构之外。

作为原生 SQL 查询,这就是我要实现的目标:

SELECT *
FROM event E JOIN support_module_interaction smi ON E.support_module_interaction_id = smi.id
WHERE event_type = 'SEARCH'
  AND search_term LIKE 'searchString'

2019 年 8 月 31 日更新

我查看了 JPA Criteria API: query property of subclass 并修改了规范以使用 criteriaBuilder.treat(...),但没有成功:

 public static Specification<SupportModuleInteraction> interactionWithSearchEventContaingKeyword(String keyword) {
        return new Specification<SupportModuleInteraction>() {
            @Override
            public Predicate toPredicate(Root<SupportModuleInteraction> smi, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Join<SupportModuleInteraction, SearchEvent> events = smi.join("events");
                return criteriaBuilder.and(
                        criteriaBuilder.equal(events.type(), SearchEvent.class),
                        criteriaBuilder.like(
                                criteriaBuilder.treat(events.get("searchTerm"), SearchEvent.class).get("searchTerm"),
                                "%" + keyword + "%"));
            }
        };
    }

Hibernate 中的各种继承策略很难在 JPA Crtieria 中使用 API。

不要使用相当挑剔的 treat()(请参阅 HHH-9862 和相关问题),而是生成一个 subquery() 表达式来过滤 searchTerm 并将其添加到您的父查询。

您的整体实现不清楚,所以我创建了一个基本示例:

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

Root<SupportModuleInteraction> supModIntRoot = cq.from(SupportModuleInteraction.class);
Join<SupportModuleInteraction, Event> supModIntEventJoin = supModIntRoot.join("events"); 

// Instantiate the Subquery and Root objects for your subclass entity
Subquery<SearchEvent> searchEventSubquery = cq.from(SearchEvent.class);
Root<SearchEvent> searchEventRoot = searchEventSubquery.from(SearchEvent.class);
// Generate the subquery and filter based on the given parameter
searchEventSubquery.select(searchEventRoot);
searchEventSubquery.where(cb.like(searchEventRoot.get("searchTerm", "%" + searchTerm + "%");

// add subquery to where clause in parent query
cq.select(supModIntRoot);
cq.where(supModIntEventJoin.in(searchEventSubquery), //other subqueries and filters...);