JPA(或 Hibernate)在标准中投射相关实体集合的方式

JPA (or Hibernate) way to project over a related entity collection in criteria

下面是一些示例实体:

@Entity
@Table(name = "assessment")
public class Assessment implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    private Long id;

    @NotNull
    @Column(name = "created_date", nullable = false, updatable = false)
    private ZonedDateTime createdDate;

    @NotNull
    @Column(name = "score")
    private Float score;

    @ManyToOne
    private BusinessUnit businessUnit;

    @NotNull
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "assessment_context_type",
        joinColumns = @JoinColumn(name = "assessment_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(name = "context_type_id", referencedColumnName = "id"))
    @JsonIgnore
    private Set<ContextType> contextTypes = new HashSet<>();

...
}

@Entity
@Table(name = "context_type")
public class ContextType implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    private Long id;

    @Column(name = "name")
    private String name;
}

我想做的是获得精简版的评估

public class SlimAssessment {
    public final Long id;
    public final ZonedDateTime createdDate;
    public final Float score;
    public final Long buId;
    public final Set<Long> contextTypeIds;

    public SlimAssessment(Long id, ZonedDateTime createdDate, Float score, Long buId, Set<Long> contextTypeIds) {
        this.id = id;
        this.createdDate = createdDate;
        this.score = score;
        this.buId = buId;
        this.contextTypeIds = contextTypeIds;
    }
}

当然省略了更多内容,但我正在尝试通过 JPA 标准来做到这一点 API

CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<SlimAssessment> query = builder.createQuery(SlimAssessment.class);
        Root<Assessment> ra = query.from(Assessment.class);
    
        Join<RiskAssessment, BusinessUnit> buJoin = ra.join(Assessment_.BUSINESS_UNIT);
        SetJoin<Assessment, ContextType> contextTypeJoin = ra.join(Assessment_.contextTypes);
        query.multiselect(
            ra.get(Assessment_.ID),
            ra.get(Assessment_.SCORE),
            buJoin.get(BusinessUnit_.ID),
            contextTypeJoin.get(ContextType_.ID)
        ).orderBy(builder.asc(ra.get(Assessment_.CREATED_DATE)));

但问题是它说找不到构造函数,因为它认为 contextTypeIds 只是一个 Long。如何让查询投影到集合上并获取 ID?如果我直接 SQL 这样做,我可以加入连接 table 以获取关联的 ID,并且我想限制连接的数量以尽可能快地进行此查询(对于例如,业务部门 ID 存储在评估 table 中,所以我不需要加入那里,所以也许我应该切换它?)

感谢任何帮助

正如我在 , Hibernate or Spring Data Projection only support flat results. I think this is a perfect use case for Blaze-Persistence Entity Views 中概述的那样。

我创建了库以允许在 JPA 模型和自定义接口或抽象 class 定义的模型之间轻松映射,类似于 Spring 类固醇数据投影。这个想法是您按照自己喜欢的方式定义目标结构(领域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

您的用例的 DTO 模型与 Blaze-Persistence 实体视图类似:

@EntityView(Assessment.class)
public interface SlimAssessment {
    @IdMapping
    Long getId();
    ZonedDateTime getCreatedDate();
    Float getScore();
    String getName();
    @Mapping("businessUnit.id")
    Long getBuId();
    // You can also map just the ids if you like
    // @Mapping("contextTypes.id")
    // Set<Long> getContextTypeIds();
    Set<ContextTypeIdView> getContextTypes();

    @EntityView(ContextType.class)
    interface ContextTypeIdView {
        @IdMapping
        Long getId();
    }
}

查询就是将实体视图应用于查询,最简单的就是通过 id 进行查询。

SlimAssessment a = entityViewManager.find(entityManager, SlimAssessment.class, id);

Spring 数据集成让您几乎可以像 Spring 数据投影一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Page<SlimAssessment> findAll(Pageable pageable);

最棒的是,它只会获取实际需要的状态!