HHH-16899 refinements to the CriteriaDefinition API

This commit is contained in:
Gavin King 2023-07-08 15:50:44 +02:00
parent f3eb0ec770
commit 6b7d5bae3d
4 changed files with 184 additions and 74 deletions

View File

@ -61,6 +61,7 @@ import org.hibernate.query.Query;
import org.hibernate.query.QueryTypeMismatchException;
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.UnknownNamedQueryException;
import org.hibernate.query.criteria.CriteriaDefinition;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaInsertSelect;
import org.hibernate.query.hql.spi.SqmQueryImplementor;
@ -768,8 +769,13 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
@Override
public <R> SelectionQuery<R> createSelectionQuery(CriteriaQuery<R> criteria) {
SqmUtil.verifyIsSelectStatement( (SqmStatement<?>) criteria, null );
return new SqmSelectionQueryImpl<>( (SqmSelectStatement<R>) criteria, criteria.getResultType(), this );
if ( criteria instanceof CriteriaDefinition ) {
return ((CriteriaDefinition<R>) criteria).createSelectionQuery(this);
}
else {
SqmUtil.verifyIsSelectStatement( (SqmStatement<?>) criteria, null );
return new SqmSelectionQueryImpl<>( (SqmSelectStatement<R>) criteria, criteria.getResultType(), this );
}
}
@Override
@ -1307,25 +1313,29 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
@Override
public <T> QueryImplementor<T> createQuery(CriteriaQuery<T> criteriaQuery) {
checkOpen();
try {
final SqmSelectStatement<T> selectStatement = (SqmSelectStatement<T>) criteriaQuery;
if ( ! ( selectStatement.getQueryPart() instanceof SqmQueryGroup ) ) {
final SqmQuerySpec<T> querySpec = selectStatement.getQuerySpec();
if ( querySpec.getSelectClause().getSelections().isEmpty() ) {
if ( querySpec.getFromClause().getRoots().size() == 1 ) {
querySpec.getSelectClause().setSelection( querySpec.getFromClause().getRoots().get(0) );
if ( criteriaQuery instanceof CriteriaDefinition ) {
return (QueryImplementor<T>) ((CriteriaDefinition<T>) criteriaQuery).createSelectionQuery(this);
}
else {
try {
final SqmSelectStatement<T> selectStatement = (SqmSelectStatement<T>) criteriaQuery;
if ( ! ( selectStatement.getQueryPart() instanceof SqmQueryGroup ) ) {
final SqmQuerySpec<T> querySpec = selectStatement.getQuerySpec();
if ( querySpec.getSelectClause().getSelections().isEmpty() ) {
if ( querySpec.getFromClause().getRoots().size() == 1 ) {
querySpec.getSelectClause().setSelection( querySpec.getFromClause().getRoots().get(0) );
}
}
}
}
return createCriteriaQuery( selectStatement, criteriaQuery.getResultType() );
}
catch (RuntimeException e) {
if ( getSessionFactory().getJpaMetamodel().getJpaCompliance().isJpaTransactionComplianceEnabled() ) {
markForRollbackOnly();
return createCriteriaQuery( selectStatement, criteriaQuery.getResultType() );
}
catch (RuntimeException e) {
if ( getSessionFactory().getJpaMetamodel().getJpaCompliance().isJpaTransactionComplianceEnabled() ) {
markForRollbackOnly();
}
throw getExceptionConverter().convert( e );
}
throw getExceptionConverter().convert( e );
}
}

View File

@ -6,11 +6,15 @@
*/
package org.hibernate.query.criteria;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.*;
import jakarta.persistence.metamodel.EntityType;
import org.hibernate.Incubating;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.SharedSessionContract;
import org.hibernate.query.QueryProducer;
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.criteria.spi.HibernateCriteriaBuilderDelegate;
import org.hibernate.query.sqm.FetchClauseType;
@ -22,7 +26,7 @@ import java.util.function.Function;
/**
* A utility class that makes it easier to build {@linkplain CriteriaQuery criteria queries}.
* From within an implementation of the {@link #define()} method, all operations of the
* From within an initializer block of a (usually anonymous) subclass, all operations of the
* {@link CriteriaBuilder} and {@link CriteriaQuery} may be called without the need for
* specifying the target object.
* <p>
@ -30,17 +34,16 @@ import java.util.function.Function;
* <pre>
* sessionFactory.inTransaction(session -&gt; {
* List&lt;Book&gt; books
* = new CriteriaDefinition&lt;&gt;(sessionFactory, Book.class) {
* public void define() {
* var book = from(Book.class);
* where(like(book.get(Book_.title), "%Hibernate%"));
* orderBy(desc(book.get(Book_.publicationDate)), asc(book.get(Book_.isbn)));
* book.fetch(Book_.authors);
* }
* }
* = new CriteriaDefinition&lt;&gt;(sessionFactory, Book.class) {{
* var book = from(Book.class);
* where(like(book.get(Book_.title), "%Hibernate%"));
* orderBy(desc(book.get(Book_.publicationDate)), asc(book.get(Book_.isbn)));
* book.fetch(Book_.authors);
* }}
* .createSelectionQuery(session)
* .setMaxResults(10)
* .getResultList();
* ...
* });
* </pre>
* <p>
@ -49,20 +52,45 @@ import java.util.function.Function;
* sessionFactory.inTransaction(session -&gt; {
* List&lt;Book&gt; books
* = new CriteriaDefinition&lt;&gt;(sessionFactory, Book.class,
* "from Book left join fetch authors where type = BOOK") {
* public void define() {
* var book = (JpaRoot&lt;Book&gt;) getSelection();
* where(getRestriction(), like(book.get(Book_.title), "%Hibernate%"));
* orderBy(desc(book.get(Book_.publicationDate)), asc(book.get(Book_.isbn)));
* }
* }
* "from Book left join fetch authors where type = BOOK") {{
* var book = (JpaRoot&lt;Book&gt;) getSelection();
* where(getRestriction(), like(book.get(Book_.title), "%Hibernate%"));
* orderBy(desc(book.get(Book_.publicationDate)), asc(book.get(Book_.isbn)));
* }}
* .createSelectionQuery(session)
* .getResultList();
* ...
* });
* </pre>
* For queries which don't change between executions, the {@code CriteriaDefinition} may be
* safely built and cached at startup:
* <pre>
* // build and cache the query
* static final CriteriaQuery&lt;Book&gt; bookQuery =
* new CriteriaDefinition&lt;&gt;(sessionFactory, Book.class) {{
* var book = from(Book.class);
* where(like(book.get(Book_.title), "%Hibernate%"));
* orderBy(desc(book.get(Book_.publicationDate)), asc(book.get(Book_.isbn)));
* book.fetch(Book_.authors);
* }};
*
* ...
*
* // execute it in a session
* sessionFactory.inTransaction(session -&gt; {
* List&lt;Book&gt; books =
* session.createQuery(bookQuery)
* .setMaxResults(10)
* .getResultList();
* ...
* });
* </pre>
*
*
* @param <R> the query result type
*
* @since 6.3
*
* @author Gavin King
*/
@Incubating
public abstract class CriteriaDefinition<R>
@ -74,38 +102,64 @@ public abstract class CriteriaDefinition<R>
public CriteriaDefinition(SessionFactory factory, Class<R> resultType) {
super( factory.getCriteriaBuilder() );
query = createQuery( resultType );
define();
}
public CriteriaDefinition(SessionFactory factory, Class<R> resultType, String baseHql) {
super( factory.getCriteriaBuilder() );
query = createQuery( baseHql, resultType );
define();
}
public CriteriaDefinition(SessionFactory factory, CriteriaQuery<R> baseQuery) {
super( factory.getCriteriaBuilder() );
query = (JpaCriteriaQuery<R>) baseQuery;
define();
}
public CriteriaDefinition(Session session, Class<R> resultType) {
this( session.getSessionFactory(), resultType );
public CriteriaDefinition(EntityManagerFactory factory, Class<R> resultType) {
super( factory.getCriteriaBuilder() );
query = createQuery( resultType );
}
public CriteriaDefinition(Session session, Class<R> resultType, String baseHql) {
this( session.getSessionFactory(), resultType, baseHql );
public CriteriaDefinition(EntityManagerFactory factory, Class<R> resultType, String baseHql) {
super( factory.getCriteriaBuilder() );
query = createQuery( baseHql, resultType );
}
public CriteriaDefinition(Session session, CriteriaQuery<R> baseQuery) {
this( session.getSessionFactory(), baseQuery );
public CriteriaDefinition(EntityManagerFactory factory, CriteriaQuery<R> baseQuery) {
super( factory.getCriteriaBuilder() );
query = (JpaCriteriaQuery<R>) baseQuery;
}
public SelectionQuery<R> createSelectionQuery(Session session) {
return session.createSelectionQuery( query );
public CriteriaDefinition(SharedSessionContract session, Class<R> resultType) {
this( session.getFactory(), resultType );
}
public abstract void define();
public CriteriaDefinition(SharedSessionContract session, Class<R> resultType, String baseHql) {
this( session.getFactory(), resultType, baseHql );
}
public CriteriaDefinition(SharedSessionContract session, CriteriaQuery<R> baseQuery) {
this( session.getFactory(), baseQuery );
}
public CriteriaDefinition(EntityManager entityManager, Class<R> resultType) {
this( entityManager.getEntityManagerFactory(), resultType );
}
public CriteriaDefinition(EntityManager entityManager, Class<R> resultType, String baseHql) {
this( entityManager.getEntityManagerFactory(), resultType, baseHql );
}
public CriteriaDefinition(EntityManager entityManager, CriteriaQuery<R> baseQuery) {
this( entityManager.getEntityManagerFactory(), baseQuery );
}
public SelectionQuery<R> createSelectionQuery(QueryProducer session) {
return session.createQuery( query );
}
public TypedQuery<R> createQuery(EntityManager entityManager) {
return entityManager.createQuery( query );
}
@Override
public JpaCriteriaQuery<R> select(Selection<? extends R> selection) {

View File

@ -21,6 +21,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.persistence.criteria.CriteriaBuilder;
import org.hibernate.Incubating;
import org.hibernate.query.SortDirection;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
@ -76,6 +77,10 @@ public class HibernateCriteriaBuilderDelegate implements HibernateCriteriaBuilde
this.criteriaBuilder = criteriaBuilder;
}
public HibernateCriteriaBuilderDelegate(CriteriaBuilder criteriaBuilder) {
this.criteriaBuilder = (HibernateCriteriaBuilder) criteriaBuilder;
}
protected HibernateCriteriaBuilder getCriteriaBuilder() {
return criteriaBuilder;
}

View File

@ -1,11 +1,13 @@
package org.hibernate.orm.test.jpa.criteria.query;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Id;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.criteria.CriteriaDefinition;
import org.hibernate.query.criteria.JpaRoot;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
@ -15,6 +17,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
@SessionFactory
@DomainModel(annotatedClasses = CriteriaDefinitionTest.Message.class)
@Jpa(annotatedClasses = CriteriaDefinitionTest.Message.class)
public class CriteriaDefinitionTest {
@Test void test(SessionFactoryScope scope) {
@ -23,45 +26,83 @@ public class CriteriaDefinitionTest {
s.persist( new Message(2L, "bye") );
});
SessionFactoryImplementor sessionFactory = scope.getSessionFactory();
var factory = scope.getSessionFactory();
var query1 = new CriteriaDefinition<>(sessionFactory, Object[].class) {
public void define() {
var m = from(Message.class);
select(array(m.get("id"), m.get("text")));
where(like(m.get("text"), "hell%"), m.get("id").equalTo(1));
orderBy(asc(m.get("id")));
}
};
var query1 = new CriteriaDefinition<>(factory, Object[].class) {{
var message = from(Message.class);
select(array(message.get("id"), message.get("text")));
where(like(message.get("text"), "hell%"), message.get("id").equalTo(1));
orderBy(asc(message.get("id")));
}};
var query2 = new CriteriaDefinition<>(sessionFactory, Message.class) {
public void define() {
var m = from(Message.class);
where(like(m.get("text"), "hell%"), m.get("id").equalTo(1));
orderBy(asc(m.get("id")));
}
};
var query2 = new CriteriaDefinition<>(factory, Message.class) {{
var message = from(Message.class);
where(like(message.get("text"), "hell%"), message.get("id").equalTo(1));
orderBy(asc(message.get("id")));
}};
var query3 = new CriteriaDefinition<>(sessionFactory, Message.class, "from Msg") {
public void define() {
var message = (JpaRoot<Message>) getSelection();
where(ilike(message.get("text"), "%e%"));
orderBy(asc(message.get("text")));
}
};
var query3 = new CriteriaDefinition<>(factory, Message.class, "from Msg") {{
var message = (JpaRoot<Message>) getSelection();
where(ilike(message.get("text"), "%e%"));
orderBy(asc(message.get("text")));
}};
scope.inSession(session -> {
var idAndText = query1.createSelectionQuery(session).getSingleResult();
var idAndText = session.createSelectionQuery(query1).getSingleResult();
assertNotNull(idAndText);
assertEquals(1L,idAndText[0]);
assertEquals("hello",idAndText[1]);
var message = query2.createSelectionQuery(session).getSingleResult();
var message = session.createSelectionQuery(query2).getSingleResult();
assertNotNull(message);
assertEquals(1L,message.id);
assertEquals("hello",message.text);
var messages = query3.createSelectionQuery(session).getResultList();
var messages = session.createSelectionQuery(query3).getResultList();
assertEquals(2,messages.size());
});
}
@Test void test(EntityManagerFactoryScope scope) {
scope.inTransaction( s -> {
s.persist( new Message(1L, "hello") );
s.persist( new Message(2L, "bye") );
});
EntityManagerFactory factory = scope.getEntityManagerFactory();
var query1 = new CriteriaDefinition<>(factory, Object[].class) {{
var message = from(Message.class);
select(array(message.get("id"), message.get("text")));
where(like(message.get("text"), "hell%"), message.get("id").equalTo(1));
orderBy(asc(message.get("id")));
}};
var query2 = new CriteriaDefinition<>(factory, Message.class) {{
var message = from(Message.class);
where(like(message.get("text"), "hell%"), message.get("id").equalTo(1));
orderBy(asc(message.get("id")));
}};
var query3 = new CriteriaDefinition<>(factory, Message.class, "from Msg") {{
var message = (JpaRoot<Message>) getSelection();
where(ilike(message.get("text"), "%e%"));
orderBy(asc(message.get("text")));
}};
scope.inTransaction(entityManager -> {
var idAndText = entityManager.createQuery(query1).getSingleResult();
assertNotNull(idAndText);
assertEquals(1L,idAndText[0]);
assertEquals("hello",idAndText[1]);
var message = entityManager.createQuery(query2).getSingleResult();
assertNotNull(message);
assertEquals(1L,message.id);
assertEquals("hello",message.text);
var messages = entityManager.createQuery(query3).getResultList();
assertEquals(2,messages.size());
});