From dd64fe21ea75ce976ea5bf7575e568fc6b42eb85 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Mon, 24 Mar 2014 16:22:34 +0100 Subject: [PATCH] DATAJPA-466 - Add support for lazy loading configuration via JPA 2.1 fetch-/loadgraph. We now support load-graph / fetch-graph QueryHints on repository query methods, which are applied when a JPA 2.1 capable JPA implementation is used. We explicitly reject the usage of those hints in case the user is running a JPA 2.0 provider. FetchGraphs / LoadGraphs can now be defined on the Entity via the @NamedEntityGraphs annotation. @Entity @QueryEntity @NamedEntityGraphs(@NamedEntityGraph(name = "GroupInfo.members", attributeNodes = @NamedAttributeNode("members"))) public class GroupInfo { @ManyToMany List members = new ArrayList(); //default fetch mode is "lazy". } The entity graph "GroupInfo.members" overwrites the fetch-mode of the members collection to be "eager". The entity graph to be used can now configured on a repository query method. @Repository public interface GroupRepository extends CrudRepository { @EntityGraph("GroupInfo.members") GroupInfo getByGroupName(String name); } The new method JpaQueryMethod#getEntityGraph analyses an @EntityGraph annotation and constructs a new JpaEntityGraph value object that contains the information form the annotation. The new method AbstractJpaQuery#applyEntityGraphConfiguration tries to apply the given EntityGraph configuration if the used JPA persistence provider supports the JPA 2.1 spec. Changed the class path order such that EclipseLink is now placed before the eclipse dependency. EclipseLink references the JPA 2.1 API and allows us to provide type-safe support for the new JPA 2.1 features. Original pull request: #74. --- pom.xml | 13 +-- .../data/jpa/repository/EntityGraph.java | 88 +++++++++++++++++++ .../repository/query/AbstractJpaQuery.java | 43 ++++++++- .../query/Jpa21QueryCustomizer.java | 77 ++++++++++++++++ .../jpa/repository/query/JpaEntityGraph.java | 75 ++++++++++++++++ .../jpa/repository/query/JpaQueryMethod.java | 15 +++- .../data/jpa/domain/sample/User.java | 7 ++ .../query/AbstractJpaQueryTests.java | 69 ++++++++++++++- .../query/JpaQueryMethodUnitTests.java | 32 +++++-- .../query/QueryUtilsIntegrationTests.java | 43 +++++++-- 10 files changed, 440 insertions(+), 22 deletions(-) create mode 100644 src/main/java/org/springframework/data/jpa/repository/EntityGraph.java create mode 100644 src/main/java/org/springframework/data/jpa/repository/query/Jpa21QueryCustomizer.java create mode 100644 src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java diff --git a/pom.xml b/pom.xml index 56c829ab7..27cb5f784 100644 --- a/pom.xml +++ b/pom.xml @@ -138,12 +138,6 @@ - - org.hibernate - hibernate-entitymanager - ${hibernate} - true - org.eclipse.persistence @@ -152,6 +146,13 @@ true + + org.hibernate + hibernate-entitymanager + ${hibernate} + true + + org.apache.openjpa openjpa-persistence-jdbc diff --git a/src/main/java/org/springframework/data/jpa/repository/EntityGraph.java b/src/main/java/org/springframework/data/jpa/repository/EntityGraph.java new file mode 100644 index 000000000..f316c6d1d --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/EntityGraph.java @@ -0,0 +1,88 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.data.annotation.QueryAnnotation; + +/** + * Annotation to configure the JPA 2.1 {@link javax.persistence.EntityGraph}s that should be used on repository methods. + * + * @author Thomas Darimont + * @since 1.6 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@QueryAnnotation +@Documented +public @interface EntityGraph { + + /** + * The name of the EntityGraph to use. + * + * @return + */ + String value(); + + /** + * The {@link Type} of the EntityGraph to use, defaults to {@link Type#FETCH}. + * + * @return + */ + EntityGraphType type() default EntityGraphType.FETCH; + + /** + * Enum for JPA 2.1 {@link javax.persistence.EntityGraph} types. + * + * @author Thomas Darimont + * @since 1.6 + */ + public enum EntityGraphType { + + /** + * When the javax.persistence.loadgraph property is used to specify an entity graph, attributes that are specified + * by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are + * treated according to their specified or default FetchType. + * + * @see JPA 2.1 Specification: 3.7.4.2 Load Graph Semantics + */ + LOAD("javax.persistence.loadgraph"), + + /** + * When the javax.persistence.fetchgraph property is used to specify an entity graph, attributes that are specified + * by attribute nodes of the entity graph are treated as FetchType.EAGER and attributes that are not specified are + * treated as FetchType.LAZY + * + * @see JPA 2.1 Specification: 3.7.4.1 Fetch Graph Semantics + */ + FETCH("javax.persistence.fetchgraph"); + + private final String key; + + private EntityGraphType(String value) { + this.key = value; + } + + public String getKey() { + return key; + } + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index 5426f47bc..2c2c63551 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2013 the original author or authors. + * Copyright 2008-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import javax.persistence.Query; import javax.persistence.QueryHint; import javax.persistence.TypedQuery; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.query.JpaQueryExecution.CollectionExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution; import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution; @@ -121,12 +122,26 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { private T applyHints(T query, JpaQueryMethod method) { for (QueryHint hint : method.getHints()) { - query.setHint(hint.name(), hint.value()); + applyQueryHint(query, hint); } return query; } + /** + * Protected to be able to customize in sub-classes. + * + * @param query must not be {@literal null}. + * @param hint must not be {@literal null}. + */ + protected void applyQueryHint(T query, QueryHint hint) { + + Assert.notNull(query, "Query must not be null!"); + Assert.notNull(hint, "QueryHint must not be null!"); + + query.setHint(hint.name(), hint.value()); + } + /** * Applies the {@link LockModeType} provided by the {@link JpaQueryMethod} to the given {@link Query}. * @@ -145,7 +160,29 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { } protected Query createQuery(Object[] values) { - return applyLockMode(applyHints(doCreateQuery(values), method), method); + return applyLockMode(applyEntityGraphConfiguration(applyHints(doCreateQuery(values), method), method), method); + } + + /** + * Configures the {@link javax.persistence.EntityGraph} to use for the given {@link JpaQueryMethod} if the + * {@link EntityGraph} annotation is present. + * + * @param query must not be {@literal null}. + * @param method must not be {@literal null}. + * @return + */ + private Query applyEntityGraphConfiguration(Query query, JpaQueryMethod method) { + + Assert.notNull(query, "Query must not be null!"); + Assert.notNull(method, "JpaQueryMethod must not be null!"); + + JpaEntityGraph entityGraph = method.getEntityGraph(); + + if (entityGraph != null) { + Jpa21QueryCustomizer.INSTANCE.tryConfigureFetchGraph(em, query, entityGraph); + } + + return query; } protected TypedQuery createCountQuery(Object[] values) { diff --git a/src/main/java/org/springframework/data/jpa/repository/query/Jpa21QueryCustomizer.java b/src/main/java/org/springframework/data/jpa/repository/query/Jpa21QueryCustomizer.java new file mode 100644 index 000000000..d33de0edb --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/query/Jpa21QueryCustomizer.java @@ -0,0 +1,77 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import java.lang.reflect.Method; + +import javax.persistence.EntityGraph; +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +/** + * Customizes a given JPA query with JPA 2.1 features. + * + * @author Thomas Darimont + * @since 1.6 + */ +enum Jpa21QueryCustomizer { + + INSTANCE; + + private static final Method GET_ENTITY_GRAPH_METHOD; + private static final boolean JPA21_AVAILABLE = ClassUtils.isPresent("javax.persistence.NamedEntityGraph", + Jpa21QueryCustomizer.class.getClassLoader()); + + static { + + if (JPA21_AVAILABLE) { + GET_ENTITY_GRAPH_METHOD = ReflectionUtils.findMethod(EntityManager.class, "getEntityGraph", String.class); + } else { + GET_ENTITY_GRAPH_METHOD = null; + } + } + + /** + * Adds a JPA 2.1 fetch-graph or load-graph hint to the given {@link Query} if running under JPA 2.1. + * + * @see JPA 2.1 Specfication 3.7.4 - Use of Entity Graphs in find and query operations P.117 + * @param em must not be {@literal null} + * @param query must not be {@literal null} + * @param entityGraph must not be {@literal null} + */ + public void tryConfigureFetchGraph(EntityManager em, Query query, JpaEntityGraph entityGraph) { + + Assert.notNull(em, "EntityManager must not be null!"); + Assert.notNull(query, "Query must not be null!"); + Assert.notNull(entityGraph, "EntityGraph must not be null!"); + + Assert.isTrue(JPA21_AVAILABLE, "The EntityGraph-Feature requires at least a JPA 2.1 persistence provider!"); + Assert.isTrue(GET_ENTITY_GRAPH_METHOD != null, + "It seems that you have the JPA 2.1 API but a JPA 2.0 implementation on the classpath!"); + + EntityGraph graph = em.getEntityGraph(entityGraph.getName()); + + if (graph == null) { + return; + } + + query.setHint(entityGraph.getType().getKey(), graph); + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java new file mode 100644 index 000000000..0368d4de3 --- /dev/null +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java @@ -0,0 +1,75 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.query; + +import javax.persistence.EntityGraph; + +import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; +import org.springframework.util.Assert; + +/** + * EntityGraph configuration for JPA 2.1 {@link EntityGraph}s. + * + * @author Thomas Darimont + * @since 1.6 + */ +public class JpaEntityGraph { + + private final String name; + private final EntityGraphType type; + + /** + * Creates an {@link JpaEntityGraph}. + * + * @param name must not be {@null}. + * @param type must not be {@null}. + */ + public JpaEntityGraph(String name, EntityGraphType type) { + + Assert.hasText(name, "The name of an EntityGraph must not be null or empty!"); + Assert.notNull(type, "FetchGraphType must not be null!"); + + this.name = name; + this.type = type; + } + + /** + * Returns the name of the {@link EntityGraph} configuration to use. + * + * @return + */ + public String getName() { + return name; + } + + /** + * Returns the {@link EntityGraphType} of the {@link EntityGraph} to use. + * + * @return + */ + public EntityGraphType getType() { + return type; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "JpaEntityGraph [name=" + name + ", type=" + type + "]"; + } +} diff --git a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index bc2ae187f..0342e786e 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2013 the original author or authors. + * Copyright 2008-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import javax.persistence.LockModeType; import javax.persistence.QueryHint; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -143,6 +144,18 @@ public class JpaQueryMethod extends QueryMethod { return (LockModeType) AnnotationUtils.getValue(annotation); } + /** + * Returns the {@link EntityGraph} to be used for the query. + * + * @return + * @since 1.6 + */ + JpaEntityGraph getEntityGraph() { + + EntityGraph annotation = findAnnotation(method, EntityGraph.class); + return annotation == null ? null : new JpaEntityGraph(annotation.value(), annotation.type()); + } + /** * Returns whether the potentially configured {@link QueryHint}s shall be applied when triggering the count query for * pagination. diff --git a/src/test/java/org/springframework/data/jpa/domain/sample/User.java b/src/test/java/org/springframework/data/jpa/domain/sample/User.java index c2031f50a..f0e74b056 100644 --- a/src/test/java/org/springframework/data/jpa/domain/sample/User.java +++ b/src/test/java/org/springframework/data/jpa/domain/sample/User.java @@ -30,6 +30,9 @@ import javax.persistence.Id; import javax.persistence.Lob; import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; +import javax.persistence.NamedAttributeNode; +import javax.persistence.NamedEntityGraph; +import javax.persistence.NamedEntityGraphs; import javax.persistence.NamedQuery; import javax.persistence.Temporal; import javax.persistence.TemporalType; @@ -42,6 +45,10 @@ import javax.persistence.TemporalType; * @author Thomas Darimont */ @Entity +@NamedEntityGraphs({ + @NamedEntityGraph(name = "User.overview", attributeNodes = { @NamedAttributeNode("roles") }), + @NamedEntityGraph(name = "User.detail", attributeNodes = { @NamedAttributeNode("roles"), + @NamedAttributeNode("manager"), @NamedAttributeNode("colleagues") }) }) @NamedQuery(name = "User.findByEmailAddress", query = "SELECT u FROM User u WHERE u.emailAddress = ?1") public class User { diff --git a/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java b/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java index 7228c3a09..ba142dc53 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/AbstractJpaQueryTests.java @@ -28,10 +28,13 @@ import javax.persistence.Query; import javax.persistence.QueryHint; import javax.persistence.TypedQuery; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.jpa.repository.support.PersistenceProvider; @@ -39,6 +42,8 @@ import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.ReflectionUtils; /** * Integration test for {@link AbstractJpaQuery}. @@ -49,8 +54,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration("classpath:infrastructure.xml") public class AbstractJpaQueryTests { - @PersistenceContext - EntityManager em; + @PersistenceContext EntityManager em; Query query; TypedQuery countQuery; @@ -122,6 +126,55 @@ public class AbstractJpaQueryTests { verify(result).setLockMode(LockModeType.PESSIMISTIC_WRITE); } + /** + * @see DATAJPA-466 + */ + @Test + @Transactional + public void shouldAddEntityGraphHintForFetch() throws Exception { + + Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager()); + + Method findAllMethod = SampleRepository.class.getMethod("findAll"); + QueryExtractor provider = PersistenceProvider.fromEntityManager(em); + JpaQueryMethod queryMethod = new JpaQueryMethod(findAllMethod, + new DefaultRepositoryMetadata(SampleRepository.class), provider); + + javax.persistence.EntityGraph entityGraph = em.getEntityGraph("User.overview"); + + AbstractJpaQuery jpaQuery = new DummyJpaQuery(queryMethod, em); + Query result = jpaQuery.createQuery(new Object[0]); + + verify(result).setHint("javax.persistence.fetchgraph", entityGraph); + } + + /** + * @see DATAJPA-466 + */ + @Test + @Transactional + public void shouldAddEntityGraphHintForLoad() throws Exception { + + Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager()); + + Method getByIdMethod = SampleRepository.class.getMethod("getById", Integer.class); + QueryExtractor provider = PersistenceProvider.fromEntityManager(em); + JpaQueryMethod queryMethod = new JpaQueryMethod(getByIdMethod, + new DefaultRepositoryMetadata(SampleRepository.class), provider); + + javax.persistence.EntityGraph entityGraph = em.getEntityGraph("User.detail"); + + AbstractJpaQuery jpaQuery = new DummyJpaQuery(queryMethod, em); + Query result = jpaQuery.createQuery(new Object[] { 1 }); + + verify(result).setHint("javax.persistence.loadgraph", entityGraph); + } + + private boolean currentEntityManagerIsAJpa21EntityManager() { + return ReflectionUtils.findMethod(((org.springframework.orm.jpa.EntityManagerProxy) em).getTargetEntityManager() + .getClass(), "getEntityGraph", String.class) != null; + } + interface SampleRepository extends Repository { @QueryHints({ @QueryHint(name = "foo", value = "bar") }) @@ -133,6 +186,18 @@ public class AbstractJpaQueryTests { @Lock(LockModeType.PESSIMISTIC_WRITE) @org.springframework.data.jpa.repository.Query("select u from User u where u.id = ?1") List findOneLocked(Integer primaryKey); + + /** + * @see DATAJPA-466 + */ + @EntityGraph(value = "User.detail", type = EntityGraphType.LOAD) + User getById(Integer id); + + /** + * @see DATAJPA-466 + */ + @EntityGraph("User.overview") + List findAll(); } class DummyJpaQuery extends AbstractJpaQuery { diff --git a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java index 12f61413e..00026864b 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryMethodUnitTests.java @@ -36,6 +36,8 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.sample.User; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -57,14 +59,12 @@ public class JpaQueryMethodUnitTests { static final Class DOMAIN_CLASS = User.class; static final String METHOD_NAME = "findByFirstname"; - @Mock - QueryExtractor extractor; - @Mock - RepositoryMetadata metadata; + @Mock QueryExtractor extractor; + @Mock RepositoryMetadata metadata; Method repositoryMethod, invalidReturnType, pageableAndSort, pageableTwice, sortableTwice, modifyingMethod, nativeQuery, namedQuery, findWithLockMethod, invalidNamedParameter, findsProjections, findsProjection, - withMetaAnnotation; + withMetaAnnotation, queryMethodWithCustomEntityFetchGraph; /** * @throws Exception @@ -91,6 +91,9 @@ public class JpaQueryMethodUnitTests { findsProjection = ValidRepository.class.getMethod("findsProjection"); withMetaAnnotation = ValidRepository.class.getMethod("withMetaAnnotation"); + + queryMethodWithCustomEntityFetchGraph = ValidRepository.class.getMethod("queryMethodWithCustomEntityFetchGraph", + Integer.class); } @Test @@ -313,6 +316,19 @@ public class JpaQueryMethodUnitTests { assertThat(method.getHints().get(0).value(), is("bar")); } + /** + * @see DATAJPA-466 + */ + @Test + public void shouldStoreJpa21FetchGraphInformationAsHint() { + + JpaQueryMethod method = new JpaQueryMethod(queryMethodWithCustomEntityFetchGraph, metadata, extractor); + + assertThat(method.getEntityGraph(), is(notNullValue())); + assertThat(method.getEntityGraph().getName(), is("User.propertyLoadPath")); + assertThat(method.getEntityGraph().getType(), is(EntityGraphType.LOAD)); + } + /** * Interface to define invalid repository methods for testing. * @@ -367,6 +383,12 @@ public class JpaQueryMethodUnitTests { @CustomAnnotation void withMetaAnnotation(); + + /** + * @see DATAJPA-466 + */ + @EntityGraph(value = "User.propertyLoadPath", type = EntityGraphType.LOAD) + User queryMethodWithCustomEntityFetchGraph(Integer id); } @Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT) diff --git a/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java index 65a0f0b7c..4f8deaddc 100644 --- a/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/query/QueryUtilsIntegrationTests.java @@ -18,6 +18,8 @@ package org.springframework.data.jpa.repository.query; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import java.util.Arrays; +import java.util.List; import java.util.Set; import javax.persistence.Entity; @@ -30,7 +32,11 @@ import javax.persistence.PersistenceContext; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; +import javax.persistence.spi.PersistenceProvider; +import javax.persistence.spi.PersistenceProviderResolver; +import javax.persistence.spi.PersistenceProviderResolverHolder; +import org.hibernate.ejb.HibernatePersistence; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.jpa.domain.sample.Order; @@ -118,12 +124,22 @@ public class QueryUtilsIntegrationTests { @Test public void traversesPluralAttributeCorrectly() { - EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("merchant"); - CriteriaBuilder builder = entityManagerFactory.createEntityManager().getCriteriaBuilder(); - CriteriaQuery query = builder.createQuery(Merchant.class); - Root root = query.from(Merchant.class); + PersistenceProviderResolver originalPersistenceProviderResolver = PersistenceProviderResolverHolder + .getPersistenceProviderResolver(); - QueryUtils.toExpressionRecursively(root, PropertyPath.from("employeesCredentialsUid", Merchant.class)); + try { + + PersistenceProviderResolverHolder.setPersistenceProviderResolver(new HibernateOnlyPersistenceProviderResolver()); + EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("merchant"); + CriteriaBuilder builder = entityManagerFactory.createEntityManager().getCriteriaBuilder(); + CriteriaQuery query = builder.createQuery(Merchant.class); + Root root = query.from(Merchant.class); + + QueryUtils.toExpressionRecursively(root, PropertyPath.from("employeesCredentialsUid", Merchant.class)); + + } finally { + PersistenceProviderResolverHolder.setPersistenceProviderResolver(originalPersistenceProviderResolver); + } } protected void assertNoJoinRequestedForOptionalAssociation(Root root) { @@ -150,4 +166,21 @@ public class QueryUtilsIntegrationTests { @Id String id; String uid; } + + /** + * A {@link PersistenceProviderResolver} that returns only {@link HibernatePersistence} and ignores other + * {@link PersistenceProvider}s. + * + * @author Thomas Darimont + */ + static class HibernateOnlyPersistenceProviderResolver implements PersistenceProviderResolver { + + @Override + public List getPersistenceProviders() { + return Arrays. asList(new HibernatePersistence()); + } + + @Override + public void clearCachedProviders() {} + } }