Reputation: 33
I have difficulties creating a query with Criteria API that projects attributes of the queried entity and instantiates a DTO. One of the projected attributes maps a one-to-many relationship with another entity, so it is a set of dependent entities. I am using fetch join to retrieve the set. But I am getting the following error:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
I have already tried using a regular join, but in this case the set of dependent entities won't get populated. Removing the join and / or fetch completely didn't help either.
I am using JPA specification 2.0, Hibernate 4.2.21.Final, Spring Data JPA 1.10.11.RELEASE.
Could anybody advise me on this? I would be happy for a working JPQL also.
This is my implementation of the query:
@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();
}
The entities look as follows:
@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.
}
And finally the DTO:
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.
}
Upvotes: 0
Views: 2524
Reputation: 11551
A join gives you a collection of rows result in sql:
Parent Child
p1 c1
p1 c2
p1 c3
and so on. There is no mechanism for passing the resulting collection into a constructor.
JPA Spec 4.14
constructor_expression ::=
NEW constructor_name ( constructor_item {, constructor_item}* )
constructor_item ::=
single_valued_path_expression |
scalar_expression |
aggregate_expression |
identification_variable
Also, another issue is that your query might return more than one parent or child.
Parent Child Child2
p1 c111 c121
p1 c121
p1 c131 c122
p2 c211 c211
p2 c221 c212
p2 c231
I'm guessing that the reason is that becomes too complicated for the underlying JPA provider to know where to split this up or which values to use to pass to a child constructor or perhaps more subtle reasons I'm not familiar with. Bottom line it requires that you provide code for parsing this matrix and if you're going to do that you might as well just parse the result without JPA.
Upvotes: 1