DATAJPA-696 - Polishing.

Moved logic to create an ad-hoc entity graph into Jpa21Utils to avoid having to create a PersistenceProvider instance just for the sake of creating such a graph. Moved test code for that into newly introduced Jpa21UtilsUnitTests.

JpaEntityGraph now uses an unmodifiable List instead of an array to prevent having to deal with modifications. Jpa21Utils now uses Collection sorting and reversing to be able to use the for-each loop.

Polished imports, JavaDoc and @since tags where necessary.

Original pull request: #140.
This commit is contained in:
Oliver Gierke
2015-05-12 17:10:53 +02:00
parent 1443b155db
commit 70ad616c4c
11 changed files with 165 additions and 163 deletions

View File

@@ -15,17 +15,8 @@
*/
package org.springframework.data.jpa.provider;
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 static org.springframework.data.jpa.provider.JpaClassUtils.*;
import static org.springframework.data.jpa.provider.PersistenceProvider.Constants.*;
import java.util.Arrays;
import java.util.Collection;
@@ -33,10 +24,8 @@ 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;
@@ -53,12 +42,10 @@ 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.
@@ -367,69 +354,6 @@ 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

@@ -16,17 +16,20 @@
package org.springframework.data.jpa.repository.query;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityGraph;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.Subgraph;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Utils for bridging various JPA 2.1 features.
@@ -57,11 +60,10 @@ public class Jpa21Utils {
/**
* Returns a {@link Map} with hints for a JPA 2.1 fetch-graph or load-graph if running under JPA 2.1.
*
* @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
* @param em 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,
@@ -104,7 +106,65 @@ public class Jpa21Utils {
return em.getEntityGraph(jpaEntityGraph.getName());
} catch (Exception ex) {
// try to create and dynamically register the entityGraph
return PersistenceProvider.fromEntityManager(em).createDynamicEntityGraph(em, jpaEntityGraph, entityType);
return createDynamicEntityGraph(em, jpaEntityGraph, entityType);
}
}
/**
* Creates a dynamic {@link EntityGraph} from the given {@link JpaEntityGraph} information.
*
* @param em must not be {@literal null}.
* @param jpaEntityGraph must not be {@literal null}.
* @param entityType must not be {@literal null}.
* @return
* @since 1.9
*/
private static EntityGraph<?> createDynamicEntityGraph(EntityManager em, JpaEntityGraph jpaEntityGraph,
Class<?> entityType) {
Assert.notNull(em, "EntityManager must not be null!");
Assert.notNull(jpaEntityGraph, "JpaEntityGraph must not be null!");
Assert.notNull(entityType, "Entity type must not be null!");
Assert.isTrue(jpaEntityGraph.isAdHocEntityGraph(), "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
*/
static void configureFetchGraphFrom(JpaEntityGraph jpaEntityGraph, EntityGraph<?> entityGraph) {
List<String> attributePaths = new ArrayList<String>(jpaEntityGraph.getAttributePaths());
// Sort to ensure that the intermediate entity subgraphs are created accordingly.
Collections.sort(attributePaths);
Collections.reverse(attributePaths);
// We build the entity graph based on the paths with highest depth first
for (String path : attributePaths) {
// 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++) {
parent = c == 0 ? entityGraph.addSubgraph(pathComponents[c]) : parent.addSubgraph(pathComponents[c]);
}
parent.addAttributeNodes(pathComponents[pathComponents.length - 1]);
}
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.jpa.repository.query;
import java.util.Arrays;
import java.util.List;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
@@ -34,7 +35,7 @@ public class JpaEntityGraph {
private final String name;
private final EntityGraphType type;
private final String[] attributePaths;
private final List<String> attributePaths;
/**
* Creates an {@link JpaEntityGraph}.
@@ -48,11 +49,12 @@ public class JpaEntityGraph {
}
/**
* Creates an {@link JpaEntityGraph}.
* Creates an {@link JpaEntityGraph} with the given name, {@link EntityGraphType} and attribute paths.
*
* @param name must not be {@literal null} or empty.
* @param type must not be {@literal null}.
* @param attributePaths may be {@literal null}.
* @since 1.9
*/
public JpaEntityGraph(String name, EntityGraphType type, String[] attributePaths) {
@@ -61,7 +63,7 @@ public class JpaEntityGraph {
this.name = name;
this.type = type;
this.attributePaths = attributePaths == null ? EMPTY_ATTRIBUTE_PATHS : attributePaths;
this.attributePaths = Arrays.asList(attributePaths == null ? EMPTY_ATTRIBUTE_PATHS : attributePaths);
}
/**
@@ -88,7 +90,7 @@ public class JpaEntityGraph {
* @return
* @since 1.9
*/
public String[] getAttributePaths() {
public List<String> getAttributePaths() {
return attributePaths;
}
@@ -96,9 +98,10 @@ public class JpaEntityGraph {
* Return {@literal true} if this {@link JpaEntityGraph} needs to be generated on-the-fly.
*
* @return
* @since 1.9
*/
public boolean isDynamicEntityGraph() {
return this.attributePaths.length > 0;
public boolean isAdHocEntityGraph() {
return !attributePaths.isEmpty();
}
/*
@@ -107,7 +110,6 @@ public class JpaEntityGraph {
*/
@Override
public String toString() {
return "JpaEntityGraph [name=" + name + ", type=" + type + ", attributePaths=" + Arrays.toString(attributePaths)
+ "]";
return "JpaEntityGraph [name=" + name + ", type=" + type + ", attributePaths=" + attributePaths.toString() + "]";
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2014 the original author or authors.
* Copyright 2008-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,8 +15,7 @@
*/
package org.springframework.data.jpa.repository.query;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
import static org.springframework.core.annotation.AnnotationUtils.getAnnotation;
import static org.springframework.core.annotation.AnnotationUtils.*;
import java.lang.reflect.Method;
import java.util.ArrayList;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-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,7 +21,6 @@ import java.util.Map;
import javax.persistence.LockModeType;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.query.JpaEntityGraph;
/**
* Interface to abstract {@link CrudMethodMetadata} that provide the {@link LockModeType} to be used for query
@@ -53,7 +52,7 @@ public interface CrudMethodMetadata {
* @since 1.9
*/
EntityGraph getEntityGraph();
/**
* Returns the {@link Method} to be used.
*

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-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.
@@ -34,10 +34,8 @@ import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.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;
@@ -203,16 +201,18 @@ enum CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor {
public Map<String, Object> getQueryHints() {
return queryHints;
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getEntityGraph()
*/
@Override
public EntityGraph getEntityGraph() {
return entityGraph;
}
/* (non-Javadoc)
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.CrudMethodMetadata#getMethod()
*/
@Override

View File

@@ -15,11 +15,7 @@
*/
package org.springframework.data.jpa.repository.support;
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 static org.springframework.data.jpa.repository.query.QueryUtils.*;
import java.io.Serializable;
import java.util.ArrayList;
@@ -260,7 +256,7 @@ public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepos
}
private JpaEntityGraph getEntityGraph() {
String fallbackName = this.entityInformation.getEntityName() + "." + metadata.getMethod().getName();
return new JpaEntityGraph(metadata.getEntityGraph(), fallbackName);
}

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,37 +15,21 @@
*/
package org.springframework.data.jpa.provider;
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 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 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.repository.EntityGraph.EntityGraphType;
import org.springframework.data.jpa.repository.query.JpaEntityGraph;
import org.springframework.instrument.classloading.ShadowingClassLoader;
import org.springframework.util.ClassUtils;
@@ -118,26 +102,6 @@ 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

@@ -31,13 +31,16 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.sample.Role;
import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.jpa.repository.sample.RepositoryMethodsWithEntityGraphConfigJpaRepository;
import org.springframework.data.jpa.repository.sample.RepositoryMethodsWithEntityGraphConfigRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
/**
* Integration tests for RepositoryMethodsWithEntityGraphConfigJpaRepository.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:config/namespace-autoconfig-context.xml")
@@ -45,25 +48,25 @@ import org.springframework.transaction.annotation.Transactional;
public class EntityGraphRepositoryMethodsIntegrationTests {
@Autowired EntityManager em;
@Autowired RepositoryMethodsWithEntityGraphConfigJpaRepository repository;
@Autowired RepositoryMethodsWithEntityGraphConfigRepository repository;
User tom;
User olli;
User ollie;
Role role;
@Before
public void setup() {
tom = new User("Thomas", "Darimont", "tdarimont@example.org");
olli = new User("Oliver", "Gierke", "ogierke@example.org");
ollie = new User("Oliver", "Gierke", "ogierke@example.org");
role = new Role("Developer");
em.persist(role);
tom.getRoles().add(role);
tom = repository.save(tom);
olli = repository.save(olli);
tom.getColleagues().add(olli);
ollie = repository.save(ollie);
tom.getColleagues().add(ollie);
}
/**
@@ -95,7 +98,7 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
assertThat("colleages should be fetched with 'user.detail' fetchgraph",
Persistence.getPersistenceUtil().isLoaded(user.getColleagues()), is(true));
}
/**
* @see DATAJPA-696
*/
@@ -110,7 +113,7 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
assertThat("colleages should be fetched with 'user.detail' fetchgraph",
Persistence.getPersistenceUtil().isLoaded(user.getColleagues()), is(true));
}
/**
* @see DATAJPA-696
*/

View File

@@ -0,0 +1,54 @@
/*
* Copyright 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import javax.persistence.EntityGraph;
import javax.persistence.Subgraph;
import org.junit.Test;
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
/**
* Unit tests for {@link Jpa21Utils}.
*
* @author Thomas Darimont
* @author Oliver Gierke
*/
public class Jpa21UtilsUnitTests {
/**
* @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" });
Jpa21Utils.configureFetchGraphFrom(jpaEntityGraph, entityGraph);
verify(entityGraph, times(1)).addAttributeNodes("foo");
verify(entityGraph, times(1)).addSubgraph("gugu");
verify(subgraph, times(1)).addAttributeNodes("gaga");
}
}

View File

@@ -21,6 +21,7 @@ import org.springframework.data.jpa.domain.sample.User;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
/**
* Custom repository interface that customizes the fetching behavior of querys of well known repository interface
@@ -28,7 +29,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
*
* @author Thomas Darimont
*/
public interface RepositoryMethodsWithEntityGraphConfigJpaRepository extends JpaRepository<User, Integer> {
public interface RepositoryMethodsWithEntityGraphConfigRepository extends CrudRepository<User, Integer> {
/**
* Should find all users.