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:
committed by
Oliver Gierke
parent
febd41b10b
commit
d07aeae814
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user