DATAJPA-476 - Mitigate spec violations in Hibernate for query creation.
Hibernate invalidly returns null for getModel() on its PluralAttribute implementation which causes the necessity for joins not having been detected previously. We now fall back to joining in case we don't find a Model and deal with a PluralAttribute.
This commit is contained in:
@@ -50,6 +50,7 @@ import javax.persistence.metamodel.Attribute;
|
||||
import javax.persistence.metamodel.Attribute.PersistentAttributeType;
|
||||
import javax.persistence.metamodel.Bindable;
|
||||
import javax.persistence.metamodel.ManagedType;
|
||||
import javax.persistence.metamodel.PluralAttribute;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -439,22 +440,25 @@ public abstract class QueryUtils {
|
||||
static <T> Expression<T> toExpressionRecursively(From<?, ?> from, PropertyPath property) {
|
||||
|
||||
Bindable<?> propertyPathModel = null;
|
||||
if (from.getModel() instanceof ManagedType) {
|
||||
Bindable<?> model = from.getModel();
|
||||
String segment = property.getSegment();
|
||||
|
||||
if (model instanceof ManagedType) {
|
||||
|
||||
/*
|
||||
* Required to keep support for EclipseLink 2.4.x. TODO: Remove once we drop that (probably Dijkstra M1)
|
||||
* See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=413892
|
||||
*/
|
||||
propertyPathModel = (Bindable<?>) ((ManagedType<?>) from.getModel()).getAttribute(property.getSegment());
|
||||
propertyPathModel = (Bindable<?>) ((ManagedType<?>) model).getAttribute(segment);
|
||||
} else {
|
||||
propertyPathModel = from.get(property.getSegment()).getModel();
|
||||
propertyPathModel = from.get(segment).getModel();
|
||||
}
|
||||
|
||||
if (requiresJoin(propertyPathModel)) {
|
||||
Join<?, ?> join = getOrCreateJoin(from, property.getSegment());
|
||||
if (requiresJoin(propertyPathModel, model instanceof PluralAttribute)) {
|
||||
Join<?, ?> join = getOrCreateJoin(from, segment);
|
||||
return (Expression<T>) (property.hasNext() ? toExpressionRecursively(join, property.next()) : join);
|
||||
} else {
|
||||
Path<Object> path = from.get(property.getSegment());
|
||||
Path<Object> path = from.get(segment);
|
||||
return (Expression<T>) (property.hasNext() ? toExpressionRecursively(path, property.next()) : path);
|
||||
}
|
||||
}
|
||||
@@ -464,9 +468,14 @@ public abstract class QueryUtils {
|
||||
* non-optional association.
|
||||
*
|
||||
* @param propertyPathModel must not be {@literal null}.
|
||||
* @param for
|
||||
* @return
|
||||
*/
|
||||
private static boolean requiresJoin(Bindable<?> propertyPathModel) {
|
||||
private static boolean requiresJoin(Bindable<?> propertyPathModel, boolean forPluralAttribute) {
|
||||
|
||||
if (propertyPathModel == null && forPluralAttribute) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(propertyPathModel instanceof Attribute)) {
|
||||
return false;
|
||||
|
||||
@@ -18,7 +18,14 @@ package org.springframework.data.jpa.repository.query;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Persistence;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
@@ -105,7 +112,42 @@ public class QueryUtilsIntegrationTests {
|
||||
assertThat(root.getJoins(), hasSize(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAJPA-476
|
||||
*/
|
||||
@Test
|
||||
public void traversesPluralAttributeCorrectly() {
|
||||
|
||||
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("merchant");
|
||||
CriteriaBuilder builder = entityManagerFactory.createEntityManager().getCriteriaBuilder();
|
||||
CriteriaQuery<Merchant> query = builder.createQuery(Merchant.class);
|
||||
Root<Merchant> root = query.from(Merchant.class);
|
||||
|
||||
QueryUtils.toExpressionRecursively(root, PropertyPath.from("employeesCredentialsUid", Merchant.class));
|
||||
}
|
||||
|
||||
protected void assertNoJoinRequestedForOptionalAssociation(Root<Order> root) {
|
||||
assertThat(root.getJoins(), is(empty()));
|
||||
}
|
||||
|
||||
@Entity
|
||||
static class Merchant {
|
||||
|
||||
@Id String id;
|
||||
@OneToMany Set<Employee> employees;
|
||||
}
|
||||
|
||||
@Entity
|
||||
static class Employee {
|
||||
|
||||
@Id String id;
|
||||
@OneToMany Set<Credential> credentials;
|
||||
}
|
||||
|
||||
@Entity
|
||||
static class Credential {
|
||||
|
||||
@Id String id;
|
||||
String uid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,18 @@
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
|
||||
<!-- DATAJPA-476 -->
|
||||
<persistence-unit name="merchant">
|
||||
<class>org.springframework.data.jpa.domain.sample.User</class>
|
||||
<class>org.springframework.data.jpa.repository.query.QueryUtilsIntegrationTests$Merchant</class>
|
||||
<class>org.springframework.data.jpa.repository.query.QueryUtilsIntegrationTests$Employee</class>
|
||||
<class>org.springframework.data.jpa.repository.query.QueryUtilsIntegrationTests$Credential</class>
|
||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||
<properties>
|
||||
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
|
||||
<!-- Custom PUs for metadata tests -->
|
||||
|
||||
<persistence-unit name="metadata">
|
||||
|
||||
Reference in New Issue
Block a user