HHH-17772 support KeysetAwarePage from Jakarta Data

+ fix some bugs
This commit is contained in:
Gavin King 2024-02-29 06:28:18 +01:00
parent 9e5ce60ac1
commit 4fe6dad95b
6 changed files with 163 additions and 117 deletions

View File

@ -11,8 +11,6 @@ import org.hibernate.AssertionFailure;
import org.hibernate.jpamodelgen.model.MetaAttribute;
import org.hibernate.jpamodelgen.model.Metamodel;
import org.hibernate.jpamodelgen.util.Constants;
import org.hibernate.query.Order;
import org.hibernate.query.SortDirection;
import java.util.Arrays;
import java.util.List;
@ -24,6 +22,7 @@ import static org.hibernate.jpamodelgen.util.Constants.HIB_KEYED_RESULT_LIST;
import static org.hibernate.jpamodelgen.util.Constants.HIB_ORDER;
import static org.hibernate.jpamodelgen.util.Constants.HIB_PAGE;
import static org.hibernate.jpamodelgen.util.Constants.HIB_SELECTION_QUERY;
import static org.hibernate.jpamodelgen.util.Constants.JD_KEYED_PAGE;
import static org.hibernate.jpamodelgen.util.Constants.JD_KEYED_SLICE;
import static org.hibernate.jpamodelgen.util.Constants.JD_LIMIT;
import static org.hibernate.jpamodelgen.util.Constants.JD_ORDER;
@ -230,10 +229,9 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
if ( paramType.startsWith(JD_ORDER) ) {
final String sortableEntityClass = getSortableEntityClass();
if ( sortableEntityClass != null ) {
annotationMetaEntity.staticImport(SortDirection.class.getName(), "ASCENDING");
annotationMetaEntity.staticImport(SortDirection.class.getName(), "DESCENDING");
annotationMetaEntity.staticImport("org.hibernate.query.SortDirection", "*");
annotationMetaEntity.staticImport(Collectors.class.getName(), "toList");
annotationMetaEntity.staticImport(Order.class.getName(), "by");
annotationMetaEntity.staticImport(HIB_ORDER, "by");
declaration
.append("\n\t\t\t.setOrder(")
.append(paramName)
@ -253,16 +251,14 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
else if ( paramType.startsWith(JD_SORT) && paramType.endsWith("...") ) {
final String sortableEntityClass = getSortableEntityClass();
if ( sortableEntityClass != null ) {
annotationMetaEntity.staticImport(SortDirection.class.getName(), "ASCENDING");
annotationMetaEntity.staticImport(SortDirection.class.getName(), "DESCENDING");
annotationMetaEntity.staticImport("org.hibernate.query.SortDirection", "*");
annotationMetaEntity.staticImport(Arrays.class.getName(), "asList");
annotationMetaEntity.staticImport(Order.class.getName(), "by");
annotationMetaEntity.staticImport(Collectors.class.getName(), "toList");
annotationMetaEntity.staticImport(HIB_ORDER, "by");
declaration
.append("\n\t\t\t.setOrder(asList(")
.append(paramName)
.append(").stream().map(_sort -> ")
.append("by(")
.append(").stream().map(_sort -> by(")
.append(annotationMetaEntity.importType(sortableEntityClass))
.append(".class, ")
.append("_sort.property()")
@ -274,11 +270,10 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
else if ( paramType.startsWith(JD_SORT) ) {
final String sortableEntityClass = getSortableEntityClass();
if ( sortableEntityClass != null ) {
annotationMetaEntity.staticImport(SortDirection.class.getName(), "ASCENDING");
annotationMetaEntity.staticImport(SortDirection.class.getName(), "DESCENDING");
annotationMetaEntity.staticImport("org.hibernate.query.SortDirection", "*");
declaration
.append("\n\t\t\t.setOrder(")
.append(annotationMetaEntity.importType(Order.class.getName()))
.append(annotationMetaEntity.importType(HIB_ORDER))
.append(".by(")
.append(annotationMetaEntity.importType(sortableEntityClass))
.append(".class, ")
@ -293,7 +288,7 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
else if ( paramType.endsWith("...") ) {
declaration
.append("\n\t\t\t.setOrder(")
.append(annotationMetaEntity.importType(Constants.LIST))
.append(annotationMetaEntity.importType(LIST))
.append(".of(")
.append(paramName)
.append("))");
@ -384,7 +379,8 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
static boolean isKeyedResultList(String returnType) {
return returnType.startsWith(HIB_KEYED_RESULT_LIST)
|| returnType.startsWith(JD_KEYED_SLICE);
|| returnType.startsWith(JD_KEYED_SLICE)
|| returnType.startsWith(JD_KEYED_PAGE);
}
static boolean isPageParam(String parameterType) {
@ -400,16 +396,28 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
|| parameterType.startsWith(JD_ORDER);
}
static boolean isJakartaKeyedSlice(@Nullable String containerType) {
return JD_KEYED_SLICE.equals(containerType)
|| JD_KEYED_PAGE.equals(containerType);
}
void makeKeyedPage(StringBuilder declaration, List<String> paramTypes) {
annotationMetaEntity.importType("org.hibernate.query.Page");
annotationMetaEntity.importType("jakarta.data.page.impl.KeysetAwareSliceRecord");
annotationMetaEntity.importType("jakarta.data.page.PageRequest.Cursor");
annotationMetaEntity.staticImport("org.hibernate.query.SortDirection", "*");
annotationMetaEntity.staticImport("org.hibernate.query.KeyedPage.KeyInterpretation", "*");
annotationMetaEntity.staticImport("org.hibernate.query.Order", "by");
annotationMetaEntity.importType("org.hibernate.query.Page");
annotationMetaEntity.staticImport(Collectors.class.getName(), "toList");
for (int i = 0; i < paramTypes.size(); i++) {
if ( isKeyedPageParam( paramTypes.get(i) ) ) {
declaration
.append(MAKE_KEYED_PAGE.replace("pageRequest", paramNames.get(i)))
.append("\t\tvar results = ");
final String entityClass = getSortableEntityClass();
if ( entityClass == null ) {
throw new AssertionFailure("entity class cannot be null");
}
else {
declaration
.append(MAKE_KEYED_PAGE.replace("pageRequest", paramNames.get(i))
.replace("Entity", annotationMetaEntity.importType(entityClass)));
}
}
}
}
@ -425,28 +433,93 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
"\t\t\t\t\t\t.sortBy(pageRequest.sorts())\n" +
"\t\t\t\t\t\t.size(pageRequest.size())\n" +
"\t\t\t\t\t\t.page(pageRequest.page() + 1);\n" +
"\t\treturn new KeysetAwareSliceRecord<>( results.getResultList(), cursors, pageRequest,\n" +
"\t\treturn new KeysetAwareSliceRecord<>( results.getResultList(), cursors, totalResults, pageRequest,\n" +
"\t\t\t\tresults.isLastPage() ? null : page.afterKeyset( results.getNextPage().getKey().toArray() ),\n" +
"\t\t\t\tresults.isFirstPage() ? null : page.beforeKeyset( results.getPreviousPage().getKey().toArray() ) );\n";
static final String MAKE_KEYED_PAGE
= "\t\tvar unkeyedPage =\n" +
"\t\t\t\tPage.page(pageRequest.size(), (int) pageRequest.page()-1)\n" +
"\t\t\t\t\t\t.keyedBy(pageRequest.sorts().stream()\n" +
"\t\t\t\t\t\t\t\t.map(_sort -> by(Book.class, _sort.property(),\n" +
"\t\t\t\t\t\t\t\t\t\t_sort.isAscending() ? ASCENDING : DESCENDING,\n" +
"\t\t\t\t\t\t\t\t\t\t_sort.ignoreCase())).collect(toList()));\n" +
"\t\tvar keyedPage =\n" +
"\t\t\t\tpageRequest.cursor()\n" +
"\t\t\t\t\t\t.map(_cursor -> {\n" +
"\t\t\t\t\t\t\t@SuppressWarnings(\"unchecked\")\n" +
"\t\t\t\t\t\t\tvar elements = (List<Comparable<?>>) _cursor.elements();\n" +
"\t\t\t\t\t\t\treturn switch ( pageRequest.mode() ) {\n" +
"\t\t\t\t\t\t\t\tcase CURSOR_NEXT -> unkeyedPage.withKey(elements, KEY_OF_LAST_ON_PREVIOUS_PAGE);\n" +
"\t\t\t\t\t\t\t\tcase CURSOR_PREVIOUS -> unkeyedPage.withKey(elements, KEY_OF_FIRST_ON_NEXT_PAGE);\n" +
"\t\t\t\t\t\t\t\tdefault -> unkeyedPage;\n" +
"\t\t\t\t\t\t\t};\n" +
"\t\t\t\t\t\t}).orElse(unkeyedPage);\n";
= "\tvar unkeyedPage =\n" +
"\t\t\tPage.page(pageRequest.size(), (int) pageRequest.page()-1)\n" +
"\t\t\t\t\t.keyedBy(pageRequest.sorts().stream()\n" +
"\t\t\t\t\t\t\t.map(_sort -> by(Entity.class, _sort.property(),\n" +
"\t\t\t\t\t\t\t\t\t_sort.isAscending() ? ASCENDING : DESCENDING,\n" +
"\t\t\t\t\t\t\t\t\t_sort.ignoreCase())).collect(toList()));\n" +
"\tvar keyedPage =\n" +
"\t\t\tpageRequest.cursor()\n" +
"\t\t\t\t\t.map(_cursor -> {\n" +
"\t\t\t\t\t\t@SuppressWarnings(\"unchecked\")\n" +
"\t\t\t\t\t\tvar elements = (List<Comparable<?>>) _cursor.elements();\n" +
"\t\t\t\t\t\treturn switch ( pageRequest.mode() ) {\n" +
"\t\t\t\t\t\t\tcase CURSOR_NEXT -> unkeyedPage.withKey(elements, KEY_OF_LAST_ON_PREVIOUS_PAGE);\n" +
"\t\t\t\t\t\t\tcase CURSOR_PREVIOUS -> unkeyedPage.withKey(elements, KEY_OF_FIRST_ON_NEXT_PAGE);\n" +
"\t\t\t\t\t\t\tdefault -> unkeyedPage;\n" +
"\t\t\t\t\t\t};\n" +
"\t\t\t\t\t}).orElse(unkeyedPage);\n";
void createQuery(StringBuilder declaration) {}
void setParameters(StringBuilder declaration, List<String> paramTypes) {}
void tryReturn(StringBuilder declaration, List<String> paramTypes, @Nullable String containerType) {
if ( isJakartaKeyedSlice(containerType) ) {
makeKeyedPage( declaration, paramTypes );
}
if ( dataRepository ) {
declaration
.append("\ttry {\n");
}
if ( JD_KEYED_PAGE.equals(containerType) ) {
if ( dataRepository ) {
declaration
.append('\t');
}
declaration
.append("\tlong totalResults = ");
createQuery( declaration );
setParameters( declaration, paramTypes );
unwrapQuery( declaration, !isUsingEntityManager() );
declaration
.append("\n\t\t\t\t\t\t.getResultCount();\n");
}
if ( dataRepository ) {
declaration
.append('\t');
}
declaration
.append('\t');
if ( isJakartaKeyedSlice(containerType) ) {
final String entityClass = getSortableEntityClass();
if ( entityClass != null && isUsingEntityManager() ) {
// this is necessary to avoid losing the type
// after unwrapping the Query object
declaration
.append(annotationMetaEntity.importType(HIB_KEYED_RESULT_LIST))
.append('<')
.append(annotationMetaEntity.importType(entityClass))
.append('>');
}
else {
declaration
.append("var");
}
declaration
.append(" results = ");
}
else {
if ( !"void".equals(returnTypeName) ) {
declaration
.append("return ");
}
}
}
boolean unwrapIfNecessary(StringBuilder declaration, @Nullable String containerType, boolean unwrapped) {
if ( OPTIONAL.equals(containerType) || isJakartaKeyedSlice(containerType) ) {
unwrapQuery( declaration, unwrapped );
unwrapped = true;
}
return unwrapped;
}
protected void executeSelect(
StringBuilder declaration,
@ -483,6 +556,7 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
}
break;
case JD_KEYED_SLICE:
case JD_KEYED_PAGE:
for (int i = 0; i < paramTypes.size(); i++) {
if ( isKeyedPageParam( paramTypes.get(i) ) ) {
final String entityClass = getSortableEntityClass();
@ -490,12 +564,19 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
throw new AssertionFailure("entity class cannot be null");
}
else {
final String entity = annotationMetaEntity.importType(entityClass);
declaration
.append("\t\t\t.getKeyedResultList(keyedPage);\n")
.append(MAKE_KEYED_SLICE
.replace("pageRequest", paramNames.get(i))
.replace("Entity", entity));
.append("\t\t\t.getKeyedResultList(keyedPage);\n");
annotationMetaEntity.importType("jakarta.data.page.PageRequest");
annotationMetaEntity.importType("jakarta.data.page.PageRequest.Cursor");
String fragment = MAKE_KEYED_SLICE
.replace("pageRequest", paramNames.get(i))
.replace("Entity", annotationMetaEntity.importType(entityClass))
.replace("KeysetAwareSliceRecord", implType(containerType));
if ( JD_KEYED_SLICE.equals(containerType) ) {
fragment = fragment.replace("totalResults, ", "");
}
declaration
.append(fragment);
}
}
}
@ -515,4 +596,19 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
}
}
}
private String implType(String containerType) {
String recordType;
switch (containerType) {
case JD_KEYED_SLICE:
recordType = annotationMetaEntity.importType("jakarta.data.page.impl.KeysetAwareSliceRecord");
break;
case JD_KEYED_PAGE:
recordType = annotationMetaEntity.importType("jakarta.data.page.impl.KeysetAwarePageRecord");
break;
default:
throw new AssertionFailure("unrecognized slice type");
}
return recordType;
}
}

View File

@ -631,7 +631,9 @@ public class AnnotationMetaEntity extends AnnotationMeta {
= Set.of(LIST, QUERY, HIB_QUERY);
private static final Set<String> LEGAL_GENERIC_RESULT_TYPES
= Set.of(LIST, STREAM, OPTIONAL, TYPED_QUERY, HIB_QUERY, HIB_SELECTION_QUERY, HIB_KEYED_RESULT_LIST, JD_KEYED_SLICE);
= Set.of(LIST, STREAM, OPTIONAL,
TYPED_QUERY, HIB_QUERY, HIB_SELECTION_QUERY,
HIB_KEYED_RESULT_LIST, JD_KEYED_SLICE, JD_KEYED_PAGE);
private void addQueryMethod(
ExecutableElement method,

View File

@ -15,10 +15,8 @@ import java.util.List;
import java.util.StringTokenizer;
import java.util.stream.Collectors;
import static org.hibernate.jpamodelgen.util.Constants.JD_KEYED_SLICE;
import static org.hibernate.jpamodelgen.util.Constants.JD_SORT;
import static org.hibernate.jpamodelgen.util.Constants.LIST;
import static org.hibernate.jpamodelgen.util.Constants.OPTIONAL;
import static org.hibernate.jpamodelgen.util.TypeUtils.isPrimitive;
/**
@ -91,7 +89,7 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
private void executeQuery(StringBuilder declaration, List<String> paramTypes) {
declaration
.append('\n');
tryReturn( declaration, paramTypes );
tryReturn( declaration, paramTypes, containerType );
castResult( declaration );
createQuery( declaration );
boolean unwrapped = specialNeeds( declaration, paramTypes );
@ -112,22 +110,15 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
boolean unwrapped = !isUsingEntityManager();
unwrapped = handleSpecialParameters( declaration, paramTypes, unwrapped );
unwrapped = enableFetchProfile( declaration, unwrapped );
unwrapped = unwrapIfOptional( declaration, unwrapped );
unwrapped = unwrapIfNecessary( declaration, containerType, unwrapped );
if ( unwrapped ) {
declaration.append("\n\t\t\t");
}
return unwrapped;
}
private boolean unwrapIfOptional(StringBuilder declaration, boolean unwrapped) {
if ( OPTIONAL.equals(containerType) ) {
unwrapQuery(declaration, unwrapped);
unwrapped = true;
}
return unwrapped;
}
private void createQuery(StringBuilder declaration) {
@Override
void createQuery(StringBuilder declaration) {
declaration
.append(sessionName)
.append(".createQuery(query)");
@ -144,7 +135,7 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
}
private boolean handleSpecialParameters(StringBuilder declaration, List<String> paramTypes, boolean unwrapped) {
if ( containerType == null || !containerType.equals(JD_KEYED_SLICE) ) {
if ( !isJakartaKeyedSlice(containerType) ) {
for ( int i = 0; i < paramNames.size(); i ++ ) {
final String paramName = paramNames.get(i);
final String paramType = paramTypes.get(i);
@ -160,25 +151,6 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
return unwrapped;
}
private void tryReturn(StringBuilder declaration, List<String> paramTypes) {
if (dataRepository) {
declaration
.append("\ttry {\n");
}
if ( containerType != null
&& containerType.equals(JD_KEYED_SLICE) ) {
makeKeyedPage(declaration, paramTypes);
}
else {
if ( dataRepository ) {
declaration
.append('\t');
}
declaration
.append("\treturn ");
}
}
private void createCriteriaQuery(StringBuilder declaration) {
declaration
.append("\n\tvar builder = ")

View File

@ -16,9 +16,7 @@ import java.util.List;
import static org.hibernate.jpamodelgen.util.Constants.HIB_ORDER;
import static org.hibernate.jpamodelgen.util.Constants.HIB_QUERY;
import static org.hibernate.jpamodelgen.util.Constants.HIB_SELECTION_QUERY;
import static org.hibernate.jpamodelgen.util.Constants.JD_KEYED_SLICE;
import static org.hibernate.jpamodelgen.util.Constants.LIST;
import static org.hibernate.jpamodelgen.util.Constants.OPTIONAL;
import static org.hibernate.jpamodelgen.util.Constants.QUERY;
import static org.hibernate.jpamodelgen.util.Constants.TYPED_QUERY;
import static org.hibernate.jpamodelgen.util.StringUtil.getUpperUnderscoreCaseFromLowerCamelCase;
@ -93,10 +91,10 @@ public class QueryMethod extends AbstractQueryMethod {
comment( declaration );
modifiers( paramTypes, declaration );
preamble( declaration, returnType, paramTypes );
tryReturn( declaration );
tryReturn( declaration, paramTypes, containerType );
castResult( declaration, returnType );
createQuery( declaration );
setParameters( paramTypes, declaration );
setParameters( declaration, paramTypes );
boolean unwrapped = specialNeeds( paramTypes, declaration );
execute( declaration, unwrapped );
convertExceptions( declaration );
@ -106,14 +104,14 @@ public class QueryMethod extends AbstractQueryMethod {
private boolean specialNeeds(List<String> paramTypes, StringBuilder declaration) {
boolean unwrapped;
if ( containerType == null || !containerType.equals(JD_KEYED_SLICE) ) {
if ( !isJakartaKeyedSlice(containerType) ) {
unwrapped = handleSpecialParameters( paramTypes, declaration );
unwrapped = orderBy( declaration, unwrapped );
}
else {
unwrapped = !isUsingEntityManager();
}
unwrapped = unwrapIfOptional( declaration, unwrapped );
unwrapped = unwrapIfNecessary( declaration, containerType, unwrapped );
if ( isUpdate || containerType == null || !isQueryType(containerType)) {
declaration
.append("\n\t\t\t");
@ -121,14 +119,6 @@ public class QueryMethod extends AbstractQueryMethod {
return unwrapped;
}
private boolean unwrapIfOptional(StringBuilder declaration, boolean unwrapped) {
if ( OPTIONAL.equals(containerType) ) {
unwrapQuery(declaration, unwrapped);
unwrapped = true;
}
return unwrapped;
}
private boolean isQueryType(String containerType) {
return HIB_QUERY.equals(containerType)
|| HIB_SELECTION_QUERY.equals(containerType)
@ -174,7 +164,8 @@ public class QueryMethod extends AbstractQueryMethod {
}
}
private void createQuery(StringBuilder declaration) {
@Override
void createQuery(StringBuilder declaration) {
declaration
.append(sessionName)
.append(isNative ? ".createNativeQuery" : ".createQuery")
@ -200,29 +191,6 @@ public class QueryMethod extends AbstractQueryMethod {
}
}
private void tryReturn(StringBuilder declaration) {
if (dataRepository) {
declaration
.append("\ttry {\n");
}
if ( containerType != null
&& containerType.equals(JD_KEYED_SLICE) ) {
makeKeyedPage( declaration, paramTypes );
}
else {
if ( dataRepository ) {
declaration
.append('\t');
}
declaration
.append("\t");
if ( returnTypeName == null || !returnTypeName.equals("void") ) {
declaration
.append("return ");
}
}
}
private void preamble(StringBuilder declaration, StringBuilder returnType, List<String> paramTypes) {
declaration
.append(returnType)
@ -255,7 +223,8 @@ public class QueryMethod extends AbstractQueryMethod {
}
}
private void setParameters(List<String> paramTypes, StringBuilder declaration) {
@Override
void setParameters(StringBuilder declaration, List<String> paramTypes) {
for ( int i = 0; i < paramNames.size(); i++ ) {
final String paramName = paramNames.get(i);
final String paramType = paramTypes.get(i);

View File

@ -73,6 +73,7 @@ public final class Constants {
public static final String JD_SORT = "jakarta.data.Sort";
public static final String JD_ORDER = "jakarta.data.Order";
public static final String JD_KEYED_SLICE = "jakarta.data.page.KeysetAwareSlice";
public static final String JD_KEYED_PAGE = "jakarta.data.page.KeysetAwarePage";
public static final String HIB_ORDER = "org.hibernate.query.Order";
public static final String HIB_PAGE = "org.hibernate.query.Page";

View File

@ -118,4 +118,10 @@ public interface BookAuthorRepository {
@Query("from Book where title like :titlePattern")
KeysetAwareSlice<Book> everyBook7(String titlePattern, PageRequest<Book> pageRequest);
@Find
KeysetAwarePage<Book> everyBook8(String title, PageRequest<Book> pageRequest);
@Query("from Book where title like :titlePattern")
KeysetAwarePage<Book> everyBook9(String titlePattern, PageRequest<Book> pageRequest);
}