diff --git a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc index 18cea708582f..e9c941b0d091 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc @@ -326,16 +326,36 @@ type is the `LegalEntity` base type we might have: ==== [source, java, indent=0] ---- +responsibleParty:Corporation(ceo) +---- +==== +[WARNING] +==== +Another syntax you may have seen is the one below. +[source, java, indent=0] +---- responsibleParty(Corporation: ceo) ---- +This syntax is deprecated and will no longer be supported in the next major release. + ==== +Additionally, it is possible to define subgraphs that apply to a specific **root subtype** using the syntax: + +==== +[source, java, indent=0] +---- +:Corporation(name, ceo), :NonProfit(sector) +---- + +==== +This allows expressing that the graph should apply when the root entity itself is a Corporation. We can even duplicate the attribute names to apply different subtype subgraphs: ==== [source, java, indent=0] ---- -responsibleParty(taxIdNumber), responsibleParty(Corporation: ceo), responsibleParty(NonProfit: sector) +responsibleParty(taxIdNumber), responsibleParty:Corporation(ceo), responsibleParty:NonProfit(sector) ---- ==== @@ -380,7 +400,7 @@ to the `@jakarta.persistence.NamedEntityGraph`, supporting the text representati may be placed on an entity or on a package. -.@NamedEntityGraph example +.@NamedEntityGraph example of annotation placed on an entity ==== [source, java, indent=0] ---- @@ -390,6 +410,37 @@ class Book { // ... } ---- + +==== +.@NamedEntityGraph example of annotation placed on a package +==== +[source, java, indent=0] +---- +// package-info.java +@NamedEntityGraph(root = Book.class, name = "book-title", graph = "title") +package org.somepackage; + +import org.hibernate.annotations.NamedEntityGraph; +---- + +==== +[WARNING] +==== +You may have already seen the syntax below, which specifies the type of graph via the graph attribute. +[source, java, indent=0] +---- +@NamedEntityGraph( + name = "book-title", + graph = "Book: title" // Here +) +package org.somepackage; + +import org.hibernate.annotations.NamedEntityGraph; +---- + +This syntax is deprecated and will no longer be supported in the next major release. + + ==== diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageParser.g4 index a0e6c33b9974..36a09e098ae8 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageParser.g4 @@ -21,15 +21,32 @@ package org.hibernate.grammars.graph; */ } - graph - : typeIndicator? attributeList - ; + : typeIndicator? graphElementList + ; + +graphElementList + : graphElement (COMMA graphElement)* + ; + + +graphElement + : treatedSubGraph + | attributeNode + ; + +treatedSubGraph + : subTypeIndicator LPAREN attributeList RPAREN + ; typeIndicator : TYPE_NAME COLON ; +subTypeIndicator + : COLON TYPE_NAME + ; + attributeList : attributeNode (COMMA attributeNode)* ; @@ -47,6 +64,10 @@ attributeQualifier ; subGraph - : LPAREN typeIndicator? attributeList RPAREN - ; + : subGraphWithTypeIndicator + | treatedSubGraph + ; +subGraphWithTypeIndicator + : LPAREN typeIndicator? attributeList RPAREN + ; diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java b/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java index aa5aed0ac291..6335a35c50dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java @@ -39,6 +39,14 @@ @Retention(RUNTIME) @Repeatable(NamedEntityGraphs.class) public @interface NamedEntityGraph { + + /** + * Names the entity that is the root of the {@linkplain #graph graph}. + * When the annotation is applied to a class, the class itself is assumed. + * When applied to a package, this attribute is required. + */ + Class root() default void.class; + /** * The name used to identify the entity graph in calls to * {@linkplain org.hibernate.Session#getEntityGraph(String)}. diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java index a264f3521fd2..08eb353a0ef7 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/NamedGraphCreatorParsed.java @@ -13,9 +13,11 @@ import org.hibernate.grammars.graph.GraphLanguageLexer; import org.hibernate.grammars.graph.GraphLanguageParser; import org.hibernate.graph.InvalidGraphException; +import org.hibernate.graph.InvalidNamedEntityGraphParameterException; import org.hibernate.graph.internal.parse.EntityNameResolver; import org.hibernate.graph.internal.parse.GraphParsing; import org.hibernate.graph.spi.RootGraphImplementor; +import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.metamodel.model.domain.EntityDomainType; import java.util.function.Function; @@ -60,24 +62,67 @@ public EntityDomainType resolveEntityName(String entityName) { } }; + final EntityDomainType entityDomainType = resolveEntityDomainType( + graphContext, + entityDomainClassResolver, + entityDomainNameResolver + ); + + final String name = this.name == null ? entityDomainType.getName() : this.name; + + return GraphParsing.parse( name, entityDomainType, graphContext.graphElementList(), entityNameResolver ); + + } + + private EntityDomainType resolveEntityDomainType( + GraphLanguageParser.GraphContext graphContext, + Function, EntityDomainType> entityDomainClassResolver, + Function> entityDomainNameResolver) { + var typeIndicator = graphContext.typeIndicator(); + final Class annotationRootAttribute = annotation.root(); + if ( entityType == null ) { - if ( graphContext.typeIndicator() == null ) { - throw new InvalidGraphException( "Expecting graph text to include an entity name : " + annotation.graph() ); + + if ( typeIndicator == null && void.class.equals( annotationRootAttribute ) ) { + throw new InvalidNamedEntityGraphParameterException( + "The 'root' parameter of the @NamedEntityGraph should be passed. Graph : " + annotation.name() + ); + } + + if ( typeIndicator != null ) { + DeprecationLogger.DEPRECATION_LOGGER.deprecatedNamedEntityGraphTextThatContainTypeIndicator(); + + //noinspection unchecked + return (EntityDomainType) entityDomainNameResolver.apply( + typeIndicator.TYPE_NAME().toString() ); + } - final String jpaEntityName = graphContext.typeIndicator().TYPE_NAME().toString(); + //noinspection unchecked - final EntityDomainType entityDomainType = (EntityDomainType) entityDomainNameResolver.apply( jpaEntityName ); - final String name = this.name == null ? jpaEntityName : this.name; - return GraphParsing.parse( name, entityDomainType, graphContext.attributeList(), entityNameResolver ); + return (EntityDomainType) entityDomainClassResolver.apply( (Class) annotationRootAttribute ); } - else { - if ( graphContext.typeIndicator() != null ) { - throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + annotation.graph() ); + + + if ( typeIndicator != null ) { + throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + annotation.graph() ); + } + + if ( !void.class.equals( annotationRootAttribute ) ) { + + if ( !annotationRootAttribute.equals( entityType ) ) { + throw new InvalidNamedEntityGraphParameterException( + "The 'root' parameter of the @NamedEntityGraph annotation must reference the entity '" + + entityType.getName() + + "', but '" + annotationRootAttribute.getName() + "' was provided." + + " Graph :" + annotation.name() + ); } + //noinspection unchecked - final EntityDomainType entityDomainType = (EntityDomainType) entityDomainClassResolver.apply( (Class) entityType ); - final String name = this.name == null ? entityDomainType.getName() : this.name; - return GraphParsing.parse( name, entityDomainType, graphContext.attributeList(), entityNameResolver ); + return (EntityDomainType) entityDomainClassResolver.apply( (Class) annotationRootAttribute ); } + + //noinspection unchecked + return (EntityDomainType) entityDomainClassResolver.apply( (Class) entityType ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/NamedEntityGraphAnnotation.java b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/NamedEntityGraphAnnotation.java index 488734b42b83..6e6f17b9dcb5 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/NamedEntityGraphAnnotation.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/NamedEntityGraphAnnotation.java @@ -17,6 +17,7 @@ public class NamedEntityGraphAnnotation implements NamedEntityGraph { private String name; private String graph; + private Class root; /** * Used in creating dynamic annotation instances (e.g. from XML) @@ -31,6 +32,7 @@ public NamedEntityGraphAnnotation(ModelsContext modelContext) { public NamedEntityGraphAnnotation(NamedEntityGraph annotation, ModelsContext modelContext) { this.name = annotation.name(); this.graph = annotation.graph(); + this.root = annotation.root(); } /** @@ -39,6 +41,7 @@ public NamedEntityGraphAnnotation(NamedEntityGraph annotation, ModelsContext mod public NamedEntityGraphAnnotation(Map attributeValues, ModelsContext modelContext) { this.name = (String) attributeValues.get( "name" ); this.graph = (String) attributeValues.get( "graph" ); + this.root = (Class) attributeValues.get( "root" ); } @Override @@ -46,6 +49,15 @@ public Class annotationType() { return NamedEntityGraph.class; } + @Override + public Class root() { + return this.root; + } + + public void root(Class root) { + this.root = root; + } + @Override public String name() { return name; diff --git a/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java b/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java index 0bfcb46624ad..0ebce8b77f11 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/GraphParser.java @@ -116,16 +116,18 @@ public static RootGraph parse( * @see org.hibernate.SessionFactory#parseEntityGraph(Class, CharSequence) * * @since 7.0 + * @deprecated This usage is deprecated. You must specify the entityType {see {@link #parse(Class, CharSequence, SessionFactory)} or {@link #parse(String, CharSequence, SessionFactory)} */ - public static RootGraph parse( - final CharSequence graphText, - final SessionFactory sessionFactory) { - if ( graphText == null ) { - return null; - } - return GraphParsing.parse( - graphText.toString(), - sessionFactory.unwrap( SessionFactoryImplementor.class ) + @Deprecated(forRemoval = true) + public static RootGraph parse( + final CharSequence graphText, + final SessionFactory sessionFactory) { + if ( graphText == null ) { + return null; + } + return GraphParsing.parse( + graphText.toString(), + sessionFactory.unwrap( SessionFactoryImplementor.class ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/InvalidNamedEntityGraphParameterException.java b/hibernate-core/src/main/java/org/hibernate/graph/InvalidNamedEntityGraphParameterException.java new file mode 100644 index 000000000000..1b1b65a4a7dc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/graph/InvalidNamedEntityGraphParameterException.java @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.graph; + +import org.hibernate.HibernateException; + +public class InvalidNamedEntityGraphParameterException extends HibernateException { + private static final long serialVersionUID = 1L; + + public InvalidNamedEntityGraphParameterException(String message) { + super( message ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/EntityNameResolver.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/EntityNameResolver.java index 10073e2468b4..d03bc96bf164 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/EntityNameResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/EntityNameResolver.java @@ -5,6 +5,7 @@ package org.hibernate.graph.internal.parse; import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.metamodel.model.domain.ManagedDomainType; /** * @author Steve Ebersole @@ -12,4 +13,12 @@ @FunctionalInterface public interface EntityNameResolver { EntityDomainType resolveEntityName(String entityName); + + static ManagedDomainType managedType(String subtypeName, EntityNameResolver entityNameResolver) { + final EntityDomainType entityDomainType = entityNameResolver.resolveEntityName( subtypeName ); + if ( entityDomainType == null ) { + throw new IllegalArgumentException( "Unknown managed type: " + subtypeName ); + } + return entityDomainType; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java index e02e5f06ee03..c99d881f71bc 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParser.java @@ -12,6 +12,7 @@ import org.hibernate.graph.spi.AttributeNodeImplementor; import org.hibernate.graph.spi.GraphImplementor; import org.hibernate.graph.spi.SubGraphImplementor; +import org.hibernate.internal.log.DeprecationLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; @@ -27,7 +28,7 @@ public class GraphParser extends GraphLanguageParserBaseVisitor> { private final EntityNameResolver entityNameResolver; private final Stack> graphStack = new StandardStack<>(); - private final Stack> attributeNodeStack = new StandardStack<>(); + private final Stack> attributeNodeStack = new StandardStack<>(); private final Stack graphSourceStack = new StandardStack<>(); public GraphParser(EntityNameResolver entityNameResolver) { @@ -37,7 +38,6 @@ public GraphParser(EntityNameResolver entityNameResolver) { /** * @apiNote It is important that this form only be used after the session-factory is fully * initialized, especially the {@linkplain SessionFactoryImplementor#getJpaMetamodel()} JPA metamodel}. - * * @see GraphParser#GraphParser(EntityNameResolver) */ public GraphParser(SessionFactoryImplementor sessionFactory) { @@ -49,7 +49,50 @@ public Stack> getGraphStack() { } @Override - public AttributeNodeImplementor visitAttributeNode(GraphLanguageParser.AttributeNodeContext attributeNodeContext) { + public GraphNode visitTreatedSubGraph(GraphLanguageParser.TreatedSubGraphContext treatedSubGraphContext) { + final String subTypeName = treatedSubGraphContext.subTypeIndicator().TYPE_NAME() == null ? + null : + treatedSubGraphContext.subTypeIndicator().TYPE_NAME().getText(); + + if ( PARSING_LOGGER.isDebugEnabled() ) { + PARSING_LOGGER.debugf( + "%s Starting subtype graph : %s", + StringHelper.repeat( ">>", attributeNodeStack.depth() + 2 ), + subTypeName + ); + } + + var currentGraph = graphStack.getCurrent(); + + var subTypeSubGraph = currentGraph.addTreatedSubgraph( + EntityNameResolver.managedType( + subTypeName, + entityNameResolver + ) + ); + + graphStack.push( subTypeSubGraph ); + + try { + treatedSubGraphContext.attributeList().accept( this ); + } + finally { + graphStack.pop(); + } + + if ( PARSING_LOGGER.isDebugEnabled() ) { + PARSING_LOGGER.debugf( + "%s Finished subtype graph : %s", + StringHelper.repeat( "<<", attributeNodeStack.depth() + 2 ), + subTypeSubGraph.getGraphedType().getTypeName() + ); + } + + return subTypeSubGraph; + } + + @Override + public AttributeNodeImplementor visitAttributeNode(GraphLanguageParser.AttributeNodeContext attributeNodeContext) { final String attributeName = attributeNodeContext.attributePath().ATTR_NAME().getText(); final SubGraphGenerator subGraphCreator; @@ -66,7 +109,10 @@ public AttributeNodeImplementor visitAttributeNode(GraphLanguageParser.At subGraphCreator = PathQualifierType.VALUE.getSubGraphCreator(); } else { - final String qualifierName = attributeNodeContext.attributePath().attributeQualifier().ATTR_NAME().getText(); + final String qualifierName = attributeNodeContext.attributePath() + .attributeQualifier() + .ATTR_NAME() + .getText(); if ( PARSING_LOGGER.isTraceEnabled() ) { PARSING_LOGGER.tracef( @@ -81,7 +127,7 @@ public AttributeNodeImplementor visitAttributeNode(GraphLanguageParser.At subGraphCreator = pathQualifierType.getSubGraphCreator(); } - final AttributeNodeImplementor attributeNode = resolveAttributeNode( attributeName ); + final AttributeNodeImplementor attributeNode = resolveAttributeNode( attributeName ); if ( attributeNodeContext.subGraph() != null ) { attributeNodeStack.push( attributeNode ); @@ -108,11 +154,11 @@ public AttributeNodeImplementor visitAttributeNode(GraphLanguageParser.At return attributeNode; } - private AttributeNodeImplementor resolveAttributeNode(String attributeName) { + private AttributeNodeImplementor resolveAttributeNode(String attributeName) { final GraphImplementor currentGraph = graphStack.getCurrent(); assert currentGraph != null; - final AttributeNodeImplementor attributeNode = currentGraph.findOrCreateAttributeNode( attributeName ); + final AttributeNodeImplementor attributeNode = currentGraph.findOrCreateAttributeNode( attributeName ); assert attributeNode != null; return attributeNode; @@ -132,7 +178,7 @@ private PathQualifierType resolvePathQualifier(String qualifier) { @Override public SubGraphImplementor visitSubGraph(GraphLanguageParser.SubGraphContext subGraphContext) { - final String subTypeName = subGraphContext.typeIndicator() == null ? null : subGraphContext.typeIndicator().TYPE_NAME().getText(); + final String subTypeName = getSubGraphSubTypeName( subGraphContext ); if ( PARSING_LOGGER.isTraceEnabled() ) { PARSING_LOGGER.tracef( @@ -142,7 +188,7 @@ public SubGraphImplementor visitSubGraph(GraphLanguageParser.SubGraphContext ); } - final AttributeNodeImplementor attributeNode = attributeNodeStack.getCurrent(); + final AttributeNodeImplementor attributeNode = attributeNodeStack.getCurrent(); final SubGraphGenerator subGraphCreator = graphSourceStack.getCurrent(); final SubGraphImplementor subGraph = subGraphCreator.createSubGraph( @@ -151,10 +197,14 @@ public SubGraphImplementor visitSubGraph(GraphLanguageParser.SubGraphContext entityNameResolver ); + final GraphLanguageParser.AttributeListContext attributListContext = getSubGraphAttributeListContext( + subGraphContext ); + graphStack.push( subGraph ); + try { - subGraphContext.attributeList().accept( this ); + attributListContext.accept( this ); } finally { graphStack.pop(); @@ -170,4 +220,33 @@ public SubGraphImplementor visitSubGraph(GraphLanguageParser.SubGraphContext return subGraph; } + + private static String getSubGraphSubTypeName(GraphLanguageParser.SubGraphContext subGraphContext) { + var treatedSubGraph = subGraphContext.treatedSubGraph(); + var subGraphWithTypeIndicator = subGraphContext.subGraphWithTypeIndicator(); + + if ( treatedSubGraph != null && treatedSubGraph.subTypeIndicator() != null ) { + return treatedSubGraph.subTypeIndicator().TYPE_NAME().getText(); + } + + if ( subGraphWithTypeIndicator != null && subGraphWithTypeIndicator.typeIndicator() != null ) { + + DeprecationLogger.DEPRECATION_LOGGER.deprecatedSubGraphWithTypeIndicatorSyntax(); + + return subGraphWithTypeIndicator.typeIndicator().TYPE_NAME().getText(); + } + + return null; + } + + private static GraphLanguageParser.AttributeListContext getSubGraphAttributeListContext(GraphLanguageParser.SubGraphContext subGraphContext) { + var treatedSubGraph = subGraphContext.treatedSubGraph(); + var subGraphWithTypeIndicator = subGraphContext.subGraphWithTypeIndicator(); + + if ( subGraphWithTypeIndicator != null ) { + return subGraphWithTypeIndicator.attributeList(); + } + + return treatedSubGraph.attributeList(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java index 2c8be74ef601..d1742f21edd9 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/GraphParsing.java @@ -7,6 +7,7 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.checkerframework.checker.nullness.qual.Nullable; + import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.grammars.graph.GraphLanguageLexer; import org.hibernate.grammars.graph.GraphLanguageParser; @@ -41,7 +42,7 @@ public static RootGraphImplementor parse( } final EntityDomainType entityType = sessionFactory.getJpaMetamodel().entity( entityClass ); - return parse( entityType, graphContext.attributeList(), sessionFactory ); + return parse( entityType, graphContext.graphElementList(), sessionFactory ); } public static RootGraphImplementor parse( @@ -62,7 +63,7 @@ public static RootGraphImplementor parse( throw new InvalidGraphException( "Expecting graph text to not include an entity name : " + graphText ); } - return parse( entityDomainType, graphContext.attributeList(), sessionFactory ); + return parse( entityDomainType, graphContext.graphElementList(), sessionFactory ); } public static RootGraphImplementor parse( @@ -84,10 +85,12 @@ public static RootGraphImplementor parse( } //noinspection unchecked - final EntityDomainType entityType = (EntityDomainType) sessionFactory.getJpaMetamodel().entity( entityName ); - return parse( entityType, graphContext.attributeList(), sessionFactory ); + final EntityDomainType entityType = (EntityDomainType) sessionFactory.getJpaMetamodel() + .entity( entityName ); + return parse( entityType, graphContext.graphElementList(), sessionFactory ); } + @Deprecated(forRemoval = true) public static RootGraphImplementor parse( String graphText, SessionFactoryImplementor sessionFactory) { @@ -106,35 +109,36 @@ public static RootGraphImplementor parse( final String entityName = graphContext.typeIndicator().TYPE_NAME().getText(); //noinspection unchecked - final EntityDomainType entityType = (EntityDomainType) sessionFactory.getJpaMetamodel().entity( entityName ); - return parse( entityType, graphContext.attributeList(), sessionFactory ); + final EntityDomainType entityType = (EntityDomainType) sessionFactory.getJpaMetamodel() + .entity( entityName ); + return parse( entityType, graphContext.graphElementList(), sessionFactory ); } public static RootGraphImplementor parse( EntityDomainType rootType, - GraphLanguageParser.AttributeListContext attributeListContext, + GraphLanguageParser.GraphElementListContext graphElementListContext, SessionFactoryImplementor sessionFactory) { - return parse( rootType, attributeListContext, new EntityNameResolverSessionFactory( sessionFactory ) ); + return parse( rootType, graphElementListContext, new EntityNameResolverSessionFactory( sessionFactory ) ); } public static RootGraphImplementor parse( EntityDomainType rootType, - GraphLanguageParser.AttributeListContext attributeListContext, + GraphLanguageParser.GraphElementListContext graphElementListContext, EntityNameResolver entityNameResolver) { - return parse( null, rootType, attributeListContext, entityNameResolver ); + return parse( null, rootType, graphElementListContext, entityNameResolver ); } public static RootGraphImplementor parse( @Nullable String name, EntityDomainType rootType, - GraphLanguageParser.AttributeListContext attributeListContext, + GraphLanguageParser.GraphElementListContext graphElementListContext, EntityNameResolver entityNameResolver) { final RootGraphImpl targetGraph = new RootGraphImpl<>( name, rootType ); final GraphParser visitor = new GraphParser( entityNameResolver ); visitor.getGraphStack().push( targetGraph ); try { - visitor.visitAttributeList( attributeListContext ); + visitor.visitGraphElementList( graphElementListContext ); } finally { visitor.getGraphStack().pop(); @@ -167,7 +171,7 @@ public static void parseInto( visitor.getGraphStack().push( targetGraph ); try { - visitor.visitAttributeList( graphContext.attributeList() ); + visitor.visitGraphElementList( graphContext.graphElementList() ); } finally { visitor.getGraphStack().pop(); diff --git a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/PathQualifierType.java b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/PathQualifierType.java index 3687da5df37f..0fc19b6d3c84 100644 --- a/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/PathQualifierType.java +++ b/hibernate-core/src/main/java/org/hibernate/graph/internal/parse/PathQualifierType.java @@ -5,8 +5,7 @@ package org.hibernate.graph.internal.parse; -import org.hibernate.metamodel.model.domain.EntityDomainType; -import org.hibernate.metamodel.model.domain.ManagedDomainType; +import static org.hibernate.graph.internal.parse.EntityNameResolver.managedType; /** * @author Steve Ebersole @@ -23,14 +22,6 @@ public enum PathQualifierType { : attributeNode.addValueSubgraph().addTreatedSubgraph( managedType( subtypeName, entityNameResolver ) ) ); - private static ManagedDomainType managedType(String subtypeName, EntityNameResolver entityNameResolver) { - final EntityDomainType entityDomainType = entityNameResolver.resolveEntityName( subtypeName ); - if ( entityDomainType == null ) { - throw new IllegalArgumentException( "Unknown managed type: " + subtypeName ); - } - return entityDomainType; - } - private final SubGraphGenerator subGraphCreator; PathQualifierType(SubGraphGenerator subgraphCreator) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java index 1a710831a140..8206a3e14517 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/log/DeprecationLogger.java @@ -226,4 +226,19 @@ void recognizedObsoleteHibernateNamespace( value = "Encountered deprecated hint [%s], use [%s] instead" ) void deprecatedHint(String deprecatedHint, String replacementHint); + + @LogMessage(level = WARN) + @Message( + id = 90000038, + value = "Deprecated graph syntax: use 'field:SubType(attr1, ...)' instead of 'field(SubType: attr1, ...)'." + ) + void deprecatedSubGraphWithTypeIndicatorSyntax(); + + @LogMessage(level = WARN) + @Message( + id = 90000039, + value = "Deprecated syntax when using @NamedEntityGraph: 'Type: attr1, attr2' is deprecated. " + + "Specify the root entity using the 'root' attribute instead of prefixing the graph with the entity type." + ) + void deprecatedNamedEntityGraphTextThatContainTypeIndicator(); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ClassLevelTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ClassLevelTests.java index 2051af3f2f86..00a94a0b8ac6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ClassLevelTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/ClassLevelTests.java @@ -9,6 +9,8 @@ import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.graph.InvalidGraphException; +import org.hibernate.graph.InvalidNamedEntityGraphParameterException; +import org.hibernate.orm.test.entitygraph.named.parsed.entity.BadRootClassEntity; import org.hibernate.orm.test.entitygraph.named.parsed.entity.Book; import org.hibernate.orm.test.entitygraph.named.parsed.entity.DomesticPublishingHouse; import org.hibernate.orm.test.entitygraph.named.parsed.entity.Duplicator; @@ -18,6 +20,7 @@ import org.hibernate.orm.test.entitygraph.named.parsed.entity.Person; import org.hibernate.orm.test.entitygraph.named.parsed.entity.Publisher; import org.hibernate.orm.test.entitygraph.named.parsed.entity.PublishingHouse; + import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModelScope; import org.hibernate.testing.orm.junit.ServiceRegistry; @@ -53,10 +56,25 @@ void testRegistrations(SessionFactoryScope factoryScope) { final SessionFactoryImplementor sessionFactory = factoryScope.getSessionFactory(); assertBasicAttributes( sessionFactory.findEntityGraphByName( "book-title-isbn" ), "title", "isbn" ); - assertBasicAttributes( sessionFactory.findEntityGraphByName( "book-title-isbn-author" ), "title", "isbn", "author" ); - assertBasicAttributes( sessionFactory.findEntityGraphByName( "book-title-isbn-editor" ), "title", "isbn", "editor" ); + assertBasicAttributes( + sessionFactory.findEntityGraphByName( "book-title-isbn-author" ), + "title", + "isbn", + "author" + ); + assertBasicAttributes( + sessionFactory.findEntityGraphByName( "book-title-isbn-editor" ), + "title", + "isbn", + "editor" + ); - assertBasicAttributes( sessionFactory.findEntityGraphByName( "publishing-house-bio" ), "name", "ceo", "boardMembers" ); + assertBasicAttributes( + sessionFactory.findEntityGraphByName( "publishing-house-bio" ), + "name", + "ceo", + "boardMembers" + ); } @Test @@ -74,6 +92,18 @@ void testInvalidParsedGraph(DomainModelScope modelScope) { } } + @Test + @DomainModel(annotatedClasses = BadRootClassEntity.class) + void testRootEntityDifferentFromEntityMarkedWithAnnotation(DomainModelScope modelScope) { + final MetadataImplementor domainModel = modelScope.getDomainModel(); + + try (org.hibernate.SessionFactory sessionFactory = domainModel.buildSessionFactory()) { + fail( "Expecting an exception" ); + } + catch (InvalidNamedEntityGraphParameterException expected) { + } + } + @Test @ServiceRegistry void testDuplicateNames(ServiceRegistryScope registryScope) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/PackageLevelTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/PackageLevelTests.java index 97ef1080c219..33ec5bad4b15 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/PackageLevelTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/PackageLevelTests.java @@ -8,7 +8,7 @@ import org.hibernate.boot.MetadataSources; import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.graph.InvalidGraphException; +import org.hibernate.graph.InvalidNamedEntityGraphParameterException; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.orm.test.entitygraph.named.parsed.pkg.Book; import org.hibernate.orm.test.entitygraph.named.parsed.pkg.Duplicator; @@ -43,6 +43,8 @@ void testDiscovery(SessionFactoryScope factoryScope) { assertBasicGraph( sessionFactory, "book-title-isbn", "title", "isbn" ); assertBasicGraph( sessionFactory, "book-title-isbn-author", "title", "isbn", "author" ); assertBasicGraph( sessionFactory, "book-title-isbn-editor", "title", "isbn", "editor" ); + assertBasicGraph( sessionFactory, "book-title-with-root-attribute", "title" ); + assertBasicGraph( sessionFactory, "book-title-with-root-attribute-and-type-indicator", "title" ); } private static void assertBasicGraph(SessionFactoryImplementor sessionFactory, String name, String... names) { @@ -75,7 +77,7 @@ void testInvalid(DomainModelScope modelScope) { try (org.hibernate.SessionFactory sessionFactory = modelScope.getDomainModel().buildSessionFactory()) { fail( "Expected an exception" ); } - catch (InvalidGraphException expected) { + catch (InvalidNamedEntityGraphParameterException expected) { } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/BadRootClassEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/BadRootClassEntity.java new file mode 100644 index 000000000000..5d0b1c2d7cd3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/BadRootClassEntity.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.named.parsed.entity; + + +import org.hibernate.annotations.NamedEntityGraph; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +@Entity(name = "BadRootClassEntity") +@Table(name = "BadRootClassEntity") +@NamedEntityGraph(root = Book.class, name = "bad-root", graph = "name") +public class BadRootClassEntity { + @Id + private Integer id; + private String name; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/GraphWithRootClassEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/GraphWithRootClassEntity.java new file mode 100644 index 000000000000..8dcf720e7ac5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/entity/GraphWithRootClassEntity.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.entitygraph.named.parsed.entity; + +import org.hibernate.annotations.NamedEntityGraph; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + + +@Entity(name = "GraphWithRootClassEntity") +@Table(name = "GraphWithRootClassEntity") +@NamedEntityGraph(root = GraphWithRootClassEntity.class, name = "valid-root-on-annotation", graph = "name") +public class GraphWithRootClassEntity { + @Id + private Integer id; + private String name; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg/package-info.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg/package-info.java index c0dfa7ae3ed0..a5b0a69d6273 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg/package-info.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/named/parsed/pkg/package-info.java @@ -7,10 +7,12 @@ * @author Steve Ebersole */ -@NamedEntityGraph( name = "book-title-isbn", graph = "Book: title, isbn") -@NamedEntityGraph( name = "book-title-isbn-author", graph = "Book: title, isbn, author") -@NamedEntityGraph( name = "book-title-isbn-editor", graph = "Book: title, isbn, editor") -@NamedEntityGraph( name = "duplicated-name", graph = "Book: title") +@NamedEntityGraph(name = "book-title-isbn", graph = "Book: title, isbn") +@NamedEntityGraph(name = "book-title-isbn-author", graph = "Book: title, isbn, author") +@NamedEntityGraph(name = "book-title-isbn-editor", graph = "Book: title, isbn, editor") +@NamedEntityGraph(name = "duplicated-name", graph = "Book: title") +@NamedEntityGraph(root = Book.class, name = "book-title-with-root-attribute", graph = "title") +@NamedEntityGraph(root = Book.class, name = "book-title-with-root-attribute-and-type-indicator", graph = "Book: title") package org.hibernate.orm.test.entitygraph.named.parsed.pkg; import org.hibernate.annotations.NamedEntityGraph; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/EntityGraphParserTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/EntityGraphParserTest.java index d53bbd42882e..20266b7740c7 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/EntityGraphParserTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/parser/EntityGraphParserTest.java @@ -65,6 +65,45 @@ public void testLinkParsing() { AssertionHelper.assertBasicAttributes( sub.get( GraphParsingTestEntity.class ), "name", "description" ); } + @Test + public void testSubtypeAndTwoBasicAttributesParsing() { + var graph = parseGraph( "name, :GraphParsingTestSubEntity(sub), description" ); + assertNotNull( graph ); + + AssertionHelper.assertBasicAttributes( graph, "name", "description" ); + + var treatedSubgraphs = graph.getTreatedSubgraphs(); + assertEquals( 1, treatedSubgraphs.size() ); + + var subEntityGraph = treatedSubgraphs.get( GraphParsingTestSubEntity.class ); + var subEntityGraphAttributes = subEntityGraph.getAttributeNodes(); + assertNotNull( subEntityGraphAttributes ); + assertEquals( 1, subEntityGraphAttributes.size() ); + + var subEntityGraphAttributeNode = subEntityGraphAttributes.get( 0 ); + assertNotNull( subEntityGraphAttributeNode ); + assertEquals( "sub", subEntityGraphAttributeNode.getAttributeName() ); + } + + @Test + public void testSubtypeParsing() { + var graph = parseGraph( ":GraphParsingTestSubEntity(sub)" ); + assertNotNull( graph ); + + var treatedSubgraphs = graph.getTreatedSubgraphs(); + assertEquals( 1, treatedSubgraphs.size() ); + + var subEntityGraph = treatedSubgraphs.get( GraphParsingTestSubEntity.class ); + var subEntityGraphAttributes = subEntityGraph.getAttributeNodes(); + + assertNotNull( subEntityGraphAttributes ); + assertEquals( 1, subEntityGraphAttributes.size() ); + + var attributeNode = subEntityGraphAttributes.get( 0 ); + assertNotNull( attributeNode ); + assertEquals( "sub", attributeNode.getAttributeName() ); + } + @Test public void testMapKeyParsing() { EntityGraph graph = parseGraph( "map.key(name, description)" ); @@ -173,14 +212,37 @@ public void testMixParsingWithSimplifiedMaps() { @Test public void testLinkSubtypeParsing() { - RootGraphImplementor graph = parseGraph( "linkToOne(name, description), linkToOne(GraphParsingTestSubEntity: sub)" ); + RootGraphImplementor graph = parseGraph( + "linkToOne(name, description), linkToOne(GraphParsingTestSubEntity: sub)" ); + assertNotNull( graph ); + + List> attrs = graph.getAttributeNodeList(); + assertNotNull( attrs ); + assertEquals( 1, attrs.size() ); + + AttributeNodeImplementor linkToOneNode = attrs.get( 0 ); + assertNotNull( linkToOneNode ); + assertEquals( "linkToOne", linkToOneNode.getAttributeName() ); + + AssertionHelper.assertNullOrEmpty( linkToOneNode.getKeySubgraphs() ); + + final SubGraphImplementor subgraph = linkToOneNode.getSubGraphs().get( GraphParsingTestSubEntity.class ); + assertNotNull( subgraph ); + + AssertionHelper.assertBasicAttributes( subgraph, "sub" ); + } + + @Test + public void testLinkSubtypeParsingWithNewSyntax() { + RootGraphImplementor graph = parseGraph( + "linkToOne(name, description), linkToOne:GraphParsingTestSubEntity(sub)" ); assertNotNull( graph ); - List> attrs = graph.getAttributeNodeList(); + List> attrs = graph.getAttributeNodeList(); assertNotNull( attrs ); assertEquals( 1, attrs.size() ); - AttributeNodeImplementor linkToOneNode = attrs.get( 0 ); + AttributeNodeImplementor linkToOneNode = attrs.get( 0 ); assertNotNull( linkToOneNode ); assertEquals( "linkToOne", linkToOneNode.getAttributeName() ); @@ -204,7 +266,7 @@ public void testHHH10378IsNotFixedYet() { assertEquals( subGraph.getGraphedType().getJavaType(), GraphParsingTestSubEntity.class ); - final AttributeNodeImplementor subTypeAttrNode = subGraph.findOrCreateAttributeNode( "sub" ); + final AttributeNodeImplementor subTypeAttrNode = subGraph.findOrCreateAttributeNode( "sub" ); assert subTypeAttrNode != null; } @@ -221,7 +283,10 @@ public void testHHH12696MapSubgraphsKeyFirst() { checkMapKeyAndValueSubgraphs( graph, mapAttributeName, keySubgraph, valueSubgraph ); } - private void checkMapKeyAndValueSubgraphs(EntityGraph graph, final String mapAttributeName, Subgraph keySubgraph, + private void checkMapKeyAndValueSubgraphs( + EntityGraph graph, + final String mapAttributeName, + Subgraph keySubgraph, Subgraph valueSubgraph) { int count = 0; for ( AttributeNode node : graph.getAttributeNodes() ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/EntityGraphsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/EntityGraphsTest.java index 50a9f5cf1060..4c734cbb705d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/graph/EntityGraphsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/graph/EntityGraphsTest.java @@ -13,7 +13,6 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.testing.orm.junit.JiraKey; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; public class EntityGraphsTest extends AbstractEntityGraphTest { @@ -64,10 +63,9 @@ public void testEqualLinksEqual1() { } @Test - @Ignore("Cannot run due to Hibernate bug: https://hibernate.atlassian.net/browse/HHH-10378") public void testEqualLinksWithSubclassesEqual() { - EntityGraph a = parseGraph( "linkToOne(name), linkToOne:MockSubentity(description)" ); - EntityGraph b = parseGraph( "linkToOne:MockSubentity(description), linkToOne(name)" ); + EntityGraph a = parseGraph( "linkToOne(name), linkToOne:GraphParsingTestSubentity(description)" ); + EntityGraph b = parseGraph( "linkToOne:GraphParsingTestSubentity(description), linkToOne(name)" ); Assert.assertTrue( EntityGraphs.areEqual( a, b ) ); } @@ -87,7 +85,7 @@ public void testDifferentLinksEqual2() { @Test public void testDifferentLinksEqual3() { - EntityGraph a = parseGraph( "linkToOne(name), linkToOne:MockSubentity(description)" ); + EntityGraph a = parseGraph( "linkToOne(name), linkToOne:GraphParsingTestSubentity(description)" ); EntityGraph b = parseGraph( "linkToOne(name, description)" ); Assert.assertFalse( EntityGraphs.areEqual( a, b ) ); }