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:
committed by
Oliver Gierke
parent
7f45d759b3
commit
1443b155db
2
pom.xml
2
pom.xml
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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({ //
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user