From 698b245753a347e07ddcfd0008f3c1664638b2e6 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 16 Jun 2023 21:02:39 +0200 Subject: [PATCH] HHH-16633 introduce query methods to JPA metamodel generator --- .../java/org/hibernate/annotations/Hql.java | 66 +++++ .../hibernate-jpamodelgen.gradle | 2 + .../hibernate/jpamodelgen/ClassWriter.java | 3 +- .../JPAMetaModelEntityProcessor.java | 21 +- .../annotation/AnnotationMeta.java | 3 +- .../annotation/AnnotationMetaEntity.java | 225 ++++++++++++++++-- .../annotation/AnnotationMetaPackage.java | 3 +- .../jpamodelgen/annotation/QueryMethod.java | 155 ++++++++++++ .../jpamodelgen/model/Metamodel.java | 4 + .../hibernate/jpamodelgen/util/Constants.java | 2 + .../jpamodelgen/xml/XmlMetaEntity.java | 5 + .../test/namedquery/AuxiliaryTest.java | 3 +- .../jpamodelgen/test/namedquery/Dao.java | 17 ++ 13 files changed, 482 insertions(+), 27 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/annotations/Hql.java create mode 100644 tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/QueryMethod.java create mode 100644 tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Dao.java diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Hql.java b/hibernate-core/src/main/java/org/hibernate/annotations/Hql.java new file mode 100644 index 0000000000..636ef057a0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Hql.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.annotations; + +import org.hibernate.Incubating; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.CLASS; + +/** + * Identifies a method of an abstract class or interface + * as defining the signature of a method which is used to + * execute the given {@linkplain #value HQL query}, and is + * generated automatically by the Hibernate Metamodel + * Generator. + *

+ * For example: + *

+ * public interface Books {
+ *     @Hql("from Book where isbn = :isbn")
+ *     Book findBookByIsbn(String isbn);
+ *
+ *     @Hql("from Book where title like ?1 order by title offset ?3 fetch first ?2 rows only")
+ *     List<Book> findBooksByTitleWithPagination(String title, int max, int start);
+ *
+ *     @Hql("from Book where title like ?1")
+ *     TypedQuery<Book> findBooksByTitle(String title);
+ * }
+ * 
+ *

+ * The Metamodel Generator automatically creates an + * "implementation" of these methods in the static metamodel + * class {@code Books_}. + *

+ * Book book = Books_.findBookByIsbn(session, isbn);
+ * List<Book> books = Books_.findBooksByTitleWithPagination(session, pattern, 10, 0);
+ * 
+ *

+ * The return type of an annotated method must be: + *

+ *

+ * The method parameters must match the parameters of the + * HQL query, either by name or by position. + * + * @author Gavin King + * @since 6.3 + */ +@Target(METHOD) +@Retention(CLASS) +@Incubating +public @interface Hql { + String value(); +} diff --git a/tooling/metamodel-generator/hibernate-jpamodelgen.gradle b/tooling/metamodel-generator/hibernate-jpamodelgen.gradle index f323a05f1c..9e58a18ee8 100644 --- a/tooling/metamodel-generator/hibernate-jpamodelgen.gradle +++ b/tooling/metamodel-generator/hibernate-jpamodelgen.gradle @@ -21,6 +21,8 @@ ext { dependencies { implementation jakartaLibs.jaxbApi implementation jakartaLibs.jaxb + implementation libs.antlrRuntime + implementation project( ':hibernate-core' ) xjc jakartaLibs.xjc xjc jakartaLibs.jaxb diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/ClassWriter.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/ClassWriter.java index 1dfc692c7c..805d62d819 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/ClassWriter.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/ClassWriter.java @@ -116,7 +116,8 @@ public final class ClassWriter { List members = entity.getMembers(); for (MetaAttribute metaMember : members) { if (metaMember.hasTypedAttribute()) { - pw.println(" " + metaMember.getAttributeDeclarationString()); + metaMember.getAttributeDeclarationString().lines() + .forEach(line -> pw.println(" " + line)); } } pw.println(); diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/JPAMetaModelEntityProcessor.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/JPAMetaModelEntityProcessor.java index e1d07b4ceb..0212b04c05 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/JPAMetaModelEntityProcessor.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/JPAMetaModelEntityProcessor.java @@ -39,6 +39,9 @@ import org.hibernate.jpamodelgen.xml.JpaDescriptorParser; import org.checkerframework.checker.nullness.qual.Nullable; +import static org.hibernate.jpamodelgen.util.Constants.QUERY_METHOD; +import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation; + /** * Main annotation processor. * @@ -137,6 +140,16 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor { context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class " + element.toString() ); handleRootElementAuxiliaryAnnotationMirrors( element ); } + else if ( element instanceof TypeElement ) { + for ( Element enclosedElement : element.getEnclosedElements() ) { + if ( containsAnnotation( enclosedElement, QUERY_METHOD ) ) { + AnnotationMetaEntity metaEntity = + AnnotationMetaEntity.create( (TypeElement) element, context, false ); + context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity ); + break; + } + } + } } createMetaModelClasses(); @@ -228,7 +241,7 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor { } private boolean isJPAEntity(Element element) { - return TypeUtils.containsAnnotation( + return containsAnnotation( element, Constants.ENTITY, Constants.MAPPED_SUPERCLASS, @@ -237,7 +250,7 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor { } private boolean hasAuxiliaryAnnotations(Element element) { - return TypeUtils.containsAnnotation( + return containsAnnotation( element, Constants.NAMED_QUERY, Constants.NAMED_QUERIES, @@ -275,8 +288,8 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor { boolean requiresLazyMemberInitialization = false; AnnotationMetaEntity metaEntity; - if ( TypeUtils.containsAnnotation( element, Constants.EMBEDDABLE ) || - TypeUtils.containsAnnotation( element, Constants.MAPPED_SUPERCLASS ) ) { + if ( containsAnnotation( element, Constants.EMBEDDABLE ) || + containsAnnotation( element, Constants.MAPPED_SUPERCLASS ) ) { requiresLazyMemberInitialization = true; } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java index 722a3337d6..50f010143b 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMeta.java @@ -6,6 +6,7 @@ */ package org.hibernate.jpamodelgen.annotation; +import org.hibernate.jpamodelgen.model.MetaAttribute; import org.hibernate.jpamodelgen.model.Metamodel; import org.hibernate.jpamodelgen.util.Constants; import org.hibernate.jpamodelgen.util.TypeUtils; @@ -66,5 +67,5 @@ public abstract class AnnotationMeta implements Metamodel { }); } - abstract void putMember(String name, NameMetaAttribute nameMetaAttribute); + abstract void putMember(String name, MetaAttribute nameMetaAttribute); } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java index 173e3c2141..e637718e65 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaEntity.java @@ -7,19 +7,36 @@ package org.hibernate.jpamodelgen.annotation; import java.util.ArrayList; +import java.util.BitSet; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic; +import org.antlr.v4.runtime.ANTLRErrorListener; +import org.antlr.v4.runtime.BailErrorStrategy; +import org.antlr.v4.runtime.DefaultErrorStrategy; +import org.antlr.v4.runtime.Parser; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.atn.ATNConfigSet; +import org.antlr.v4.runtime.atn.PredictionMode; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.grammars.hql.HqlLexer; +import org.hibernate.grammars.hql.HqlParser; import org.hibernate.jpamodelgen.Context; import org.hibernate.jpamodelgen.ImportContextImpl; import org.hibernate.jpamodelgen.model.ImportContext; @@ -30,6 +47,14 @@ import org.hibernate.jpamodelgen.util.AccessTypeInformation; import org.hibernate.jpamodelgen.util.Constants; import org.hibernate.jpamodelgen.util.NullnessUtil; import org.hibernate.jpamodelgen.util.TypeUtils; +import org.hibernate.query.hql.internal.HqlParseTreeBuilder; + +import static java.util.stream.Collectors.toList; +import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation; +import static org.hibernate.jpamodelgen.util.TypeUtils.determineAnnotationSpecifiedAccessType; +import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationMirror; +import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationValue; +import static org.hibernate.query.hql.internal.StandardHqlTranslator.prettifyAntlrError; /** * Class used to collect meta information about an annotated type (entity, embeddable or mapped superclass). @@ -37,6 +62,7 @@ import org.hibernate.jpamodelgen.util.TypeUtils; * @author Max Andersen * @author Hardy Ferentschik * @author Emmanuel Bernard + * @author Gavin King */ public class AnnotationMetaEntity extends AnnotationMeta { @@ -86,6 +112,7 @@ public class AnnotationMetaEntity extends AnnotationMeta { return entityAccessTypeInfo; } + @Override public final Context getContext() { return context; } @@ -168,7 +195,7 @@ public class AnnotationMetaEntity extends AnnotationMeta { } @Override - void putMember(String name, NameMetaAttribute nameMetaAttribute) { + void putMember(String name, MetaAttribute nameMetaAttribute) { members.put( name, nameMetaAttribute ); } @@ -194,20 +221,26 @@ public class AnnotationMetaEntity extends AnnotationMeta { List methodsOfClass = ElementFilter.methodsIn( element.getEnclosedElements() ); List gettersAndSettersOfClass = new ArrayList<>(); + List queryMethods = new ArrayList<>(); for ( Element rawMethodOfClass: methodsOfClass ) { if ( isGetterOrSetter( rawMethodOfClass ) ) { gettersAndSettersOfClass.add( rawMethodOfClass ); } + else if ( rawMethodOfClass instanceof ExecutableElement + && containsAnnotation( rawMethodOfClass, Constants.QUERY_METHOD ) ) { + queryMethods.add( (ExecutableElement) rawMethodOfClass ); + } } addPersistentMembers( gettersAndSettersOfClass, AccessType.PROPERTY ); addAuxiliaryMembers(); + addQueryMethods( queryMethods ); + initialized = true; } - /** * Check if method respects Java Bean conventions for getter and setters. * @@ -221,31 +254,30 @@ public class AnnotationMetaEntity extends AnnotationMeta { List methodParameterTypes = methodType.getParameterTypes(); TypeMirror returnType = methodType.getReturnType(); - if( - methodSimpleName.startsWith("set") && - methodParameterTypes.size() == 1 && - "void".equalsIgnoreCase( returnType.toString() ) ) { - return true; - } - else if( - ( methodSimpleName.startsWith("get") || methodSimpleName.startsWith("is") ) && - methodParameterTypes.isEmpty() && - !"void".equalsIgnoreCase( returnType.toString() ) ) { - return true; - } - else { - return false; - } + return isSetter(methodSimpleName, methodParameterTypes, returnType) + || isGetter(methodSimpleName, methodParameterTypes, returnType); + } + + private static boolean isGetter(String methodSimpleName, List methodParameterTypes, TypeMirror returnType) { + return (methodSimpleName.startsWith("get") || methodSimpleName.startsWith("is")) + && methodParameterTypes.isEmpty() + && !"void".equalsIgnoreCase(returnType.toString()); + } + + private static boolean isSetter(String methodSimpleName, List methodParameterTypes, TypeMirror returnType) { + return methodSimpleName.startsWith("set") + && methodParameterTypes.size() == 1 + && "void".equalsIgnoreCase(returnType.toString()); } private void addPersistentMembers(List membersOfClass, AccessType membersKind) { for ( Element memberOfClass : membersOfClass ) { - AccessType forcedAccessType = TypeUtils.determineAnnotationSpecifiedAccessType( memberOfClass ); + AccessType forcedAccessType = determineAnnotationSpecifiedAccessType( memberOfClass ); if ( entityAccessTypeInfo.getAccessType() != membersKind && forcedAccessType == null ) { continue; } - if ( TypeUtils.containsAnnotation( memberOfClass, Constants.TRANSIENT ) + if ( containsAnnotation( memberOfClass, Constants.TRANSIENT ) || memberOfClass.getModifiers().contains( Modifier.TRANSIENT ) || memberOfClass.getModifiers().contains( Modifier.STATIC ) ) { continue; @@ -259,4 +291,159 @@ public class AnnotationMetaEntity extends AnnotationMeta { } } + private void addQueryMethods(List queryMethods) { + for ( ExecutableElement method : queryMethods) { + addQueryMethod(method); + } + } + + private void addQueryMethod(ExecutableElement method) { + final String methodName = method.getSimpleName().toString(); + final TypeMirror returnType = method.getReturnType(); + if ( returnType instanceof DeclaredType ) { + final DeclaredType declaredType = (DeclaredType) returnType; + final List typeArguments = declaredType.getTypeArguments(); + if ( typeArguments.size() == 0 ) { + if ( containsAnnotation( declaredType.asElement(), Constants.ENTITY ) ) { + addQueryMethod(method, methodName, declaredType.toString(), null); + } + else { + final String containerTypeName = declaredType.toString(); + if (isLegalRawResultType(containerTypeName)) { + addQueryMethod(method, methodName, null, containerTypeName); + } + else { + displayError(method, "incorrect return type '" + containerTypeName + "'"); + } + } + } + else if ( typeArguments.size() == 1 ) { + final String containerTypeName = declaredType.asElement().toString(); + final String returnTypeName = typeArguments.get(0).toString(); + if (isLegalGenericResultType(containerTypeName)) { + addQueryMethod(method, methodName, returnTypeName, containerTypeName); + } + else { + displayError(method, "incorrect return type '" + containerTypeName + "'"); + } + } + else { + displayError(method, "incorrect return type '" + declaredType + "'"); + } + } + } + + private static boolean isLegalRawResultType(String containerTypeName) { + return containerTypeName.equals("java.util.List") + || containerTypeName.equals("jakarta.persistence.Query") + || containerTypeName.equals("org.hibernate.query.Query"); + } + + private static boolean isLegalGenericResultType(String containerTypeName) { + return containerTypeName.equals("java.util.List") + || containerTypeName.equals("jakarta.persistence.TypedQuery") + || containerTypeName.equals("org.hibernate.query.Query"); + } + + private void addQueryMethod( + ExecutableElement method, + String methodName, + @Nullable String returnTypeName, @Nullable String containerTypeName) { + final AnnotationMirror mirror = getAnnotationMirror(method, Constants.QUERY_METHOD ); + if ( mirror != null ) { + final Object queryString = getAnnotationValue( mirror, "value" ); + if ( queryString instanceof String ) { + final List paramNames = + method.getParameters().stream() + .map(param -> param.getSimpleName().toString()) + .collect(toList()); + final List paramTypes = + method.getParameters().stream() + .map(param -> param.asType().toString()) + .collect(toList()); + final String hql = (String) queryString; + final QueryMethod attribute = + new QueryMethod( + this, + methodName, + hql, + returnTypeName, + containerTypeName, + paramNames, + paramTypes + ); + putMember( attribute.getPropertyName(), attribute ); + + checkParameters(method, paramNames, mirror, hql); + checkHqlSyntax(method, mirror, hql); + } + } + } + + private void checkParameters(ExecutableElement method, List paramNames, 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) ) { + displayError(method, mirror, "missing query parameter for '" + param + + "' (no parameter named :" + param + " or ?" + i + ")"); + } + } + } + + private void checkHqlSyntax(ExecutableElement method, AnnotationMirror mirror, String queryString) { + ANTLRErrorListener errorListener = new ANTLRErrorListener() { + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String message, RecognitionException e) { + displayError(method, mirror, "illegal HQL syntax - " + + prettifyAntlrError( offendingSymbol, line, charPositionInLine, message, e, queryString, false )); + } + + @Override + public void reportAmbiguity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, boolean exact, BitSet ambigAlts, ATNConfigSet configs) { + } + + @Override + public void reportAttemptingFullContext(Parser recognizer, DFA dfa, int startIndex, int stopIndex, BitSet conflictingAlts, ATNConfigSet configs) { + } + + @Override + public void reportContextSensitivity(Parser recognizer, DFA dfa, int startIndex, int stopIndex, int prediction, ATNConfigSet configs) { + } + }; + + final HqlLexer hqlLexer = HqlParseTreeBuilder.INSTANCE.buildHqlLexer( queryString ); + final HqlParser hqlParser = HqlParseTreeBuilder.INSTANCE.buildHqlParser( queryString, hqlLexer ); + hqlLexer.addErrorListener( errorListener ); + hqlParser.getInterpreter().setPredictionMode( PredictionMode.SLL ); + hqlParser.removeErrorListeners(); + hqlParser.addErrorListener( errorListener ); + hqlParser.setErrorHandler( new BailErrorStrategy() ); + + try { + hqlParser.statement(); + } + catch ( ParseCancellationException e) { + // reset the input token stream and parser state + hqlLexer.reset(); + hqlParser.reset(); + + // fall back to LL(k)-based parsing + hqlParser.getInterpreter().setPredictionMode( PredictionMode.LL ); + hqlParser.setErrorHandler( new DefaultErrorStrategy() ); + + hqlParser.statement(); + } + } + + private void displayError(ExecutableElement method, String message) { + context.getProcessingEnvironment().getMessager() + .printMessage( Diagnostic.Kind.ERROR, message, method ); + } + private void displayError(ExecutableElement method, AnnotationMirror mirror, String message) { + context.getProcessingEnvironment().getMessager() + .printMessage( Diagnostic.Kind.ERROR, message, method, mirror, + mirror.getElementValues().entrySet().stream() + .filter( entry -> entry.getKey().getSimpleName().toString().equals("value") ) + .map(Map.Entry::getValue).findAny().orElseThrow() ); + } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaPackage.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaPackage.java index 0d8d141b1c..bd1deedc1f 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaPackage.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/AnnotationMetaPackage.java @@ -46,6 +46,7 @@ public class AnnotationMetaPackage extends AnnotationMeta { return new AnnotationMetaPackage( element, context ); } + @Override public final Context getContext() { return context; } @@ -121,7 +122,7 @@ public class AnnotationMetaPackage extends AnnotationMeta { } @Override - void putMember(String name, NameMetaAttribute nameMetaAttribute) { + void putMember(String name, MetaAttribute nameMetaAttribute) { members.put( name, nameMetaAttribute ); } } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/QueryMethod.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/QueryMethod.java new file mode 100644 index 0000000000..36f890d1f6 --- /dev/null +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/annotation/QueryMethod.java @@ -0,0 +1,155 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.jpamodelgen.annotation; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.jpamodelgen.model.MetaAttribute; +import org.hibernate.jpamodelgen.model.Metamodel; + +import java.util.List; + +import static org.hibernate.jpamodelgen.util.StringUtil.getUpperUnderscoreCaseFromLowerCamelCase; + +/** + * @author Gavin King + */ +public class QueryMethod implements MetaAttribute { + private final Metamodel annotationMetaEntity; + private final String methodName; + private final String queryString; + private final @Nullable String returnTypeName; + private final @Nullable String containerTypeName; + private final List paramNames; + private final List paramTypes; + + public QueryMethod( + Metamodel annotationMetaEntity, + String methodName, + String queryString, + @Nullable + String returnTypeName, + @Nullable + String containerTypeName, + List paramNames, + List paramTypes + ) { + this.annotationMetaEntity = annotationMetaEntity; + this.methodName = methodName; + this.queryString = queryString; + this.returnTypeName = returnTypeName; + this.containerTypeName = containerTypeName; + this.paramNames = paramNames; + this.paramTypes = paramTypes; + } + + @Override + public boolean hasTypedAttribute() { + return true; + } + + @Override + public String getAttributeDeclarationString() { + StringBuilder declaration = new StringBuilder(); + declaration.append("public static "); + if (containerTypeName != null) { + declaration + .append(annotationMetaEntity.importType(containerTypeName)); + if (returnTypeName != null) { + declaration + .append("<") + .append(annotationMetaEntity.importType(returnTypeName)) + .append(">"); + } + } + else if (returnTypeName != null) { + declaration.append(annotationMetaEntity.importType(returnTypeName)); + } + declaration + .append(" ") + .append(methodName) + .append("(") + .append(annotationMetaEntity.importType("jakarta.persistence.EntityManager")) + .append(" entityManager"); + + for (int i =0; i COLLECTIONS = allCollectionTypes(); private static Map allCollectionTypes() { diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/xml/XmlMetaEntity.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/xml/XmlMetaEntity.java index 48a201944a..9395f5ac4e 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/xml/XmlMetaEntity.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/xml/XmlMetaEntity.java @@ -627,4 +627,9 @@ public class XmlMetaEntity implements Metamodel { return ElementKind.METHOD; } } + + @Override + public Context getContext() { + return context; + } } diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/AuxiliaryTest.java b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/AuxiliaryTest.java index 7640fab670..4c4410a7d8 100644 --- a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/AuxiliaryTest.java +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/AuxiliaryTest.java @@ -19,9 +19,10 @@ import static org.hibernate.jpamodelgen.test.util.TestUtil.assertPresenceOfNameF */ public class AuxiliaryTest extends CompilationTest { @Test - @WithClasses({ Book.class, Main.class }) + @WithClasses({ Book.class, Main.class, Dao.class }) public void testGeneratedAnnotationNotGenerated() { System.out.println( TestUtil.getMetaModelSourceAsString( Main.class ) ); + System.out.println( TestUtil.getMetaModelSourceAsString( Dao.class ) ); assertMetamodelClassGeneratedFor( Book.class ); assertMetamodelClassGeneratedFor( Main.class ); assertPresenceOfNameFieldInMetamodelFor( diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Dao.java b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Dao.java new file mode 100644 index 0000000000..532eb4346d --- /dev/null +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/jpamodelgen/test/namedquery/Dao.java @@ -0,0 +1,17 @@ +package org.hibernate.jpamodelgen.test.namedquery; + +import jakarta.persistence.TypedQuery; +import org.hibernate.annotations.Hql; + +import java.util.List; + +public interface Dao { + @Hql("from Book where title like ?1") + TypedQuery findByTitle(String title); + + @Hql("from Book where title like ?1 order by title fetch first ?2 rows only") + List findFirstNByTitle(String title, int N); + + @Hql("from Book where isbn = :isbn") + Book findByIsbn(String isbn); +}