Skip to content

Commit 280d6cc

Browse files
committed
HHH-19610 make GraphParser support subTypeSubGraph
1 parent 59d1662 commit 280d6cc

File tree

50 files changed

+1836
-550
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1836
-550
lines changed

documentation/src/main/asciidoc/userguide/chapters/fetching/Fetching.adoc

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ Hibernate allows the creation of Jakarta Persistence fetch/load graphs by parsin
285285
of the graph. Generally speaking, the textual representation of a graph is a comma-separated
286286
list of attribute names, optionally including any subgraph specifications.
287287
The starting point for such parsing operations is either `org.hibernate.graph.GraphParser`
288-
or `SessionFactory#parseEntityGraph`
288+
or `SessionFactory#parseEntityGraph`.
289289

290290
[NOTE]
291291
====
@@ -294,6 +294,21 @@ syntax described here is specific to Hibernate. We do hope to eventually make th
294294
the Jakarta Persistence specification proper.
295295
====
296296

297+
===== Graph parser mode configuration
298+
299+
Since Hibernate 7.3, a new configuration setting controls which syntax the `GraphParser` uses:
300+
301+
`hibernate.graph_parser_mode`::
302+
Determines which parsing syntax is active.
303+
+
304+
* `legacy` — The historical syntax used before Hibernate 7.3.
305+
* `modern` — Enables the new syntax that supports root-subtype subgraphs and improved readability.
306+
307+
.Default value
308+
[source,properties]
309+
----
310+
hibernate.graph_parser_mode = legacy
311+
----
297312

298313
.Parsing a simple graph
299314
====
@@ -318,26 +333,52 @@ include::{example-dir-fetching}/GraphParsingTest.java[tags=fetching-strategies-d
318333
----
319334
====
320335

336+
===== Subtype-specific subgraphs (examples showing both supported forms)
337+
321338
Parsing can also handle subtype specific subgraphs. For example, given an entity hierarchy of
322339
`LegalEntity` <- (`Corporation` | `Person` | `NonProfit`) and an attribute named `responsibleParty` whose
323340
type is the `LegalEntity` base type we might have:
324341

342+
* **Legacy form (hibernate.graph_parser_mode = legacy)**
343+
325344
====
326345
[source, java, indent=0]
327346
----
328347
responsibleParty(Corporation: ceo)
329348
----
330349
====
331350

351+
* **Modern form (hibernate.graph_parser_mode = modern)**
352+
353+
====
354+
[source, java, indent=0]
355+
----
356+
responsibleParty:Corporation(ceo)
357+
----
358+
====
359+
360+
Both forms produce the same runtime EntityGraph structure.
361+
332362
We can even duplicate the attribute names to apply different subtype subgraphs:
333363

364+
* **Legacy form (hibernate.graph_parser_mode = legacy)**
365+
334366
====
335367
[source, java, indent=0]
336368
----
337369
responsibleParty(taxIdNumber), responsibleParty(Corporation: ceo), responsibleParty(NonProfit: sector)
338370
----
339371
====
340372

373+
* **Modern form (hibernate.graph_parser_mode = modern)**
374+
375+
====
376+
[source, java, indent=0]
377+
----
378+
responsibleParty(taxIdNumber), responsibleParty:Corporation(ceo), responsibleParty:NonProfit(sector)
379+
----
380+
====
381+
341382
The duplicated attribute names are handled according to the Jakarta Persistence specification which says that duplicate
342383
specification of the attribute node results in the originally registered AttributeNode to be re-used
343384
effectively merging the 2 AttributeNode specifications together. In other words, the above specification
@@ -355,6 +396,20 @@ invoiceGraph.addSubgraph( "responsibleParty", NonProfit.class ).addAttributeNode
355396
----
356397
====
357398

399+
===== Root-subtype subgraphs (modern-only feature)
400+
401+
The modern parser mode also supports defining subgraphs that originate at a subtype of the **root entity**.
402+
This feature is **only available** when `hibernate.graph_parser_mode=modern`.
403+
Given an entity hierarchy of `LegalEntity` <- (`Corporation` | `Person` | `NonProfit`) with an attribute `ceo` that exists only on `Corporation`
404+
and an attribute `sector` that exists only on `NonProfit`, you can define such subgraphs directly in a `LegalEntity` graph definition:
405+
406+
====
407+
[source,java,indent=0]
408+
----
409+
:Corporation(ceo), :NonProfit(sector)
410+
----
411+
====
412+
358413
[[fetching-strategies-dynamic-fetching-entity-graph-merging]]
359414
==== Combining multiple Jakarta Persistence entity graphs into one
360415

@@ -391,6 +446,29 @@ class Book {
391446
----
392447
====
393448

449+
Since Hibernate 7.3 a `root` attribute is available on the Hibernate-specific `@NamedEntityGraph` annotation
450+
to explicitly indicate the entity type that is the root of the graph.
451+
452+
When `@NamedEntityGraph` is placed on a package, the `root` attribute **must** be specified.
453+
If the annotation is placed on a package and the `root` is omitted:
454+
* In `legacy` parser mode a deprecation warning will be emitted.
455+
* In `modern` parser mode the omission is **not allowed** and results in an error.
456+
457+
.Package-level @NamedEntityGraph example
458+
====
459+
[source, java, indent=0]
460+
----
461+
@org.hibernate.annotations.NamedEntityGraph(
462+
name = "Book.graph",
463+
root = Book.class,
464+
graph = "title,isbn,author(name,phoneNumber)"
465+
)
466+
package com.example.model;
467+
----
468+
====
469+
470+
This annotation works in conjunction with the `GraphParser` and respects the syntax
471+
defined by `hibernate.graph_parser_mode`.
394472

395473
[[fetching-strategies-dynamic-fetching-profile]]
396474
=== Dynamic fetching via Hibernate profiles
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
lexer grammar ModernGraphLanguageLexer;
2+
3+
@header {
4+
/*
5+
* SPDX-License-Identifier: Apache-2.0
6+
* Copyright Red Hat Inc. and Hibernate Authors
7+
*/
8+
package org.hibernate.grammars.graph;
9+
}
10+
11+
@members {
12+
/*
13+
* Lexer for the Hibernate EntityGraph Language
14+
*
15+
* It is generated by Antlr via the lexer grammar file `ModernGraphLanguageLexer.g4`
16+
*/
17+
}
18+
19+
channels {
20+
WHITESPACE_CHANNEL
21+
}
22+
23+
WS : ( ' ' | '\t' | '\f' | EOL ) -> channel(WHITESPACE_CHANNEL);
24+
25+
fragment EOL : [\r\n]+;
26+
27+
COLON: ':';
28+
29+
COMMA: ',';
30+
31+
DOT: '.';
32+
33+
LPAREN: '(';
34+
35+
RPAREN: ')';
36+
37+
/**
38+
* In this grammar, basically any string since we (atm) have no keywords
39+
*/
40+
ATTR_NAME : ATTR_NAME_START NAME_CONTINUATION*;
41+
42+
TYPE_NAME : TYPE_NAME_START NAME_CONTINUATION*;
43+
44+
fragment NON_ALPHANUM_EXTENTION
45+
: '_'
46+
| '$'
47+
// HHH-558 : Allow unicode chars in identifiers
48+
//| '\u0080'..'\ufffe'
49+
;
50+
51+
fragment ATTR_NAME_START
52+
: NON_ALPHANUM_EXTENTION
53+
| 'a'..'z'
54+
;
55+
56+
fragment TYPE_NAME_START
57+
: NON_ALPHANUM_EXTENTION
58+
| 'A'..'Z'
59+
;
60+
61+
fragment NAME_CONTINUATION
62+
: NON_ALPHANUM_EXTENTION
63+
| 'a'..'z'
64+
| 'A'..'Z'
65+
| '0'..'9'
66+
;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
parser grammar ModernGraphLanguageParser;
2+
3+
options {
4+
tokenVocab=ModernGraphLanguageLexer;
5+
}
6+
7+
@header {
8+
/*
9+
* SPDX-License-Identifier: Apache-2.0
10+
* Copyright Red Hat Inc. and Hibernate Authors
11+
*/
12+
package org.hibernate.grammars.graph;
13+
}
14+
15+
@members {
16+
/*
17+
* Antlr grammar describing the Hibernate EntityGraph Language - for parsing a structured
18+
* textual representation of an entity graph
19+
*
20+
* `ModernGraphLanguageParser.g4`
21+
*/
22+
}
23+
24+
graph
25+
: graphElementList
26+
;
27+
28+
graphElementList
29+
: graphElement (COMMA graphElement)*
30+
;
31+
32+
33+
graphElement
34+
: subGraph
35+
| attributeNode
36+
;
37+
38+
subGraph
39+
: subTypeIndicator? LPAREN attributeList RPAREN
40+
;
41+
42+
typeIndicator
43+
: TYPE_NAME COLON
44+
;
45+
46+
subTypeIndicator
47+
: COLON TYPE_NAME
48+
;
49+
50+
attributeList
51+
: attributeNode (COMMA attributeNode)*
52+
;
53+
54+
attributeNode
55+
: attributePath subGraph?
56+
;
57+
58+
attributePath
59+
: ATTR_NAME attributeQualifier?
60+
;
61+
62+
attributeQualifier
63+
: DOT ATTR_NAME
64+
;

hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageLexer.g4 renamed to hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/GraphLanguageLexer.g4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ lexer grammar GraphLanguageLexer;
55
* SPDX-License-Identifier: Apache-2.0
66
* Copyright Red Hat Inc. and Hibernate Authors
77
*/
8-
package org.hibernate.grammars.graph;
8+
package org.hibernate.grammars.graph.legacy;
99
}
1010

1111
@members {

hibernate-core/src/main/antlr/org/hibernate/grammars/graph/GraphLanguageParser.g4 renamed to hibernate-core/src/main/antlr/org/hibernate/grammars/graph/legacy/GraphLanguageParser.g4

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ options {
99
* SPDX-License-Identifier: Apache-2.0
1010
* Copyright Red Hat Inc. and Hibernate Authors
1111
*/
12-
package org.hibernate.grammars.graph;
12+
package org.hibernate.grammars.graph.legacy;
1313
}
1414

1515
@members {
@@ -21,7 +21,6 @@ package org.hibernate.grammars.graph;
2121
*/
2222
}
2323

24-
2524
graph
2625
: typeIndicator? attributeList
2726
;
@@ -48,5 +47,4 @@ attributeQualifier
4847

4948
subGraph
5049
: LPAREN typeIndicator? attributeList RPAREN
51-
;
52-
50+
;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate;
6+
7+
/**
8+
* Enumeration of available graph parser syntax modes.
9+
*
10+
*/
11+
public enum GraphParserMode {
12+
/**
13+
* Legacy syntax: attribute(SubType: attributes)
14+
* This is the legacy syntax.
15+
*/
16+
LEGACY( "legacy" ),
17+
18+
/**
19+
* Modern syntax: attribute:SubType(attributes)
20+
* This is the preferred new syntax.
21+
*/
22+
MODERN( "modern" );
23+
24+
private final String configValue;
25+
26+
GraphParserMode(String configValue) {
27+
this.configValue = configValue;
28+
}
29+
30+
public String getConfigValue() {
31+
return configValue;
32+
}
33+
34+
/**
35+
* Interpret the configured valueHandlingMode value.
36+
* Valid values are either a {@link GraphParserMode} object or its String representation.
37+
* For string values, the matching is case insensitive, so you can use either {@code MODERN} or {@code modern}.
38+
*
39+
* @param graphParserMode configured {@link GraphParserMode} representation
40+
*
41+
* @return associated {@link GraphParserMode} object
42+
*/
43+
public static GraphParserMode interpret(Object graphParserMode) {
44+
if ( graphParserMode == null ) {
45+
return LEGACY;
46+
}
47+
else if ( graphParserMode instanceof GraphParserMode mode ) {
48+
return mode;
49+
}
50+
else if ( graphParserMode instanceof String string ) {
51+
for ( GraphParserMode value : values() ) {
52+
if ( value.name().equalsIgnoreCase( string ) ) {
53+
return value;
54+
}
55+
}
56+
}
57+
throw new HibernateException(
58+
"Unrecognized graph_parser_mode value : " + graphParserMode
59+
+ ". Supported values include 'modern' and 'legacy'."
60+
);
61+
}
62+
}

hibernate-core/src/main/java/org/hibernate/annotations/NamedEntityGraph.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@
3939
@Retention(RUNTIME)
4040
@Repeatable(NamedEntityGraphs.class)
4141
public @interface NamedEntityGraph {
42+
43+
/**
44+
* The entity that is the root of the {@linkplain #graph graph}.
45+
* When the annotation is applied to a class, the class itself is assumed.
46+
* When applied to a package, this attribute is required.
47+
*/
48+
Class<?> root() default void.class;
49+
4250
/**
4351
* The name used to identify the entity graph in calls to
4452
* {@linkplain org.hibernate.Session#getEntityGraph(String)}.

0 commit comments

Comments
 (0)