HHH-16633 support new Order and Page objects as parameters of query methods

This commit is contained in:
Gavin King 2023-07-05 22:50:47 +02:00
parent 19a75aad9e
commit f933b064e9
8 changed files with 134 additions and 12 deletions

View File

@ -46,12 +46,28 @@ import static java.lang.annotation.RetentionPolicy.CLASS;
* <li>an entity type, * <li>an entity type,
* <li>{@link java.util.List}, * <li>{@link java.util.List},
* <li>{@link org.hibernate.query.Query}, * <li>{@link org.hibernate.query.Query},
* <li>{@link org.hibernate.query.SelectionQuery},
* <li>{@link jakarta.persistence.Query}, or * <li>{@link jakarta.persistence.Query}, or
* <li>{@link jakarta.persistence.TypedQuery}. * <li>{@link jakarta.persistence.TypedQuery}.
* </ul> * </ul>
* <p> * <p>
* The method parameters must match the parameters of the HQL query, * The method parameters must match the parameters of the HQL query,
* either by name or by position. * either by name or by position:
* <ul>
* <li>an ordinal query parameter of form {@code ?n} is matched to
* the <em>n</em>th parameter of the method, and
* <li>a named query parameter of form {@code :name} is matched to
* the method parameter {@code name}.
* </ul>
* <p>
* As an exception, the method may have:
* <ul>
* <li>a parameter with type {@code Page}, and/or
* <li>a parameter with type {@code Order<? super E>},
* {@code List<Order<? super E>>}, or {@code Order<? super E>...}
* (varargs) where {@code E} is the entity type returned by the
* query.
* </ul>
* <p> * <p>
* Queries specified using this annotation are always validated by * Queries specified using this annotation are always validated by
* the Metamodel Generator, and so it isn't necessary to specify the * the Metamodel Generator, and so it isn't necessary to specify the

View File

@ -51,7 +51,13 @@ import static java.lang.annotation.RetentionPolicy.CLASS;
* </ul> * </ul>
* <p> * <p>
* The method parameters must match the parameters of the SQL query, * The method parameters must match the parameters of the SQL query,
* either by name or by position. * either by name or by position:
* <ul>
* <li>an ordinal query parameter of form {@code ?n} is matched to
* the <em>n</em>th parameter of the method, and
* <li>a named query parameter of form {@code :name} is matched to
* the method parameter {@code name}.
* </ul>
* *
* @author Gavin King * @author Gavin King
* @since 6.3 * @since 6.3

View File

@ -17,6 +17,11 @@ import java.util.Objects;
* This is a convenience class which allows query result ordering * This is a convenience class which allows query result ordering
* rules to be passed around the system before being applied to * rules to be passed around the system before being applied to
* a {@link Query} by calling {@link SelectionQuery#setOrder}. * a {@link Query} by calling {@link SelectionQuery#setOrder}.
* <p>
* A parameter of a {@linkplain org.hibernate.annotations.processing.HQL
* HQL query method} may be declared with type {@code Order<? super E>},
* {@code List<Order<? super E>>}, or {@code Order<? super E>...} (varargs)
* where {@code E} is the entity type returned by the query.
* *
* @param <X> The result type of the query to be sorted * @param <X> The result type of the query to be sorted
* *

View File

@ -15,6 +15,9 @@ import org.hibernate.Incubating;
* This is a convenience class which allows a reference to a page of * This is a convenience class which allows a reference to a page of
* results to be passed around the system before being applied to * results to be passed around the system before being applied to
* a {@link Query} by calling {@link Query#setPage(Page)}. * a {@link Query} by calling {@link Query#setPage(Page)}.
* <p>
* A parameter of a {@linkplain org.hibernate.annotations.processing.HQL
* HQL query method} may be declared with type {@code Page}.
* *
* @see Query#setPage(Page) * @see Query#setPage(Page)
* *

View File

@ -76,6 +76,11 @@ public class ImportContextImpl implements ImportContext {
result = result.substring( 0, fqcn.indexOf( '[' ) ); result = result.substring( 0, fqcn.indexOf( '[' ) );
fqcn = result; fqcn = result;
} }
else if ( fqcn.endsWith( "..." ) ) {
additionalTypePart = "...";
result = result.substring( 0, fqcn.indexOf( "..." ) );
fqcn = result;
}
String pureFqcn = fqcn.replace( '$', '.' ); String pureFqcn = fqcn.replace( '$', '.' );

View File

@ -39,6 +39,8 @@ import org.hibernate.jpamodelgen.validation.Validation;
import static java.util.Collections.emptySet; import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static org.hibernate.jpamodelgen.annotation.QueryMethod.isOrderParam;
import static org.hibernate.jpamodelgen.annotation.QueryMethod.isPageParam;
import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation; import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation;
import static org.hibernate.jpamodelgen.util.TypeUtils.determineAnnotationSpecifiedAccessType; import static org.hibernate.jpamodelgen.util.TypeUtils.determineAnnotationSpecifiedAccessType;
import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationMirror; import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationMirror;
@ -385,7 +387,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
); );
putMember( attribute.getPropertyName() + paramTypes, attribute ); putMember( attribute.getPropertyName() + paramTypes, attribute );
checkParameters( method, paramNames, mirror, hql ); checkParameters( method, paramNames, paramTypes, mirror, hql );
if ( !isNative ) { if ( !isNative ) {
// checkHqlSyntax( method, mirror, hql ); // checkHqlSyntax( method, mirror, hql );
Validation.validate( Validation.validate(
@ -399,10 +401,12 @@ public class AnnotationMetaEntity extends AnnotationMeta {
} }
} }
private void checkParameters(ExecutableElement method, List<String> paramNames, AnnotationMirror mirror, String hql) { private void checkParameters(ExecutableElement method, List<String> paramNames, List<String> paramTypes, AnnotationMirror mirror, String hql) {
for (int i = 1; i <= paramNames.size(); i++) { for (int i = 1; i <= paramNames.size(); i++) {
final String param = paramNames.get(i-1); final String param = paramNames.get(i-1);
if ( !hql.contains(":" + param) && !hql.contains("?" + i) ) { final String ptype = paramTypes.get(i-1);
if ( !hql.contains(":" + param) && !hql.contains("?" + i)
&& !isPageParam(ptype) && !isOrderParam(ptype)) {
context.message( method, mirror, "missing query parameter for '" + param context.message( method, mirror, "missing query parameter for '" + param
+ "' (no parameter named :" + param + " or ?" + i + ")", Diagnostic.Kind.ERROR ); + "' (no parameter named :" + param + " or ?" + i + ")", Diagnostic.Kind.ERROR );
} }

View File

@ -10,6 +10,9 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.jpamodelgen.model.MetaAttribute; import org.hibernate.jpamodelgen.model.MetaAttribute;
import org.hibernate.jpamodelgen.model.Metamodel; import org.hibernate.jpamodelgen.model.Metamodel;
import org.hibernate.query.Order;
import org.hibernate.query.Page;
import org.hibernate.query.SelectionQuery;
import java.util.List; import java.util.List;
@ -58,6 +61,9 @@ public class QueryMethod implements MetaAttribute {
@Override @Override
public String getAttributeDeclarationString() { public String getAttributeDeclarationString() {
List<String> paramTypes = this.paramTypes.stream()
.map(ptype->isOrderParam(ptype) && ptype.endsWith("[]") ? ptype.replace("[]", "...") : ptype)
.collect(toList());
StringBuilder declaration = new StringBuilder(); StringBuilder declaration = new StringBuilder();
declaration declaration
.append("\n/**\n * @see ") .append("\n/**\n * @see ")
@ -65,9 +71,14 @@ public class QueryMethod implements MetaAttribute {
.append("#") .append("#")
.append(methodName) .append(methodName)
.append("(") .append("(")
.append(join(",", paramTypes.stream().map(annotationMetaEntity::importType).toArray())) .append(join(",", paramTypes.stream().map(this::strip).map(annotationMetaEntity::importType).toArray()))
.append(")") .append(")")
.append("\n **/\n") .append("\n **/\n");
if ( paramTypes.stream().anyMatch(ptype -> ptype.endsWith("..."))) {
declaration
.append("@SafeVarargs\n");
}
declaration
.append("public static "); .append("public static ");
StringBuilder type = new StringBuilder(); StringBuilder type = new StringBuilder();
if (containerTypeName != null) { if (containerTypeName != null) {
@ -88,11 +99,16 @@ public class QueryMethod implements MetaAttribute {
.append(" entityManager"); .append(" entityManager");
for (int i =0; i<paramNames.size(); i++) { for (int i =0; i<paramNames.size(); i++) {
String ptype = paramTypes.get(i);
String param = paramNames.get(i);
String rptype = returnTypeName != null
? ptype.replace(returnTypeName, annotationMetaEntity.importType(returnTypeName))
: ptype;
declaration declaration
.append(", ") .append(", ")
.append(annotationMetaEntity.importType(paramTypes.get(i))) .append(annotationMetaEntity.importType(rptype))
.append(" ") .append(" ")
.append(paramNames.get(i)); .append(param);
} }
declaration declaration
.append(")") .append(")")
@ -104,7 +120,7 @@ public class QueryMethod implements MetaAttribute {
} }
declaration declaration
.append("entityManager.") .append("entityManager.")
.append(isNative ? "createNativeQuery" :"createQuery") .append(isNative ? "createNativeQuery" : "createQuery")
.append("(") .append("(")
.append(getConstantName()); .append(getConstantName());
if (returnTypeName != null) { if (returnTypeName != null) {
@ -114,8 +130,10 @@ public class QueryMethod implements MetaAttribute {
.append(".class"); .append(".class");
} }
declaration.append(")"); declaration.append(")");
boolean unwrapped = false;
for (int i = 1; i <= paramNames.size(); i++) { for (int i = 1; i <= paramNames.size(); i++) {
String param = paramNames.get(i-1); String param = paramNames.get(i-1);
String ptype = paramTypes.get(i-1);
if (queryString.contains(":" + param)) { if (queryString.contains(":" + param)) {
declaration declaration
.append("\n .setParameter(\"") .append("\n .setParameter(\"")
@ -132,6 +150,32 @@ public class QueryMethod implements MetaAttribute {
.append(param) .append(param)
.append(")"); .append(")");
} }
else if (isPageParam(ptype)) {
unwrap( declaration, unwrapped );
unwrapped = true;
declaration
.append("\n .setPage(")
.append(param)
.append(")");
}
else if (isOrderParam(ptype)) {
unwrap( declaration, unwrapped );
unwrapped = true;
if (ptype.endsWith("...")) {
declaration
.append("\n .setOrder(")
.append(annotationMetaEntity.importType(List.class.getName()))
.append(".of(")
.append(param)
.append("))");
}
else {
declaration
.append("\n .setOrder(")
.append(param)
.append(")");
}
}
} }
if ( containerTypeName == null) { if ( containerTypeName == null) {
declaration.append("\n .getSingleResult()"); declaration.append("\n .getSingleResult()");
@ -143,9 +187,34 @@ public class QueryMethod implements MetaAttribute {
return declaration.toString(); return declaration.toString();
} }
private String strip(String type) {
int index = type.indexOf("<");
String stripped = index > 0 ? type.substring(0, index) : type;
return type.endsWith("...") ? stripped + "..." : stripped;
}
static boolean isPageParam(String ptype) {
return Page.class.getName().equals(ptype);
}
static boolean isOrderParam(String ptype) {
return ptype.startsWith(Order.class.getName())
|| ptype.startsWith(List.class.getName() + "<" + Order.class.getName());
}
private void unwrap(StringBuilder declaration, boolean unwrapped) {
if ( !unwrapped ) {
declaration
.append("\n .unwrap(")
.append(annotationMetaEntity.importType(SelectionQuery.class.getName()))
.append(".class)");
}
}
@Override @Override
public String getAttributeNameDeclarationString() { public String getAttributeNameDeclarationString() {
return new StringBuilder().append("public static final String ") return new StringBuilder()
.append("public static final String ")
.append(getConstantName()) .append(getConstantName())
.append(" = \"") .append(" = \"")
.append(queryString) .append(queryString)
@ -160,7 +229,9 @@ public class QueryMethod implements MetaAttribute {
} }
else { else {
return stem + "_" + StringHelper.join("_", return stem + "_" + StringHelper.join("_",
paramTypes.stream().map(StringHelper::unqualify).collect(toList())); paramTypes.stream()
.filter(name -> !isPageParam(name) && !isOrderParam(name))
.map(StringHelper::unqualify).collect(toList()));
} }
} }

View File

@ -3,6 +3,9 @@ package org.hibernate.jpamodelgen.test.hqlsql;
import jakarta.persistence.TypedQuery; import jakarta.persistence.TypedQuery;
import org.hibernate.annotations.processing.HQL; import org.hibernate.annotations.processing.HQL;
import org.hibernate.annotations.processing.SQL; import org.hibernate.annotations.processing.SQL;
import org.hibernate.query.Order;
import org.hibernate.query.Page;
import org.hibernate.query.SelectionQuery;
import java.util.List; import java.util.List;
@ -13,6 +16,15 @@ public interface Dao {
@HQL("from Book where title like ?1 order by title fetch first ?2 rows only") @HQL("from Book where title like ?1 order by title fetch first ?2 rows only")
List<Book> findFirstNByTitle(String title, int N); List<Book> findFirstNByTitle(String title, int N);
@HQL("from Book where title like :title")
List<Book> findByTitleWithPagination(String title, Order<? super Book> order, Page page);
@HQL("from Book where title like :title")
SelectionQuery<Book> findByTitleWithOrdering(String title, List<Order<? super Book>> order);
@HQL("from Book where title like :title")
SelectionQuery<Book> findByTitleWithOrderingByVarargs(String title, Order<? super Book>... order);
@HQL("from Book where isbn = :isbn") @HQL("from Book where isbn = :isbn")
Book findByIsbn(String isbn); Book findByIsbn(String isbn);