Java 如何在CriteriaAPI/JPQL中使用具有一对多属性的JPA投影

Java 如何在CriteriaAPI/JPQL中使用具有一对多属性的JPA投影,java,hibernate,spring-data-jpa,jpql,criteria-api,Java,Hibernate,Spring Data Jpa,Jpql,Criteria Api,我很难使用CriteriaAPI创建一个查询,该API可以投影查询实体的属性并实例化DTO。其中一个投影属性将一对多关系映射到另一个实体,因此它是一组依赖实体。我正在使用fetch join检索集合。但我得到了以下错误: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list 我

我很难使用CriteriaAPI创建一个查询,该API可以投影查询实体的属性并实例化DTO。其中一个投影属性将一对多关系映射到另一个实体,因此它是一组依赖实体。我正在使用fetch join检索集合。但我得到了以下错误:

org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
我已经尝试过使用常规联接,但是在这种情况下,依赖实体集不会被填充。完全删除联接和/或获取也没有帮助

我使用的是JPA规范2.0、Hibernate 4.2.21.Final、Spring数据JPA 1.10.11.RELEASE。

有人能给我提些建议吗?我也会很高兴有一个工作的JPQL

这是我对查询的实现:

@Override
public List<EntityADto> findByPartialKey1OrderByPartialKey2(String partialKey1) {
    // Create query
    final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    final CriteriaQuery<EntityADto> criteriaQuery = criteriaBuilder.createQuery(EntityADto.class);

    // Define FROM clause
    final Root<EntityA> root = criteriaQuery.from(EntityA.class);
    root.fetch(EntityA_.oneToManyAttribute);

    // Define DTO projection
    criteriaQuery
            .select(criteriaBuilder.construct(
                    EntityADto.class,
                    root.get(EntityA_.id).get(EntityAId_.partialKey1),
                    root.get(EntityA_.id).get(EntityAId_.partialKey2),
                    root.get(EntityA_.stringAttribute1),
                    root.get(EntityA_.stringAttribute2),
                    root.get(EntityA_.oneToManyAttribute)))
            .orderBy(criteriaBuilder.asc(root.get(EntityA_.id).get(EntityAId_.partialKey2)))
            .distinct(true);

    // Define WHERE clause
    final ParameterExpression<String> parameterPartialKey1 = criteriaBuilder.parameter(String.class);
    criteriaQuery.where(criteriaBuilder.equal(root.get(EntityA_.id).get(EntityAId_.partialKey1), parameterPartialKey1));

    // Execute query
    final TypedQuery<EntityADto> typedQuery = entityManager.createQuery(criteriaQuery);
    typedQuery.setParameter(parameterPartialKey1, partialKey1);

    return typedQuery.getResultList();
}
@Entity
@Table(name = "TABLE_A", uniqueConstraints = 
@UniqueConstraint(columnNames = {
    "PARTIAL_KEY_1", "STRING_ATTR_1", "STRING_ATTR_2" }))
public class EntityA {
    @EmbeddedId
    @AttributeOverrides({
        @AttributeOverride(name = "partialKey1", column = @Column(name = "PARTIAL_KEY_1", nullable = false)),
        @AttributeOverride(name = "partialKey2", column = @Column(name = "PARTIAL_KEY_2", nullable = false))})
    private EntityAId id;
    @Column(name = "STRING_ATTR_1", nullable = false)
    private String stringAttribute1;
    @Column(name = "STRING_ATTR_2", nullable = false)
    private String stringAttribute2;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "entityA")
    private Set<EntityB> entityBs;

    // getters and setters omitted for brevity.
}

@Entity
@Table(name = "TABLE_2")
public class EntityB {
    @EmbeddedId
    @AttributeOverrides({
        @AttributeOverride(name = "partialKey3", column = @Column(name = "PARTIAL_KEY_3", nullable = false)),
        @AttributeOverride(name = "partialKey1", column = @Column(name = "PARTIAL_KEY_1", nullable = false)),
        @AttributeOverride(name = "partialKey2", column = @Column(name = "PARTIAL_KEY_2", nullable = false))})
    private EntityBId id;
    @Column(name = "VALUE")
    private String value;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns({
        @JoinColumn(name = "PARTIAL_KEY_1", referencedColumnName = "PARTIAL_KEY_1", nullable = false, insertable = false, updatable = false),
        @JoinColumn(name = "PARTIAL_KEY_2", referencedColumnName = "PARTIAL_KEY_2", nullable = false, insertable = false, updatable = false)})
    private EntityA entityA;

    // getters and setters omitted for brevity.
public class EntityADto implements Serializable {
    private static final long serialVersionUID = -5343329086697620178L;

    private String partialKey1;
    private Integer partialKey2;
    private String stringAttribute1;
    private String stringAttribute2;
    private Map<String, String> additionalAttributes;

    public ProzessdatStandardDto() { }

    public ProzessdatStandardDto(String partialKey1,
                                 Integer partialKey2,
                                 String stringAttribute1,
                                 String stringAttribute2,
                                 Set<EntityB> entityBs) {
        this.partialKey1 = partialKey1;
        this.partialKey2 = partialKey2;
        this.stringAttribute1 = stringAttribute1;
        this.stringAttribute2 = stringAttribute2;

        final Map<String, String> entityBsConverted = new HashMap<>();
        if (!CollectionUtils.isEmpty(entityBs)) {
            for (EntityB entityB : entityBs) {
                entityBsConverted.put(entityB.getPartialKey3(), entityB.getValue());
            }
        }
        this.additionalAttributes = prozessdatExpansionsConverted;
    }

    // getters and setters omitted for brevity.
}
@覆盖
公共列表FindBypartialkey1或ByPartialkey2(字符串partialKey1){
//创建查询
final CriteriaBuilder CriteriaBuilder=entityManager.getCriteriaBuilder();
final-CriteriaQuery-CriteriaQuery=criteriaBuilder.createQuery(EntityADto.class);
//从子句定义
最终根=criteriaQuery.from(EntityA.class);
root.fetch(EntityA_u2;oneToManyAttribute);
//定义DTO投影
标准查询
.选择(criteriaBuilder.construct(
EntityADto.class,
root.get(EntityA.id).get(EntityAId.partialKey1),
root.get(EntityA.id).get(EntityAId.partialKey2),
root.get(EntityA_uu.stringAttribute1),
root.get(EntityA_uu.stringAttribute2),
root.get(EntityA_uu.oneToManyAttribute)))
.orderBy(criteriaBuilder.asc(root.get(EntityA_u2;.id).get(EntityAId_2;u2b.partialKey2)))
.清晰(真实);
//定义WHERE子句
最终参数expression参数partialkey1=criteriaBuilder.parameter(String.class);
where(criteriaBuilder.equal(root.get(EntityA_u.id).get(EntityAId_u.partialKey1),parameterPartialKey1));
//执行查询
final-TypedQuery-TypedQuery=entityManager.createQuery(criteriaQuery);
typedQuery.setParameter(parameterPartialKey1,partialKey1);
返回typedQuery.getResultList();
}
实体如下所示:

@Override
public List<EntityADto> findByPartialKey1OrderByPartialKey2(String partialKey1) {
    // Create query
    final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    final CriteriaQuery<EntityADto> criteriaQuery = criteriaBuilder.createQuery(EntityADto.class);

    // Define FROM clause
    final Root<EntityA> root = criteriaQuery.from(EntityA.class);
    root.fetch(EntityA_.oneToManyAttribute);

    // Define DTO projection
    criteriaQuery
            .select(criteriaBuilder.construct(
                    EntityADto.class,
                    root.get(EntityA_.id).get(EntityAId_.partialKey1),
                    root.get(EntityA_.id).get(EntityAId_.partialKey2),
                    root.get(EntityA_.stringAttribute1),
                    root.get(EntityA_.stringAttribute2),
                    root.get(EntityA_.oneToManyAttribute)))
            .orderBy(criteriaBuilder.asc(root.get(EntityA_.id).get(EntityAId_.partialKey2)))
            .distinct(true);

    // Define WHERE clause
    final ParameterExpression<String> parameterPartialKey1 = criteriaBuilder.parameter(String.class);
    criteriaQuery.where(criteriaBuilder.equal(root.get(EntityA_.id).get(EntityAId_.partialKey1), parameterPartialKey1));

    // Execute query
    final TypedQuery<EntityADto> typedQuery = entityManager.createQuery(criteriaQuery);
    typedQuery.setParameter(parameterPartialKey1, partialKey1);

    return typedQuery.getResultList();
}
@Entity
@Table(name = "TABLE_A", uniqueConstraints = 
@UniqueConstraint(columnNames = {
    "PARTIAL_KEY_1", "STRING_ATTR_1", "STRING_ATTR_2" }))
public class EntityA {
    @EmbeddedId
    @AttributeOverrides({
        @AttributeOverride(name = "partialKey1", column = @Column(name = "PARTIAL_KEY_1", nullable = false)),
        @AttributeOverride(name = "partialKey2", column = @Column(name = "PARTIAL_KEY_2", nullable = false))})
    private EntityAId id;
    @Column(name = "STRING_ATTR_1", nullable = false)
    private String stringAttribute1;
    @Column(name = "STRING_ATTR_2", nullable = false)
    private String stringAttribute2;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "entityA")
    private Set<EntityB> entityBs;

    // getters and setters omitted for brevity.
}

@Entity
@Table(name = "TABLE_2")
public class EntityB {
    @EmbeddedId
    @AttributeOverrides({
        @AttributeOverride(name = "partialKey3", column = @Column(name = "PARTIAL_KEY_3", nullable = false)),
        @AttributeOverride(name = "partialKey1", column = @Column(name = "PARTIAL_KEY_1", nullable = false)),
        @AttributeOverride(name = "partialKey2", column = @Column(name = "PARTIAL_KEY_2", nullable = false))})
    private EntityBId id;
    @Column(name = "VALUE")
    private String value;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns({
        @JoinColumn(name = "PARTIAL_KEY_1", referencedColumnName = "PARTIAL_KEY_1", nullable = false, insertable = false, updatable = false),
        @JoinColumn(name = "PARTIAL_KEY_2", referencedColumnName = "PARTIAL_KEY_2", nullable = false, insertable = false, updatable = false)})
    private EntityA entityA;

    // getters and setters omitted for brevity.
public class EntityADto implements Serializable {
    private static final long serialVersionUID = -5343329086697620178L;

    private String partialKey1;
    private Integer partialKey2;
    private String stringAttribute1;
    private String stringAttribute2;
    private Map<String, String> additionalAttributes;

    public ProzessdatStandardDto() { }

    public ProzessdatStandardDto(String partialKey1,
                                 Integer partialKey2,
                                 String stringAttribute1,
                                 String stringAttribute2,
                                 Set<EntityB> entityBs) {
        this.partialKey1 = partialKey1;
        this.partialKey2 = partialKey2;
        this.stringAttribute1 = stringAttribute1;
        this.stringAttribute2 = stringAttribute2;

        final Map<String, String> entityBsConverted = new HashMap<>();
        if (!CollectionUtils.isEmpty(entityBs)) {
            for (EntityB entityB : entityBs) {
                entityBsConverted.put(entityB.getPartialKey3(), entityB.getValue());
            }
        }
        this.additionalAttributes = prozessdatExpansionsConverted;
    }

    // getters and setters omitted for brevity.
}
@实体
@表(name=“Table_A”,唯一约束=
@唯一约束(列名称={
“部分键1”、“字符串属性1”、“字符串属性2”})
公共类实体{
@嵌入ID
@属性溢出({
@AttributeOverride(name=“partialKey1”,column=@column(name=“PARTIAL\u KEY\u 1”,nullable=false)),
@AttributeOverride(name=“partialKey2”,column=@column(name=“PARTIAL\u KEY\u 2”,nullable=false)))
私人实体id;
@列(name=“STRING\u ATTR\u 1”,null=false)
私有字符串stringAttribute1;
@列(name=“STRING\u ATTR\u 2”,null=false)
私有字符串stringAttribute2;
@OneToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY,mappedBy=“entityA”)
私有集合实体;
//为简洁起见,省略了getter和setter。
}
@实体
@表(name=“表2”)
公共类实体B{
@嵌入ID
@属性溢出({
@AttributeOverride(name=“partialKey3”,column=@column(name=“PARTIAL\u KEY\u 3”,nullable=false)),
@AttributeOverride(name=“partialKey1”,column=@column(name=“PARTIAL\u KEY\u 1”,nullable=false)),
@AttributeOverride(name=“partialKey2”,column=@column(name=“PARTIAL\u KEY\u 2”,nullable=false)))
私人实体投标id;
@列(name=“VALUE”)
私有字符串值;
@manytone(fetch=FetchType.LAZY)
@连接柱({
@JoinColumn(name=“PARTIAL\u KEY\u 1”,referencedColumnName=“PARTIAL\u KEY\u 1”,nullable=false,insertable=false,updateable=false),
@JoinColumn(name=“PARTIAL\u KEY\u 2”,referencedColumnName=“PARTIAL\u KEY\u 2”,nullable=false,insertable=false,updateable=false)})
私人实体a实体a;
//为简洁起见,省略了getter和setter。
}

最后是DTO:

@Override
public List<EntityADto> findByPartialKey1OrderByPartialKey2(String partialKey1) {
    // Create query
    final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
    final CriteriaQuery<EntityADto> criteriaQuery = criteriaBuilder.createQuery(EntityADto.class);

    // Define FROM clause
    final Root<EntityA> root = criteriaQuery.from(EntityA.class);
    root.fetch(EntityA_.oneToManyAttribute);

    // Define DTO projection
    criteriaQuery
            .select(criteriaBuilder.construct(
                    EntityADto.class,
                    root.get(EntityA_.id).get(EntityAId_.partialKey1),
                    root.get(EntityA_.id).get(EntityAId_.partialKey2),
                    root.get(EntityA_.stringAttribute1),
                    root.get(EntityA_.stringAttribute2),
                    root.get(EntityA_.oneToManyAttribute)))
            .orderBy(criteriaBuilder.asc(root.get(EntityA_.id).get(EntityAId_.partialKey2)))
            .distinct(true);

    // Define WHERE clause
    final ParameterExpression<String> parameterPartialKey1 = criteriaBuilder.parameter(String.class);
    criteriaQuery.where(criteriaBuilder.equal(root.get(EntityA_.id).get(EntityAId_.partialKey1), parameterPartialKey1));

    // Execute query
    final TypedQuery<EntityADto> typedQuery = entityManager.createQuery(criteriaQuery);
    typedQuery.setParameter(parameterPartialKey1, partialKey1);

    return typedQuery.getResultList();
}
@Entity
@Table(name = "TABLE_A", uniqueConstraints = 
@UniqueConstraint(columnNames = {
    "PARTIAL_KEY_1", "STRING_ATTR_1", "STRING_ATTR_2" }))
public class EntityA {
    @EmbeddedId
    @AttributeOverrides({
        @AttributeOverride(name = "partialKey1", column = @Column(name = "PARTIAL_KEY_1", nullable = false)),
        @AttributeOverride(name = "partialKey2", column = @Column(name = "PARTIAL_KEY_2", nullable = false))})
    private EntityAId id;
    @Column(name = "STRING_ATTR_1", nullable = false)
    private String stringAttribute1;
    @Column(name = "STRING_ATTR_2", nullable = false)
    private String stringAttribute2;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "entityA")
    private Set<EntityB> entityBs;

    // getters and setters omitted for brevity.
}

@Entity
@Table(name = "TABLE_2")
public class EntityB {
    @EmbeddedId
    @AttributeOverrides({
        @AttributeOverride(name = "partialKey3", column = @Column(name = "PARTIAL_KEY_3", nullable = false)),
        @AttributeOverride(name = "partialKey1", column = @Column(name = "PARTIAL_KEY_1", nullable = false)),
        @AttributeOverride(name = "partialKey2", column = @Column(name = "PARTIAL_KEY_2", nullable = false))})
    private EntityBId id;
    @Column(name = "VALUE")
    private String value;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns({
        @JoinColumn(name = "PARTIAL_KEY_1", referencedColumnName = "PARTIAL_KEY_1", nullable = false, insertable = false, updatable = false),
        @JoinColumn(name = "PARTIAL_KEY_2", referencedColumnName = "PARTIAL_KEY_2", nullable = false, insertable = false, updatable = false)})
    private EntityA entityA;

    // getters and setters omitted for brevity.
public class EntityADto implements Serializable {
    private static final long serialVersionUID = -5343329086697620178L;

    private String partialKey1;
    private Integer partialKey2;
    private String stringAttribute1;
    private String stringAttribute2;
    private Map<String, String> additionalAttributes;

    public ProzessdatStandardDto() { }

    public ProzessdatStandardDto(String partialKey1,
                                 Integer partialKey2,
                                 String stringAttribute1,
                                 String stringAttribute2,
                                 Set<EntityB> entityBs) {
        this.partialKey1 = partialKey1;
        this.partialKey2 = partialKey2;
        this.stringAttribute1 = stringAttribute1;
        this.stringAttribute2 = stringAttribute2;

        final Map<String, String> entityBsConverted = new HashMap<>();
        if (!CollectionUtils.isEmpty(entityBs)) {
            for (EntityB entityB : entityBs) {
                entityBsConverted.put(entityB.getPartialKey3(), entityB.getValue());
            }
        }
        this.additionalAttributes = prozessdatExpansionsConverted;
    }

    // getters and setters omitted for brevity.
}
公共类EntityADto实现可序列化{ 私有静态最终长serialVersionUID=-5343329086697620178L; 私有字符串partialKey1; 私有整数partialKey2; 私有字符串stringAttribute1; 私有字符串stringAttribute2; 私有地图附加属性; 公共ProzessdatStandardDto(){} 公共项目DATStandardDTO(字符串partialKey1, 整数partialKey2, 字符串stringAttribute1, 字符串stringAttribute2, 设置实体(b){ this.partialKey1=partialKey1; this.partialKey2=partialKey2; this.stringAttribute1=stringAttribute1; this.stringAttribute2=stringAttribute2; 最终映射entityBsConverted=新HashMap(); 如果(!CollectionUtils.isEmpty(entityBs)){ for(EntityB EntityB:entityBs){ entityBsConverted.put(entityB.getPartialKey3(),entityB.getValue()); } } this.additionalAttributes=prozessDateExpansConverted; } //为简洁起见,省略了getter和setter。 }
联接为您提供了一组行,结果为sql:

Parent    Child
p1        c1
p1        c2
p1        c3
等等。没有将结果集合传递到构造函数的机制

JPA规范4.14

constructor_expression ::=
NEW constructor_name ( constructor_item {, constructor_item}* )

constructor_item ::=
single_valued_path_expression |
scalar_expression |
aggregate_expression |
identification_variable
另外,另一个问题是查询可能返回多个父级或子级

Parent    Child    Child2
p1        c111     c121
p1        c121     
p1        c131     c122
p2        c211     c211
p2        c221     c212
p2        c231     
我猜原因是,对于底层的JPA提供者来说,这变得太复杂了,他们不知道在哪里拆分它,或者使用哪些值传递给子构造函数,或者我不熟悉的更微妙的原因。一句话,它要求您提供解析这个矩阵的代码,如果您要这样做,您最好只解析结果而不使用JPA。

C