diff --git a/pom.xml b/pom.xml index 3e76b546e..d674e070a 100644 --- a/pom.xml +++ b/pom.xml @@ -326,7 +326,7 @@ **/EclipseLink*Tests.java - -javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring}/spring-instrument-${spring}.jar + -javaagent:${settings.localRepository}/org/eclipse/persistence/org.eclipse.persistence.jpa/${eclipselink}/org.eclipse.persistence.jpa-${eclipselink}.jar -javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring}/spring-instrument-${spring}.jar diff --git a/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java b/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java index eb5020ce2..a2278e11d 100644 --- a/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java +++ b/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java @@ -15,8 +15,17 @@ */ package org.springframework.data.jpa.provider; -import static org.springframework.data.jpa.provider.JpaClassUtils.*; -import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.*; +import static org.springframework.data.jpa.provider.JpaClassUtils.isEntityManagerOfType; +import static org.springframework.data.jpa.provider.JpaClassUtils.isMetamodelOfType; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.ECLIPSELINK_ENTITY_MANAGER_INTERFACE; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.ECLIPSELINK_JPA_METAMODEL_TYPE; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.GENERIC_JPA_ENTITY_MANAGER_INTERFACE; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.HIBERNATE43_ENTITY_MANAGER_INTERFACE; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.HIBERNATE43_JPA_METAMODEL_TYPE; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.HIBERNATE_ENTITY_MANAGER_INTERFACE; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.HIBERNATE_JPA_METAMODEL_TYPE; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.OPENJPA_ENTITY_MANAGER_INTERFACE; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.OPENJPA_JPA_METAMODEL_TYPE; import java.util.Arrays; import java.util.Collection; @@ -24,8 +33,10 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import javax.persistence.EntityGraph; import javax.persistence.EntityManager; import javax.persistence.Query; +import javax.persistence.Subgraph; import javax.persistence.metamodel.Metamodel; import org.apache.openjpa.enhance.PersistenceCapable; @@ -42,10 +53,12 @@ import org.hibernate.ScrollableResults; import org.hibernate.ejb.HibernateQuery; import org.hibernate.proxy.HibernateProxy; import org.springframework.beans.DirectFieldAccessor; +import org.springframework.data.jpa.repository.query.JpaEntityGraph; import org.springframework.data.util.CloseableIterator; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; /** * Enumeration representing persistence providers to be used. @@ -354,6 +367,69 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor { + name()); } + /** + * Creates a dynamic {@link EntityGraph} from the given {@link JpaEntityGraph} information. + * + * @param em + * @param jpaEntityGraph + * @param entityType + * @return + * + * @since 1.9 + */ + public EntityGraph createDynamicEntityGraph(EntityManager em, JpaEntityGraph jpaEntityGraph, Class entityType) { + + Assert.isTrue(jpaEntityGraph.isDynamicEntityGraph(), "The given " + jpaEntityGraph + " is not dynamic!"); + + EntityGraph entityGraph = em.createEntityGraph(entityType); + + configureFetchGraphFrom(jpaEntityGraph, entityGraph); + + return entityGraph; + } + + + /** + * Configures the given {@link EntityGraph} with the fetch graph information stored in {@link JpaEntityGraph}. + * + * @param jpaEntityGraph + * @param entityGraph + */ + /* visible for testing */ + void configureFetchGraphFrom(JpaEntityGraph jpaEntityGraph, EntityGraph entityGraph) { + + String[] attributePaths = jpaEntityGraph.getAttributePaths().clone(); + + // sort to ensure that the intermediate entity subgraphs are created accordingly. + Arrays.sort(attributePaths); + + // we build the entity graph based on the paths with highest depth first + for (int i = attributePaths.length - 1; i >= 0; i--) { + + String path = attributePaths[i]; + + //fast path just single attribute + if (!path.contains(".")) { + entityGraph.addAttributeNodes(path); + continue; + } + + //we need to build nested sub fetch graphs + String[] pathComponents = StringUtils.delimitedListToStringArray(path, "."); + + Subgraph parent = null; + for (int c = 0; c < pathComponents.length - 1; c++) { + + if (c == 0) { + parent = entityGraph.addSubgraph(pathComponents[c]); + } else { + parent = parent.addSubgraph(pathComponents[c]); + } + } + parent.addAttributeNodes(pathComponents[pathComponents.length - 1]); + } + } + /** * {@link CloseableIterator} for Hibernate. * diff --git a/src/main/java/org/springframework/data/jpa/repository/EntityGraph.java b/src/main/java/org/springframework/data/jpa/repository/EntityGraph.java index d8dddd254..7a2bc5452 100644 --- a/src/main/java/org/springframework/data/jpa/repository/EntityGraph.java +++ b/src/main/java/org/springframework/data/jpa/repository/EntityGraph.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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,9 +21,19 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import javax.persistence.NamedAttributeNode; + +import org.springframework.data.jpa.repository.query.JpaQueryMethod; + /** * Annotation to configure the JPA 2.1 {@link javax.persistence.EntityGraph}s that should be used on repository methods. * + * Since 1.9 we support the definition of dynamic {@link EntityGraph}s by allowing to customize the fetch-graph via + * via {@link #attributePaths()} ad-hoc fetch-graph configuration. + * + * If {@link #attributePaths()} are specified then we ignore the entity-graph name {@link #value()} + * and treat this {@link EntityGraph} as dynamic. + * * @author Thomas Darimont * @since 1.6 */ @@ -34,10 +44,11 @@ public @interface EntityGraph { /** * The name of the EntityGraph to use. + * If empty we fall-back to {@link JpaQueryMethod#getNamedQueryName()} as the value. * * @return */ - String value(); + String value() default ""; /** * The {@link Type} of the EntityGraph to use, defaults to {@link Type#FETCH}. @@ -45,6 +56,16 @@ public @interface EntityGraph { * @return */ EntityGraphType type() default EntityGraphType.FETCH; + + /** + * The paths of attributes of this {@link EntityGraph} to use, empty by default. + * + * You can refer to direct properties of the entity or nested properties via a {@code property.nestedProperty}. + * + * @return + * @since 1.9 + */ + String[] attributePaths() default {}; /** * Enum for JPA 2.1 {@link javax.persistence.EntityGraph} types. 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 41269876e..d87ee193b 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 @@ -182,7 +182,7 @@ public abstract class AbstractJpaQuery implements RepositoryQuery { Assert.notNull(query, "Query must not be null!"); Assert.notNull(method, "JpaQueryMethod must not be null!"); - Map hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph()); + Map hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph(), getQueryMethod().getEntityInformation().getJavaType()); for (Map.Entry hint : hints.entrySet()) { query.setHint(hint.getKey(), hint.getValue()); diff --git a/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java b/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java index 63793443a..87aa84fbf 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java @@ -23,6 +23,7 @@ import javax.persistence.EntityGraph; import javax.persistence.EntityManager; import javax.persistence.Query; +import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -59,16 +60,18 @@ public class Jpa21Utils { * @param em must not be {@literal null} * @param query must not be {@literal null} * @param entityGraph can be {@literal null} + * @param entityType must not be {@literal null} * @return a {@code Map} with the hints or an empty {@code Map} if no hints were found * @since 1.8 */ - public static Map tryGetFetchGraphHints(EntityManager em, JpaEntityGraph entityGraph) { + public static Map tryGetFetchGraphHints(EntityManager em, JpaEntityGraph entityGraph, + Class entityType) { if (entityGraph == null) { return Collections.emptyMap(); } - EntityGraph graph = tryGetFetchGraph(em, entityGraph); + EntityGraph graph = tryGetFetchGraph(em, entityGraph, entityType); if (graph == null) { return Collections.emptyMap(); @@ -83,17 +86,25 @@ public class Jpa21Utils { * @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 jpaEntityGraph must not be {@literal null}. + * @param entityType must not be {@literal null}. * @return the {@link EntityGraph} described by the given {@code entityGraph}. */ - public static EntityGraph tryGetFetchGraph(EntityManager em, JpaEntityGraph jpaEntityGraph) { + private static EntityGraph tryGetFetchGraph(EntityManager em, JpaEntityGraph jpaEntityGraph, Class entityType) { Assert.notNull(em, "EntityManager must not be null!"); Assert.notNull(jpaEntityGraph, "EntityGraph must not be null!"); + Assert.notNull(entityType, "EntityType 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!"); - return em.getEntityGraph(jpaEntityGraph.getName()); + try { + // first check whether an entityGraph with that name is already registered. + return em.getEntityGraph(jpaEntityGraph.getName()); + } catch (Exception ex) { + // try to create and dynamically register the entityGraph + return PersistenceProvider.fromEntityManager(em).createDynamicEntityGraph(em, jpaEntityGraph, entityType); + } } } 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 index 47c56f6ac..877ceb42a 100644 --- a/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java +++ b/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -15,10 +15,12 @@ */ package org.springframework.data.jpa.repository.query; -import javax.persistence.EntityGraph; +import java.util.Arrays; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * EntityGraph configuration for JPA 2.1 {@link EntityGraph}s. @@ -28,22 +30,38 @@ import org.springframework.util.Assert; */ public class JpaEntityGraph { + private static String[] EMPTY_ATTRIBUTE_PATHS = {}; + private final String name; private final EntityGraphType type; + private final String[] attributePaths; /** * Creates an {@link JpaEntityGraph}. * - * @param name must not be {@null} or empty. - * @param type must not be {@null}. + * @param entityGraph must not be {@literal null}. + * @param nameFallback must not be {@literal null} or empty. */ - public JpaEntityGraph(String name, EntityGraphType type) { + public JpaEntityGraph(EntityGraph entityGraph, String nameFallback) { + this(StringUtils.hasText(entityGraph.value()) ? entityGraph.value() : nameFallback, entityGraph.type(), entityGraph + .attributePaths()); + } + + /** + * Creates an {@link JpaEntityGraph}. + * + * @param name must not be {@literal null} or empty. + * @param type must not be {@literal null}. + * @param attributePaths may be {@literal null}. + */ + public JpaEntityGraph(String name, EntityGraphType type, String[] attributePaths) { 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; + this.attributePaths = attributePaths == null ? EMPTY_ATTRIBUTE_PATHS : attributePaths; } /** @@ -64,12 +82,32 @@ public class JpaEntityGraph { return type; } + /** + * Returns the attribute node names to be used for this {@link JpaEntityGraph}. + * + * @return + * @since 1.9 + */ + public String[] getAttributePaths() { + return attributePaths; + } + + /** + * Return {@literal true} if this {@link JpaEntityGraph} needs to be generated on-the-fly. + * + * @return + */ + public boolean isDynamicEntityGraph() { + return this.attributePaths.length > 0; + } + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { - return "JpaEntityGraph [name=" + name + ", type=" + type + "]"; + return "JpaEntityGraph [name=" + name + ", type=" + type + ", attributePaths=" + Arrays.toString(attributePaths) + + "]"; } } 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 084c5e30b..5416a5850 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 @@ -15,7 +15,8 @@ */ package org.springframework.data.jpa.repository.query; -import static org.springframework.core.annotation.AnnotationUtils.*; +import static org.springframework.core.annotation.AnnotationUtils.findAnnotation; +import static org.springframework.core.annotation.AnnotationUtils.getAnnotation; import java.lang.reflect.Method; import java.util.ArrayList; @@ -173,7 +174,7 @@ public class JpaQueryMethod extends QueryMethod { JpaEntityGraph getEntityGraph() { EntityGraph annotation = findAnnotation(method, EntityGraph.class); - return annotation == null ? null : new JpaEntityGraph(annotation.value(), annotation.type()); + return annotation == null ? null : new JpaEntityGraph(annotation, getNamedQueryName()); } /** diff --git a/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java b/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java index af4679414..93b915f9f 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java @@ -15,10 +15,12 @@ */ package org.springframework.data.jpa.repository.support; +import java.lang.reflect.Method; import java.util.Map; import javax.persistence.LockModeType; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.query.JpaEntityGraph; /** @@ -45,10 +47,18 @@ public interface CrudMethodMetadata { Map getQueryHints(); /** - * Returns the {@link JpaEntityGraph} to be used. + * Returns the {@link EntityGraph} to be used. * * @return - * @since 1.8 + * @since 1.9 */ - JpaEntityGraph getEntityGraph(); + EntityGraph getEntityGraph(); + + /** + * Returns the {@link Method} to be used. + * + * @return + * @since 1.9 + */ + Method getMethod(); } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java b/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java index 5c524c48d..bf4394a5c 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java @@ -37,6 +37,7 @@ import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.jpa.repository.query.JpaEntityGraph; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; +import org.springframework.data.util.ReflectionUtils; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; @@ -60,7 +61,7 @@ enum CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor { public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) { factory.addAdvice(ExposeInvocationInterceptor.INSTANCE); - factory.addAdvice(CrudMethodMetadataPopulatingMethodIntercceptor.INSTANCE); + factory.addAdvice(CrudMethodMetadataPopulatingMethodInterceptor.INSTANCE); } /** @@ -85,7 +86,7 @@ enum CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor { * @author Oliver Gierke * @author Thomas Darimont */ - static enum CrudMethodMetadataPopulatingMethodIntercceptor implements MethodInterceptor { + static enum CrudMethodMetadataPopulatingMethodInterceptor implements MethodInterceptor { INSTANCE; @@ -136,7 +137,8 @@ enum CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor { private final LockModeType lockModeType; private final Map queryHints; - private final JpaEntityGraph entityGraph; + private final EntityGraph entityGraph; + private final Method method; /** * Creates a new {@link DefaultCrudMethodMetadata} for the given {@link Method}. @@ -150,13 +152,11 @@ enum CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor { this.lockModeType = findLockModeType(method); this.queryHints = findQueryHints(method); this.entityGraph = findEntityGraph(method); + this.method = method; } - private static JpaEntityGraph findEntityGraph(Method method) { - - EntityGraph entityGraphAnnotation = AnnotationUtils.findAnnotation(method, EntityGraph.class); - return entityGraphAnnotation == null ? null : new JpaEntityGraph(entityGraphAnnotation.value(), - entityGraphAnnotation.type()); + private static EntityGraph findEntityGraph(Method method) { + return AnnotationUtils.findAnnotation(method, EntityGraph.class); } private static LockModeType findLockModeType(Method method) { @@ -203,14 +203,21 @@ enum CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor { public Map getQueryHints() { return queryHints; } - - /* - * (non-Javadoc) - * @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getEntityGraphHint() + + /* (non-Javadoc) + * @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getEntityGraph() */ @Override - public JpaEntityGraph getEntityGraph() { - return this.entityGraph; + public EntityGraph getEntityGraph() { + return entityGraph; + } + + /* (non-Javadoc) + * @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getMethod() + */ + @Override + public Method getMethod() { + return method; } } diff --git a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index 8f6797395..6111a0157 100644 --- a/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -15,7 +15,11 @@ */ package org.springframework.data.jpa.repository.support; -import static org.springframework.data.jpa.repository.query.QueryUtils.*; +import static org.springframework.data.jpa.repository.query.QueryUtils.COUNT_QUERY_STRING; +import static org.springframework.data.jpa.repository.query.QueryUtils.DELETE_ALL_QUERY_STRING; +import static org.springframework.data.jpa.repository.query.QueryUtils.applyAndBind; +import static org.springframework.data.jpa.repository.query.QueryUtils.getQueryString; +import static org.springframework.data.jpa.repository.query.QueryUtils.toOrders; import java.io.Serializable; import java.util.ArrayList; @@ -49,6 +53,7 @@ import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.query.Jpa21Utils; +import org.springframework.data.jpa.repository.query.JpaEntityGraph; import org.springframework.data.jpa.repository.query.QueryUtils; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -248,11 +253,18 @@ public class SimpleJpaRepository implements JpaRepos Map hints = new HashMap(); hints.putAll(metadata.getQueryHints()); - hints.putAll(Jpa21Utils.tryGetFetchGraphHints(em, metadata.getEntityGraph())); + + hints.putAll(Jpa21Utils.tryGetFetchGraphHints(em, getEntityGraph(), getDomainClass())); return hints; } + private JpaEntityGraph getEntityGraph() { + + String fallbackName = this.entityInformation.getEntityName() + "." + metadata.getMethod().getName(); + return new JpaEntityGraph(metadata.getEntityGraph(), fallbackName); + } + /* * (non-Javadoc) * @see org.springframework.data.jpa.repository.JpaRepository#getOne(java.io.Serializable) 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 3ca89ed80..962972c46 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 @@ -52,6 +52,8 @@ import javax.persistence.TemporalType; @NamedEntityGraphs({ @NamedEntityGraph(name = "User.overview", attributeNodes = { @NamedAttributeNode("roles") }), @NamedEntityGraph(name = "User.detail", attributeNodes = { @NamedAttributeNode("roles"), + @NamedAttributeNode("manager"), @NamedAttributeNode("colleagues") }), + @NamedEntityGraph(name = "User.getOneWithDefinedEntityGraphById", attributeNodes = { @NamedAttributeNode("roles"), @NamedAttributeNode("manager"), @NamedAttributeNode("colleagues") }) }) @NamedQuery(name = "User.findByEmailAddress", query = "SELECT u FROM User u WHERE u.emailAddress = ?1") @NamedStoredProcedureQueries({ // diff --git a/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java b/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java index 41f809bd6..24039fca1 100644 --- a/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/provider/PersistenceProviderUnitTests.java @@ -15,22 +15,37 @@ */ package org.springframework.data.jpa.provider; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.springframework.data.jpa.provider.PersistenceProvider.*; -import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.*; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.data.jpa.provider.PersistenceProvider.ECLIPSELINK; +import static org.springframework.data.jpa.provider.PersistenceProvider.GENERIC_JPA; +import static org.springframework.data.jpa.provider.PersistenceProvider.HIBERNATE; +import static org.springframework.data.jpa.provider.PersistenceProvider.OPEN_JPA; +import static org.springframework.data.jpa.provider.PersistenceProvider.fromEntityManager; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.ECLIPSELINK_ENTITY_MANAGER_INTERFACE; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.HIBERNATE43_ENTITY_MANAGER_INTERFACE; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.HIBERNATE_ENTITY_MANAGER_INTERFACE; +import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.OPENJPA_ENTITY_MANAGER_INTERFACE; import java.util.ArrayList; import java.util.List; +import javax.persistence.EntityGraph; import javax.persistence.EntityManager; +import javax.persistence.Subgraph; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.springframework.asm.ClassWriter; import org.springframework.asm.Opcodes; -import org.springframework.data.jpa.provider.PersistenceProvider; +import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; +import org.springframework.data.jpa.repository.query.JpaEntityGraph; import org.springframework.instrument.classloading.ShadowingClassLoader; import org.springframework.util.ClassUtils; @@ -103,6 +118,26 @@ public class PersistenceProviderUnitTests { assertThat(fromEntityManager(em), is(GENERIC_JPA)); } + /** + * @see DATAJPA-696 + */ + @Test + public void shouldBuildCorrectSubgraphForJpaEntityGraph() throws Exception { + + EntityGraph entityGraph = mock(EntityGraph.class); + Subgraph subgraph = mock(Subgraph.class); + doReturn(subgraph).when(entityGraph).addSubgraph(anyString()); + + JpaEntityGraph jpaEntityGraph = new JpaEntityGraph("foo", EntityGraphType.FETCH, + new String[] { "foo", "gugu.gaga" }); + + PersistenceProvider.GENERIC_JPA.configureFetchGraphFrom(jpaEntityGraph, entityGraph); + + verify(entityGraph, times(1)).addAttributeNodes("foo"); + verify(entityGraph, times(1)).addSubgraph("gugu"); + verify(subgraph, times(1)).addAttributeNodes("gaga"); + } + private EntityManager mockProviderSpecificEntityManagerInterface(String interfaceName) throws ClassNotFoundException { Class providerSpecificEntityManagerInterface = InterfaceGenerator.generate(interfaceName, shadowingClassLoader, diff --git a/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java b/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java index 9dc459f30..bc2ba63d3 100644 --- a/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/EntityGraphRepositoryMethodsIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -60,6 +60,10 @@ public class EntityGraphRepositoryMethodsIntegrationTests { role = new Role("Developer"); em.persist(role); tom.getRoles().add(role); + tom = repository.save(tom); + + olli = repository.save(olli); + tom.getColleagues().add(olli); } /** @@ -70,11 +74,9 @@ public class EntityGraphRepositoryMethodsIntegrationTests { Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); - tom = repository.save(tom); - List result = repository.findAll(); - assertThat(result.size(), is(1)); + assertThat(result.size(), is(2)); assertThat(Persistence.getPersistenceUtil().isLoaded(result.get(0).getRoles()), is(true)); assertThat(result.get(0), is(tom)); } @@ -87,16 +89,40 @@ public class EntityGraphRepositoryMethodsIntegrationTests { Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); - olli = repository.save(olli); - tom.getColleagues().add(olli); - tom = repository.save(tom); - - em.flush(); - User user = repository.findOne(tom.getId()); assertThat(user, is(notNullValue())); assertThat("colleages should be fetched with 'user.detail' fetchgraph", Persistence.getPersistenceUtil().isLoaded(user.getColleagues()), is(true)); } + + /** + * @see DATAJPA-696 + */ + @Test + public void shouldRespectInferFetchGraphFromMethodName() { + + Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); + + User user = repository.getOneWithDefinedEntityGraphById(tom.getId()); + + assertThat(user, is(notNullValue())); + assertThat("colleages should be fetched with 'user.detail' fetchgraph", + Persistence.getPersistenceUtil().isLoaded(user.getColleagues()), is(true)); + } + + /** + * @see DATAJPA-696 + */ + @Test + public void shouldRespectDynamicFetchGraphForGetOneWithAttributeNamesById() { + + Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em)); + + User user = repository.getOneWithAttributeNamesById(tom.getId()); + + assertThat(user, is(notNullValue())); + assertThat("colleages should be fetched with 'user.detail' fetchgraph", + Persistence.getPersistenceUtil().isLoaded(user.getColleagues()), is(true)); + } } 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 7bd7ebfe3..29672d482 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 @@ -324,6 +324,9 @@ public class JpaQueryMethodUnitTests { */ @Test public void shouldStoreJpa21FetchGraphInformationAsHint() { + + doReturn(User.class).when(metadata).getDomainType(); + doReturn(User.class).when(metadata).getReturnedDomainClass(queryMethodWithCustomEntityFetchGraph); JpaQueryMethod method = new JpaQueryMethod(queryMethodWithCustomEntityFetchGraph, metadata, extractor); @@ -338,6 +341,9 @@ public class JpaQueryMethodUnitTests { @Test public void shouldFindEntityGraphAnnotationOnOverriddenSimpleJpaRepositoryMethod() throws Exception { + doReturn(User.class).when(metadata).getDomainType(); + doReturn(User.class).when(metadata).getReturnedDomainClass((Method)any()); + JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("findAll"), metadata, extractor); assertThat(method.getEntityGraph(), is(notNullValue())); @@ -351,12 +357,31 @@ public class JpaQueryMethodUnitTests { @Test public void shouldFindEntityGraphAnnotationOnOverriddenSimpleJpaRepositoryMethodFindOne() throws Exception { - JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("findOne"), metadata, extractor); + doReturn(User.class).when(metadata).getDomainType(); + doReturn(User.class).when(metadata).getReturnedDomainClass((Method)any()); + + JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("findOne", Long.class), metadata, extractor); assertThat(method.getEntityGraph(), is(notNullValue())); assertThat(method.getEntityGraph().getName(), is("User.detail")); assertThat(method.getEntityGraph().getType(), is(EntityGraphType.FETCH)); } + + /** + * DATAJPA-696 + */ + @Test + public void shouldFindEntityGraphAnnotationOnQueryMethodGetOneByWithDerivedName() throws Exception { + + doReturn(User.class).when(metadata).getDomainType(); + doReturn(User.class).when(metadata).getReturnedDomainClass((Method)any()); + + JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("getOneById", Long.class), metadata, extractor); + + assertThat(method.getEntityGraph(), is(notNullValue())); + assertThat(method.getEntityGraph().getName(), is("User.getOneById")); + assertThat(method.getEntityGraph().getType(), is(EntityGraphType.FETCH)); + } /** * Interface to define invalid repository methods for testing. @@ -433,7 +458,13 @@ public class JpaQueryMethodUnitTests { * DATAJPA-689 */ @EntityGraph("User.detail") - User findOne(); + User findOne(Long id); + + /** + * DATAJPA-696 + */ + @EntityGraph + User getOneById(Long id); } @Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT) diff --git a/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigJpaRepository.java b/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigJpaRepository.java index a10c65a8e..6b24249e9 100644 --- a/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigJpaRepository.java +++ b/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigJpaRepository.java @@ -41,4 +41,16 @@ public interface RepositoryMethodsWithEntityGraphConfigJpaRepository extends Jpa */ @EntityGraph(type = EntityGraphType.FETCH, value = "User.detail") User findOne(Integer id); + + /** + * @see DATAJPA-696 + */ + @EntityGraph + User getOneWithDefinedEntityGraphById(Integer id); + + /** + * @see DATAJPA-696 + */ + @EntityGraph(attributePaths = { "roles", "colleagues.roles" }) + User getOneWithAttributeNamesById(Integer id); } diff --git a/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java index 452b6a2c3..fbcc8d605 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPopulatingMethodInterceptorUnitTests.java @@ -29,11 +29,11 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.jpa.repository.Lock; -import org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor.CrudMethodMetadataPopulatingMethodIntercceptor; +import org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor.CrudMethodMetadataPopulatingMethodInterceptor; import org.springframework.transaction.support.TransactionSynchronizationManager; /** - * Unit tests for {@link CrudMethodMetadataPopulatingMethodIntercceptor}. + * Unit tests for {@link CrudMethodMetadataPopulatingMethodInterceptor}. * * @author Oliver Gierke */ @@ -51,7 +51,7 @@ public class CrudMethodMetadataPopulatingMethodInterceptorUnitTests { Method method = Sample.class.getMethod("someMethod"); when(invocation.getMethod()).thenReturn(method); - CrudMethodMetadataPopulatingMethodIntercceptor interceptor = CrudMethodMetadataPopulatingMethodIntercceptor.INSTANCE; + CrudMethodMetadataPopulatingMethodInterceptor interceptor = CrudMethodMetadataPopulatingMethodInterceptor.INSTANCE; interceptor.invoke(invocation); assertThat(TransactionSynchronizationManager.getResource(method), is(nullValue())); diff --git a/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java b/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java index 070f5a1c1..2536fb363 100644 --- a/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java +++ b/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java @@ -15,8 +15,12 @@ */ package org.springframework.data.jpa.repository.support; -import static java.util.Collections.*; -import static org.mockito.Mockito.*; +import static java.util.Collections.singletonMap; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.Serializable; import javax.persistence.EntityGraph; import javax.persistence.EntityManager; @@ -33,7 +37,7 @@ import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; -import org.springframework.data.jpa.repository.query.JpaEntityGraph; +import org.springframework.data.repository.CrudRepository; /** * Unit tests for {@link SimpleJpaRepository}. @@ -55,6 +59,7 @@ public class SimpleJpaRepositoryUnitTests { @Mock JpaEntityInformation information; @Mock CrudMethodMetadata metadata; @Mock EntityGraph entityGraph; + @Mock org.springframework.data.jpa.repository.EntityGraph entityGraphAnnotation; @Before public void setUp() { @@ -97,15 +102,20 @@ public class SimpleJpaRepositoryUnitTests { /** * @see DATAJPA-689 + * @see DATAJPA-696 */ @Test @SuppressWarnings({ "rawtypes", "unchecked" }) - public void shouldPropagateConfiguredEntityGraphToFindOne() { + public void shouldPropagateConfiguredEntityGraphToFindOne() throws Exception{ String entityGraphName = "User.detail"; - when(metadata.getEntityGraph()).thenReturn(new JpaEntityGraph(entityGraphName, EntityGraphType.LOAD)); + when(entityGraphAnnotation.value()).thenReturn(entityGraphName); + when(entityGraphAnnotation.type()).thenReturn(EntityGraphType.LOAD); + when(metadata.getEntityGraph()).thenReturn(entityGraphAnnotation); when(em.getEntityGraph(entityGraphName)).thenReturn((EntityGraph) entityGraph); - + when(information.getEntityName()).thenReturn("User"); + when(metadata.getMethod()).thenReturn(CrudRepository.class.getMethod("findOne", Serializable.class)); + Integer id = 0; repo.findOne(id);