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>{@link java.util.List},
* <li>{@link org.hibernate.query.Query},
* <li>{@link org.hibernate.query.SelectionQuery},
* <li>{@link jakarta.persistence.Query}, or
* <li>{@link jakarta.persistence.TypedQuery}.
* </ul>
* <p>
* 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>
* Queries specified using this annotation are always validated by
* 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>
* <p>
* 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
* @since 6.3

View File

@ -17,6 +17,11 @@ import java.util.Objects;
* This is a convenience class which allows query result ordering
* rules to be passed around the system before being applied to
* 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
*

View File

@ -15,6 +15,9 @@ import org.hibernate.Incubating;
* This is a convenience class which allows a reference to a page of
* results to be passed around the system before being applied to
* 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)
*

View File

@ -76,6 +76,11 @@ public class ImportContextImpl implements ImportContext {
result = result.substring( 0, fqcn.indexOf( '[' ) );
fqcn = result;
}
else if ( fqcn.endsWith( "..." ) ) {
additionalTypePart = "...";
result = result.substring( 0, fqcn.indexOf( "..." ) );
fqcn = result;
}
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.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.determineAnnotationSpecifiedAccessType;
import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationMirror;
@ -385,7 +387,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
);
putMember( attribute.getPropertyName() + paramTypes, attribute );
checkParameters( method, paramNames, mirror, hql );
checkParameters( method, paramNames, paramTypes, mirror, hql );
if ( !isNative ) {
// checkHqlSyntax( method, mirror, hql );
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++) {
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
+ "' (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.jpamodelgen.model.MetaAttribute;
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;
@ -58,6 +61,9 @@ public class QueryMethod implements MetaAttribute {
@Override
public String getAttributeDeclarationString() {
List<String> paramTypes = this.paramTypes.stream()
.map(ptype->isOrderParam(ptype) && ptype.endsWith("[]") ? ptype.replace("[]", "...") : ptype)
.collect(toList());
StringBuilder declaration = new StringBuilder();
declaration
.append("\n/**\n * @see ")
@ -65,9 +71,14 @@ public class QueryMethod implements MetaAttribute {
.append("#")
.append(methodName)
.append("(")
.append(join(",", paramTypes.stream().map(annotationMetaEntity::importType).toArray()))
.append(join(",", paramTypes.stream().map(this::strip).map(annotationMetaEntity::importType).toArray()))
.append(")")
.append("\n **/\n")
.append("\n **/\n");
if ( paramTypes.stream().anyMatch(ptype -> ptype.endsWith("..."))) {
declaration
.append("@SafeVarargs\n");
}
declaration
.append("public static ");
StringBuilder type = new StringBuilder();
if (containerTypeName != null) {
@ -88,11 +99,16 @@ public class QueryMethod implements MetaAttribute {
.append(" entityManager");
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
.append(", ")
.append(annotationMetaEntity.importType(paramTypes.get(i)))
.append(annotationMetaEntity.importType(rptype))
.append(" ")
.append(paramNames.get(i));
.append(param);
}
declaration
.append(")")
@ -104,7 +120,7 @@ public class QueryMethod implements MetaAttribute {
}
declaration
.append("entityManager.")
.append(isNative ? "createNativeQuery" :"createQuery")
.append(isNative ? "createNativeQuery" : "createQuery")
.append("(")
.append(getConstantName());
if (returnTypeName != null) {
@ -114,8 +130,10 @@ public class QueryMethod implements MetaAttribute {
.append(".class");
}
declaration.append(")");
boolean unwrapped = false;
for (int i = 1; i <= paramNames.size(); i++) {
String param = paramNames.get(i-1);
String ptype = paramTypes.get(i-1);
if (queryString.contains(":" + param)) {
declaration
.append("\n .setParameter(\"")
@ -132,6 +150,32 @@ public class QueryMethod implements MetaAttribute {
.append(param)
.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) {
declaration.append("\n .getSingleResult()");
@ -143,9 +187,34 @@ public class QueryMethod implements MetaAttribute {
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
public String getAttributeNameDeclarationString() {
return new StringBuilder().append("public static final String ")
return new StringBuilder()
.append("public static final String ")
.append(getConstantName())
.append(" = \"")
.append(queryString)
@ -160,7 +229,9 @@ public class QueryMethod implements MetaAttribute {
}
else {
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 org.hibernate.annotations.processing.HQL;
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;
@ -13,6 +16,15 @@ public interface Dao {
@HQL("from Book where title like ?1 order by title fetch first ?2 rows only")
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")
Book findByIsbn(String isbn);