DATAJPA-1075 - Append ad hoc EntityGraph subgraphs properly.
We now make sure to append instead of recreate subgraphs correctly when AttributeNodes already exist for a given property or dot path. This change fixes errors when creating ad hoc fetch graphs like below where multiple properties are loaded via a subgraph on the same type.
@EntityGraph(attributePaths = { "colleagues.roles", "colleagues.colleagues" })
Previously the first path was accidentally dropped. Additionally we fixed issues with the creation of "deep" ad hoc fetch graphs via @EntityGraph so that
@EntityGraph(attributePaths = { "roles", "colleagues.roles", "colleagues.colleagues.roles" })
can be used as a short form of
@NamedEntityGraph(name = "User.deepGraph",
attributeNodes = {
@NamedAttributeNode("roles"),
@NamedAttributeNode(value="colleagues", subgraph = "User.colleagues")
},
subgraphs = {
@NamedSubgraph(name = "User.colleagues", attributeNodes = {
@NamedAttributeNode("roles"),
@NamedAttributeNode(value = "colleagues", subgraph = "User.colleaguesOfColleagues")
}),
@NamedSubgraph(name="User.colleaguesOfColleagues", attributeNodes = {
@NamedAttributeNode("roles"),
})
})
})
We had to disable EclipseLink tests for this one since there seems to be a glitch when calling EntityManager.clear() prior to applying the fetch-/loadgraph causing the properties not to be in the correct load state. Omitting EntityManager.clear() is not an option since the load state is always LOADED even when leaving out query hints.
Related issue: DATAJPA-1041.
Original pull request: #192.
Related pull request: #188.
This commit is contained in:
committed by
Oliver Gierke
parent
3748e6cb3b
commit
d98ce700e7
@@ -148,81 +148,103 @@ public class Jpa21Utils {
|
||||
|
||||
// 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(".")) {
|
||||
|
||||
if (findAttributeNode(path, entityGraph) == null) {
|
||||
entityGraph.addAttributeNodes(path);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need to build nested sub fetch graphs
|
||||
String[] pathComponents = StringUtils.delimitedListToStringArray(path, ".");
|
||||
Subgraph<?> parent = null;
|
||||
createGraph(pathComponents, 0, entityGraph, null);
|
||||
}
|
||||
}
|
||||
|
||||
for (int c = 0; c < pathComponents.length - 1; c++) {
|
||||
parent = c == 0 ? findOrCreateSubgraph(pathComponents[c], entityGraph) : parent.addSubgraph(pathComponents[c]);
|
||||
private static void createGraph(String[] pathComponents, int offset, EntityGraph<?> root, Subgraph<?> parent) {
|
||||
|
||||
String attributeName = pathComponents[offset];
|
||||
|
||||
// we found our leaf property, now let's see if it already exists and add it if not
|
||||
if (pathComponents.length - 1 == offset) {
|
||||
|
||||
if (parent == null && !exists(attributeName, root.getAttributeNodes())) {
|
||||
root.addAttributeNodes(attributeName);
|
||||
} else if (parent != null && !exists(attributeName, parent.getAttributeNodes())) {
|
||||
parent.addAttributeNodes(attributeName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
parent.addAttributeNodes(pathComponents[pathComponents.length - 1]);
|
||||
AttributeNode<?> node = findAttributeNode(attributeName, root, parent);
|
||||
if (node != null) {
|
||||
|
||||
Subgraph<?> subgraph = getSubgraph(node);
|
||||
if (subgraph == null) {
|
||||
subgraph = parent != null ? parent.addSubgraph(attributeName) : root.addSubgraph(attributeName);
|
||||
}
|
||||
createGraph(pathComponents, offset + 1, root, subgraph);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent == null) {
|
||||
createGraph(pathComponents, offset + 1, root, root.addSubgraph(attributeName));
|
||||
} else {
|
||||
createGraph(pathComponents, offset + 1, root, parent.addSubgraph(attributeName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Subgraph} with the given name fro the given {@link EntityGraph} or creates a new one if none
|
||||
* already available.
|
||||
*
|
||||
* @param name
|
||||
* @param entityGraph
|
||||
* Checks the given {@link List} of {@link AttributeNode}s for the existence of an {@link AttributeNode} matching the
|
||||
* given {@literal attributeNodeName}.
|
||||
*
|
||||
* @param attributeNodeName
|
||||
* @param nodes
|
||||
* @return
|
||||
*/
|
||||
private static Subgraph<?> findOrCreateSubgraph(String name, EntityGraph<?> entityGraph) {
|
||||
|
||||
Subgraph<?> subgraph = findSubgraph(name, entityGraph);
|
||||
|
||||
return subgraph != null ? subgraph : entityGraph.addSubgraph(name);
|
||||
private static boolean exists(String attributeNodeName, List<AttributeNode<?>> nodes) {
|
||||
return findAttributeNode(attributeNodeName, nodes) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Subgraph} with the given name from the given {@link EntityGraph}.
|
||||
*
|
||||
* @param name
|
||||
* Find the {@link AttributeNode} matching the given {@literal attributeNodeName} in given {@link Subgraph} or
|
||||
* {@link EntityGraph} favoring matches {@link Subgraph} over {@link EntityGraph}.
|
||||
*
|
||||
* @param attributeNodeName
|
||||
* @param entityGraph
|
||||
* @return
|
||||
* @param parent
|
||||
* @return {@literal null} if not found.
|
||||
*/
|
||||
private static Subgraph<?> findSubgraph(String name, EntityGraph<?> entityGraph) {
|
||||
|
||||
AttributeNode<?> node = findAttributeNode(name, entityGraph);
|
||||
|
||||
if (node != null && !ObjectUtils.isEmpty(node.getSubgraphs())) {
|
||||
return node.getSubgraphs().values().iterator().next();
|
||||
}
|
||||
|
||||
return null;
|
||||
private static AttributeNode<?> findAttributeNode(String attributeNodeName, EntityGraph<?> entityGraph,
|
||||
Subgraph parent) {
|
||||
return findAttributeNode(attributeNodeName,
|
||||
parent != null ? parent.getAttributeNodes() : entityGraph.getAttributeNodes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link AttributeNode} with the given name if present in the given {@link EntityGraph}.
|
||||
*
|
||||
* @param name
|
||||
* @param entityGraph must not be {@literal null}.
|
||||
* @return
|
||||
* Find the {@link AttributeNode} matching the given {@literal attributeNodeName} in given {@link List} of
|
||||
* {@link AttributeNode}s.
|
||||
*
|
||||
* @param attributeNodeName
|
||||
* @param nodes
|
||||
* @return {@literal null} if not found.
|
||||
*/
|
||||
private static AttributeNode<?> findAttributeNode(String name, EntityGraph<?> entityGraph) {
|
||||
private static AttributeNode<?> findAttributeNode(String attributeNodeName, List<AttributeNode<?>> nodes) {
|
||||
|
||||
for (AttributeNode<?> node : entityGraph.getAttributeNodes()) {
|
||||
if (ObjectUtils.nullSafeEquals(node.getAttributeName(), name)) {
|
||||
for (AttributeNode<?> node : nodes) {
|
||||
if (ObjectUtils.nullSafeEquals(node.getAttributeName(), attributeNodeName)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the first {@link Subgraph} from the given {@link AttributeNode}. Ignores any potential different
|
||||
* {@link Subgraph}s registered for more concrete {@link Class}es as the dynamically created graph does not
|
||||
* distinguish between those.
|
||||
*
|
||||
* @param node
|
||||
* @return
|
||||
*/
|
||||
private static Subgraph<?> getSubgraph(AttributeNode<?> node) {
|
||||
return node.getSubgraphs().isEmpty() ? null : node.getSubgraphs().values().iterator().next();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -64,7 +64,17 @@ import javax.persistence.TemporalType;
|
||||
attributeNodes = { @NamedAttributeNode("roles"),
|
||||
@NamedAttributeNode(value = "colleagues", subgraph = "User.colleagues") },
|
||||
subgraphs = { @NamedSubgraph(name = "User.colleagues",
|
||||
attributeNodes = { @NamedAttributeNode("colleagues"), @NamedAttributeNode("roles") }) }) })
|
||||
attributeNodes = { @NamedAttributeNode("colleagues"),
|
||||
@NamedAttributeNode("roles") }) }),
|
||||
@NamedEntityGraph(name = "User.deepGraph",
|
||||
attributeNodes = { @NamedAttributeNode("roles"),
|
||||
@NamedAttributeNode(value = "colleagues", subgraph = "User.colleagues") },
|
||||
subgraphs = {
|
||||
@NamedSubgraph(name = "User.colleagues",
|
||||
attributeNodes = { @NamedAttributeNode("roles"),
|
||||
@NamedAttributeNode(value = "colleagues", subgraph = "User.colleaguesOfColleagues") }),
|
||||
@NamedSubgraph(name = "User.colleaguesOfColleagues",
|
||||
attributeNodes = { @NamedAttributeNode("roles"), }) }) })
|
||||
@NamedQuery(name = "User.findByEmailAddress", query = "SELECT u FROM User u WHERE u.emailAddress = ?1")
|
||||
@NamedStoredProcedureQueries({ //
|
||||
@NamedStoredProcedureQuery(name = "User.plus1", procedureName = "plus1inout",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2017 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,6 +21,7 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@ContextConfiguration("classpath:eclipselink.xml")
|
||||
public class EclipseLinkEntityGraphRepositoryMethodsIntegrationTests
|
||||
@@ -37,4 +38,16 @@ public class EclipseLinkEntityGraphRepositoryMethodsIntegrationTests
|
||||
@Ignore
|
||||
@Test
|
||||
public void shouldRespectDynamicFetchGraphForGetOneWithAttributeNamesById() {}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void shouldCreateDynamicGraphWithMultipleLevelsOfSubgraphs() {}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void shouldRespectConfiguredJpaEntityGraphInFindOne() {}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void shouldRespectInferFetchGraphFromMethodName() {}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
|
||||
ollie = repository.save(ollie);
|
||||
tom.getColleagues().add(ollie);
|
||||
|
||||
christoph.addRole(role);
|
||||
christoph = repository.save(christoph);
|
||||
|
||||
ollie.getColleagues().add(christoph);
|
||||
@@ -91,6 +92,8 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
|
||||
public void shouldRespectConfiguredJpaEntityGraph() {
|
||||
|
||||
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
List<User> result = repository.findAll();
|
||||
|
||||
@@ -106,6 +109,8 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
|
||||
public void shouldRespectConfiguredJpaEntityGraphInFindOne() {
|
||||
|
||||
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
User user = repository.findOne(tom.getId());
|
||||
|
||||
@@ -121,6 +126,8 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
|
||||
public void shouldRespectInferFetchGraphFromMethodName() {
|
||||
|
||||
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
User user = repository.getOneWithDefinedEntityGraphById(tom.getId());
|
||||
|
||||
@@ -136,6 +143,8 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
|
||||
public void shouldRespectDynamicFetchGraphForGetOneWithAttributeNamesById() {
|
||||
|
||||
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
em.flush();
|
||||
em.clear();
|
||||
@@ -160,6 +169,8 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
|
||||
public void shouldRespectConfiguredJpaEntityGraphWithPaginationAndQueryDslPredicates() {
|
||||
|
||||
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
Page<User> page = repository.findAll(QUser.user.firstname.isNotNull(), new PageRequest(0, 100));
|
||||
List<User> result = page.getContent();
|
||||
@@ -210,4 +221,28 @@ public class EntityGraphRepositoryMethodsIntegrationTests {
|
||||
assertThat(util.isLoaded(colleague, "roles"), is(true));
|
||||
}
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1041, DATAJPA-1075
|
||||
public void shouldCreateDynamicGraphWithMultipleLevelsOfSubgraphs() {
|
||||
|
||||
Assume.assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
em.flush();
|
||||
em.clear();
|
||||
|
||||
User user = repository.findOneWithDeepGraphById(tom.getId());
|
||||
|
||||
assertThat(user, is(notNullValue()));
|
||||
|
||||
assertThat("colleagues on root should have been fetched by dynamic subgraph declaration",
|
||||
Persistence.getPersistenceUtil().isLoaded(user, "colleagues"), is(true));
|
||||
|
||||
for (User colleague : user.getColleagues()) {
|
||||
assertThat(Persistence.getPersistenceUtil().isLoaded(colleague, "colleagues"), is(true));
|
||||
assertThat(Persistence.getPersistenceUtil().isLoaded(colleague, "roles"), is(true));
|
||||
for (User colleagueOfColleague : colleague.getColleagues()) {
|
||||
assertThat(Persistence.getPersistenceUtil().isLoaded(colleagueOfColleague, "roles"), is(true));
|
||||
assertThat(Persistence.getPersistenceUtil().isLoaded(colleagueOfColleague, "colleagues"), is(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2017 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 org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@ContextConfiguration("classpath:eclipselink.xml")
|
||||
public class EclipseLinkJpa21UtilsTests extends Jpa21UtilsTests {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2017 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.junit.Assert.*;
|
||||
import static org.junit.Assume.*;
|
||||
import static org.springframework.data.jpa.support.EntityManagerTestUtils.*;
|
||||
import static org.springframework.data.jpa.util.IsAttributeNode.*;
|
||||
|
||||
import javax.persistence.AttributeNode;
|
||||
import javax.persistence.EntityGraph;
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.jpa.domain.sample.User;
|
||||
import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration("classpath:application-context.xml")
|
||||
@Transactional
|
||||
public class Jpa21UtilsTests {
|
||||
|
||||
@Autowired EntityManager em;
|
||||
|
||||
@Test // DATAJPA-1041, DATAJPA-1075
|
||||
public void shouldCreateGraphWithoutSubGraphCorrectly() {
|
||||
|
||||
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
|
||||
EntityGraph<User> graph = em.createEntityGraph(User.class);
|
||||
Jpa21Utils.configureFetchGraphFrom(
|
||||
new JpaEntityGraph("name", EntityGraphType.FETCH, new String[] { "roles", "colleagues" }), graph);
|
||||
|
||||
AttributeNode<?> roles = findNode("roles", graph);
|
||||
assertThat(roles, terminatesGraph());
|
||||
|
||||
AttributeNode<?> colleagues = findNode("colleagues", graph);
|
||||
assertThat(colleagues, terminatesGraph());
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1041, DATAJPA-1075
|
||||
public void shouldCreateGraphWithMultipleSubGraphCorrectly() {
|
||||
|
||||
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
|
||||
EntityGraph<User> graph = em.createEntityGraph(User.class);
|
||||
Jpa21Utils.configureFetchGraphFrom(new JpaEntityGraph("name", EntityGraphType.FETCH,
|
||||
new String[] { "roles", "colleagues.roles", "colleagues.colleagues" }), graph);
|
||||
|
||||
AttributeNode<?> roles = findNode("roles", graph);
|
||||
assertThat(roles, terminatesGraph());
|
||||
|
||||
AttributeNode<?> colleagues = findNode("colleagues", graph);
|
||||
assertThat(colleagues, terminatesGraphWith("roles", "colleagues"));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1041, DATAJPA-1075
|
||||
public void shouldCreateGraphWithDeepSubGraphCorrectly() {
|
||||
|
||||
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
|
||||
EntityGraph<User> graph = em.createEntityGraph(User.class);
|
||||
Jpa21Utils.configureFetchGraphFrom(new JpaEntityGraph("name", EntityGraphType.FETCH,
|
||||
new String[] { "roles", "colleagues.roles", "colleagues.colleagues.roles" }), graph);
|
||||
|
||||
AttributeNode<?> roles = findNode("roles", graph);
|
||||
assertThat(roles, terminatesGraph());
|
||||
|
||||
AttributeNode<?> colleagues = findNode("colleagues", graph);
|
||||
assertThat(colleagues, terminatesGraphWith("roles"));
|
||||
assertThat(colleagues, hasSubgraphs("colleagues"));
|
||||
|
||||
AttributeNode colleaguesOfColleagues = findNode("colleagues", colleagues);
|
||||
assertThat(colleaguesOfColleagues, terminatesGraphWith("roles"));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1041, DATAJPA-1075
|
||||
public void shouldIgnoreIntermedeateSubGraphNodesThatAreNotNeeded() {
|
||||
|
||||
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
|
||||
EntityGraph<User> graph = em.createEntityGraph(User.class);
|
||||
Jpa21Utils.configureFetchGraphFrom(new JpaEntityGraph("name", EntityGraphType.FETCH, new String[] { "roles",
|
||||
"colleagues", "colleagues.roles", "colleagues.colleagues", "colleagues.colleagues.roles" }), graph);
|
||||
|
||||
AttributeNode<?> roles = findNode("roles", graph);
|
||||
assertThat(roles, terminatesGraph());
|
||||
|
||||
AttributeNode<?> colleagues = findNode("colleagues", graph);
|
||||
assertThat(colleagues, terminatesGraphWith("roles"));
|
||||
assertThat(colleagues, hasSubgraphs("colleagues"));
|
||||
|
||||
AttributeNode colleaguesOfColleagues = findNode("colleagues", colleagues);
|
||||
assertThat(colleaguesOfColleagues, terminatesGraphWith("roles"));
|
||||
}
|
||||
|
||||
@Test // DATAJPA-1041, DATAJPA-1075
|
||||
public void orderOfSubGraphsShouldNotMatter() {
|
||||
|
||||
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
|
||||
EntityGraph<User> graph = em.createEntityGraph(User.class);
|
||||
Jpa21Utils.configureFetchGraphFrom(new JpaEntityGraph("name", EntityGraphType.FETCH, new String[] {
|
||||
"colleagues.colleagues.roles", "roles", "colleagues.colleagues", "colleagues", "colleagues.roles" }), graph);
|
||||
|
||||
AttributeNode<?> roles = findNode("roles", graph);
|
||||
assertThat(roles, terminatesGraph());
|
||||
|
||||
AttributeNode<?> colleagues = findNode("colleagues", graph);
|
||||
assertThat(colleagues, terminatesGraphWith("roles"));
|
||||
assertThat(colleagues, hasSubgraphs("colleagues"));
|
||||
|
||||
AttributeNode colleaguesOfColleagues = findNode("colleagues", colleagues);
|
||||
assertThat(colleaguesOfColleagues, terminatesGraphWith("roles"));
|
||||
}
|
||||
|
||||
@Test(expected = Exception.class) // DATAJPA-1041, DATAJPA-1075
|
||||
public void errorsOnUnknownProperties() {
|
||||
|
||||
assumeTrue(currentEntityManagerIsAJpa21EntityManager(em));
|
||||
|
||||
Jpa21Utils.configureFetchGraphFrom(new JpaEntityGraph("name", EntityGraphType.FETCH, new String[] { "¯\\_(ツ)_/¯" }),
|
||||
em.createEntityGraph(User.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2017 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 org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@ContextConfiguration("classpath:openjpa.xml")
|
||||
public class OpenJpaJpa21UtilsTests extends Jpa21UtilsTests {
|
||||
|
||||
}
|
||||
@@ -75,4 +75,8 @@ public interface RepositoryMethodsWithEntityGraphConfigRepository
|
||||
// DATAJPA-1041
|
||||
@EntityGraph(attributePaths = { "colleagues", "colleagues.roles", "colleagues.colleagues" })
|
||||
User findOneWithMultipleSubGraphsById(Integer id);
|
||||
|
||||
// DATAJPA-1041, DATAJPA-1075
|
||||
@EntityGraph(attributePaths = { "colleagues", "colleagues.roles", "colleagues.colleagues.roles" })
|
||||
User findOneWithDeepGraphById(Integer id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright 2017 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.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.AttributeNode;
|
||||
import javax.persistence.EntityGraph;
|
||||
import javax.persistence.Subgraph;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class IsAttributeNode<T> extends TypeSafeMatcher<AttributeNode<T>> {
|
||||
|
||||
private boolean terminatingNodeCheck = false;
|
||||
private List<String> nodes;
|
||||
private List<String> subgraphs;
|
||||
private List<String> errors = new ArrayList<String>();
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(AttributeNode<T> item) {
|
||||
|
||||
if (item == null) {
|
||||
|
||||
errors.add("AttributeNode was null!");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (terminatingNodeCheck) {
|
||||
|
||||
if (!CollectionUtils.isEmpty(item.getSubgraphs())) {
|
||||
|
||||
errors.add(String.format("'%s' was expected to be a terminating node but has subgraphs %s.",
|
||||
item.getAttributeName(), extractExistingAttributeNames(item.getSubgraphs().values().iterator().next())));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CollectionUtils.isEmpty(item.getSubgraphs())) {
|
||||
|
||||
if (!CollectionUtils.isEmpty(nodes)) {
|
||||
|
||||
errors
|
||||
.add(String.format("Leaf properties %s could not be found. The node does not have any subgraphs.", nodes));
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(subgraphs)) {
|
||||
errors.add(String.format("Subgraphs %s could not be found. The node does not have any subgraphs.", subgraphs));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Subgraph<?> graph = item.getSubgraphs().values().iterator().next();
|
||||
|
||||
if (!CollectionUtils.isEmpty(nodes)) {
|
||||
for (String nodeName : nodes) {
|
||||
|
||||
AttributeNode<?> node = findNode(nodeName, graph.getAttributeNodes());
|
||||
if (node == null) {
|
||||
|
||||
errors.add(String.format("AttributeNode '%s' could not be found in subgraph for '%s'. Know nodes are: %s.",
|
||||
nodeName, item.getAttributeName(), extractExistingAttributeNames(graph)));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(node.getSubgraphs())) {
|
||||
|
||||
errors.add(String.format("AttributeNode %s of subgraph %s is not a leaf property but has % SubGraph(s).",
|
||||
nodeName, item.getAttributeName(), node.getSubgraphs().size()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(subgraphs)) {
|
||||
for (String subgraphName : subgraphs) {
|
||||
|
||||
AttributeNode<?> node = findNode(subgraphName, graph.getAttributeNodes());
|
||||
if (node == null) {
|
||||
|
||||
errors.add(String.format("Subgraph '%s' could not be found in SubGraph for '%s'. Know nodes are: %s.",
|
||||
subgraphName, item.getAttributeName(), extractExistingAttributeNames(graph)));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CollectionUtils.isEmpty(node.getSubgraphs())) {
|
||||
|
||||
errors.add(String.format("'%s' of SubGraph '%s' is not a SubGraph.", subgraphName, item.getAttributeName()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
|
||||
for (String error : errors) {
|
||||
description.appendText(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the {@link AttributeNode} with given {@literal nodeName} in the root of the given {@literal graph}.
|
||||
*
|
||||
* @param nodeName
|
||||
* @param graph
|
||||
* @return
|
||||
*/
|
||||
public static AttributeNode<?> findNode(String nodeName, EntityGraph<?> graph) {
|
||||
|
||||
if (graph == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return findNode(nodeName, graph.getAttributeNodes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the {@link AttributeNode} with given {@literal nodeName} in the {@link List} of given {@literal nodes}.
|
||||
*
|
||||
* @param nodeName
|
||||
* @param nodes
|
||||
* @return
|
||||
*/
|
||||
public static AttributeNode<?> findNode(String nodeName, List<AttributeNode<?>> nodes) {
|
||||
|
||||
if (CollectionUtils.isEmpty(nodes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (AttributeNode<?> node : nodes) {
|
||||
if (ObjectUtils.nullSafeEquals(node.getAttributeName(), nodeName)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the {@link AttributeNode} with given {@literal nodeName} in the first {@link Subgraph} of the given
|
||||
* {@literal node}.
|
||||
*
|
||||
* @param attributeName
|
||||
* @param node
|
||||
* @return
|
||||
*/
|
||||
public static AttributeNode<?> findNode(String attributeName, AttributeNode<?> node) {
|
||||
|
||||
if (CollectionUtils.isEmpty(node.getSubgraphs())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Subgraph<?> subgraph = node.getSubgraphs().values().iterator().next();
|
||||
return findNode(attributeName, subgraph.getAttributeNodes());
|
||||
}
|
||||
|
||||
private List<String> extractExistingAttributeNames(Subgraph<?> graph) {
|
||||
|
||||
List<String> result = new ArrayList<String>(graph.getAttributeNodes().size());
|
||||
for (AttributeNode<?> node : graph.getAttributeNodes()) {
|
||||
result.add(node.getAttributeName());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the fetch graph terminates with {@link AttributeNode}s having the given {@literal nodeNames}.
|
||||
*
|
||||
* @param nodeNames
|
||||
* @return
|
||||
*/
|
||||
public static IsAttributeNode terminatesGraphWith(String... nodeNames) {
|
||||
|
||||
IsAttributeNode matcher = new IsAttributeNode();
|
||||
matcher.nodes = Arrays.asList(nodeNames);
|
||||
return matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the fetch graph continues with {@link AttributeNode}s having {@link AttributeNode#getSubgraphs()} with
|
||||
* given {@literal subgraphNames}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static IsAttributeNode hasSubgraphs(String... subgraphNames) {
|
||||
|
||||
IsAttributeNode matcher = new IsAttributeNode();
|
||||
matcher.subgraphs = Arrays.asList(subgraphNames);
|
||||
return matcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the fetch graph terminates with the given {@link AttributeNode} by checking
|
||||
* {@link AttributeNode#getSubgraphs()} is empty.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static IsAttributeNode terminatesGraph() {
|
||||
|
||||
IsAttributeNode matcher = new IsAttributeNode();
|
||||
matcher.terminatingNodeCheck = true;
|
||||
return matcher;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user