DATAJPA-696 - Support ad-hoc entity graph definitions for repository finder methods.

We now support the specification of ad-hoc entity graphs on repository finder methods by allowing to specify the fetch graph paths via the "attributePaths" attribute on the EntityGraph annotation.

Configured EclipseLink tests to use dynamic weaving, required for dynamic entity fetch graphs. Fixed typo in CrudMethodMetadataPopulatingMethodInterceptor.

Original pull request: #140.
This commit is contained in:
Thomas Darimont
2015-04-01 14:19:08 +02:00
committed by Oliver Gierke
parent 7f45d759b3
commit 1443b155db
17 changed files with 355 additions and 63 deletions

View File

@@ -326,7 +326,7 @@
<includes>
<include>**/EclipseLink*Tests.java</include>
</includes>
<argLine>-javaagent:${settings.localRepository}/org/springframework/spring-instrument/${spring}/spring-instrument-${spring}.jar</argLine>
<argLine>-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</argLine>
</configuration>
</execution>
<execution>

View File

@@ -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.
*

View File

@@ -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.

View File

@@ -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<String, Object> hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph());
Map<String, Object> hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph(), getQueryMethod().getEntityInformation().getJavaType());
for (Map.Entry<String, Object> hint : hints.entrySet()) {
query.setHint(hint.getKey(), hint.getValue());

View File

@@ -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<String, Object> tryGetFetchGraphHints(EntityManager em, JpaEntityGraph entityGraph) {
public static Map<String, Object> 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);
}
}
}

View File

@@ -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)
+ "]";
}
}

View File

@@ -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());
}
/**

View File

@@ -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<String, Object> 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();
}

View File

@@ -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<String, Object> 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<String, Object> 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;
}
}

View File

@@ -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<T, ID extends Serializable> implements JpaRepos
Map<String, Object> hints = new HashMap<String, Object>();
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)

View File

@@ -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({ //

View File

@@ -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,

View File

@@ -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<User> 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));
}
}

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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()));

View File

@@ -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<User, Long> information;
@Mock CrudMethodMetadata metadata;
@Mock EntityGraph<User> 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);