org.hibernate.graph.EntityGraphs Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core-jakarta Show documentation
Show all versions of hibernate-core-jakarta Show documentation
Hibernate O/RM implementation of the Jakarta Persistence specification
/*
* 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 .
*/
package org.hibernate.graph;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.persistence.AttributeNode;
import jakarta.persistence.EntityGraph;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.Subgraph;
import jakarta.persistence.TypedQuery;
import org.hibernate.Session;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.graph.spi.GraphImplementor;
import org.hibernate.graph.spi.RootGraphImplementor;
/**
* A collection of {@link EntityGraph} utilities.
* These methods really belong inside other classes that we cannot modify, hence here.
*
* @author asusnjar
*/
@SuppressWarnings("WeakerAccess")
public final class EntityGraphs {
/**
* Merges multiple entity graphs into a single graph that specifies the fetching/loading of all attributes the input
* graphs specify.
*
* @param 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.
*/
@SuppressWarnings("unchecked")
public static EntityGraph merge(EntityManager em, Class rootType, EntityGraph... graphs) {
return merge( (SessionImplementor) em, rootType, (Object[]) graphs );
}
@SafeVarargs
public static EntityGraph merge(Session session, Class rootType, Graph... graphs) {
return merge( (SessionImplementor) session, rootType, (Object[]) graphs );
}
@SafeVarargs
public static EntityGraph merge(SessionImplementor session, Class rootType, GraphImplementor... graphs) {
return merge( session, rootType, (Object[]) graphs );
}
@SuppressWarnings("unchecked")
private static EntityGraph merge(SessionImplementor session, Class rootType, Object... graphs) {
RootGraphImplementor merged = session.createEntityGraph( rootType );
if ( graphs != null ) {
for ( Object graph : graphs ) {
merged.merge( (GraphImplementor) graph );
}
}
return merged;
}
/**
* Convenience method for {@linkplain Query#getResultList() executing} the Query, applying the
* given EntityGraph using the specified semantic
*
* @param query The JPA Query
* @param graph The graph to apply
* @param semantic The semantic to use when applying the graph
*/
@SuppressWarnings("unchecked")
public static List executeList(Query query, EntityGraph graph, GraphSemantic semantic) {
return query.unwrap( org.hibernate.query.Query.class )
.applyGraph( (RootGraph) graph, semantic )
.list();
}
/**
* Form of {@link #executeList(Query, EntityGraph, GraphSemantic)} accepting a TypedQuery.
*
* @param query The JPA Query
* @param graph The graph to apply
* @param semantic The semantic to use when applying the graph
*
* @apiNote This signature assumes that the Query's return is an entity and that the graph
* applies to that entity's type. JPA does not necessarily require that, but it is by
* far the most common usage.
*/
@SuppressWarnings({"unused", "unchecked"})
public static List executeList(TypedQuery query, EntityGraph graph, GraphSemantic semantic) {
return executeList( (Query) query, graph, semantic );
}
/**
* Convenience method for {@linkplain Query#getResultList() executing} the Query, applying the
* given EntityGraph using the named semantic using JPA's "hint name" - see
* {@link GraphSemantic#fromJpaHintName}
*
* @param query The JPA Query
* @param graph The graph to apply
* @param semanticJpaHintName See {@link GraphSemantic#fromJpaHintName}
*
* @return The result list
*/
@SuppressWarnings({"unused", "unchecked"})
public static List executeList(Query query, EntityGraph graph, String semanticJpaHintName) {
return query.unwrap( org.hibernate.query.Query.class )
.applyGraph( (RootGraph) graph, GraphSemantic.fromJpaHintName( semanticJpaHintName ) )
.list();
}
/**
* Form of {@link #executeList(Query, EntityGraph, String)} accepting a TypedQuery
*
* @param query The JPA Query
* @param graph The graph to apply
* @param semanticJpaHintName See {@link GraphSemantic#fromJpaHintName}
*
* @apiNote This signature assumes that the Query's return is an entity and that the graph
* applies to that entity's type. JPA does not necessarily require that, but it is by
* far the most common usage.
*/
@SuppressWarnings({"unused", "unchecked"})
public static List executeList(TypedQuery query, EntityGraph graph, String semanticJpaHintName) {
return executeList( (Query) query, graph, semanticJpaHintName );
}
/**
* Convenience method for {@linkplain Query#getResultList() executing} the Query using the
* given EntityGraph
*
* @param query The JPA Query
* @param graph The graph to apply
*
* @apiNote operates on the assumption that the "default" semantic for an
* entity graph applied to a Query is {@link GraphSemantic#FETCH}. This is simply
* knowledge from JPA EG discussions, nothing that is specifically mentioned or
* discussed in the spec.
*/
@SuppressWarnings({"unused", "unchecked"})
public static List executeList(Query query, EntityGraph graph) {
return query.unwrap( org.hibernate.query.Query.class )
.applyFetchGraph( (RootGraph) graph )
.list();
}
/**
* Form of {@link #executeList(Query, EntityGraph, String)} accepting a TypedQuery
*
* @param query The JPA Query
* @param graph The graph to apply
*
* @apiNote This signature assumes that the Query's return is an entity and that the graph
* applies to that entity's type. JPA does not necessarily require that, but it is by
* far the most common usage.
*/
@SuppressWarnings("unused")
public static List executeList(TypedQuery query, EntityGraph graph) {
return executeList( query, graph, GraphSemantic.FETCH );
}
// todo : ? - we could add JPA's other Query execution methods
// but really, I think unwrapping as Hibernate's Query and using our
// "proprietary" methods is better (this class is "proprietary" too).
/**
* Compares two entity graphs and returns {@code true} if they are equal, ignoring attribute order.
*
* @param Root entity type of BOTH graphs.
* @param a Graph to compare.
* @param b Graph to compare.
*
*/
public static boolean areEqual(EntityGraph a, EntityGraph b) {
if ( a == b ) {
return true;
}
if ( ( a == null ) || ( b == null ) ) {
return false;
}
List> aNodes = a.getAttributeNodes();
List> 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 ( !areEqual( 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 areEqual(AttributeNode> a, AttributeNode> b) {
if ( a == b ) {
return true;
}
if ( ( a == null ) || ( b == null ) ) {
return false;
}
if ( a.getAttributeName().equals( b.getAttributeName() ) ) {
return areEqual( a.getSubgraphs(), b.getSubgraphs() ) && areEqual( 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 areEqual(@SuppressWarnings("rawtypes") Map a, @SuppressWarnings("rawtypes") Map b) {
if ( a == b ) {
return true;
}
if ( ( a == null ) || ( b == null ) ) {
return false;
}
@SuppressWarnings("rawtypes")
Set aKeys = a.keySet();
@SuppressWarnings("rawtypes")
Set bKeys = b.keySet();
if ( aKeys.equals( bKeys ) ) {
for ( Class> clazz : aKeys ) {
if ( !bKeys.contains( clazz ) ) {
return false;
}
if ( !areEqual( 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 areEqual(@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> aNodes = a.getAttributeNodes();
@SuppressWarnings("unchecked")
List> 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 ( !areEqual( aNode, bNode ) ) {
return false;
}
}
return true;
}
}