diff --git a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java index 406af5662d52..90351f03ddcd 100644 --- a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java +++ b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java @@ -276,6 +276,38 @@ public interface StatelessSession extends SharedSessionContract { */ T get(Class entityClass, Object id, LockMode lockMode); + /** + * Retrieve a record, fetching associations specified by the + * given {@link EntityGraph}, which is interpreted as a + * {@linkplain org.hibernate.graph.GraphSemantic#LOAD load graph}. + * + * @param graph The {@link EntityGraph}, interpreted as a + * {@linkplain org.hibernate.graph.GraphSemantic#LOAD load graph} + * @param id The id of the entity to retrieve + * + * @return a detached entity instance + * + * @since 7.0 + */ + T get(EntityGraph graph, Object id); + + /** + * Retrieve a record, fetching associations specified by the + * given {@link EntityGraph}, which is interpreted as a + * {@linkplain org.hibernate.graph.GraphSemantic#LOAD load graph}, + * and obtaining the specified lock mode. + * + * @param graph The {@link EntityGraph}, interpreted as a + * {@linkplain org.hibernate.graph.GraphSemantic#LOAD load graph} + * @param id The id of the entity to retrieve + * @param lockMode The lock mode to apply to the entity + * + * @return a detached entity instance + * + * @since 7.0 + */ + T get(EntityGraph graph, Object id, LockMode lockMode); + /** * Retrieve a record, fetching associations specified by the * given {@link EntityGraph}. diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java index 7e58fdee2be7..f5ac0c6c581a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java @@ -586,6 +586,11 @@ public SelectionQuery createSelectionQuery(String hqlString, Class res return queryDelegate().createSelectionQuery( hqlString, resultType ); } + @Override + public SelectionQuery createSelectionQuery(String hqlString, EntityGraph resultGraph) { + return queryDelegate().createSelectionQuery( hqlString, resultGraph ); + } + @Override public SelectionQuery createSelectionQuery(CriteriaQuery criteria) { return queryDelegate().createSelectionQuery( criteria ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java index 7ae375c70e88..adcd2648457e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionLazyDelegator.java @@ -699,6 +699,11 @@ public SelectionQuery createSelectionQuery(String hqlString, Class res return this.lazySession.get().createSelectionQuery( hqlString, resultType ); } + @Override + public SelectionQuery createSelectionQuery(String hqlString, EntityGraph resultGraph) { + return this.lazySession.get().createSelectionQuery( hqlString, resultGraph ); + } + @Override public SelectionQuery createSelectionQuery(CriteriaQuery criteria) { return this.lazySession.get().createSelectionQuery( criteria ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionDelegatorBaseImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionDelegatorBaseImpl.java index 452989a79525..b42cf98efd11 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionDelegatorBaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionDelegatorBaseImpl.java @@ -147,6 +147,11 @@ public SelectionQuery createSelectionQuery(String hqlString, Class res return queryDelegate().createSelectionQuery( hqlString, resultType ); } + @Override + public SelectionQuery createSelectionQuery(String hqlString, EntityGraph resultGraph) { + return queryDelegate().createSelectionQuery( hqlString, resultGraph ); + } + @Override public SelectionQuery createSelectionQuery(CriteriaQuery criteria) { return queryDelegate().createSelectionQuery( criteria ); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index 8d3b18433a85..ea3479b4dbad 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -49,6 +49,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.transaction.internal.TransactionImpl; import org.hibernate.event.monitor.spi.EventMonitor; +import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.internal.RootGraphImpl; import org.hibernate.graph.spi.RootGraphImplementor; @@ -877,6 +878,13 @@ public SelectionQuery createSelectionQuery(String hqlString, Class exp return interpretAndCreateSelectionQuery( hqlString, expectedResultType ); } + @Override + public SelectionQuery createSelectionQuery(String hqlString, EntityGraph resultGraph) { + final RootGraph rootGraph = (RootGraph) resultGraph; + return interpretAndCreateSelectionQuery( hqlString, rootGraph.getGraphedType().getJavaType() ) + .setEntityGraph( resultGraph, GraphSemantic.LOAD ); + } + @Override public SelectionQuery createSelectionQuery(CriteriaQuery criteria) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java index 97a3c875cfb8..a7a7cb4fe71d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -728,6 +728,16 @@ public Object get(String entityName, Object id, LockMode lockMode) { return result; } + @Override + public T get(EntityGraph graph, Object id) { + return get( graph, GraphSemantic.LOAD , id); + } + + @Override + public T get(EntityGraph graph, Object id, LockMode lockMode) { + return get( graph, GraphSemantic.LOAD, id, lockMode); + } + @Override public T get(EntityGraph graph, GraphSemantic graphSemantic, Object id) { return get( graph, graphSemantic, id, LockMode.NONE ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java b/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java index cda51846e8cd..09dff40648c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/QueryProducer.java @@ -4,6 +4,7 @@ */ package org.hibernate.query; +import jakarta.persistence.EntityGraph; import org.hibernate.query.criteria.JpaCriteriaInsert; import jakarta.persistence.TypedQueryReference; @@ -337,6 +338,45 @@ public interface QueryProducer { */ SelectionQuery createSelectionQuery(String hqlString, Class resultType); + /** + * Create a {@link SelectionQuery} instance for the given HQL query + * string and given {@link EntityGraph}, which is interpreted as a + * {@linkplain org.hibernate.graph.GraphSemantic#LOAD load graph}. + * The query result type is the root entity of the given graph. + *
    + *
  • If the query has an explicit {@code select} clause, there must + * be a single item in the {@code select} list, and the select + * item must be assignable to the root type of the given graph. + *
  • Otherwise, if a query has no explicit {@code select} list, the + * select list is inferred from the given entity graph. The query + * must have exactly one root entity in the {@code from} clause, + * it must be assignable to the root type of the given graph, and + * the inferred select list will contain just that entity. + *
+ *

+ * If a query has no explicit {@code from} clause, and the given + * result type is an entity type, the root entity is inferred to + * be the result type. + *

+ * The returned {@code Query} may be executed by calling + * {@link Query#getResultList()} or {@link Query#getSingleResult()}. + + * @param hqlString The HQL {@code select} query as a string + * @param resultGraph An {@link EntityGraph} whose root type is the + * query result type, which is interpreted as a + * {@linkplain org.hibernate.graph.GraphSemantic#LOAD + * load graph} + * + * @see jakarta.persistence.EntityManager#createQuery(String) + * + * @throws IllegalSelectQueryException if the given HQL query + * is an {@code insert}, {@code update} or {@code delete} + * statement + * + * @since 7.0 + */ + SelectionQuery createSelectionQuery(String hqlString, EntityGraph resultGraph); + /** * Create a {@link SelectionQuery} reference for the given * {@link CriteriaQuery}. diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/fetchprofile/NewGraphTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/fetchprofile/NewGraphTest.java index 6c4da7bafd52..dfa4a21abe41 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/fetchprofile/NewGraphTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/fetchprofile/NewGraphTest.java @@ -6,6 +6,7 @@ import jakarta.persistence.*; import org.hibernate.annotations.NaturalId; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.testing.orm.junit.DomainModel; @@ -57,6 +58,74 @@ public class NewGraphTest { assertTrue( isInitialized( ee.f ) ); } + @Test void testFind(SessionFactoryScope scope) { + scope.inTransaction( s-> { + G g = new G(); + F f = new F(); + E e = new E(); + f.g = g; + e.f = f; + s.persist(g); + s.persist(f); + s.persist(e); + }); + + F f = scope.fromSession( s -> s.find(F.class, 1) ); + assertFalse( isInitialized( f.g ) ); + assertFalse( isInitialized( f.es ) ); + F ff = scope.fromSession( s -> { + RootGraph graph = s.createEntityGraph(F.class); + graph.addAttributeNodes("g", "es"); + return s.find(graph, 1); + }); + assertTrue( isInitialized( ff.g ) ); + assertTrue( isInitialized( ff.es ) ); + + E e = scope.fromSession( s -> s.find(E.class, 1) ); + assertFalse( isInitialized( e.f ) ); + E ee = scope.fromSession( s -> { + RootGraph graph = s.createEntityGraph(E.class); + graph.addAttributeNodes("f"); + return s.find(graph, 1); + }); + assertTrue( isInitialized( ee.f ) ); + } + + @Test void testGet(SessionFactoryScope scope) { + scope.inTransaction( s-> { + G g = new G(); + F f = new F(); + E e = new E(); + f.g = g; + e.f = f; + s.persist(g); + s.persist(f); + s.persist(e); + }); + + SessionFactoryImplementor factory = scope.getSessionFactory(); + + F f = factory.fromStatelessSession( s -> s.get(F.class, 1L) ); + assertFalse( isInitialized( f.g ) ); + assertFalse( isInitialized( f.es ) ); + F ff = factory.fromStatelessSession( s -> { + RootGraph graph = s.createEntityGraph(F.class); + graph.addAttributeNodes("g", "es"); + return s.get(graph, 1L); + }); + assertTrue( isInitialized( ff.g ) ); + assertTrue( isInitialized( ff.es ) ); + + E e = factory.fromStatelessSession( s -> s.get(E.class, 1L) ); + assertFalse( isInitialized( e.f ) ); + E ee = factory.fromStatelessSession( s -> { + RootGraph graph = s.createEntityGraph(E.class); + graph.addAttributeNodes("f"); + return s.get(graph, 1L); + }); + assertTrue( isInitialized( ee.f ) ); + } + @Test void testBySimpleNaturalIdEntityGraph(SessionFactoryScope scope) { scope.inTransaction( s-> { G g = new G(); @@ -168,6 +237,45 @@ public class NewGraphTest { assertTrue( isInitialized( ee.f ) ); } + @Test void testSelectionQuery(SessionFactoryScope scope) { + scope.inTransaction( s-> { + G g = new G(); + F f = new F(); + E e = new E(); + f.g = g; + e.f = f; + s.persist(g); + s.persist(f); + s.persist(e); + }); + + F f = scope.fromSession(s -> + s.createSelectionQuery("from F where id = 1", F.class) + .getSingleResult()); + assertFalse( isInitialized( f.g ) ); + assertFalse( isInitialized( f.es ) ); + F ff = scope.fromSession(s -> { + RootGraph graph = s.createEntityGraph(F.class); + graph.addAttributeNodes("g", "es"); + return s.createSelectionQuery("from F where id = 1", graph) + .getSingleResult(); + }); + assertTrue( isInitialized( ff.g ) ); + assertTrue( isInitialized( ff.es ) ); + + E e = scope.fromSession(s -> + s.createSelectionQuery("from E where id = 1", E.class) + .getSingleResult()); + assertFalse( isInitialized( e.f ) ); + E ee = scope.fromSession(s -> { + RootGraph graph = s.createEntityGraph(E.class); + graph.addAttributeNodes("f"); + return s.createSelectionQuery("from E where id = 1", graph) + .getSingleResult(); + }); + assertTrue( isInitialized( ee.f ) ); + } + @Entity(name = "E") static class E { @Id @GeneratedValue