Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ Hibernate allows the creation of Jakarta Persistence fetch/load graphs by parsin
of the graph. Generally speaking, the textual representation of a graph is a comma-separated
list of attribute names, optionally including any subgraph specifications.
The starting point for such parsing operations is either `org.hibernate.graph.GraphParser`
or `SessionFactory#parseEntityGraph`
or `SessionFactory#parseEntityGraph`.

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

===== Graph parser mode configuration

Since Hibernate 7.3, a new configuration setting controls which syntax the `GraphParser` uses:

`hibernate.graph_parser_mode`::
Determines which parsing syntax is active.
+
* `legacy` — The historical syntax used before Hibernate 7.3.
* `modern` — Enables the new syntax that supports root-subtype subgraphs and improved readability.

.Default value
[source,properties]
----
hibernate.graph_parser_mode = legacy
----

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

===== Subtype-specific subgraphs (examples showing both supported forms)

Parsing can also handle subtype specific subgraphs. For example, given an entity hierarchy of
`LegalEntity` <- (`Corporation` | `Person` | `NonProfit`) and an attribute named `responsibleParty` whose
type is the `LegalEntity` base type we might have:

* **Legacy form (hibernate.graph_parser_mode = legacy)**

====
[source, java, indent=0]
----
responsibleParty(Corporation: ceo)
----
====

* **Modern form (hibernate.graph_parser_mode = modern)**

====
[source, java, indent=0]
----
responsibleParty:Corporation(ceo)
----
====

Both forms produce the same runtime EntityGraph structure.

We can even duplicate the attribute names to apply different subtype subgraphs:

* **Legacy form (hibernate.graph_parser_mode = legacy)**

====
[source, java, indent=0]
----
responsibleParty(taxIdNumber), responsibleParty(Corporation: ceo), responsibleParty(NonProfit: sector)
----
====

* **Modern form (hibernate.graph_parser_mode = modern)**

====
[source, java, indent=0]
----
responsibleParty(taxIdNumber), responsibleParty:Corporation(ceo), responsibleParty:NonProfit(sector)
----
====

The duplicated attribute names are handled according to the Jakarta Persistence specification which says that duplicate
specification of the attribute node results in the originally registered AttributeNode to be re-used
effectively merging the 2 AttributeNode specifications together. In other words, the above specification
Expand All @@ -355,6 +396,20 @@ invoiceGraph.addSubgraph( "responsibleParty", NonProfit.class ).addAttributeNode
----
====

===== Root-subtype subgraphs (modern-only feature)

The modern parser mode also supports defining subgraphs that originate at a subtype of the **root entity**.
This feature is **only available** when `hibernate.graph_parser_mode=modern`.
Given an entity hierarchy of `LegalEntity` <- (`Corporation` | `Person` | `NonProfit`) with an attribute `ceo` that exists only on `Corporation`
and an attribute `sector` that exists only on `NonProfit`, you can define such subgraphs directly in a `LegalEntity` graph definition:

====
[source,java,indent=0]
----
:Corporation(ceo), :NonProfit(sector)
----
====

[[fetching-strategies-dynamic-fetching-entity-graph-merging]]
==== Combining multiple Jakarta Persistence entity graphs into one

Expand Down Expand Up @@ -391,6 +446,29 @@ class Book {
----
====

Since Hibernate 7.3 a `root` attribute is available on the Hibernate-specific `@NamedEntityGraph` annotation
to explicitly indicate the entity type that is the root of the graph.

When `@NamedEntityGraph` is placed on a package, the `root` attribute **must** be specified.
If the annotation is placed on a package and the `root` is omitted:
* In `legacy` parser mode a deprecation warning will be emitted.
* In `modern` parser mode the omission is **not allowed** and results in an error.

.Package-level @NamedEntityGraph example
====
[source, java, indent=0]
----
@org.hibernate.annotations.NamedEntityGraph(
name = "Book.graph",
root = Book.class,
graph = "title,isbn,author(name,phoneNumber)"
)
package com.example.model;
----
====

This annotation works in conjunction with the `GraphParser` and respects the syntax
defined by `hibernate.graph_parser_mode`.

[[fetching-strategies-dynamic-fetching-profile]]
=== Dynamic fetching via Hibernate profiles
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
lexer grammar ModernGraphLanguageLexer;

@header {
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.grammars.graph;
}

@members {
/*
* Lexer for the Hibernate EntityGraph Language
*
* It is generated by Antlr via the lexer grammar file `ModernGraphLanguageLexer.g4`
*/
}

channels {
WHITESPACE_CHANNEL
}

WS : ( ' ' | '\t' | '\f' | EOL ) -> channel(WHITESPACE_CHANNEL);

fragment EOL : [\r\n]+;

COLON: ':';

COMMA: ',';

DOT: '.';

LPAREN: '(';

RPAREN: ')';

/**
* In this grammar, basically any string since we (atm) have no keywords
*/
ATTR_NAME : ATTR_NAME_START NAME_CONTINUATION*;

TYPE_NAME : TYPE_NAME_START NAME_CONTINUATION*;

fragment NON_ALPHANUM_EXTENTION
: '_'
| '$'
// HHH-558 : Allow unicode chars in identifiers
//| '\u0080'..'\ufffe'
;

fragment ATTR_NAME_START
: NON_ALPHANUM_EXTENTION
| 'a'..'z'
;

fragment TYPE_NAME_START
: NON_ALPHANUM_EXTENTION
| 'A'..'Z'
;

fragment NAME_CONTINUATION
: NON_ALPHANUM_EXTENTION
| 'a'..'z'
| 'A'..'Z'
| '0'..'9'
;
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
parser grammar ModernGraphLanguageParser;

options {
tokenVocab=ModernGraphLanguageLexer;
}

@header {
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.grammars.graph;
}

@members {
/*
* Antlr grammar describing the Hibernate EntityGraph Language - for parsing a structured
* textual representation of an entity graph
*
* `ModernGraphLanguageParser.g4`
*/
}

graph
: graphElementList
;

graphElementList
: graphElement (COMMA graphElement)*
;


graphElement
: subGraph
| attributeNode
;

subGraph
: subTypeIndicator? LPAREN attributeList RPAREN
;

typeIndicator
: TYPE_NAME COLON
;

subTypeIndicator
: COLON TYPE_NAME
;

attributeList
: attributeNode (COMMA attributeNode)*
;

attributeNode
: attributePath subGraph?
;

attributePath
: ATTR_NAME attributeQualifier?
;

attributeQualifier
: DOT ATTR_NAME
;
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ lexer grammar GraphLanguageLexer;
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.grammars.graph;
package org.hibernate.grammars.graph.legacy;
}

@members {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ options {
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.grammars.graph;
package org.hibernate.grammars.graph.legacy;
}

@members {
Expand All @@ -21,7 +21,6 @@ package org.hibernate.grammars.graph;
*/
}


graph
: typeIndicator? attributeList
;
Expand All @@ -48,5 +47,4 @@ attributeQualifier

subGraph
: LPAREN typeIndicator? attributeList RPAREN
;

;
62 changes: 62 additions & 0 deletions hibernate-core/src/main/java/org/hibernate/GraphParserMode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate;

/**
* Enumeration of available graph parser syntax modes.
*
*/
public enum GraphParserMode {
/**
* Legacy syntax: attribute(SubType: attributes)
* This is the legacy syntax.
*/
LEGACY( "legacy" ),

/**
* Modern syntax: attribute:SubType(attributes)
* This is the preferred new syntax.
*/
MODERN( "modern" );

private final String configValue;

GraphParserMode(String configValue) {
this.configValue = configValue;
}

public String getConfigValue() {
return configValue;
}

/**
* Interpret the configured valueHandlingMode value.
* Valid values are either a {@link GraphParserMode} object or its String representation.
* For string values, the matching is case insensitive, so you can use either {@code MODERN} or {@code modern}.
*
* @param graphParserMode configured {@link GraphParserMode} representation
*
* @return associated {@link GraphParserMode} object
*/
public static GraphParserMode interpret(Object graphParserMode) {
if ( graphParserMode == null ) {
return LEGACY;
}
else if ( graphParserMode instanceof GraphParserMode mode ) {
return mode;
}
else if ( graphParserMode instanceof String string ) {
for ( GraphParserMode value : values() ) {
if ( value.name().equalsIgnoreCase( string ) ) {
return value;
}
}
}
throw new HibernateException(
"Unrecognized graph_parser_mode value : " + graphParserMode
+ ". Supported values include 'modern' and 'legacy'."
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
@Retention(RUNTIME)
@Repeatable(NamedEntityGraphs.class)
public @interface NamedEntityGraph {

/**
* 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)}.
Expand Down
Loading