diff --git a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java index 9281282a7419..edb20f38cf0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java +++ b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java @@ -383,8 +383,6 @@ public interface StatelessSession extends SharedSessionContract { * * @param entityGraph The {@link EntityGraph}, interpreted as a * {@linkplain org.hibernate.graph.GraphSemantic#LOAD load graph} - * @param graphSemantic a {@link GraphSemantic} specifying - * how the graph should be interpreted * @param ids The ids of the entities to retrieve * @return an ordered list of detached entity instances, with * null elements representing missing entities diff --git a/hibernate-core/src/main/java/org/hibernate/query/Query.java b/hibernate-core/src/main/java/org/hibernate/query/Query.java index 4d705a96fddb..c31c0a0a7fe5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/query/Query.java @@ -26,7 +26,6 @@ import org.hibernate.dialect.Dialect; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; -import org.hibernate.query.restriction.Restriction; import org.hibernate.query.spi.QueryOptions; import org.hibernate.transform.ResultTransformer; @@ -935,9 +934,6 @@ default Query setPage(Page page) { @Override @Incubating Query setOrder(Order order); - @Override @Incubating - Query addRestriction(Restriction restriction); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // deprecated methods diff --git a/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java index 53f3a0124311..b1a89eb88855 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/SelectionQuery.java @@ -35,7 +35,6 @@ import jakarta.persistence.TemporalType; import org.hibernate.engine.profile.DefaultFetchProfile; import org.hibernate.graph.GraphSemantic; -import org.hibernate.query.restriction.Restriction; /** * Within the context of an active {@linkplain org.hibernate.Session session}, @@ -623,19 +622,6 @@ default Stream stream() { @Incubating SelectionQuery setOrder(Order order); - /** - * If the result type of this query is an entity class, add a - * {@linkplain Restriction rule} for restricting the query results. - * - * @param restriction an instance of {@link Restriction} - * - * @see Restriction - * - * @since 7.0 - */ - @Incubating - SelectionQuery addRestriction(Restriction restriction); - /** * Specifies whether follow-on locking should be applied */ diff --git a/hibernate-core/src/main/java/org/hibernate/query/restriction/Restriction.java b/hibernate-core/src/main/java/org/hibernate/query/restriction/Restriction.java index e35f1719177c..9b83f4c88a80 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/restriction/Restriction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/restriction/Restriction.java @@ -12,7 +12,6 @@ import org.hibernate.Incubating; import org.hibernate.Internal; import org.hibernate.query.Order; -import org.hibernate.query.SelectionQuery; import org.hibernate.query.criteria.JpaCriteriaQuery; import org.hibernate.query.range.Range; @@ -21,7 +20,7 @@ /** * A rule for restricting query results. This allows restrictions to be added to * a {@link org.hibernate.query.programmatic.SelectionSpecification} by calling - * {@link SelectionQuery#addRestriction(Restriction)}. + * {@link org.hibernate.query.programmatic.SelectionSpecification#restrict(Restriction)}. *
  * SelectionSpecification.create(Book.class)
  *         .restrict(Restriction.like(Book_.title, "%Hibernate%", false))
diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java
index 67a5edf9863e..51cd37f39cfa 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/spi/AbstractQuery.java
@@ -39,7 +39,6 @@
 import org.hibernate.query.Order;
 import org.hibernate.query.Query;
 import org.hibernate.query.QueryParameter;
-import org.hibernate.query.restriction.Restriction;
 import org.hibernate.query.ResultListTransformer;
 import org.hibernate.query.TupleTransformer;
 import org.hibernate.query.named.NamedQueryMemento;
@@ -301,11 +300,6 @@ public Query setOrder(Order order) {
 		throw new UnsupportedOperationException( "Should be implemented by " + this.getClass().getName() );
 	}
 
-	@Override
-	public Query addRestriction(Restriction restriction) {
-		throw new UnsupportedOperationException( "Should be implemented by " + this.getClass().getName() );
-	}
-
 	@Override
 	public String getComment() {
 		return super.getComment();
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java
index ccacf69f192f..21e96b8a95a6 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java
@@ -55,7 +55,6 @@
 import org.hibernate.query.PathException;
 import org.hibernate.query.Query;
 import org.hibernate.query.QueryParameter;
-import org.hibernate.query.restriction.Restriction;
 import org.hibernate.query.ResultListTransformer;
 import org.hibernate.query.TupleTransformer;
 import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext;
@@ -1662,11 +1661,6 @@ public Query setOrder(Order order) {
 		throw new UnsupportedOperationException("Ordering not currently supported for native queries");
 	}
 
-	@Override
-	public Query addRestriction(Restriction restriction) {
-		throw new UnsupportedOperationException("Restrictions not currently supported for native queries");
-	}
-
 	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	// Hints
 
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java
index 0c37e737355b..1591f9b8fd1c 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/AbstractSqmSelectionQuery.java
@@ -15,7 +15,6 @@
 import org.hibernate.query.Order;
 import org.hibernate.query.Page;
 import org.hibernate.query.QueryLogging;
-import org.hibernate.query.restriction.Restriction;
 import org.hibernate.query.SelectionQuery;
 import org.hibernate.query.criteria.JpaSelection;
 import org.hibernate.query.hql.internal.QuerySplitter;
@@ -205,16 +204,16 @@ public SelectionQuery setOrder(Order order) {
 		return this;
 	}
 
-	@Override
-	public SelectionQuery addRestriction(Restriction restriction) {
-		final SqmSelectStatement selectStatement = getSqmSelectStatement().copy( noParamCopyContext( SqmQuerySource.CRITERIA ) );
-		restriction.apply( selectStatement, selectStatement.getRoot( 0, getExpectedResultType() ) );
-		// TODO: when the QueryInterpretationCache can handle caching criteria queries,
-		//       simply cache the new SQM as if it were a criteria query, and remove this:
-		getQueryOptions().setQueryPlanCachingEnabled( false );
-		setSqmStatement( selectStatement );
-		return this;
-	}
+//	@Override
+//	public SelectionQuery addRestriction(Restriction restriction) {
+//		final SqmSelectStatement selectStatement = getSqmSelectStatement().copy( noParamCopyContext( SqmQuerySource.CRITERIA ) );
+//		restriction.apply( selectStatement, selectStatement.getRoot( 0, getExpectedResultType() ) );
+//		// TODO: when the QueryInterpretationCache can handle caching criteria queries,
+//		//       simply cache the new SQM as if it were a criteria query, and remove this:
+//		getQueryOptions().setQueryPlanCachingEnabled( false );
+//		setSqmStatement( selectStatement );
+//		return this;
+//	}
 
 	@Override
 	public SelectionQuery setPage(Page page) {
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java
index 04f6cca16cc8..44bb193dd6ed 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java
@@ -47,7 +47,6 @@
 import org.hibernate.query.hql.spi.SqmQueryImplementor;
 import org.hibernate.query.internal.DelegatingDomainQueryExecutionContext;
 import org.hibernate.query.internal.ParameterMetadataImpl;
-import org.hibernate.query.restriction.Restriction;
 import org.hibernate.query.spi.DelegatingQueryOptions;
 import org.hibernate.query.spi.DomainQueryExecutionContext;
 import org.hibernate.query.spi.HqlInterpretation;
@@ -781,12 +780,6 @@ public Query setOrder(Order order) {
 		return this;
 	}
 
-	@Override
-	public Query addRestriction(Restriction restriction) {
-		super.addRestriction( restriction );
-		return this;
-	}
-
 	@Override
 	public Query setOrder(List> orders) {
 		super.setOrder(orders);
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java
index 69668013ddbb..29e6b35e4b73 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java
@@ -865,22 +865,17 @@ public Map, SqmJpaCriteriaParameterWrapper> getJpaCri
 
 	public static SqmSortSpecification sortSpecification(SqmSelectStatement sqm, Order order) {
 		final List> items = sqm.getQuerySpec().getSelectClause().getSelectionItems();
-		final int element = order.getElement();
-		if ( element < 1) {
-			throw new IllegalQueryOperationException("Cannot order by element " + element
-					+ " (the first select item is element 1)");
-		}
-		if ( element > items.size() ) {
-			throw new IllegalQueryOperationException("Cannot order by element " + element
-					+ " (there are only " + items.size() + " select items)");
-		}
-		final SqmSelectableNode selected = items.get( element-1 );
+		final SqmSelectableNode selected = selectedNode( sqm, order ); // does validation by side effect!
+		return createSortSpecification( sqm, order, items, selected );
+	}
 
+	private static SqmSortSpecification createSortSpecification(
+			SqmSelectStatement sqm, Order order, List> items, SqmSelectableNode selected) {
 		final NodeBuilder builder = sqm.nodeBuilder();
 		if ( order.getEntityClass() == null ) {
 			// ordering by an element of the select list
 			return new SqmSortSpecification(
-					new SqmAliasedNodeRef( element, builder.getIntegerType(), builder ),
+					new SqmAliasedNodeRef( order.getElement(), builder.getIntegerType(), builder ),
 					order.getDirection(),
 					order.getNullPrecedence(),
 					order.isCaseInsensitive()
@@ -888,7 +883,7 @@ public static SqmSortSpecification sortSpecification(SqmSelectStatement sqm,
 		}
 		else {
 			// ordering by an attribute of the returned entity
-			if ( items.size() == 1) {
+			if ( items.size() <= 1) {
 				if ( selected instanceof SqmFrom root ) {
 					if ( !order.getEntityClass().isAssignableFrom( root.getJavaType() ) ) {
 						throw new IllegalQueryOperationException("Select item was of wrong entity type");
@@ -898,7 +893,10 @@ public static SqmSortSpecification sortSpecification(SqmSelectStatement sqm,
 					while ( tokens.hasMoreTokens() ) {
 						path = path.get( tokens.nextToken() );
 					}
-					return builder.sort( path, order.getDirection(), order.getNullPrecedence(), order.isCaseInsensitive() );
+					return builder.sort( path,
+							order.getDirection(),
+							order.getNullPrecedence(),
+							order.isCaseInsensitive() );
 				}
 				else {
 					throw new IllegalQueryOperationException("Select item was not an entity type");
@@ -910,6 +908,32 @@ public static SqmSortSpecification sortSpecification(SqmSelectStatement sqm,
 		}
 	}
 
+	private static SqmSelectableNode selectedNode(SqmSelectStatement sqm, Order order) {
+		final int element = order.getElement();
+		if ( element < 1) {
+			throw new IllegalQueryOperationException("Cannot order by element " + element
+					+ " (the first select item is element 1)");
+		}
+		final var selectionItems = sqm.getQuerySpec().getSelectClause().getSelectionItems();
+		final int items = selectionItems.size();
+		if ( items == 0 && element == 1 ) {
+			if ( order.getEntityClass() == null || sqm.getQuerySpec().getRootList().size() > 1 ) {
+				throw new IllegalQueryOperationException("Cannot order by element " + element
+						+ " (there is no select list)");
+			}
+			else {
+				return sqm.getQuerySpec().getRootList().get(0);
+			}
+		}
+		else if ( element > items ) {
+			throw new IllegalQueryOperationException( "Cannot order by element " + element
+					+ " (there are only " + items + " select items)");
+		}
+		else {
+			return selectionItems.get( element - 1 );
+		}
+	}
+
 	public static boolean isSelectionAssignableToResultType(SqmSelection selection, Class expectedResultType) {
 		if ( expectedResultType == null ) {
 			return true;
diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/DelegatingSqmSelectionQueryImplementor.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/DelegatingSqmSelectionQueryImplementor.java
index 3e98330a685f..0930c184f9a5 100644
--- a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/DelegatingSqmSelectionQueryImplementor.java
+++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/DelegatingSqmSelectionQueryImplementor.java
@@ -31,7 +31,6 @@
 import org.hibernate.query.QueryParameter;
 import org.hibernate.query.ResultListTransformer;
 import org.hibernate.query.TupleTransformer;
-import org.hibernate.query.restriction.Restriction;
 import org.hibernate.query.spi.QueryOptions;
 import org.hibernate.query.sqm.SqmSelectionQuery;
 import org.hibernate.query.sqm.tree.SqmStatement;
@@ -310,12 +309,6 @@ public SqmSelectionQueryImplementor setOrder(Order order) {
 		return this;
 	}
 
-	@Override
-	public SqmSelectionQueryImplementor addRestriction(Restriction restriction) {
-		getDelegate().addRestriction( restriction );
-		return this;
-	}
-
 	@Override
 	public SqmSelectionQueryImplementor setFollowOnLocking(boolean enable) {
 		getDelegate().setFollowOnLocking( enable );
diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java
index 533b7805cd64..df2bcb5a9f65 100644
--- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java
+++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/restriction/RestrictionTest.java
@@ -16,6 +16,7 @@
 
 import java.util.List;
 
+import org.hibernate.query.programmatic.SelectionSpecification;
 import static org.hibernate.query.Order.asc;
 import static org.hibernate.query.Order.desc;
 import static org.hibernate.query.restriction.Path.from;
@@ -57,92 +58,108 @@ void test(SessionFactoryScope scope) {
 		var pages = (SingularAttribute) bookType.findSingularAttribute("pages");
 
 		Book book = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class )
-						.addRestriction( equal( isbn, "9781932394153" ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( equal( isbn, "9781932394153" ) )
+						.createQuery( session )
 						.getSingleResult() );
 		assertEquals( "Hibernate in Action", book.title );
 		List books = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( like( title, "%Hibernate%" ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( like( title, "%Hibernate%" ) )
+						.createQuery( session )
 						.setOrder( desc( title ) )
 						.getResultList() );
 		assertEquals( 2, books.size() );
 		assertEquals( "Java Persistence with Hibernate", books.get(0).title );
 		assertEquals( "Hibernate in Action", books.get(1).title );
 		List booksByIsbn = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( in( isbn, List.of("9781932394153", "9781617290459") ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( in( isbn, List.of("9781932394153", "9781617290459") ) )
+						.createQuery( session )
 						.setOrder( asc( title ) )
 						.getResultList() );
 		assertEquals( 2, booksByIsbn.size() );
 		assertEquals( "Hibernate in Action", booksByIsbn.get(0).title );
 		assertEquals( "Java Persistence with Hibernate", booksByIsbn.get(1).title );
 		List booksByPages = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( greaterThan( pages, 500 ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( greaterThan( pages, 500 ) )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 1, booksByPages.size() );
 		List booksByPageRange = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( between( pages, 150, 400 ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( between( pages, 150, 400 ) )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 1, booksByPageRange.size() );
 		Book bookByTitle = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( equalIgnoringCase( title, "hibernate in action" ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( equalIgnoringCase( title, "hibernate in action" ) )
+						.createQuery( session )
 						.getSingleResultOrNull() );
 		assertEquals( "9781932394153", bookByTitle.isbn );
 		Book bookByTitleUnsafe = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( restrict( Book.class, "title",
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( restrict( Book.class, "title",
 								singleCaseInsensitiveValue("hibernate in action") ) )
+						.createQuery( session )
 						.getSingleResultOrNull() );
 		assertEquals( "9781932394153", bookByTitleUnsafe.isbn );
 		List allBooks = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( unrestricted() )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( unrestricted() )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 2, allBooks.size() );
 		List noBooks = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( unrestricted().negated() )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( unrestricted().negated() )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 0, noBooks.size() );
 		List books1 = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( endsWith(title, "Hibernate") )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( endsWith(title, "Hibernate") )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 1, books1.size() );
 		List books2 = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( like(title, "*Hibernat?", false, '?', '*') )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( like(title, "*Hibernat?", false, '?', '*') )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 1, books2.size() );
 		List books3 = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( contains(title, "Hibernate") )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( contains(title, "Hibernate") )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 2, books3.size() );
 		List booksByTitleAndIsbn = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( all( contains(title, "Hibernate"),
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( all( contains(title, "Hibernate"),
 								equal( isbn, "9781932394153" ) ) )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 1, booksByTitleAndIsbn.size() );
 		List booksByTitleOrIsbn = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( any( contains(title, "Hibernate"),
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( any( contains(title, "Hibernate"),
 								equal( isbn, "9781932394153" ) ) )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 2, booksByTitleOrIsbn.size() );
 		List booksByIsbn1 = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( in( isbn, "9781932394153", "9781617290459", "XYZ" ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( in( isbn, "9781932394153", "9781617290459", "XYZ" ) )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 2, booksByIsbn1.size() );
 		List booksByIsbn2 = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( in( isbn, List.of("9781617290459", "XYZ", "ABC") ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( in( isbn, List.of("9781617290459", "XYZ", "ABC") ) )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 1, booksByIsbn2.size() );
 	}
@@ -174,44 +191,51 @@ void testPath(SessionFactoryScope scope) {
 		var version = (SingularAttribute) pubType.findSingularAttribute("version");
 
 		scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( from(Book.class).equalTo( session.find(Book.class, "9781932394153") ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( from(Book.class).equalTo( session.find(Book.class, "9781932394153") ) )
+						.createQuery( session )
 						.getSingleResult() );
 
 		List booksInIsbn = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( from(Book.class).to(isbn).in( List.of("9781932394153", "9781617290459") ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( from(Book.class).to(isbn).in( List.of("9781932394153", "9781617290459") ) )
+						.createQuery( session )
 						.setOrder( desc( isbn ) )
 						.getResultList() );
 		assertEquals( 2, booksInIsbn.size() );
 		List booksWithPub = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( from(Book.class).to(publisher).to(name).equalTo("Manning") )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( from(Book.class).to(publisher).to(name).equalTo("Manning") )
+						.createQuery( session )
 						.setOrder( desc( title ) )
 						.getResultList() );
 		assertEquals( 2, booksWithPub.size() );
 		List noBookWithPub = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( from(Book.class).to(publisher).to(name).notEqualTo("Manning") )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( from(Book.class).to(publisher).to(name).notEqualTo("Manning") )
+						.createQuery( session )
 						.setOrder( desc( title ) )
 						.getResultList() );
 		assertEquals( 0, noBookWithPub.size() );
 		List books = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( from(Book.class).to(title).restrict( containing("hibernate", false) ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( from(Book.class).to(title).restrict( containing("hibernate", false) ) )
+						.createQuery( session )
 						.setOrder( desc( title ) )
 						.getResultList() );
 		assertEquals( 2, books.size() );
 		List booksWithPubVersion = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( from(Book.class).to(publisher).to(version).restrict( greaterThan(5) ) )
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( from(Book.class).to(publisher).to(version).restrict( greaterThan(5) ) )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 0, booksWithPubVersion.size() );
 		List unsafeTest = scope.fromSession( session ->
-				session.createSelectionQuery( "from Book", Book.class)
-						.addRestriction( from(Book.class)
+				SelectionSpecification.create( Book.class, "from Book" )
+						.restrict( from(Book.class)
 								.to("publisher", Publisher.class)
 								.to("name", String.class).equalTo("Manning") )
+						.createQuery( session )
 						.getResultList() );
 		assertEquals( 2, unsafeTest.size() );
 	}
diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractCriteriaMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractCriteriaMethod.java
index cd33def591f1..9fb1da1ffb09 100644
--- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractCriteriaMethod.java
+++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractCriteriaMethod.java
@@ -74,13 +74,40 @@ public String getAttributeDeclarationString() {
 
 	abstract String createQueryMethod();
 
+	String specificationType() {
+		return "org.hibernate.query.programmatic.SelectionSpecification";
+	}
+
 	@Override
 	void createQuery(StringBuilder declaration) {
-		declaration
-				.append(localSessionName())
-				.append(".")
-				.append(createQueryMethod())
-				.append("(_query)\n");
+		if ( isUsingSpecification() ) {
+			declaration
+					.append( "\t\t\t.createQuery(" )
+					.append( localSessionName() )
+					.append( ")\n" );
+		}
+		else {
+			declaration
+					.append(localSessionName())
+					.append(".")
+					.append(createQueryMethod())
+					.append("(_query)\n");
+		}
+	}
+
+	@Override
+	void createSpecification(StringBuilder declaration) {
+		if ( isUsingSpecification() ) {
+			declaration
+					.append( annotationMetaEntity.importType( specificationType() ) )
+					.append( ".create(_query)\n" );
+		}
+	}
+
+	@Override
+	boolean isUsingSpecification() {
+		return !isReactive()
+			&& ( hasRestriction() || hasOrder() && !isJakartaCursoredPage(containerType) );
 	}
 
 	void createCriteriaQuery(StringBuilder declaration) {
diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractQueryMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractQueryMethod.java
index 7c42ea1ea71b..545f47cdd72b 100644
--- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractQueryMethod.java
+++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AbstractQueryMethod.java
@@ -110,6 +110,17 @@ String parameterList() {
 				.orElse("");
 	}
 
+	boolean hasRestriction() {
+		return paramTypes.stream()
+				.anyMatch( type -> isRestrictionParam( type )
+								|| isRangeParam( type ) );
+	}
+
+	boolean hasOrder() {
+		return paramTypes.stream().anyMatch(AbstractQueryMethod::isOrderParam)
+			|| !orderBys.isEmpty();
+	}
+
 	String strip(final String fullType) {
 		String type = fullType;
 		// strip off type annotations
@@ -264,10 +275,23 @@ else if ( jakartaPageRequest ) {
 		}
 	}
 
+	void handleSorting(
+			StringBuilder declaration, List paramTypes,
+			@Nullable String containerType) {
+		if ( !isJakartaCursoredPage(containerType)
+				&& isUsingSpecification()
+				&& hasOrdering(paramTypes) ) {
+			declaration
+					.append("\t\t\t.resort(_orders)\n");
+		}
+	}
+
 	boolean applyOrder(
 			StringBuilder declaration, List paramTypes,
 			@Nullable String containerType, boolean unwrapped) {
-		if ( !isJakartaCursoredPage(containerType) && hasOrdering(paramTypes) ) {
+		if ( !isJakartaCursoredPage(containerType)
+				&& !isUsingSpecification()
+				&& hasOrdering(paramTypes) ) {
 			unwrapQuery( declaration, unwrapped );
 			declaration
 					.append("\t\t\t.setOrder(_orders)\n");
@@ -298,7 +322,7 @@ void handleRestrictionParameters(
 			if ( isRestrictionParam(paramType) ) {
 				if ( paramType.startsWith(LIST) || paramType.endsWith("[]") ) {
 					declaration
-							.append( "\t\t\t.addRestriction(" )
+							.append( "\t\t\t.restrict(" )
 							.append( annotationMetaEntity.importType(HIB_RESTRICTION) )
 							.append( ".all(" )
 							.append( paramName )
@@ -307,7 +331,7 @@ void handleRestrictionParameters(
 				}
 				else {
 					declaration
-							.append( "\t\t\t.addRestriction(" )
+							.append( "\t\t\t.restrict(" )
 							.append( paramName )
 							.append( ")\n" );
 				}
@@ -316,7 +340,7 @@ else if ( isRangeParam(paramType) && returnTypeName!= null ) {
 				final TypeElement entityElement = annotationMetaEntity.getContext().getElementUtils()
 						.getTypeElement( returnTypeName );
 				declaration
-						.append("\t\t\t.addRestriction(")
+						.append("\t\t\t.restrict(")
 						.append(annotationMetaEntity.importType(HIB_RESTRICTION))
 						.append(".restrict(")
 						.append(annotationMetaEntity.importType(
@@ -363,7 +387,7 @@ static void closingBrace(StringBuilder declaration) {
 	}
 
 	void unwrapQuery(StringBuilder declaration, boolean unwrapped) {
-		if ( !unwrapped && isUsingEntityManager() ) {
+		if ( !unwrapped && isUsingEntityManager() && !isUsingSpecification() ) {
 			declaration
 					.append("\t\t\t.unwrap(")
 					.append(annotationMetaEntity.importType(HIB_SELECTION_QUERY))
@@ -461,6 +485,8 @@ void makeKeyedPage(StringBuilder declaration, List paramTypes) {
 
 	void createQuery(StringBuilder declaration) {}
 
+	void createSpecification(StringBuilder declaration) {}
+
 	void setParameters(StringBuilder declaration, List paramTypes, String indent) {}
 
 	void tryReturn(StringBuilder declaration, List paramTypes, @Nullable String containerType) {
@@ -521,6 +547,9 @@ private void totalResults(StringBuilder declaration, List paramTypes) {
 			declaration
 					.append(parameterName(JD_PAGE_REQUEST, paramTypes, paramNames))
 					.append(".requestTotal()\n\t\t\t\t\t\t? ");
+			// TODO: indentation is all messed up here!
+			createSpecification( declaration );
+			handleRestrictionParameters( declaration, paramTypes );
 			createQuery( declaration );
 			setParameters( declaration, paramTypes, "\t\t\t\t\t");
 			if ( isUsingEntityManager() ) {
@@ -650,6 +679,10 @@ private boolean hasOrdering(List paramTypes) {
 			|| !orderBys.isEmpty();
 	}
 
+	boolean isUsingSpecification() {
+		return false;
+	}
+
 	protected void executeSelect(
 			StringBuilder declaration,
 			List paramTypes,
@@ -751,7 +784,7 @@ protected void executeSelect(
 					}
 					break;
 				default:
-					if ( isUsingEntityManager() && !unwrapped && mustUnwrap ) {
+					if ( isUsingEntityManager() && !unwrapped && mustUnwrap && !isUsingSpecification() ) {
 						declaration
 								.append("\t\t\t.unwrap(")
 								.append(annotationMetaEntity.importType(containerType))
diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CriteriaDeleteMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CriteriaDeleteMethod.java
index b695c033ebd9..a9ce68603208 100644
--- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CriteriaDeleteMethod.java
+++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CriteriaDeleteMethod.java
@@ -55,6 +55,8 @@ boolean singleResult() {
 	@Override
 	void executeQuery(StringBuilder declaration, List paramTypes) {
 		tryReturn(declaration);
+		createSpecification( declaration );
+		handleRestrictionParameters( declaration, paramTypes );
 		createQuery( declaration );
 		execute( declaration );
 	}
@@ -75,6 +77,11 @@ String createQueryMethod() {
 				: "createMutationQuery";
 	}
 
+	@Override
+	String specificationType() {
+		return "org.hibernate.query.programmatic.MutationSpecification";
+	}
+
 	private void execute(StringBuilder declaration) {
 		declaration
 				.append("\t\t\t.executeUpdate()");
diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CriteriaFinderMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CriteriaFinderMethod.java
index a342146f05bf..5a4feb6b6eda 100644
--- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CriteriaFinderMethod.java
+++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/CriteriaFinderMethod.java
@@ -57,8 +57,10 @@ void executeQuery(StringBuilder declaration, List paramTypes) {
 		collectOrdering( declaration, paramTypes );
 		tryReturn( declaration, paramTypes, containerType );
 		castResult( declaration );
-		createQuery( declaration );
+		createSpecification( declaration );
 		handleRestrictionParameters( declaration, paramTypes );
+		handleSorting( declaration, paramTypes, containerType );
+		createQuery( declaration );
 		handlePageParameters( declaration, paramTypes, containerType );
 		boolean unwrapped = !isUsingEntityManager();
 		unwrapped = enableFetchProfile( declaration, unwrapped );
diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/QueryMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/QueryMethod.java
index dc5c7b8c22a3..40e7684b6644 100644
--- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/QueryMethod.java
+++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/QueryMethod.java
@@ -96,8 +96,10 @@ public String getAttributeDeclarationString() {
 		chainSession( declaration );
 		tryReturn( declaration, paramTypes, containerType );
 		castResult( declaration );
-		createQuery( declaration );
+		createSpecification( declaration );
 		handleRestrictionParameters( declaration, paramTypes );
+		handleSorting( declaration, paramTypes, containerType );
+		createQuery( declaration );
 		setParameters( declaration, paramTypes, "");
 		handlePageParameters( declaration, paramTypes, containerType );
 		boolean unwrapped = !isUsingEntityManager();
@@ -109,21 +111,56 @@ public String getAttributeDeclarationString() {
 		return declaration.toString();
 	}
 
+	String specificationType() {
+		return isUpdate
+				? "org.hibernate.query.programmatic.MutationSpecification"
+				: "org.hibernate.query.programmatic.SelectionSpecification";
+	}
+
 	@Override
 	void createQuery(StringBuilder declaration) {
-		declaration
-				.append(localSessionName())
-				.append('.')
-				.append(createQueryMethod())
-				.append("(")
-				.append(getConstantName());
-		if ( returnTypeClass != null && !isUpdate ) {
+		if ( isUsingSpecification() ) {
 			declaration
-					.append(", ")
-					.append(annotationMetaEntity.importType(returnTypeClass))
-					.append(".class");
+					.append( "\t\t\t.createQuery(" )
+					.append( localSessionName() )
+					.append( ")\n" );
 		}
-		declaration.append(")\n");
+		else {
+			// can't use Specification
+			declaration
+					.append(localSessionName())
+					.append('.')
+					.append(createQueryMethod())
+					.append("(")
+					.append(getConstantName());
+			if ( returnTypeClass != null && !isUpdate ) {
+				declaration
+						.append(", ")
+						.append(annotationMetaEntity.importType(returnTypeClass))
+						.append(".class");
+			}
+			declaration.append(")\n");
+		}
+	}
+
+	@Override
+	void createSpecification(StringBuilder declaration) {
+		if ( returnTypeClass != null && isUsingSpecification() ) {
+			declaration
+					.append( annotationMetaEntity.importType( specificationType() ) )
+					.append( ".create(" )
+					.append( annotationMetaEntity.importType( returnTypeClass ) )
+					.append( ".class, " )
+					.append( getConstantName() )
+					.append( ")\n" );
+		}
+	}
+
+	@Override
+	boolean isUsingSpecification() {
+		return returnTypeClass != null
+			&& !isReactive()
+			&& ( hasRestriction() || hasOrder() && !isJakartaCursoredPage(containerType) );
 	}
 
 	private String createQueryMethod() {