Graphpocalypse: modernize EntityGraphs utility class with new operations

This commit is contained in:
Gavin King 2025-01-02 16:58:26 +01:00
parent 4e14127f65
commit ebfdcdc87e
2 changed files with 112 additions and 30 deletions

View File

@ -4,67 +4,146 @@
*/ */
package org.hibernate.graph; package org.hibernate.graph;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream;
import jakarta.persistence.AttributeNode; import jakarta.persistence.AttributeNode;
import jakarta.persistence.EntityGraph; import jakarta.persistence.EntityGraph;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.Graph;
import jakarta.persistence.Query; import jakarta.persistence.Query;
import jakarta.persistence.Subgraph; import jakarta.persistence.Subgraph;
import jakarta.persistence.TypedQuery; import jakarta.persistence.TypedQuery;
import org.hibernate.Session;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.graph.spi.GraphImplementor; import org.hibernate.graph.spi.GraphImplementor;
import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.graph.spi.RootGraphImplementor;
import org.hibernate.query.SelectionQuery;
/** /**
* A collection of {@link EntityGraph} utilities. * A collection of {@link EntityGraph} utilities.
* *
* @apiNote These methods really belong inside other classes that we cannot modify. * @apiNote These operations are things which are arguably missing from JPA.
* *
* @author asusnjar * @author asusnjar
* @author Gavin King
*/ */
public final class EntityGraphs { public final class EntityGraphs {
/** /**
* Merges multiple entity graphs into a single graph that specifies the * Merges multiple entity graphs into a single graph that specifies the
* fetching/loading of all attributes the input graphs specify. * fetching/loading of all attributes the input graphs specify.
* *
* @param <T> Root entity type of the query and graph. * @param <T> Root entity type of the query and graph.
* *
* @param entityManager EntityManager to use to create the new merged graph. * @param entityManager {@code EntityManager} to use to create the new merged graph.
* @param rootType Root type of the entity for which the graph is being merged. * @param root Root type of the entity for which the graph is being merged.
* @param graphs Graphs to merge. * @param graphs Graphs to merge.
* *
* @return The merged graph. * @return The merged graph.
*/ */
@SafeVarargs @SafeVarargs
public static <T> EntityGraph<T> merge(EntityManager entityManager, Class<T> rootType, EntityGraph<T>... graphs) { public static <T> EntityGraph<T> merge(EntityManager entityManager, Class<T> root, Graph<T>... graphs) {
return mergeInternal( (SessionImplementor) entityManager, rootType, graphs ); return merge( entityManager, root, Arrays.stream(graphs) );
} }
@SafeVarargs /**
public static <T> EntityGraph<T> merge(Session session, Class<T> rootType, Graph<T>... graphs) { * Merges multiple entity graphs into a single graph that specifies the
return mergeInternal( (SessionImplementor) session, rootType, graphs ); * fetching/loading of all attributes the input graphs specify.
*
* @param <T> Root entity type of the query and graph.
*
* @param entityManager {@code EntityManager} to use to create the new merged graph.
* @param root Root type of the entity for which the graph is being merged.
* @param graphs Graphs to merge.
*
* @return The merged graph.
*
* @since 7.0
*/
public static <T> EntityGraph<T> merge(EntityManager entityManager, Class<T> root, List<? extends Graph<T>> graphs) {
return merge( entityManager, root, graphs.stream() );
} }
@SafeVarargs /**
public static <T> EntityGraph<T> merge(SessionImplementor session, Class<T> rootType, GraphImplementor<T>... graphs) { * Merges multiple entity graphs into a single graph that specifies the
return mergeInternal( session, rootType, graphs ); * fetching/loading of all attributes the input graphs specify.
} *
* @param <T> Root entity type of the query and graph.
private static <T> EntityGraph<T> mergeInternal( *
SessionImplementor session, Class<T> rootType, jakarta.persistence.Graph<T>[] graphs) { * @param entityManager {@code EntityManager} to use to create the new merged graph.
final RootGraphImplementor<T> merged = session.createEntityGraph( rootType ); * @param root Root type of the entity for which the graph is being merged.
if ( graphs != null ) { * @param graphs Graphs to merge.
for ( jakarta.persistence.Graph<T> graph : graphs ) { *
merged.merge( (GraphImplementor<T>) graph ); * @return The merged graph.
} *
} * @since 7.0
*/
public static <T> EntityGraph<T> merge(EntityManager entityManager, Class<T> root, Stream<? extends Graph<T>> graphs) {
final RootGraphImplementor<T> merged = ((SessionImplementor) entityManager).createEntityGraph( root );
graphs.forEach( graph -> merged.merge( (GraphImplementor<T>) graph ) );
return merged; return merged;
} }
/**
* Convenience method to apply the given graph to the given query
* without the need for a cast when working with JPA API.
*
* @param query The JPA {@link TypedQuery}
* @param graph The JPA {@link EntityGraph} to apply
* @param semantic The semantic to use when applying the graph
*
* @see SelectionQuery#setEntityGraph(EntityGraph, GraphSemantic)
*
* @since 7.0
*/
public static <R> void setGraph(TypedQuery<R> query, EntityGraph<R> graph, GraphSemantic semantic) {
((org.hibernate.query.Query<R>) query).setEntityGraph( graph, semantic );
}
/**
* Convenience method to apply the given load graph to the given
* query without the need for a cast when working with JPA API.
*
* @param query The JPA {@link TypedQuery}
* @param graph The JPA {@link EntityGraph} to apply
*
* @since 7.0
*/
public static <R> void setLoadGraph(TypedQuery<R> query, EntityGraph<R> graph) {
setGraph( query, graph, GraphSemantic.LOAD );
}
/**
* Convenience method to apply the given fetch graph to the given
* query without the need for a cast when working with JPA API.
*
* @param query The JPA {@link TypedQuery}
* @param graph The JPA {@link EntityGraph} to apply
*
* @since 7.0
*/
public static <R> void setFetchGraph(TypedQuery<R> query, EntityGraph<R> graph) {
setGraph( query, graph, GraphSemantic.FETCH );
}
/**
* Allows a treated subgraph to ve created for a {@link Subgraph}, since the
* JPA-standard operation {@link EntityGraph#addTreatedSubgraph(Class)} is
* declared by {@link EntityGraph}.
*
* @param graph any {@linkplain Graph root graph or subgraph}
* @param subtype the treated (narrowed) type
*
* @since 7.0
*/
public <S> Subgraph<S> addTreatedSubgraph(Graph<? super S> graph, Class<S> subtype) {
return ((org.hibernate.graph.Graph<? super S>) graph).addTreatedSubGraph( subtype );
}
/** /**
* Convenience method for {@linkplain Query#getResultList() executing the query}, * Convenience method for {@linkplain Query#getResultList() executing the query},
* applying the given {@link EntityGraph} using the specified semantic * applying the given {@link EntityGraph} using the specified semantic
@ -73,7 +152,7 @@ private static <T> EntityGraph<T> mergeInternal(
* @param graph The graph to apply * @param graph The graph to apply
* @param semantic The semantic to use when applying the graph * @param semantic The semantic to use when applying the graph
* *
* @deprecated Use {@link org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, GraphSemantic)} * @deprecated Since it is not type safe and returns a raw type
*/ */
@Deprecated(since = "7.0") @Deprecated(since = "7.0")
public static @SuppressWarnings("rawtypes") List executeList(Query query, EntityGraph<?> graph, GraphSemantic semantic) { public static @SuppressWarnings("rawtypes") List executeList(Query query, EntityGraph<?> graph, GraphSemantic semantic) {
@ -94,7 +173,7 @@ private static <T> EntityGraph<T> mergeInternal(
* the graph applies to that entity's type. JPA does not necessarily * the graph applies to that entity's type. JPA does not necessarily
* require that, but it is by far the most common usage. * require that, but it is by far the most common usage.
* *
* @deprecated Use {@link org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, GraphSemantic)} * @deprecated Use {@link #setGraph(TypedQuery, EntityGraph, GraphSemantic)} instead
*/ */
@Deprecated(since = "7.0") @Deprecated(since = "7.0")
public static <R> List<R> executeList(TypedQuery<R> query, EntityGraph<R> graph, GraphSemantic semantic) { public static <R> List<R> executeList(TypedQuery<R> query, EntityGraph<R> graph, GraphSemantic semantic) {
@ -114,7 +193,7 @@ public static <R> List<R> executeList(TypedQuery<R> query, EntityGraph<R> graph,
* *
* @return The result list * @return The result list
* *
* @deprecated Use {@link org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, GraphSemantic)} * @deprecated Since it is not type safe, returns a raw type, and accepts a string
*/ */
@Deprecated(since = "7.0") @Deprecated(since = "7.0")
public static @SuppressWarnings("rawtypes") List executeList(Query query, EntityGraph<?> graph, String semanticJpaHintName) { public static @SuppressWarnings("rawtypes") List executeList(Query query, EntityGraph<?> graph, String semanticJpaHintName) {
@ -133,7 +212,7 @@ public static <R> List<R> executeList(TypedQuery<R> query, EntityGraph<R> graph,
* the graph applies to that entity's type. JPA does not necessarily * the graph applies to that entity's type. JPA does not necessarily
* require that, but it is by far the most common usage. * require that, but it is by far the most common usage.
* *
* @deprecated Use {@link org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, GraphSemantic)} * @deprecated Since it accepts a string instead of {@link GraphSemantic}
*/ */
@Deprecated(since = "7.0") @Deprecated(since = "7.0")
public static <R> List<R> executeList(TypedQuery<R> query, EntityGraph<R> graph, String semanticJpaHintName) { public static <R> List<R> executeList(TypedQuery<R> query, EntityGraph<R> graph, String semanticJpaHintName) {
@ -152,7 +231,7 @@ public static <R> List<R> executeList(TypedQuery<R> query, EntityGraph<R> graph,
* This is simply knowledge from JPA EG discussions, nothing that * This is simply knowledge from JPA EG discussions, nothing that
* is specifically mentioned or discussed in the spec. * is specifically mentioned or discussed in the spec.
* *
* @deprecated Use {@link org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, GraphSemantic)} * @deprecated Since it is not type safe and returns a raw type
*/ */
@Deprecated(since = "7.0") @Deprecated(since = "7.0")
public static @SuppressWarnings("rawtypes") List executeList(Query query, EntityGraph<?> graph) { public static @SuppressWarnings("rawtypes") List executeList(Query query, EntityGraph<?> graph) {
@ -170,7 +249,7 @@ public static <R> List<R> executeList(TypedQuery<R> query, EntityGraph<R> graph,
* the graph applies to that entity's type. JPA does not necessarily * the graph applies to that entity's type. JPA does not necessarily
* require that, but it is by far the most common usage. * require that, but it is by far the most common usage.
* *
* @deprecated Use {@link org.hibernate.query.SelectionQuery#setEntityGraph(EntityGraph, GraphSemantic)} * @deprecated Use {@link #setFetchGraph(TypedQuery, EntityGraph)} instead
*/ */
@Deprecated(since = "7.0") @Deprecated(since = "7.0")
public static <R> List<R> executeList(TypedQuery<R> query, EntityGraph<R> graph) { public static <R> List<R> executeList(TypedQuery<R> query, EntityGraph<R> graph) {
@ -276,7 +355,9 @@ public static boolean areEqual(
* Compares two entity subgraphs and returns {@code true} if they are equal, * Compares two entity subgraphs and returns {@code true} if they are equal,
* ignoring attribute order. * ignoring attribute order.
*/ */
public static boolean areEqual(@SuppressWarnings("rawtypes") Subgraph a, @SuppressWarnings("rawtypes") Subgraph b) { public static boolean areEqual(
@SuppressWarnings("rawtypes") Subgraph a,
@SuppressWarnings("rawtypes") Subgraph b) {
if ( a == b ) { if ( a == b ) {
return true; return true;
} }

View File

@ -24,7 +24,8 @@
* <p> * <p>
* The {@link #parseInto} methods parse the graph string into a passed graph, which may be a subgraph * The {@link #parseInto} methods parse the graph string into a passed graph, which may be a subgraph
* <p> * <p>
* Multiple graphs made for the same entity type can be merged. See {@link EntityGraphs#merge(SessionImplementor, Class, GraphImplementor...)}. * Multiple graphs made for the same entity type can be merged.
* See {@link EntityGraphs#merge(EntityManager, Class, jakarta.persistence.Graph...)}.
* *
* @author asusnjar * @author asusnjar
*/ */