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

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