DATAJPA-689 - Allow @EntityGraph on CrudRepository.findOne(…).

We now honor @EntityGraph definitions on CrudRepository.findOne(…) which was previously only the case for methods that created a Query explicitly. 

Extracted tryGetFetchGraphHints(…) method from tryConfigureFetchGraph(…) method in Jpa21Utils to allow EntityGraph hints to be used in SimpleJpaRepository.findOne(…). Construction of query hints from context information in SimpleJpaRepository is now performed via the getQueryHints(…) method. Adjusted QueryDslJpaRepository to use query hints as well.

Added unit and integration tests to verify that @EntityGraph information is propagated to findOne executions.

Original pull request: #137.
This commit is contained in:
Thomas Darimont
2015-03-09 18:27:56 +01:00
committed by Oliver Gierke
parent febd41b10b
commit d07aeae814
8 changed files with 134 additions and 49 deletions

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,6 +15,8 @@
*/
package org.springframework.data.jpa.repository.query;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.Query;
@@ -180,10 +182,10 @@ public abstract class AbstractJpaQuery implements RepositoryQuery {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(method, "JpaQueryMethod must not be null!");
JpaEntityGraph entityGraph = method.getEntityGraph();
Map<String, Object> hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph());
if (entityGraph != null) {
Jpa21Utils.tryConfigureFetchGraph(em, query, entityGraph);
for (Map.Entry<String, Object> hint : hints.entrySet()) {
query.setHint(hint.getKey(), hint.getValue());
}
return query;

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.
@@ -16,6 +16,8 @@
package org.springframework.data.jpa.repository.query;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import javax.persistence.EntityGraph;
import javax.persistence.EntityManager;
@@ -52,27 +54,27 @@ public class Jpa21Utils {
}
/**
* Adds a JPA 2.1 fetch-graph or load-graph hint to the given {@link Query} if running under JPA 2.1.
* Returns a {@link Map} with hints for a JPA 2.1 fetch-graph or load-graph if running under JPA 2.1.
*
* @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 query must not be {@literal null}.
* @param entityGraph can be {@literal null}.
* @param em must not be {@literal null}
* @param query must not be {@literal null}
* @param entityGraph can 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 <T extends Query> T tryConfigureFetchGraph(EntityManager em, T query, JpaEntityGraph entityGraph) {
public static Map<String, Object> tryGetFetchGraphHints(EntityManager em, JpaEntityGraph entityGraph) {
if (entityGraph == null) {
return query;
return Collections.emptyMap();
}
EntityGraph<?> graph = tryGetFetchGraph(em, entityGraph);
if (graph == null) {
return query;
return Collections.emptyMap();
}
query.setHint(entityGraph.getType().getKey(), graph);
return query;
return Collections.<String, Object> singletonMap(entityGraph.getType().getKey(), graph);
}
/**

View File

@@ -20,7 +20,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import javax.persistence.EntityGraph;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
@@ -28,8 +27,6 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.query.Jpa21Utils;
import org.springframework.data.jpa.repository.query.JpaEntityGraph;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QSort;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
@@ -57,7 +54,6 @@ public class QueryDslJpaRepository<T, ID extends Serializable> extends SimpleJpa
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
private final EntityManager em;
/**
* Creates a new {@link QueryDslJpaRepository} from the given domain class and {@link EntityManager}. This will use
@@ -82,7 +78,6 @@ public class QueryDslJpaRepository<T, ID extends Serializable> extends SimpleJpa
EntityPathResolver resolver) {
super(entityInformation, entityManager);
this.em = entityManager;
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
@@ -185,24 +180,10 @@ public class QueryDslJpaRepository<T, ID extends Serializable> extends SimpleJpa
LockModeType type = metadata.getLockModeType();
query = type == null ? query : query.setLockMode(type);
for (Entry<String, Object> hint : metadata.getQueryHints().entrySet()) {
for (Entry<String, Object> hint : getQueryHints().entrySet()) {
query.setHint(hint.getKey(), hint.getValue());
}
JpaEntityGraph jpaEntityGraph = metadata.getEntityGraph();
if (jpaEntityGraph == null) {
return query;
}
EntityGraph<?> entityGraph = Jpa21Utils.tryGetFetchGraph(em, jpaEntityGraph);
if (entityGraph == null) {
return query;
}
query.setHint(jpaEntityGraph.getType().getKey(), entityGraph);
return query;
}

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.
@@ -20,6 +20,7 @@ import static org.springframework.data.jpa.repository.query.QueryUtils.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -28,6 +29,7 @@ import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
@@ -43,6 +45,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.provider.PersistenceProvider;
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;
@@ -225,11 +228,31 @@ public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepos
}
LockModeType type = metadata.getLockModeType();
Map<String, Object> hints = metadata.getQueryHints();
Map<String, Object> hints = getQueryHints();
return type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints);
}
/**
* Returns a {@link Map} with the query hints based on the current {@link CrudMethodMetadata} and potential
* {@link EntityGraph} information.
*
* @return
*/
protected Map<String, Object> getQueryHints() {
if (metadata.getEntityGraph() == null) {
return metadata.getQueryHints();
}
Map<String, Object> hints = new HashMap<String, Object>();
hints.putAll(metadata.getQueryHints());
hints.putAll(Jpa21Utils.tryGetFetchGraphHints(em, metadata.getEntityGraph()));
return hints;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.JpaRepository#getOne(java.io.Serializable)
@@ -567,11 +590,16 @@ public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepos
LockModeType type = metadata.getLockModeType();
TypedQuery<T> toReturn = type == null ? query : query.setLockMode(type);
for (Entry<String, Object> hint : metadata.getQueryHints().entrySet()) {
applyQueryHints(toReturn);
return toReturn;
}
private void applyQueryHints(Query query) {
for (Entry<String, Object> hint : getQueryHints().entrySet()) {
query.setHint(hint.getKey(), hint.getValue());
}
return Jpa21Utils.tryConfigureFetchGraph(em, toReturn, metadata.getEntityGraph());
}
/**

View File

@@ -48,12 +48,15 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
@Autowired RepositoryMethodsWithEntityGraphConfigJpaRepository repository;
User tom;
User olli;
Role role;
@Before
public void setup() {
tom = new User("Thomas", "Darimont", "tdarimont@example.org");
olli = new User("Oliver", "Gierke", "ogierke@example.org");
role = new Role("Developer");
em.persist(role);
tom.getRoles().add(role);
@@ -75,4 +78,25 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
assertThat(Persistence.getPersistenceUtil().isLoaded(result.get(0).getRoles()), is(true));
assertThat(result.get(0), is(tom));
}
/**
* @see DATAJPA-689
*/
@Test
public void shouldRespectConfiguredJpaEntityGraphInFindOne() {
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));
}
}

View File

@@ -345,6 +345,19 @@ public class JpaQueryMethodUnitTests {
assertThat(method.getEntityGraph().getType(), is(EntityGraphType.FETCH));
}
/**
* @see DATAJPA-689
*/
@Test
public void shouldFindEntityGraphAnnotationOnOverriddenSimpleJpaRepositoryMethodFindOne() throws Exception {
JpaQueryMethod method = new JpaQueryMethod(JpaRepositoryOverride.class.getMethod("findOne"), metadata, extractor);
assertThat(method.getEntityGraph(), is(notNullValue()));
assertThat(method.getEntityGraph().getName(), is("User.detail"));
assertThat(method.getEntityGraph().getType(), is(EntityGraphType.FETCH));
}
/**
* Interface to define invalid repository methods for testing.
*
@@ -414,7 +427,13 @@ public class JpaQueryMethodUnitTests {
*/
@Override
@EntityGraph("User.detail")
public List<User> findAll();
List<User> findAll();
/**
* DATAJPA-689
*/
@EntityGraph("User.detail")
User findOne();
}
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)

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.
@@ -23,16 +23,22 @@ import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Custom repository interface that customizes the fetching behavior of querys of well known repository interface methods via {@link EntityGraph}
* annotation.
* Custom repository interface that customizes the fetching behavior of querys of well known repository interface
* methods via {@link EntityGraph} annotation.
*
* @author Thomas Darimont
*/
public interface RepositoryMethodsWithEntityGraphConfigJpaRepository extends JpaRepository<User, Long> {
public interface RepositoryMethodsWithEntityGraphConfigJpaRepository extends JpaRepository<User, Integer> {
/**
* Should find all users.
*/
@EntityGraph(type = EntityGraphType.LOAD, value = "User.overview")
List<User> findAll();
/**
* Should fetch all user details
*/
@EntityGraph(type = EntityGraphType.FETCH, value = "User.detail")
User findOne(Integer id);
}

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.
@@ -15,8 +15,10 @@
*/
package org.springframework.data.jpa.repository.support;
import static java.util.Collections.*;
import static org.mockito.Mockito.*;
import javax.persistence.EntityGraph;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
@@ -30,16 +32,19 @@ import org.mockito.runners.MockitoJUnitRunner;
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;
/**
* Unit tests for {@link SimpleJpaRepository}.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
@RunWith(MockitoJUnitRunner.class)
public class SimpleJpaRepositoryUnitTests {
SimpleJpaRepository<User, Long> repo;
SimpleJpaRepository<User, Integer> repo;
@Mock EntityManager em;
@Mock CriteriaBuilder builder;
@@ -49,6 +54,7 @@ public class SimpleJpaRepositoryUnitTests {
@Mock TypedQuery<Long> countQuery;
@Mock JpaEntityInformation<User, Long> information;
@Mock CrudMethodMetadata metadata;
@Mock EntityGraph<User> entityGraph;
@Before
public void setUp() {
@@ -64,7 +70,7 @@ public class SimpleJpaRepositoryUnitTests {
when(em.createQuery(criteriaQuery)).thenReturn(query);
when(em.createQuery(countCriteriaQuery)).thenReturn(countQuery);
repo = new SimpleJpaRepository<User, Long>(information, em);
repo = new SimpleJpaRepository<User, Integer>(information, em);
repo.setRepositoryMethodMetadata(metadata);
}
@@ -86,6 +92,23 @@ public class SimpleJpaRepositoryUnitTests {
@Test(expected = EmptyResultDataAccessException.class)
public void throwsExceptionIfEntityToDeleteDoesNotExist() {
repo.delete(4711L);
repo.delete(4711);
}
/**
* @see DATAJPA-689
*/
@Test
@SuppressWarnings({ "rawtypes", "unchecked" })
public void shouldPropagateConfiguredEntityGraphToFindOne() {
String entityGraphName = "User.detail";
when(metadata.getEntityGraph()).thenReturn(new JpaEntityGraph(entityGraphName, EntityGraphType.LOAD));
when(em.getEntityGraph(entityGraphName)).thenReturn((EntityGraph) entityGraph);
Integer id = 0;
repo.findOne(id);
verify(em).find(User.class, id, singletonMap(EntityGraphType.LOAD.getKey(), (Object) entityGraph));
}
}