HHH-12713 - Make EntityGraph creation more convenient
This commit is contained in:
parent
2a5f270d40
commit
526ebb4e0b
|
@ -248,6 +248,65 @@ include::{extrasdir}/fetching-strategies-dynamic-fetching-entity-subgraph-exampl
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
|
[[fetching-strategies-dynamic-fetching-entity-graph-parsing]]
|
||||||
|
==== Creating and applying JPA graphs from text representations
|
||||||
|
|
||||||
|
JPA graphs can also be created by parsing their text representations. These are not a part of the official JPA standard (yet).
|
||||||
|
Details of syntax can be found in the `EntityGraphParser` class JavaDoc. The equivalent text representation of previous example is as follows:
|
||||||
|
|
||||||
|
.Parsing a simple graph
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/GraphParsingTest.java[tags=fetching-strategies-dynamic-fetching-entity-graph-parsing-example-1]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
More complex graphs can easily be created. For example, we could add in the usernames, passwords and access levels for each project employee
|
||||||
|
and also request usernames of all other employees of departments of initially found employees:
|
||||||
|
|
||||||
|
.Parsing a more complex graph
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/GraphParsingTest.java[tags=fetching-strategies-dynamic-fetching-entity-graph-parsing-example-2]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[fetching-strategies-dynamic-fetching-entity-graph-merging]]
|
||||||
|
==== Combining multiple JPA entity graphs into one
|
||||||
|
|
||||||
|
Multiple entity graphs can be combined into a single supergraph that acts as a union. Graph from the previous example can also be build by combining separate aspect graphs into one, such as:
|
||||||
|
|
||||||
|
.Combining multiple graphs into one
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/GraphParsingTest.java[tags=fetching-strategies-dynamic-fetching-entity-graph-merging-example]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
[[fetching-strategies-dynamic-fetching-entity-graph-applying]]
|
||||||
|
==== Applying entity graphs to queries
|
||||||
|
|
||||||
|
Convenience/utility methods were created to make application of entity graphs to queries easier. Following examples show
|
||||||
|
|
||||||
|
.Applying an entity graph to entityManager.find()
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/GraphParsingTest.java[tags=fetching-strategies-dynamic-fetching-entity-graph-apply-example-find]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
|
.Applying an entity graph to entityManager.createQuery()
|
||||||
|
====
|
||||||
|
[source, JAVA, indent=0]
|
||||||
|
----
|
||||||
|
include::{sourcedir}/GraphParsingTest.java[tags=fetching-strategies-dynamic-fetching-entity-graph-apply-example-query]
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
[[fetching-strategies-dynamic-fetching-profile]]
|
[[fetching-strategies-dynamic-fetching-profile]]
|
||||||
=== Dynamic fetching via Hibernate profiles
|
=== Dynamic fetching via Hibernate profiles
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
package org.hibernate.userguide.fetching;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import javax.persistence.EntityGraph;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
|
|
||||||
|
import org.hibernate.graph.AbstractEntityGraphTest;
|
||||||
|
import org.hibernate.graph.EntityGraphParser;
|
||||||
|
import org.hibernate.graph.EntityGraphs;
|
||||||
|
import org.hibernate.userguide.fetching.GraphFetchingTest.Employee;
|
||||||
|
import org.hibernate.userguide.fetching.GraphFetchingTest.Department;
|
||||||
|
import org.hibernate.userguide.fetching.GraphFetchingTest.Project;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class GraphParsingTest extends AbstractEntityGraphTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class[]{ Project.class, Employee.class, Department.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParsingExample1() {
|
||||||
|
EntityManager entityManager = getOrCreateEntityManager();
|
||||||
|
//tag::fetching-strategies-dynamic-fetching-entity-graph-parsing-example-1[]
|
||||||
|
final EntityGraph<Project> graph = EntityGraphParser.parse(
|
||||||
|
entityManager,
|
||||||
|
Project.class,
|
||||||
|
"employees( department )"
|
||||||
|
);
|
||||||
|
//end::fetching-strategies-dynamic-fetching-entity-graph-parsing-example-1[]
|
||||||
|
|
||||||
|
Assert.assertNotNull( graph );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParsingExample2() {
|
||||||
|
EntityManager entityManager = getOrCreateEntityManager();
|
||||||
|
//tag::fetching-strategies-dynamic-fetching-entity-graph-parsing-example-2[]
|
||||||
|
final EntityGraph<Project> graph = EntityGraphParser.parse(
|
||||||
|
entityManager,
|
||||||
|
Project.class,
|
||||||
|
"employees( username, password, accessLevel, department( employees( username ) ) )"
|
||||||
|
);
|
||||||
|
//end::fetching-strategies-dynamic-fetching-entity-graph-parsing-example-2[]
|
||||||
|
|
||||||
|
Assert.assertNotNull( graph );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMergingExample() {
|
||||||
|
EntityManager entityManager = getOrCreateEntityManager();
|
||||||
|
//tag::fetching-strategies-dynamic-fetching-entity-graph-merging-example[]
|
||||||
|
final EntityGraph<Project> a = EntityGraphParser.parse(
|
||||||
|
entityManager,
|
||||||
|
Project.class,
|
||||||
|
"employees( username )"
|
||||||
|
);
|
||||||
|
|
||||||
|
final EntityGraph<Project> b = EntityGraphParser.parse(
|
||||||
|
entityManager,
|
||||||
|
Project.class,
|
||||||
|
"employees( password, accessLevel )"
|
||||||
|
);
|
||||||
|
|
||||||
|
final EntityGraph<Project> c = EntityGraphParser.parse(
|
||||||
|
entityManager,
|
||||||
|
Project.class,
|
||||||
|
"employees( department( employees( username ) ) )"
|
||||||
|
);
|
||||||
|
|
||||||
|
final EntityGraph<Project> all = EntityGraphs.merge( entityManager, Project.class, a, b, c );
|
||||||
|
//end::fetching-strategies-dynamic-fetching-entity-graph-merging-example[]
|
||||||
|
|
||||||
|
final EntityGraph<Project> expected = EntityGraphParser.parse(
|
||||||
|
entityManager,
|
||||||
|
Project.class,
|
||||||
|
"employees( username, password, accessLevel, department( employees( username ) ) )"
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertTrue( EntityGraphs.equal( expected, all ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFindExample() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
Long userId = 1L;
|
||||||
|
|
||||||
|
//tag::fetching-strategies-dynamic-fetching-entity-graph-apply-example-find[]
|
||||||
|
Employee employee = EntityGraphs.find(
|
||||||
|
entityManager,
|
||||||
|
Employee.class,
|
||||||
|
userId,
|
||||||
|
"username, accessLevel, department"
|
||||||
|
);
|
||||||
|
//end::fetching-strategies-dynamic-fetching-entity-graph-apply-example-find[]
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQueryExample() {
|
||||||
|
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
//tag::fetching-strategies-dynamic-fetching-entity-graph-apply-example-query[]
|
||||||
|
final String graphString = "username, accessLevel";
|
||||||
|
final String queryString = "select e from Employee e where e.id = 1";
|
||||||
|
|
||||||
|
final EntityGraph<Employee> graph = EntityGraphParser.parse(
|
||||||
|
entityManager,
|
||||||
|
Employee.class,
|
||||||
|
graphString // == "username, accessLevel"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Given above, the following query1:
|
||||||
|
|
||||||
|
TypedQuery<Employee> query1 = entityManager.createQuery( queryString, Employee.class );
|
||||||
|
EntityGraphs.setFetchGraph( query1, graph );
|
||||||
|
|
||||||
|
// is equal to the following query2:
|
||||||
|
|
||||||
|
TypedQuery<Employee> query2 = EntityGraphs.createQuery(
|
||||||
|
entityManager,
|
||||||
|
Employee.class,
|
||||||
|
"fetch " + graphString + " " + queryString
|
||||||
|
// == "fetch username, accessLevel select e from Employee e where e.id = 1"
|
||||||
|
);
|
||||||
|
//end::fetching-strategies-dynamic-fetching-entity-graph-apply-example-query[]
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,228 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.persistence.AttributeNode;
|
||||||
|
import javax.persistence.EntityGraph;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.Subgraph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A temporary internal representation of an entity graph attribute used in the process of
|
||||||
|
* {@linkplain EntityGraphs#merge(EntityManager, Class, EntityGraph...) merging multiple entity graphs}
|
||||||
|
* together.
|
||||||
|
*
|
||||||
|
* @author asusnjar
|
||||||
|
*/
|
||||||
|
class EntityGraphAttribute {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the attribute.
|
||||||
|
*/
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attributeSubclass->attributeName->{@code EntityGraphAttribute} map
|
||||||
|
* for map attribute keys (only applies to map attributes).
|
||||||
|
*/
|
||||||
|
Map<Class<?>, Map<String, EntityGraphAttribute>> keySubgraphs = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An attributeSubclass->attributeName->{@code EntityGraphAttribute} map
|
||||||
|
* for collection and/or map attribute values.
|
||||||
|
*/
|
||||||
|
Map<Class<?>, Map<String, EntityGraphAttribute>> valueSubgraphs = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic attribute constructor.
|
||||||
|
*
|
||||||
|
* @param name Name of the attribute.
|
||||||
|
*/
|
||||||
|
EntityGraphAttribute(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor from a JPA {@link AttributeNode}.
|
||||||
|
*
|
||||||
|
* @param attrNode JPA attribute node to replicate.
|
||||||
|
*/
|
||||||
|
EntityGraphAttribute(AttributeNode<?> attrNode) {
|
||||||
|
this( attrNode.getAttributeName() );
|
||||||
|
|
||||||
|
copySubgraphs( keySubgraphs, attrNode.getKeySubgraphs() );
|
||||||
|
copySubgraphs( valueSubgraphs, attrNode.getSubgraphs() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies a JPA class->subgraph map to an internal
|
||||||
|
* class->attributeName->{@code EntityGraphAttribute} map structure.
|
||||||
|
*
|
||||||
|
* @param dest Internal map to replicate the source subgraphs into.
|
||||||
|
* @param source JPA class-subgraph map to copy subgraphs from.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
private static void copySubgraphs(Map<Class<?>, Map<String, EntityGraphAttribute>> dest, Map<Class, Subgraph> source) {
|
||||||
|
for ( Map.Entry<Class, Subgraph> entry : source.entrySet() ) {
|
||||||
|
dest.put( entry.getKey(), mapOf( entry.getValue() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a JPA Subgraph to an internally used attributeName->{@code EntityGraphAttribute} map.
|
||||||
|
*
|
||||||
|
* @param jpaSubgraph JPA subgraph to convert.
|
||||||
|
*/
|
||||||
|
static Map<String, EntityGraphAttribute> mapOf(Subgraph<?> jpaSubgraph) {
|
||||||
|
Map<String, EntityGraphAttribute> result = null;
|
||||||
|
if ( jpaSubgraph != null ) {
|
||||||
|
result = new HashMap<>();
|
||||||
|
|
||||||
|
for ( AttributeNode<?> node : jpaSubgraph.getAttributeNodes() ) {
|
||||||
|
result.put( node.getAttributeName(), new EntityGraphAttribute( node ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges the {@code source} attributeName->{@link EntityGraphAttribute} into {@code dest}.
|
||||||
|
*
|
||||||
|
* @param dest Map of attributes to merge {@code source} into.
|
||||||
|
*
|
||||||
|
* @param source Map of attributes to merge into {@code dest}.
|
||||||
|
*
|
||||||
|
* @see #merge(Map, Subgraph)
|
||||||
|
*/
|
||||||
|
static void merge(Map<String, EntityGraphAttribute> dest, Map<String, EntityGraphAttribute> source) {
|
||||||
|
for ( Map.Entry<String, EntityGraphAttribute> entry : source.entrySet() ) {
|
||||||
|
String name = entry.getKey();
|
||||||
|
EntityGraphAttribute sourceAttr = entry.getValue();
|
||||||
|
EntityGraphAttribute destAttr = dest.get( name );
|
||||||
|
|
||||||
|
if ( destAttr == null ) {
|
||||||
|
dest.put( name, sourceAttr );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mergeSubgraphMaps( destAttr.keySubgraphs, sourceAttr.keySubgraphs );
|
||||||
|
mergeSubgraphMaps( destAttr.valueSubgraphs, sourceAttr.valueSubgraphs );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges the {@code source} subgraph attributes into the {@code dest} attributeName->{@link EntityGraphAttribute} map.
|
||||||
|
*
|
||||||
|
* @param dest Map of attributes to merge {@code source} into.
|
||||||
|
*
|
||||||
|
* @param source Subgraph to merge into {@code dest}.
|
||||||
|
*
|
||||||
|
* @see #merge(Map, Map)
|
||||||
|
*/
|
||||||
|
static void merge(Map<String, EntityGraphAttribute> dest, Subgraph<?> source) {
|
||||||
|
merge( dest, mapOf( source ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges the {@code source} class->name->{@link EntityGraphAttribute} map into {@code dest}.
|
||||||
|
*
|
||||||
|
* @param dest Map of attributes to merge {@code source} into.
|
||||||
|
*
|
||||||
|
* @param source Map of attributes to merge into {@code dest}.
|
||||||
|
*/
|
||||||
|
private static void mergeSubgraphMaps(Map<Class<?>, Map<String, EntityGraphAttribute>> dest, Map<Class<?>, Map<String, EntityGraphAttribute>> source) {
|
||||||
|
for ( Map.Entry<Class<?>, Map<String, EntityGraphAttribute>> entry : source.entrySet() ) {
|
||||||
|
Class<?> subclass = entry.getKey();
|
||||||
|
Map<String, EntityGraphAttribute> sourceMap = entry.getValue();
|
||||||
|
Map<String, EntityGraphAttribute> destMap = dest.get( subclass );
|
||||||
|
|
||||||
|
if ( destMap == null ) {
|
||||||
|
dest.put( subclass, sourceMap );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
merge( destMap, sourceMap );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the specified entity graph given a root type and attributeName->{@code EntityGraphAttribute} map.
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type of the graph.
|
||||||
|
* @param graph Graph to configure.
|
||||||
|
* @param rootType Root entity type of the graph.
|
||||||
|
* @param map An attributeName->{@code EntityGraphAttribute} map to apply to the graph.
|
||||||
|
*
|
||||||
|
* @see #configure(Subgraph, Map)
|
||||||
|
*/
|
||||||
|
static <T> void configure(EntityGraph<T> graph, Class<T> rootType, Map<String, EntityGraphAttribute> map) {
|
||||||
|
configure( new GraphAsSubgraph<T>( graph, rootType ), map );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the specified entity subgraph given an attributeName->{@code EntityGraphAttribute} map.
|
||||||
|
*
|
||||||
|
* @param subgraph Subraph to configure.
|
||||||
|
* @param map An attributeName->{@code EntityGraphAttribute} map to apply to the subgraph.
|
||||||
|
*
|
||||||
|
* @see #configure(EntityGraph, Class, Map)
|
||||||
|
*/
|
||||||
|
static void configure(Subgraph<?> subgraph, Map<String, EntityGraphAttribute> map) {
|
||||||
|
for ( EntityGraphAttribute attr : map.values() ) {
|
||||||
|
attr.configure( subgraph );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the information in 'this' {@code EntityGraphAttribute} to configure the specified subgraph.
|
||||||
|
*
|
||||||
|
* @param subgraph JPA subgraph to configure.
|
||||||
|
*/
|
||||||
|
private void configure(Subgraph<?> subgraph) {
|
||||||
|
if ( !keySubgraphs.isEmpty() ) {
|
||||||
|
for ( Map.Entry<Class<?>, Map<String, EntityGraphAttribute>> entry : keySubgraphs.entrySet() ) {
|
||||||
|
Class<?> subclass = entry.getKey();
|
||||||
|
|
||||||
|
Subgraph<?> innerSubgraph;
|
||||||
|
|
||||||
|
if ( subclass == null ) {
|
||||||
|
innerSubgraph = subgraph.addKeySubgraph( name );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
innerSubgraph = subgraph.addKeySubgraph( name, subclass );
|
||||||
|
}
|
||||||
|
|
||||||
|
configure( innerSubgraph, entry.getValue() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !valueSubgraphs.isEmpty() ) {
|
||||||
|
for ( Map.Entry<Class<?>, Map<String, EntityGraphAttribute>> entry : valueSubgraphs.entrySet() ) {
|
||||||
|
Class<?> subclass = entry.getKey();
|
||||||
|
|
||||||
|
Subgraph<?> innerSubgraph;
|
||||||
|
|
||||||
|
if ( subclass == null ) {
|
||||||
|
innerSubgraph = subgraph.addSubgraph( name );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
innerSubgraph = subgraph.addSubgraph( name, subclass );
|
||||||
|
}
|
||||||
|
|
||||||
|
configure( innerSubgraph, entry.getValue() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( keySubgraphs.isEmpty() && valueSubgraphs.isEmpty() ) {
|
||||||
|
subgraph.addAttributeNodes( name );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,396 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
import javax.persistence.EntityGraph;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.Subgraph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parser of {@link EntityGraph} string representations using a simple syntax.
|
||||||
|
* <p>
|
||||||
|
* A single graph is represented as a comma-separated list of attribute specifications, such as:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* firstName, lastName, birthDate
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Simple attributes that are to be fetched or loaded eagerly simply need their name listed. Relational/link attributes,
|
||||||
|
* such as *ToOne, collections and maps have an additional syntax to allow their attributes to be fetched - these are
|
||||||
|
* specified in parentheses following the attribute name, such as:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* firstName, lastName, parents(firstName, lastName, children(firstName, lastName))
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Maps require separate specification for their keys and values, as follows:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* mapAttributeName.key(keyAttr1, keyAttr2, ....), mapAttributeName.value(valueAttr1, valueAttr2, ...)
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* To specify additional attributes to be fetched/loaded for subclasses add the class name after the attribute name as
|
||||||
|
* follows:
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* vehicles(brand, model), vehicles:Car(topSpeed), vehicles:Truck(maxCargoWeight),
|
||||||
|
* productOwners.key(name), productOwners.key:Swag(year),
|
||||||
|
* productOwners.value(firstName, lastName), productOwners.value:HistoricalUser(employmentEndDate)
|
||||||
|
* </pre>
|
||||||
|
* <p>
|
||||||
|
* Above, the "Car", "Truck", "Swag" and "HistoricalUser" are class names and indicate for which subclass of the (root)
|
||||||
|
* property/attribute class the subgraph inside the following parentheses apply to. The class names can take three
|
||||||
|
* different forms:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>e.g. {@code Truck} - a simple name of the class inside the same package as the property class in which scope this
|
||||||
|
* declaration is made (the superclass). For example, if the superclass is
|
||||||
|
* {@code com.bluecatnetworks.proteus.data.Vehicle}, then the resulting fully qualified name is
|
||||||
|
* {@code com.bluecatnetworks.proteus.data.Truck}.</li>
|
||||||
|
* <li>e.g. {@code .cargo.Truck} - (starting with a dot) a relative name to the package of the property class in which
|
||||||
|
* scope this declaration is made. For example, if the superclass is {@code com.bluecatnetworks.proteus.data.Vehicle},
|
||||||
|
* then the resulting fully qualified name is {@code com.bluecatnetworks.proteus.data.cargo.Truck}.</li>
|
||||||
|
* <li>e.g. {@code org.acme.Truck} - (containing dots but not starting with a dot) a fully qualified class name.</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* To get an EntityGraph from a text form as per above, please use the
|
||||||
|
* {@link #parse(EntityManager, Class, CharSequence)} method. For example:
|
||||||
|
* </p>
|
||||||
|
* <code>
|
||||||
|
* {@link EntityGraph EntityGraph}<Person> personGraph = EntityGraphParser.{@link #parse(EntityManager, Class, CharSequence) parse}(entityManager, Person.class, "firstName, lastName");
|
||||||
|
* </code>
|
||||||
|
* <p>
|
||||||
|
* Multiple graphs made for the same entity type can be merged if it is necessary to satisfy multiple clients with the
|
||||||
|
* same query. Gather all the individual entity graphs, then merge them using
|
||||||
|
* {@link EntityGraphs#merge(EntityManager, Class, EntityGraph...)}.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* For additional convenience please use methods of {@link EntityGraphs}. They allow easier configuration of
|
||||||
|
* JPA/Hibernate queries and even embedding the above fetch/load graph text representation inside JPQL/HQL queries. See:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link EntityGraphs#createQuery(EntityManager, Class, CharSequence)}</li>
|
||||||
|
* <li>{@link EntityGraphs#createNamedQuery(EntityManager, Class, String, CharSequence)}</li>
|
||||||
|
* <li>{@link EntityGraphs#find(EntityManager, Class, Object, CharSequence)}</li>
|
||||||
|
* <li>{@link FetchAndLoadEntityGraphs#applyTo(javax.persistence.Query)}</li>
|
||||||
|
* <li>{@link FetchAndLoadEntityGraphs#list(org.hibernate.Session, org.hibernate.Query)}</li>
|
||||||
|
* <li>{@link FetchAndLoadEntityGraphs#scroll(org.hibernate.Session, org.hibernate.Query)}</li>
|
||||||
|
* <li>{@link FetchAndLoadEntityGraphs#iterate(org.hibernate.Session, org.hibernate.Query)}</li>
|
||||||
|
* <li>{@link FetchAndLoadEntityGraphs#run(org.hibernate.Session, java.util.function.Supplier)}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author asusnjar
|
||||||
|
*/
|
||||||
|
public final class EntityGraphParser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and returns the textual graph representation as {@linkplain EntityGraphParser described above}.
|
||||||
|
*
|
||||||
|
* @param <T> The root entity type of the graph.
|
||||||
|
*
|
||||||
|
* @param em JPA EntityManager to use to create the graph.
|
||||||
|
* @param rootType Root entity type of the graph.
|
||||||
|
* @param text Textual representation to parse.
|
||||||
|
*
|
||||||
|
* @return A parsed EntityGraph.
|
||||||
|
*
|
||||||
|
* @throw InvalidGraphException if the textual representation is invalid.
|
||||||
|
*/
|
||||||
|
public static <T> EntityGraph<T> parse(final EntityManager em, final Class<T> rootType, final CharSequence text) {
|
||||||
|
EntityGraph<T> graph = null;
|
||||||
|
if ( text != null ) {
|
||||||
|
graph = em.createEntityGraph( rootType );
|
||||||
|
parseInto( graph, rootType, text );
|
||||||
|
}
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and returns the textual graph representation as {@linkplain EntityGraphParser described above}.
|
||||||
|
*
|
||||||
|
* @param <T> The root entity type of the graph.
|
||||||
|
*
|
||||||
|
* @param em JPA EntityManager to use to create the graph.
|
||||||
|
* @param rootType Root entity type of the graph.
|
||||||
|
* @param text Textual representation to parse.
|
||||||
|
*
|
||||||
|
* @return A parsed EntityGraph.
|
||||||
|
*
|
||||||
|
* @throw InvalidGraphException if the textual representation is invalid.
|
||||||
|
*/
|
||||||
|
private static <T> EntityGraph<T> parse(final EntityManager em, final Class<T> rootType, final ParseBuffer buffer) {
|
||||||
|
EntityGraph<T> graph = null;
|
||||||
|
if ( buffer != null ) {
|
||||||
|
graph = em.createEntityGraph( rootType );
|
||||||
|
parseInto( buffer, subgraphFromGraph( rootType, graph ) );
|
||||||
|
}
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the textual graph representation as {@linkplain EntityGraphParser described above}
|
||||||
|
* into the specified graph.
|
||||||
|
*
|
||||||
|
* @param <T> The root entity type of the graph.
|
||||||
|
*
|
||||||
|
* @param graph JPA EntityGraph to parse into.
|
||||||
|
* @param rootType Root entity type of the graph.
|
||||||
|
* @param text Textual representation to parse.
|
||||||
|
*
|
||||||
|
* @throw InvalidGraphException if the textual representation is invalid.
|
||||||
|
*/
|
||||||
|
public static <T> void parseInto(EntityGraph<T> graph, final Class<T> rootType, final CharSequence text) {
|
||||||
|
if ( text != null ) {
|
||||||
|
parseInto( subgraphFromGraph( rootType, graph ), text );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the textual graph representation as {@linkplain EntityGraphParser described above}
|
||||||
|
* into the specified graph.
|
||||||
|
*
|
||||||
|
* @param <T> The root entity type of the subgraph.
|
||||||
|
*
|
||||||
|
* @param graph JPA EntityGraph to parse into.
|
||||||
|
* @param rootType Root entity type of the graph.
|
||||||
|
* @param text Textual representation to parse.
|
||||||
|
*
|
||||||
|
* @throw InvalidGraphException if the textual representation is invalid.
|
||||||
|
*/
|
||||||
|
public static <T> void parseInto(Subgraph<T> subgraph, final CharSequence text) {
|
||||||
|
if ( text != null ) {
|
||||||
|
parseInto( new ParseBuffer( text ), subgraph );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the parse buffer into the specified subgraph.
|
||||||
|
*
|
||||||
|
* @param <T> The root entity type of the subgraph.
|
||||||
|
*
|
||||||
|
* @param buffer Buffer to parse.
|
||||||
|
* @param targetSubgraph Subgraph to parse into.
|
||||||
|
*
|
||||||
|
* @throw InvalidGraphException if the textual representation is invalid.
|
||||||
|
*/
|
||||||
|
private static <T> void parseInto(ParseBuffer buffer, Subgraph<T> targetSubgraph) {
|
||||||
|
boolean expectSeparator = false;
|
||||||
|
while ( !buffer.isAtEnd() ) {
|
||||||
|
if ( expectSeparator ) {
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
if ( !buffer.match( ',' ) ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
expectSeparator = true;
|
||||||
|
}
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
final String propertyIdentifier = buffer.consumeIdentifier( false );
|
||||||
|
if ( propertyIdentifier == null ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
|
||||||
|
boolean mapKey, mapValue;
|
||||||
|
|
||||||
|
if ( buffer.match( "." ) ) {
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
mapKey = buffer.match( "keys" ) || buffer.match( "key" );
|
||||||
|
mapValue = mapKey ? false : ( buffer.match( "values" ) || buffer.match( "value" ) );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mapKey = mapValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there is a subgraph for this
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
Class<? extends T> subclass = null;
|
||||||
|
if ( buffer.match( ':' ) ) {
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
String subclassName = buffer.consumeIdentifier( true );
|
||||||
|
int firstDotPosition = subclassName.indexOf( '.' );
|
||||||
|
if ( firstDotPosition < 0 ) {
|
||||||
|
// No package specified at all, assume the same as of the owner entity
|
||||||
|
subclassName = targetSubgraph.getClassType().getPackage().getName() + "." + subclassName;
|
||||||
|
}
|
||||||
|
else if ( firstDotPosition == 0 ) {
|
||||||
|
// A subpackage of the owner entity's package specified
|
||||||
|
subclassName = targetSubgraph.getClassType().getPackage().getName() + subclassName;
|
||||||
|
}
|
||||||
|
subclass = getSubclassByName( targetSubgraph.getClassType(), subclassName );
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( buffer.match( '(' ) ) {
|
||||||
|
// Yes, a collection subgraph
|
||||||
|
final Subgraph<? extends T> subgraph;
|
||||||
|
if ( mapKey ) {
|
||||||
|
if ( subclass == null ) {
|
||||||
|
subgraph = targetSubgraph.addKeySubgraph( propertyIdentifier );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subgraph = targetSubgraph.addKeySubgraph( propertyIdentifier, subclass );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ( subclass == null ) {
|
||||||
|
subgraph = targetSubgraph.addSubgraph( propertyIdentifier );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subgraph = targetSubgraph.addSubgraph( propertyIdentifier, subclass );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parseInto( buffer, subgraph );
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
if ( !buffer.match( ')' ) ) {
|
||||||
|
throw new InvalidGraphException( "Unclosed subgraph found near character " + buffer.getPosition() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// A simple attribute?
|
||||||
|
if ( subclass != null ) {
|
||||||
|
throw new InvalidGraphException( "Subclass specified for an attribute without a subgraph near character " + buffer.getPosition() );
|
||||||
|
}
|
||||||
|
if ( mapKey ) {
|
||||||
|
throw new InvalidGraphException( "A map key without a subgraph near character " + buffer.getPosition() );
|
||||||
|
}
|
||||||
|
if ( mapValue ) {
|
||||||
|
throw new InvalidGraphException( "A map value without a subgraph near character " + buffer.getPosition() );
|
||||||
|
}
|
||||||
|
targetSubgraph.addAttributeNodes( propertyIdentifier );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the named subclass of the specified superclass if it is available and
|
||||||
|
* is indeed a subclass, throws an {@link InvalidGraphException} otherwise.
|
||||||
|
*
|
||||||
|
* @param <T> The superclass.
|
||||||
|
*
|
||||||
|
* @param superclass (Super)class that the named subclass must be a subclass of.
|
||||||
|
* @param subclassName Name of the subclass to find.
|
||||||
|
*
|
||||||
|
* @return A valid subclass of {@code superclass} with the specified name.
|
||||||
|
*
|
||||||
|
* @throws InvalidGraphException if the class is not found or is not a subclass of
|
||||||
|
* {@code superclass}.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> Class<T> getSubclassByName(Class<T> superclass, String subclassName) {
|
||||||
|
Class<T> subclass = null;
|
||||||
|
try {
|
||||||
|
subclass = (Class<T>) Class.forName( subclassName );
|
||||||
|
}
|
||||||
|
catch (ClassNotFoundException e) {
|
||||||
|
throw new InvalidGraphException( "Unavailable subclass specified: " + subclassName, e );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !superclass.isAssignableFrom( subclass ) ) {
|
||||||
|
throw new InvalidGraphException( "Specified class (" + subclassName + ") must be but is not a subclass of " + superclass.getName() );
|
||||||
|
}
|
||||||
|
return subclass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link Subgraph}-like proxy of the specified {@link EntityGraph}.
|
||||||
|
* This helps reuse the code as {@link Subgraph} and {@link EntityGraph}, unfortunately,
|
||||||
|
* do not share declarations yet are almost identical.
|
||||||
|
*
|
||||||
|
* @param <T> The root entity type.
|
||||||
|
*
|
||||||
|
* @param rootType Root entity type of the graph.
|
||||||
|
* @param graph Graph to wrap into the new proxy.
|
||||||
|
*
|
||||||
|
* @return A {@link Subgraph}-like proxy of the specified {@link EntityGraph}.
|
||||||
|
*/
|
||||||
|
private static <T> Subgraph<T> subgraphFromGraph(final Class<T> rootType, final EntityGraph<T> graph) {
|
||||||
|
return new GraphAsSubgraph<T>( graph, rootType );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the fetch and load textual graphs representations that are at the beginning of the
|
||||||
|
* JPQL or HQL query and returns the pair.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The textual representations of the graphs are the same as {@linkplain EntityGraphParser described above}
|
||||||
|
* but add extra structure. Specifically, the graphs must be preceeded with the keyword "fetch" and/or "load".
|
||||||
|
* This allows the parser to disambiguate the graphs but also to detect cases when no graphs are specified
|
||||||
|
* at all and we have a plain JPQL/HQL query.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* For example:
|
||||||
|
* </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code fetch name, description [select ...] from ...} contains a <i>fetch</i> graph specifying
|
||||||
|
* "name" and "description" attributes.</li>
|
||||||
|
* <li>{@code load name, description [select ...] from ...} contains a <i>load</i> graph specifying
|
||||||
|
* "name" and "description" attributes.</li>
|
||||||
|
* <li>{@code fetch name load description [select ...] from ...} contains a <i>fetch</i> graph specifying
|
||||||
|
* only the "name" and a <i>load</i> graph specifying the "description" attribute.</li>
|
||||||
|
* <li>{@code load description fetch name [select ...] from ...} is equivalent to previous.</li>
|
||||||
|
* <li>{@code fetch name fetch description [select ...] from ...} is invalid because it specifies two
|
||||||
|
* fetch graphs.</li>
|
||||||
|
* <li>{@code load name load description [select ...] from ...} is invalid because it specifies two
|
||||||
|
* load graphs.</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* The parser stops short (before) the actual JPQL/HQL portion and leaves it in the parse buffer so
|
||||||
|
* that it can be passed to the corresponding query parsing/creation methods. It makes no attempt to
|
||||||
|
* understand, check or reconcile the fetch/load graph specifications with the query in part due to
|
||||||
|
* the fact that JPA standard is ambiguous as to how the FetchGraph and LoadGraph hints are applied
|
||||||
|
* to multiple items in the {@code select} clause.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param <T> The root entity type.
|
||||||
|
*
|
||||||
|
* @param em Entity manager to use to create the graphs.
|
||||||
|
* @param rootType Root entity type for the graphs.
|
||||||
|
* @param buffer Parse buffer to use parse from.
|
||||||
|
*
|
||||||
|
* @return A {@link FetchAndLoadEntityGraphs} containing a pair of parsed graphs (fetch, load).
|
||||||
|
* If any of the graphs is not declared/included it will be {@code null}.
|
||||||
|
*
|
||||||
|
* @throw InvalidGraphException if the textual representation is invalid.
|
||||||
|
*/
|
||||||
|
static <T> FetchAndLoadEntityGraphs<T> parsePreQueryGraphDescriptors(final EntityManager em, Class<T> rootType, ParseBuffer buffer) {
|
||||||
|
EntityGraph<T> fetchGraph = null;
|
||||||
|
EntityGraph<T> loadGraph = null;
|
||||||
|
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
boolean foundSomething;
|
||||||
|
do {
|
||||||
|
String word = buffer.consumeIdentifier( false );
|
||||||
|
if ( "fetch".equalsIgnoreCase( word ) ) {
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
foundSomething = true;
|
||||||
|
fetchGraph = parse( em, rootType, buffer );
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
}
|
||||||
|
else if ( "load".equalsIgnoreCase( word ) ) {
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
foundSomething = true;
|
||||||
|
loadGraph = parse( em, rootType, buffer );
|
||||||
|
buffer.skipWhitespace();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ( word != null) {
|
||||||
|
buffer.back( word );
|
||||||
|
}
|
||||||
|
foundSomething = false;
|
||||||
|
}
|
||||||
|
} while ( foundSomething && ( ( fetchGraph == null ) || ( loadGraph == null ) ) );
|
||||||
|
|
||||||
|
return new FetchAndLoadEntityGraphs<T>( fetchGraph, loadGraph );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,352 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.persistence.AttributeNode;
|
||||||
|
import javax.persistence.EntityGraph;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.Query;
|
||||||
|
import javax.persistence.Subgraph;
|
||||||
|
import javax.persistence.TypedQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of {@link EntityGraph} utilities.
|
||||||
|
* These methods really belong inside other classes that we cannot modify, hence here.
|
||||||
|
*
|
||||||
|
* @author asusnjar
|
||||||
|
*/
|
||||||
|
public final class EntityGraphs {
|
||||||
|
|
||||||
|
public static String HINT_FETCHGRAPH = "javax.persistence.fetchgraph";
|
||||||
|
public static String HINT_LOADGRAPH = "javax.persistence.loadgraph";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the query hints so that ONLY the attributes specified by the graph are eagerly fetched, regardless of
|
||||||
|
* what the mapping defaults indicate (even if they specify more properties to be loaded eagerly).
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type of the query and graph.
|
||||||
|
*
|
||||||
|
* @param query Query to configure.
|
||||||
|
* @param graph Graph of properties/attributes to eagerly load.
|
||||||
|
*
|
||||||
|
* @return The same (input) query.
|
||||||
|
*
|
||||||
|
* @see #setFetchGraph(Query, EntityManager, Class, CharSequence)
|
||||||
|
* @see #setLoadGraph(Query, EntityGraph)
|
||||||
|
*/
|
||||||
|
public static <T> Query setFetchGraph(final Query query, EntityGraph<T> graph) {
|
||||||
|
return query.setHint( HINT_FETCHGRAPH, graph );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the query hints so that ONLY the attributes specified by the graph are eagerly fetched, regardless of
|
||||||
|
* what the mapping defaults indicate (even if they specify more properties to be loaded eagerly).
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type of the query and graph.
|
||||||
|
*
|
||||||
|
* @param query Query to configure.
|
||||||
|
* @param graph Graph of properties/attributes to eagerly load.
|
||||||
|
*
|
||||||
|
* @return The same (input) query.
|
||||||
|
*
|
||||||
|
* @see #setFetchGraph(Query, EntityGraph)
|
||||||
|
* @see #setLoadGraph(Query, EntityGraph)
|
||||||
|
*/
|
||||||
|
public static <T> Query setFetchGraph(final Query query, EntityManager em, Class<T> rootType, CharSequence graph) {
|
||||||
|
return setFetchGraph( query, EntityGraphParser.parse( em, rootType, graph ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the query hints so that the attributes specified by the graph are eagerly fetched IN ADDITION to those
|
||||||
|
* specified to be loaded eagerly in the mapping.
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type of the query and graph.
|
||||||
|
*
|
||||||
|
* @param query Query to configure.
|
||||||
|
* @param graph Graph of properties/attributes to eagerly load.
|
||||||
|
*
|
||||||
|
* @return The same (input) query.
|
||||||
|
*
|
||||||
|
* @see #setFetchGraph(Query, EntityGraph)
|
||||||
|
* @see #setLoadGraph(Query, EntityManager, Class, CharSequence)
|
||||||
|
*/
|
||||||
|
public static <T> Query setLoadGraph(final Query query, EntityGraph<T> graph) {
|
||||||
|
return query.setHint( HINT_LOADGRAPH, graph );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the query hints so that the attributes specified by the graph are eagerly fetched IN ADDITION to those
|
||||||
|
* specified to be loaded eagerly in the mapping.
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type of the query and graph.
|
||||||
|
*
|
||||||
|
* @param query Query to configure.
|
||||||
|
* @param graph Graph of properties/attributes to eagerly load.
|
||||||
|
*
|
||||||
|
* @return The same (input) query.
|
||||||
|
*
|
||||||
|
* @see #setFetchGraph(Query, EntityGraph)
|
||||||
|
* @see #setLoadGraph(Query, EntityGraph)
|
||||||
|
*/
|
||||||
|
public static <T> Query setLoadGraph(final Query query, EntityManager em, Class<T> rootType, CharSequence graph) {
|
||||||
|
return setLoadGraph( query, EntityGraphParser.parse( em, rootType, graph ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a query just like {@link EntityManager#createQuery(String, Class)} does but allows the query string to
|
||||||
|
* begin with the "fetch" and/or "load" entity graph specifications (see {@link EntityGraphParser}) that will
|
||||||
|
* internally converted to correct hints (see {@link #setFetchGraph(Query, EntityManager, Class, CharSequence)} and
|
||||||
|
* {@link #setLoadGraph(Query, EntityManager, Class, CharSequence)}.
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type of the query and graph.
|
||||||
|
*
|
||||||
|
* @param em EntityManager to use to create the query and entity graphs (if found).
|
||||||
|
* @param rootType The root type of results.
|
||||||
|
* @param qlString The query language string that can begin with fetch/load specification (see
|
||||||
|
* {@link EntityGraphParser}).
|
||||||
|
*
|
||||||
|
* @return The typed query preconfigured as per any fetch/load specifications in the {@code qlString}.
|
||||||
|
*
|
||||||
|
* @throws InvalidGraphException if a specified fetch or load graph is invalid.
|
||||||
|
*/
|
||||||
|
public static <T> TypedQuery<T> createQuery(final EntityManager em, Class<T> rootType, CharSequence qlString) {
|
||||||
|
ParseBuffer buffer = new ParseBuffer( qlString );
|
||||||
|
FetchAndLoadEntityGraphs<T> graphs = EntityGraphParser.parsePreQueryGraphDescriptors( em, rootType, buffer );
|
||||||
|
|
||||||
|
TypedQuery<T> query = em.createQuery( buffer.toString(), rootType );
|
||||||
|
if ( graphs.fetchGraph != null ) {
|
||||||
|
setFetchGraph( query, graphs.fetchGraph );
|
||||||
|
}
|
||||||
|
if ( graphs.loadGraph != null ) {
|
||||||
|
setLoadGraph( query, graphs.loadGraph );
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a query just like {@link EntityManager#createNamedQuery(String, Class)} does but allows the query string
|
||||||
|
* to begin with the "fetch" and/or "load" entity graph specifications (see {@link EntityGraphParser}) that will
|
||||||
|
* internally converted to correct hints (see {@link #setFetchGraph(Query, EntityManager, Class, CharSequence)} and
|
||||||
|
* {@link #setLoadGraph(Query, EntityManager, Class, CharSequence)}.
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type of the query and graph.
|
||||||
|
*
|
||||||
|
* @param em EntityManager to use to create the query and entity graphs (if found).
|
||||||
|
* @param rootType The root type of results.
|
||||||
|
* @param qlString The query language string that can begin with fetch/load specification (see
|
||||||
|
* {@link EntityGraphParser}).
|
||||||
|
*
|
||||||
|
* @return The typed query preconfigured as per any fetch/load specifications in the {@code qlString}.
|
||||||
|
*
|
||||||
|
* @throws InvalidGraphException if a specified fetch or load graph is invalid.
|
||||||
|
*/
|
||||||
|
public static <T> TypedQuery<T> createNamedQuery(final EntityManager em, Class<T> rootType, String queryName, CharSequence graphString) {
|
||||||
|
final EntityGraph<T> graph = EntityGraphParser.parse( em, rootType, graphString );
|
||||||
|
|
||||||
|
final TypedQuery<T> query = em.createNamedQuery( queryName, rootType );
|
||||||
|
setFetchGraph( query, graph );
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds an entity by its class and primary key just like
|
||||||
|
* {@link EntityManager#find(Class, Object, javax.persistence.LockModeType, Map)} does but includes the fetch entity
|
||||||
|
* graph hint as specified in the {@code graphString}.
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type of the query and graph.
|
||||||
|
*
|
||||||
|
* @param em EntityManager to use.
|
||||||
|
* @param entityClass The type of the entity to find.
|
||||||
|
* @param primaryKey The primary key of the entity to find.
|
||||||
|
* @param graphString The specification of attributes to fetch (see {@link EntityGraphParser}).
|
||||||
|
*
|
||||||
|
* @return The entity if found, same as
|
||||||
|
* {@link EntityManager#find(Class, Object, javax.persistence.LockModeType, Map)}.
|
||||||
|
*
|
||||||
|
* @throws InvalidGraphException if a specified fetch or load graph is invalid.
|
||||||
|
*/
|
||||||
|
public static <T> T find(final EntityManager em, Class<T> entityClass, Object primaryKey, CharSequence graphString) {
|
||||||
|
em.find( entityClass, primaryKey );
|
||||||
|
EntityGraph<T> graph = EntityGraphParser.parse( em, entityClass, graphString );
|
||||||
|
|
||||||
|
Map<String, Object> props = new HashMap<String, Object>();
|
||||||
|
props.put( HINT_FETCHGRAPH, graph );
|
||||||
|
|
||||||
|
return em.find( entityClass, primaryKey, props );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges multiple entity graphs into a single graph that specifies the fetching/loading of all attributes the input
|
||||||
|
* graphs specify.
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type of the query and graph.
|
||||||
|
*
|
||||||
|
* @param em EntityManager to use to create the new merged graph.
|
||||||
|
* @param rootType Root type of the entity for which the graph is being merged.
|
||||||
|
* @param graphs Graphs to merge.
|
||||||
|
*
|
||||||
|
* @return The merged graph.
|
||||||
|
*/
|
||||||
|
@SafeVarargs
|
||||||
|
public static <T> EntityGraph<T> merge(EntityManager em, Class<T> rootType, EntityGraph<T>... graphs) {
|
||||||
|
Map<String, EntityGraphAttribute> resultMap = null;
|
||||||
|
for ( EntityGraph<T> graph : graphs ) {
|
||||||
|
if ( graph != null ) {
|
||||||
|
Subgraph<T> pretendSubgraph = new GraphAsSubgraph<T>( graph, rootType );
|
||||||
|
|
||||||
|
if ( resultMap == null ) {
|
||||||
|
resultMap = EntityGraphAttribute.mapOf( pretendSubgraph );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
EntityGraphAttribute.merge( resultMap, pretendSubgraph );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EntityGraph<T> merged = em.createEntityGraph( rootType );
|
||||||
|
EntityGraphAttribute.configure( merged, rootType, resultMap );
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two entity graphs and returns {@code true} if they are equal, ignoring attribute order.
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type of BOTH graphs.
|
||||||
|
* @param a Graph to compare.
|
||||||
|
* @param b Graph to compare.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public static <T> boolean equal(EntityGraph<T> a, EntityGraph<T> b) {
|
||||||
|
if ( a == b ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( ( a == null ) || ( b == null ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AttributeNode<?>> aNodes = a.getAttributeNodes();
|
||||||
|
List<AttributeNode<?>> bNodes = b.getAttributeNodes();
|
||||||
|
|
||||||
|
if ( aNodes.size() != bNodes.size() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for ( AttributeNode<?> aNode : aNodes ) {
|
||||||
|
String attributeName = aNode.getAttributeName();
|
||||||
|
AttributeNode<?> bNode = null;
|
||||||
|
for ( AttributeNode<?> bCandidate : bNodes ) {
|
||||||
|
if ( attributeName.equals( bCandidate.getAttributeName() ) ) {
|
||||||
|
bNode = bCandidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( !equal( aNode, bNode ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two entity graph attribute node and returns {@code true} if they are equal, ignoring subgraph attribute
|
||||||
|
* order.
|
||||||
|
*/
|
||||||
|
public static boolean equal(AttributeNode<?> a, AttributeNode<?> b) {
|
||||||
|
if ( a == b ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( ( a == null ) || ( b == null ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( a.getAttributeName().equals( b.getAttributeName() ) ) {
|
||||||
|
return equal( a.getSubgraphs(), b.getSubgraphs() ) && equal( a.getKeySubgraphs(), b.getKeySubgraphs() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two entity subgraph maps and returns {@code true} if they are equal, ignoring order.
|
||||||
|
*/
|
||||||
|
public static boolean equal(@SuppressWarnings("rawtypes") Map<Class, Subgraph> a, @SuppressWarnings("rawtypes") Map<Class, Subgraph> b) {
|
||||||
|
if ( a == b ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( ( a == null ) || ( b == null ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Set<Class> aKeys = a.keySet();
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Set<Class> bKeys = b.keySet();
|
||||||
|
|
||||||
|
if ( aKeys.equals( bKeys ) ) {
|
||||||
|
for ( Class<?> clazz : aKeys ) {
|
||||||
|
if ( !bKeys.contains( clazz ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( !equal( a.get( clazz ), b.get( clazz ) ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two entity subgraphs and returns {@code true} if they are equal, ignoring attribute order.
|
||||||
|
*/
|
||||||
|
public static boolean equal(@SuppressWarnings("rawtypes") Subgraph a, @SuppressWarnings("rawtypes") Subgraph b) {
|
||||||
|
if ( a == b ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ( ( a == null ) || ( b == null ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ( a.getClassType() != b.getClassType() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<AttributeNode<?>> aNodes = a.getAttributeNodes();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
List<AttributeNode<?>> bNodes = b.getAttributeNodes();
|
||||||
|
|
||||||
|
if ( aNodes.size() != bNodes.size() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( AttributeNode<?> aNode : aNodes ) {
|
||||||
|
String attributeName = aNode.getAttributeName();
|
||||||
|
AttributeNode<?> bNode = null;
|
||||||
|
for ( AttributeNode<?> bCandidate : bNodes ) {
|
||||||
|
if ( attributeName.equals( bCandidate.getAttributeName() ) ) {
|
||||||
|
bNode = bCandidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( !equal( aNode, bNode ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import javax.persistence.EntityGraph;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.Query;
|
||||||
|
|
||||||
|
import org.hibernate.ScrollableResults;
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||||
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internally used class to carry a pair of graphs (fetch and load),
|
||||||
|
* a result of {@link EntityGraphParser#parsePreQueryGraphDescriptors(EntityManager, Class, ParseBuffer)}.
|
||||||
|
*
|
||||||
|
* @author asusnjar
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type of both graphs.
|
||||||
|
*/
|
||||||
|
class FetchAndLoadEntityGraphs<T> {
|
||||||
|
|
||||||
|
final EntityGraph<T> fetchGraph;
|
||||||
|
final EntityGraph<T> loadGraph;
|
||||||
|
|
||||||
|
public FetchAndLoadEntityGraphs(EntityGraph<T> fetchGraph, EntityGraph<T> loadGraph) {
|
||||||
|
this.fetchGraph = fetchGraph;
|
||||||
|
this.loadGraph = loadGraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FetchAndLoadEntityGraphs(EntityManager em, Class<T> rootType, CharSequence fetchGraph, CharSequence loadGraph) {
|
||||||
|
this( EntityGraphParser.parse( em, rootType, fetchGraph ), EntityGraphParser.parse( em, rootType, loadGraph ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public FetchAndLoadEntityGraphs(EntityManager em, Class<T> rootType, CharSequence fetchGraph) {
|
||||||
|
this( em, rootType, fetchGraph, null );
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityGraph<T> getFetchGraph() {
|
||||||
|
return fetchGraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityGraph<T> getLoadGraph() {
|
||||||
|
return loadGraph;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void applyTo(Query query) {
|
||||||
|
if ( fetchGraph != null ) {
|
||||||
|
query.setHint( EntityGraphs.HINT_FETCHGRAPH, fetchGraph );
|
||||||
|
}
|
||||||
|
if ( loadGraph != null ) {
|
||||||
|
query.setHint( EntityGraphs.HINT_LOADGRAPH, loadGraph );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public <R> R run(Session session, Supplier<R> supplier) {
|
||||||
|
LoadQueryInfluencers influencers = ( (SessionImplementor) session ).getLoadQueryInfluencers();
|
||||||
|
EntityGraph<?> preexistingFetchGraph = influencers.getFetchGraph();
|
||||||
|
EntityGraph<?> preexistingLoadGraph = influencers.getLoadGraph();
|
||||||
|
|
||||||
|
try {
|
||||||
|
influencers.setFetchGraph( fetchGraph );
|
||||||
|
influencers.setLoadGraph( loadGraph );
|
||||||
|
return supplier.get();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
influencers.setFetchGraph( preexistingFetchGraph );
|
||||||
|
influencers.setLoadGraph( preexistingLoadGraph );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated as {@link org.hibernate.Query} is deprecated.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "unchecked", "deprecation" })
|
||||||
|
@Deprecated
|
||||||
|
public List<T> list(Session session, @SuppressWarnings("rawtypes") org.hibernate.Query query) {
|
||||||
|
return (List<T>) run( session, () -> query.list() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated as {@link org.hibernate.Query} is deprecated.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({ "unchecked", "deprecation" })
|
||||||
|
@Deprecated
|
||||||
|
public Iterator<T> iterate(Session session, @SuppressWarnings("rawtypes") org.hibernate.Query query) {
|
||||||
|
return (Iterator<T>) run( session, () -> query.iterate() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated as {@link org.hibernate.Query} is deprecated.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Deprecated
|
||||||
|
public ScrollableResults scroll(Session session, @SuppressWarnings("rawtypes") org.hibernate.Query query) {
|
||||||
|
return run( session, () -> query.scroll() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasFetchGraph() {
|
||||||
|
return fetchGraph != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasLoadGraph() {
|
||||||
|
return loadGraph != null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.persistence.AttributeNode;
|
||||||
|
import javax.persistence.EntityGraph;
|
||||||
|
import javax.persistence.Subgraph;
|
||||||
|
import javax.persistence.metamodel.Attribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A proxy class that wraps an {@link EntityGraph} instance and mimics the {@link Subgraph} interface
|
||||||
|
* by delegating method calls.
|
||||||
|
*
|
||||||
|
* @author asusnjar
|
||||||
|
*
|
||||||
|
* @param <T> Root entity type.
|
||||||
|
*/
|
||||||
|
final class GraphAsSubgraph<T> implements Subgraph<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapped (proxied) graph.
|
||||||
|
*/
|
||||||
|
private final EntityGraph<T> graph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A root entity type.
|
||||||
|
*/
|
||||||
|
private final Class<T> rootType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sole constructor.
|
||||||
|
*
|
||||||
|
* @param graph Graph to wrap.
|
||||||
|
*
|
||||||
|
* @param rootType Root entity type of the graph
|
||||||
|
* (which, unfortunately, cannot be obtained from the graph itself)
|
||||||
|
*/
|
||||||
|
GraphAsSubgraph(EntityGraph<T> graph, Class<T> rootType) {
|
||||||
|
this.graph = graph;
|
||||||
|
this.rootType = rootType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#addAttributeNodes(String...)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addAttributeNodes(String... attributeNames) {
|
||||||
|
graph.addAttributeNodes( attributeNames );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#addAttributeNodes(Attribute...)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addAttributeNodes(@SuppressWarnings("unchecked") Attribute<T, ?>... attributes) {
|
||||||
|
graph.addAttributeNodes( attributes );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#addSubgraph(Attribute)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <X> Subgraph<X> addSubgraph(Attribute<T, X> attribute) {
|
||||||
|
return graph.addSubgraph( attribute );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#addSubgraph(Attribute, Class)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <X> Subgraph<? extends X> addSubgraph(Attribute<T, X> attribute, Class<? extends X> type) {
|
||||||
|
return graph.addSubgraph( attribute, type );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#addSubgraph(String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <X> Subgraph<X> addSubgraph(String attributeName) {
|
||||||
|
return graph.addSubgraph( attributeName );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#addSubgraph(String, Class)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <X> Subgraph<X> addSubgraph(String attributeName, Class<X> type) {
|
||||||
|
return graph.addSubgraph( attributeName, type );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#addKeySubgraph(Attribute)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <X> Subgraph<X> addKeySubgraph(Attribute<T, X> attribute) {
|
||||||
|
return graph.addKeySubgraph( attribute );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#addKeySubgraph(Attribute, Class)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <X> Subgraph<? extends X> addKeySubgraph(Attribute<T, X> attribute, Class<? extends X> type) {
|
||||||
|
return graph.addKeySubgraph( attribute, type );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#addKeySubgraph(String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <X> Subgraph<X> addKeySubgraph(String attributeName) {
|
||||||
|
return graph.addKeySubgraph( attributeName );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#addKeySubgraph(String, Class)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <X> Subgraph<X> addKeySubgraph(String attributeName, Class<X> type) {
|
||||||
|
return graph.addKeySubgraph( attributeName, type );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#getAttributeNodes()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<AttributeNode<?>> getAttributeNodes() {
|
||||||
|
return graph.getAttributeNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Subgraph#getClassType()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Class<T> getClassType() {
|
||||||
|
return rootType;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw by {@link EntityGraphParser} to indicate textual entity graph representation parsing errors.
|
||||||
|
*
|
||||||
|
* @author asusnjar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class InvalidGraphException extends RuntimeException {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public InvalidGraphException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidGraphException(String message) {
|
||||||
|
super( message );
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidGraphException(Throwable cause) {
|
||||||
|
super( cause );
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidGraphException(String message, Throwable cause) {
|
||||||
|
super( message, cause );
|
||||||
|
}
|
||||||
|
|
||||||
|
public InvalidGraphException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||||
|
super( message, cause, enableSuppression, writableStackTrace );
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,279 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parsing helper class used by the {@link EntityGraphParser} to traverse
|
||||||
|
* {@linkplain CharSequence character sequences) while tracking the parsing
|
||||||
|
* position.
|
||||||
|
*
|
||||||
|
* @author asusnjar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class ParseBuffer implements CharSequence {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Character sequence being parsed.
|
||||||
|
*/
|
||||||
|
private final CharSequence text;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the character sequence.
|
||||||
|
*/
|
||||||
|
private final int length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current parsing position.
|
||||||
|
*/
|
||||||
|
private int position = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main constructor.
|
||||||
|
*
|
||||||
|
* @param text Character sequence to parse.
|
||||||
|
*/
|
||||||
|
public ParseBuffer(CharSequence text) {
|
||||||
|
this.text = text;
|
||||||
|
this.length = text.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of characters remaining in the buffer (starting from the current parsing position).
|
||||||
|
*
|
||||||
|
* @see CharSequence#length()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int length() {
|
||||||
|
return length - position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the characters remaining in the buffer (starting from the current parsing position).
|
||||||
|
*
|
||||||
|
* @see CharSequence#toString()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return text.subSequence( position, length ).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the character that is at the specified index <u>after</u> the current parsing position.
|
||||||
|
*
|
||||||
|
* @see CharSequence#charAt(int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public char charAt(int index) {
|
||||||
|
return text.charAt( position + index );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a subsequence with {@code start} and {@code end} indexes being relative to the
|
||||||
|
* current parsing position.
|
||||||
|
*
|
||||||
|
* @see CharSequence#subSequence(int, int)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public CharSequence subSequence(int start, int end) {
|
||||||
|
return text.subSequence( position + start, position + end );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if and only if we are at the end of the buffer.
|
||||||
|
*/
|
||||||
|
public boolean isAtEnd() {
|
||||||
|
return position >= length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips all {@linkplain Character#isWhitespace(char) regular whitespace characters} in the buffer.
|
||||||
|
*/
|
||||||
|
public void skipWhitespace() {
|
||||||
|
while ( ( position < length ) && Character.isWhitespace( text.charAt( position ) ) ) {
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the character at the current parsing position, without advancing the position.
|
||||||
|
*
|
||||||
|
* @see ParseBuffer#consumeChar()
|
||||||
|
*/
|
||||||
|
public char peekChar() {
|
||||||
|
return text.charAt( position );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the character at the current parsing position and advances to the next character.
|
||||||
|
*
|
||||||
|
* @see #peekChar()
|
||||||
|
*/
|
||||||
|
public char consumeChar() {
|
||||||
|
return text.charAt( position++ );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the character sequence that follows the Java identifier name restrictions
|
||||||
|
* (see {@link Character#isJavaIdentifierStart(char)} and {@link Character#isJavaIdentifierPart(char)})
|
||||||
|
* and advances the parsing position accordingly.
|
||||||
|
*
|
||||||
|
* @param withDots If {@code true}, also allows/includes dots ({@code '.'}) in identifiers.
|
||||||
|
*
|
||||||
|
* @return A consumed identifier or {@code null} if at end of the buffer or a non-identifier
|
||||||
|
* character is at the current parsing position. Characters that match Java identifier name
|
||||||
|
* restrictions (and dots if {@code withDots==true}) are eagerly consumed.
|
||||||
|
*/
|
||||||
|
public CharSequence consumeIdentifierSeq(boolean withDots) {
|
||||||
|
if ( isAtEnd() ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
char ch = peekChar();
|
||||||
|
if ( ( ( ch != '.' ) || !withDots ) && !Character.isJavaIdentifierStart( text.charAt( position ) ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = position;
|
||||||
|
int terminator = start + 1;
|
||||||
|
|
||||||
|
while ( terminator < length ) {
|
||||||
|
ch = text.charAt( terminator );
|
||||||
|
if ( ( !Character.isJavaIdentifierPart( ch ) ) && ( ( ch != '.' ) || !withDots ) ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
terminator++;
|
||||||
|
}
|
||||||
|
|
||||||
|
position = terminator;
|
||||||
|
return text.subSequence( start, terminator );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience version of {@link #consumeIdentifierSeq(boolean)}.
|
||||||
|
*/
|
||||||
|
public String consumeIdentifier(boolean withDots) {
|
||||||
|
CharSequence id = consumeIdentifierSeq( withDots );
|
||||||
|
return ( id == null ) ? (String) null : id.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewinds the parsing position by one character back.
|
||||||
|
*
|
||||||
|
* @see #back(int)
|
||||||
|
* @see #back(CharSequence)
|
||||||
|
*/
|
||||||
|
public void back() {
|
||||||
|
position--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewinds the parsing position by the specified number of character.
|
||||||
|
*
|
||||||
|
* @param charCount Number of characters to move.
|
||||||
|
*/
|
||||||
|
public void back(int charCount) {
|
||||||
|
position -= charCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Returns" the specified character sequence to the buffer.
|
||||||
|
*
|
||||||
|
* Present implementation does not validate that these characters match the preceding
|
||||||
|
* buffer content, it only rewinds the buffer based on the sequence length.
|
||||||
|
*
|
||||||
|
* However, this should not be abused - validation may be introduced at any time.
|
||||||
|
*/
|
||||||
|
public void back(CharSequence seq) {
|
||||||
|
back( seq.length() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if and only if the next character in the buffer is equal to the specified
|
||||||
|
* character (case sensitive).
|
||||||
|
*
|
||||||
|
* @see #matchIgnoreCase(char)
|
||||||
|
* @see #match(CharSequence)
|
||||||
|
*/
|
||||||
|
public boolean match(char ch) {
|
||||||
|
if ( isAtEnd() || ( peekChar() != ch ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
position++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if and only if the next character in the buffer is equal to the specified
|
||||||
|
* character (case insensitive).
|
||||||
|
*
|
||||||
|
* @see #match(char)
|
||||||
|
* @see #matchIgnoreCase(CharSequence)
|
||||||
|
*/
|
||||||
|
public boolean matchIgnoreCase(char ch) {
|
||||||
|
if ( isAtEnd() || ( Character.toLowerCase( text.charAt( position ) ) != Character.toLowerCase( ch ) ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
position++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if and only if the next sequence of characters in the buffer
|
||||||
|
* are equal to the specified sequence (case sensitive).
|
||||||
|
*
|
||||||
|
* @see #matchIgnoreCase(CharSequence)
|
||||||
|
* @see #match(char)
|
||||||
|
*/
|
||||||
|
public boolean match(CharSequence seq) {
|
||||||
|
final int len = seq.length();
|
||||||
|
|
||||||
|
if ( len >= this.length() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( int i = 0; i < len; i++ ) {
|
||||||
|
if ( seq.charAt( i ) != text.charAt( position + i ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
position += len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if and only if the next sequence of characters in the buffer
|
||||||
|
* are equal to the specified sequence (case insensitive).
|
||||||
|
*
|
||||||
|
* @see #match(CharSequence)
|
||||||
|
* @see #matchIgnoreCase(char)
|
||||||
|
*/
|
||||||
|
public boolean matchIgnoreCase(CharSequence seq) {
|
||||||
|
final int len = seq.length();
|
||||||
|
|
||||||
|
if ( len >= this.length() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( int i = 0; i < len; i++ ) {
|
||||||
|
if ( Character.toLowerCase( seq.charAt( i ) ) != Character.toLowerCase( text.charAt( position + i ) ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
position += len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current parsing position.
|
||||||
|
*/
|
||||||
|
public int getPosition() {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.persistence.AttributeNode;
|
||||||
|
import javax.persistence.EntityGraph;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.Subgraph;
|
||||||
|
|
||||||
|
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
|
||||||
|
public abstract class AbstractEntityGraphTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
|
|
||||||
|
public AbstractEntityGraphTest() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getAnnotatedClasses() {
|
||||||
|
return new Class[]{ GraphParsingTestEntity.class, GraphParsingTestSubentity.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T> EntityGraph<T> parseGraph(Class<T> entityType, String graphString) {
|
||||||
|
EntityManager entityManager = getOrCreateEntityManager();
|
||||||
|
return EntityGraphParser.parse( entityManager, entityType, graphString );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <T> EntityGraph<GraphParsingTestEntity> parseGraph(String graphString) {
|
||||||
|
return parseGraph( GraphParsingTestEntity.class, graphString );
|
||||||
|
}
|
||||||
|
|
||||||
|
private <C extends Collection<?>> void assertNullOrEmpty(C collection) {
|
||||||
|
if ( collection != null ) {
|
||||||
|
Assert.assertEquals( 0, collection.size() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected <M extends Map<?, ?>> void assertNullOrEmpty(M map) {
|
||||||
|
if ( map != null ) {
|
||||||
|
Assert.assertEquals( 0, map.size() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertBasicAttributes(EntityGraph<?> graph, String... names) {
|
||||||
|
Assert.assertNotNull( graph );
|
||||||
|
assertBasicAttributes( graph.getAttributeNodes(), names );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertBasicAttributes(Subgraph<?> graph, String... names) {
|
||||||
|
Assert.assertNotNull( graph );
|
||||||
|
assertBasicAttributes( graph.getAttributeNodes(), names );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertBasicAttributes(List<AttributeNode<?>> attrs, String... names) {
|
||||||
|
if ( ( names == null ) || ( names.length == 0 ) ) {
|
||||||
|
assertNullOrEmpty( attrs );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Assert.assertNotNull( attrs );
|
||||||
|
Assert.assertTrue( names.length <= attrs.size() );
|
||||||
|
|
||||||
|
for ( String name : names ) {
|
||||||
|
AttributeNode<?> node = null;
|
||||||
|
for ( AttributeNode<?> candidate : attrs ) {
|
||||||
|
if ( candidate.getAttributeName().equals( name ) ) {
|
||||||
|
node = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.assertNotNull( node );
|
||||||
|
assertNullOrEmpty( node.getKeySubgraphs() );
|
||||||
|
assertNullOrEmpty( node.getSubgraphs() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AttributeNode<?> getAttributeNodeByName(EntityGraph<?> graph, String name, boolean required) {
|
||||||
|
return getAttributeNodeByName( graph.getAttributeNodes(), name, required );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AttributeNode<?> getAttributeNodeByName(Subgraph<?> graph, String name, boolean required) {
|
||||||
|
return getAttributeNodeByName( graph.getAttributeNodes(), name, required );
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttributeNode<?> getAttributeNodeByName(List<AttributeNode<?>> attrs, String name, boolean required) {
|
||||||
|
for ( AttributeNode<?> attr : attrs ) {
|
||||||
|
if ( name.equals( attr.getAttributeName() ) )
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
if ( required )
|
||||||
|
Assert.fail( "Required attribute not found." );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,266 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.persistence.AttributeNode;
|
||||||
|
import javax.persistence.EntityGraph;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.Subgraph;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A unit test of {@link EntityGraphParser}.
|
||||||
|
*
|
||||||
|
* @author asusnjar
|
||||||
|
*/
|
||||||
|
public class EntityGraphParserTest extends AbstractEntityGraphTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNullParsing() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = parseGraph( (String) null );
|
||||||
|
Assert.assertNull( graph );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOneBasicAttributeParsing() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = parseGraph( "name" );
|
||||||
|
assertBasicAttributes( graph, "name" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTwoBasicAttributesParsing() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = parseGraph( "name, description" );
|
||||||
|
assertBasicAttributes( graph, "name", "description" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLinkParsing() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = parseGraph( "linkToOne(name, description)" );
|
||||||
|
Assert.assertNotNull( graph );
|
||||||
|
List<AttributeNode<?>> attrs = graph.getAttributeNodes();
|
||||||
|
Assert.assertNotNull( attrs );
|
||||||
|
Assert.assertEquals( 1, attrs.size() );
|
||||||
|
AttributeNode<?> node = attrs.get( 0 );
|
||||||
|
Assert.assertNotNull( node );
|
||||||
|
Assert.assertEquals( "linkToOne", node.getAttributeName() );
|
||||||
|
assertNullOrEmpty( node.getKeySubgraphs() );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> sub = node.getSubgraphs();
|
||||||
|
assertBasicAttributes( sub.get( GraphParsingTestEntity.class ), "name", "description" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapKeyParsing() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = parseGraph( "map.key(name, description)" );
|
||||||
|
Assert.assertNotNull( graph );
|
||||||
|
List<AttributeNode<?>> attrs = graph.getAttributeNodes();
|
||||||
|
Assert.assertNotNull( attrs );
|
||||||
|
Assert.assertEquals( 1, attrs.size() );
|
||||||
|
AttributeNode<?> node = attrs.get( 0 );
|
||||||
|
Assert.assertNotNull( node );
|
||||||
|
Assert.assertEquals( "map", node.getAttributeName() );
|
||||||
|
assertNullOrEmpty( node.getSubgraphs() );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> sub = node.getKeySubgraphs();
|
||||||
|
assertBasicAttributes( sub.get( GraphParsingTestEntity.class ), "name", "description" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapValueParsing() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = parseGraph( "map.value(name, description)" );
|
||||||
|
Assert.assertNotNull( graph );
|
||||||
|
List<AttributeNode<?>> attrs = graph.getAttributeNodes();
|
||||||
|
Assert.assertNotNull( attrs );
|
||||||
|
Assert.assertEquals( 1, attrs.size() );
|
||||||
|
AttributeNode<?> node = attrs.get( 0 );
|
||||||
|
Assert.assertNotNull( node );
|
||||||
|
Assert.assertEquals( "map", node.getAttributeName() );
|
||||||
|
assertNullOrEmpty( node.getKeySubgraphs() );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> sub = node.getSubgraphs();
|
||||||
|
assertBasicAttributes( sub.get( GraphParsingTestEntity.class ), "name", "description" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
|
public void testMixParsingWithMaps() {
|
||||||
|
// Fails due to: https://hibernate.atlassian.net/browse/HHH-12696
|
||||||
|
String g = " name , linkToOne ( description, map . key ( name ) , map . value ( description ) , name ) , description , map . key ( name , description ) , map . value ( description ) ";
|
||||||
|
g = g.replace( " ", " " );
|
||||||
|
for ( int i = 1; i <= 2; i++, g = g.replace( " ", "" ) ) {
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = parseGraph( g );
|
||||||
|
assertBasicAttributes( graph, "name", "description" );
|
||||||
|
|
||||||
|
AttributeNode<?> linkToOne = getAttributeNodeByName( graph, "linkToOne", true );
|
||||||
|
assertNullOrEmpty( linkToOne.getKeySubgraphs() );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> linkToOneSubgraphs = linkToOne.getSubgraphs();
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Subgraph linkToOneRoot = linkToOneSubgraphs.get( GraphParsingTestEntity.class );
|
||||||
|
assertBasicAttributes( linkToOneRoot, "name", "description" );
|
||||||
|
|
||||||
|
AttributeNode<?> linkToOneMap = getAttributeNodeByName( linkToOneRoot, "map", true );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> linkToOneMapKeySubgraphs = linkToOneMap.getKeySubgraphs();
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Subgraph linkToOneMapKeyRoot = linkToOneMapKeySubgraphs.get( GraphParsingTestEntity.class );
|
||||||
|
assertBasicAttributes( linkToOneMapKeyRoot, "name" );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> linkToOneMapSubgraphs = linkToOneMap.getSubgraphs();
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Subgraph linkToOneMapRoot = linkToOneMapSubgraphs.get( GraphParsingTestEntity.class );
|
||||||
|
assertBasicAttributes( linkToOneMapRoot, "description" );
|
||||||
|
|
||||||
|
AttributeNode<?> map = getAttributeNodeByName( graph, "map", true );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> mapKeySubgraphs = map.getKeySubgraphs();
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Subgraph mapKeyRoot = mapKeySubgraphs.get( GraphParsingTestEntity.class );
|
||||||
|
assertBasicAttributes( mapKeyRoot, "name", "description" );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> mapSubgraphs = map.getSubgraphs();
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Subgraph mapRoot = mapSubgraphs.get( GraphParsingTestEntity.class );
|
||||||
|
assertBasicAttributes( mapRoot, "description" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMixParsingWithSimplifiedMaps() {
|
||||||
|
String g = " name , linkToOne ( description, map . key ( name ) , name ) , description , map . value ( description, name ) ";
|
||||||
|
g = g.replace( " ", " " );
|
||||||
|
for ( int i = 1; i <= 2; i++, g = g.replace( " ", "" ) ) {
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = parseGraph( g );
|
||||||
|
assertBasicAttributes( graph, "name", "description" );
|
||||||
|
|
||||||
|
AttributeNode<?> linkToOne = getAttributeNodeByName( graph, "linkToOne", true );
|
||||||
|
assertNullOrEmpty( linkToOne.getKeySubgraphs() );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> linkToOneSubgraphs = linkToOne.getSubgraphs();
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Subgraph linkToOneRoot = linkToOneSubgraphs.get( GraphParsingTestEntity.class );
|
||||||
|
assertBasicAttributes( linkToOneRoot, "name", "description" );
|
||||||
|
|
||||||
|
AttributeNode<?> linkToOneMap = getAttributeNodeByName( linkToOneRoot, "map", true );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> linkToOneMapKeySubgraphs = linkToOneMap.getKeySubgraphs();
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Subgraph linkToOneMapKeyRoot = linkToOneMapKeySubgraphs.get( GraphParsingTestEntity.class );
|
||||||
|
assertBasicAttributes( linkToOneMapKeyRoot, "name" );
|
||||||
|
|
||||||
|
AttributeNode<?> map = getAttributeNodeByName( graph, "map", true );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> mapSubgraphs = map.getSubgraphs();
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Subgraph mapRoot = mapSubgraphs.get( GraphParsingTestEntity.class );
|
||||||
|
assertBasicAttributes( mapRoot, "description", "name" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Cannot run due to Hibernate bug: https://hibernate.atlassian.net/browse/HHH-10378")
|
||||||
|
// TODO Re-enable when Hibernate bug HHH-10378 is fixed
|
||||||
|
public void testLinkSubtypeParsing() {
|
||||||
|
// https://hibernate.atlassian.net/browse/HHH-10378
|
||||||
|
//
|
||||||
|
// Specifically the isTreatable(...) method in org.hibernate.jpa.graph.internal.AttributeNodeImpl
|
||||||
|
//
|
||||||
|
// It states:
|
||||||
|
//
|
||||||
|
// Check to make sure that the java type of the given entity persister is treatable as the given type. In other
|
||||||
|
// words, is the given type a subclass of the class represented by the persister.
|
||||||
|
//
|
||||||
|
// But that does not clearly match the implementation (seems opposite):
|
||||||
|
//
|
||||||
|
// return type.isAssignableFrom( entityPersister.getMappedClass() );
|
||||||
|
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = parseGraph( "linkToOne(name, description), linkToOne:MockSubentity(sub)" );
|
||||||
|
Assert.assertNotNull( graph );
|
||||||
|
List<AttributeNode<?>> attrs = graph.getAttributeNodes();
|
||||||
|
Assert.assertNotNull( attrs );
|
||||||
|
Assert.assertEquals( 1, attrs.size() );
|
||||||
|
AttributeNode<?> node = attrs.get( 0 );
|
||||||
|
Assert.assertNotNull( node );
|
||||||
|
Assert.assertEquals( "linkToOne", node.getAttributeName() );
|
||||||
|
assertNullOrEmpty( node.getKeySubgraphs() );
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> sub = node.getSubgraphs();
|
||||||
|
assertBasicAttributes( sub.get( GraphParsingTestSubentity.class ), "sub" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHHH10378IsNotFixedYet() {
|
||||||
|
EntityManager entityManager = getOrCreateEntityManager();
|
||||||
|
try {
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = entityManager.createEntityGraph( GraphParsingTestEntity.class );
|
||||||
|
graph.addSubgraph( "linkToOne", GraphParsingTestSubentity.class );
|
||||||
|
Assert.fail( "https://hibernate.atlassian.net/browse/HHH-10378 appears to have been fixed. Please check and update the tests here." );
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException iax) {
|
||||||
|
if ( iax.getMessage().startsWith( "Attribute [linkToOne] cannot be treated as requested type" ) ) {
|
||||||
|
// This is, unfortunately, expected.
|
||||||
|
System.err.println( "*Sigh*, https://hibernate.atlassian.net/browse/HHH-10378 appears to not have been fixed yet." );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Assert.fail( "https://hibernate.atlassian.net/browse/HHH-10378 may have been fixed. Please check and update the tests here." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Cannot run due to Hibernate bug: https://hibernate.atlassian.net/browse/HHH-12696")
|
||||||
|
public void testHHH12696MapSubgraphsKeyFirst() {
|
||||||
|
|
||||||
|
EntityManager entityManager = getOrCreateEntityManager();
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = entityManager.createEntityGraph( GraphParsingTestEntity.class );
|
||||||
|
|
||||||
|
final String mapAttributeName = "map";
|
||||||
|
Subgraph<GraphParsingTestEntity> keySubgraph = graph.addKeySubgraph( mapAttributeName );
|
||||||
|
Subgraph<GraphParsingTestEntity> valueSubgraph = graph.addSubgraph( mapAttributeName );
|
||||||
|
|
||||||
|
checkMapKeyAndValueSubgraphs( graph, mapAttributeName, keySubgraph, valueSubgraph );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkMapKeyAndValueSubgraphs(EntityGraph<GraphParsingTestEntity> graph, final String mapAttributeName, Subgraph<GraphParsingTestEntity> keySubgraph,
|
||||||
|
Subgraph<GraphParsingTestEntity> valueSubgraph) {
|
||||||
|
int count = 0;
|
||||||
|
for ( AttributeNode<?> node : graph.getAttributeNodes() ) {
|
||||||
|
if ( mapAttributeName.equals( node.getAttributeName() ) ) {
|
||||||
|
count++;
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> keySubgraphs = node.getKeySubgraphs();
|
||||||
|
Assert.assertTrue( "Missing the key subgraph", !keySubgraphs.isEmpty() );
|
||||||
|
Assert.assertSame( keySubgraph, keySubgraphs.get( GraphParsingTestEntity.class ) );
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Map<Class, Subgraph> valueSubgraphs = node.getSubgraphs();
|
||||||
|
Assert.assertTrue( "Missing the value subgraph", !valueSubgraphs.isEmpty() );
|
||||||
|
Assert.assertSame( valueSubgraph, valueSubgraphs.get( GraphParsingTestEntity.class ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.assertEquals( 1, count );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Cannot run due to Hibernate bug: https://hibernate.atlassian.net/browse/HHH-12696")
|
||||||
|
public void testHHH12696MapSubgraphsValueFirst() {
|
||||||
|
EntityManager entityManager = getOrCreateEntityManager();
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = entityManager.createEntityGraph( GraphParsingTestEntity.class );
|
||||||
|
|
||||||
|
final String mapAttributeName = "map";
|
||||||
|
Subgraph<GraphParsingTestEntity> valueSubgraph = graph.addSubgraph( mapAttributeName );
|
||||||
|
Subgraph<GraphParsingTestEntity> keySubgraph = graph.addKeySubgraph( mapAttributeName );
|
||||||
|
|
||||||
|
checkMapKeyAndValueSubgraphs( graph, mapAttributeName, keySubgraph, valueSubgraph );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
* Hibernate, Relational Persistence for Idiomatic Java
|
||||||
|
*
|
||||||
|
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||||
|
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||||
|
*/
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
import javax.persistence.EntityGraph;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class EntityGraphsTest extends AbstractEntityGraphTest {
|
||||||
|
|
||||||
|
private final <T> void checkMerge(Class<T> rootType, EntityGraph<T> expected, @SuppressWarnings("unchecked") EntityGraph<T>... graphs) {
|
||||||
|
EntityManager entityManager = getOrCreateEntityManager();
|
||||||
|
EntityGraph<T> actual = EntityGraphs.merge( entityManager, rootType, graphs );
|
||||||
|
Assert.assertTrue( EntityGraphs.equal( expected, actual ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
private final void checkMerge(EntityGraph<GraphParsingTestEntity> expected, EntityGraph<GraphParsingTestEntity>... graphs) {
|
||||||
|
checkMerge( GraphParsingTestEntity.class, expected, graphs );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSameBasicsEqual() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> g = parseGraph( "name, description " );
|
||||||
|
Assert.assertTrue( EntityGraphs.equal( g, g ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEqualBasicsEqual() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "name, description " );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "description, name " );
|
||||||
|
Assert.assertTrue( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentBasicsEqual1() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "name, description " );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "description " );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentBasicsEqual2() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "name " );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "description " );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEqualLinksEqual1() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "linkToOne(name, description)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "linkToOne(description, name)" );
|
||||||
|
Assert.assertTrue( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Cannot run due to Hibernate bug: https://hibernate.atlassian.net/browse/HHH-10378")
|
||||||
|
public void testEqualLinksWithSubclassesEqual() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "linkToOne(name), linkToOne:MockSubentity(description)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "linkToOne:MockSubentity(description), linkToOne(name)" );
|
||||||
|
Assert.assertTrue( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentLinksEqual1() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "linkToOne(name, description)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "linkToOne(description)" );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentLinksEqual2() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "linkToOne(name)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "linkToOne(description)" );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Cannot run due to Hibernate bug: https://hibernate.atlassian.net/browse/HHH-10378")
|
||||||
|
public void testDifferentLinksEqual3() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "linkToOne(name), linkToOne:MockSubentity(description)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "linkToOne(name, description)" );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEqualMapKeysEqual() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "map.key(name, description)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "map.key(description, name)" );
|
||||||
|
Assert.assertTrue( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentMapKeysEqual1() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "map.key(name, description)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "map.key(description)" );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentMapKeysEqual2() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "map.key(name)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "map.key(description)" );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEqualMapValuesEqual() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "map.value(name, description)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "map.value(description, name)" );
|
||||||
|
Assert.assertTrue( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentMapValuesEqual1() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "map.value(name, description)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "map.value(description)" );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentMapValuesEqual2() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "map.value(name)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "map.value(description)" );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEqualComplexGraphsEqual() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "map.key(name, description), name, linkToOne(description), description" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "description, map.key(description, name), name, linkToOne(description)" );
|
||||||
|
Assert.assertTrue( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDifferentComplexGraphsEqual() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> a = parseGraph( "map.key(name, description), name, linkToOne(description), description" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> b = parseGraph( "description, map.value(description, name), name, linkToOne(description)" );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( a, b ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNullsEqual() {
|
||||||
|
Assert.assertTrue( EntityGraphs.equal( (EntityGraph<GraphParsingTestEntity>) null, (EntityGraph<GraphParsingTestEntity>) null ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNullAndNonNullEqual() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> graph = parseGraph( "name " );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( graph, (EntityGraph<GraphParsingTestEntity>) null ) );
|
||||||
|
Assert.assertFalse( EntityGraphs.equal( (EntityGraph<GraphParsingTestEntity>) null, graph ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicMerge() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> g1 = parseGraph( "name" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> g2 = parseGraph( "description" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> expected = parseGraph( "name, description " );
|
||||||
|
checkMerge( expected, g1, g2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLinkMerge() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> g1 = parseGraph( "linkToOne(name)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> g2 = parseGraph( "linkToOne(description)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> expected = parseGraph( "linkToOne(name, description) " );
|
||||||
|
checkMerge( expected, g1, g2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapKeyMerge() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> g1 = parseGraph( "map.key(name)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> g2 = parseGraph( "map.key(description)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> expected = parseGraph( "map.key(name, description) " );
|
||||||
|
checkMerge( expected, g1, g2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMapValueMerge() {
|
||||||
|
EntityGraph<GraphParsingTestEntity> g1 = parseGraph( "map.value(name)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> g2 = parseGraph( "map.value(description)" );
|
||||||
|
EntityGraph<GraphParsingTestEntity> expected = parseGraph( "map.value(name, description) " );
|
||||||
|
checkMerge( expected, g1, g2 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.persistence.Basic;
|
||||||
|
import javax.persistence.CascadeType;
|
||||||
|
import javax.persistence.ElementCollection;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.OneToMany;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class GraphParsingTestEntity {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
private GraphParsingTestEntity linkToOne;
|
||||||
|
|
||||||
|
private Map<GraphParsingTestEntity, GraphParsingTestEntity> map;
|
||||||
|
|
||||||
|
@Id
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Basic
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
public GraphParsingTestEntity getLinkToOne() {
|
||||||
|
return linkToOne;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLinkToOne(GraphParsingTestEntity linkToOne) {
|
||||||
|
this.linkToOne = linkToOne;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||||
|
@ElementCollection(targetClass = GraphParsingTestEntity.class)
|
||||||
|
public Map<GraphParsingTestEntity, GraphParsingTestEntity> getMap() {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMap(Map<GraphParsingTestEntity, GraphParsingTestEntity> map) {
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Basic
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.hibernate.graph;
|
||||||
|
|
||||||
|
import javax.persistence.Basic;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
public class GraphParsingTestSubentity extends GraphParsingTestEntity {
|
||||||
|
|
||||||
|
private String sub;
|
||||||
|
|
||||||
|
@Basic
|
||||||
|
public String getSub() {
|
||||||
|
return sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSub(String sub) {
|
||||||
|
this.sub = sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue