diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMeta.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMeta.java index 1c454e9bd3..9f5d6bba0d 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMeta.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/AnnotationMeta.java @@ -13,7 +13,11 @@ import org.hibernate.processor.model.Metamodel; import org.hibernate.processor.util.Constants; import org.hibernate.processor.validation.ProcessorSessionFactory; import org.hibernate.processor.validation.Validation; +import org.hibernate.query.criteria.JpaEntityJoin; +import org.hibernate.query.criteria.JpaRoot; +import org.hibernate.query.criteria.JpaSelection; import org.hibernate.query.sqm.tree.SqmStatement; +import org.hibernate.query.sqm.tree.select.SqmSelectClause; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import javax.lang.model.element.AnnotationMirror; @@ -103,25 +107,56 @@ public abstract class AnnotationMeta implements Metamodel { ProcessorSessionFactory.create( context.getProcessingEnvironment(), context.getEntityNameMappings(), context.getEnumTypesByValue() ) ); - if ( statement instanceof SqmSelectStatement - && isQueryMethodName( name ) ) { - putMember( name, - new NamedQueryMethod( - this, - (SqmSelectStatement) statement, - name.substring(1), - isRepository(), - getSessionType(), - getSessionVariableName(), - context.addNonnullAnnotation() - ) - ); + if ( statement instanceof SqmSelectStatement selectStatement ) { + if ( isQueryMethodName( name ) ) { + putMember( name, + new NamedQueryMethod( + this, + selectStatement, + name.substring(1), + isRepository(), + getSessionType(), + getSessionVariableName(), + context.addNonnullAnnotation() + ) + ); + } + if ( !isJakartaDataStyle() + && getAnnotationValue( mirror, "resultClass" ) == null ) { + final String resultType = resultType( selectStatement ); + if ( resultType != null ) { + putMember( "QUERY_" + name, + new TypedMetaAttribute( this, name, "QUERY_", resultType, + "jakarta.persistence.TypedQueryReference" ) ); + } + } } } } } } + private static @Nullable String resultType(SqmSelectStatement selectStatement) { + final JpaSelection selection = selectStatement.getSelection(); + if (selection == null) { + return null; + } + else if (selection instanceof SqmSelectClause from) { + return from.getSelectionItems().size() > 1 + ? "Object[]" + : from.getSelectionItems().get(0).getJavaTypeName(); + } + else if (selection instanceof JpaRoot root) { + return root.getModel().getTypeName(); + } + else if (selection instanceof JpaEntityJoin join) { + return join.getModel().getTypeName(); + } + else { + return selection.getJavaTypeName(); + } + } + private static boolean isQueryMethodName(String name) { return name.length() >= 2 && name.charAt(0) == '#' @@ -165,8 +200,9 @@ public abstract class AnnotationMeta implements Metamodel { private NameMetaAttribute auxiliaryMember(AnnotationMirror mirror, String prefix, String name) { if ( !isJakartaDataStyle() && "QUERY_".equals(prefix) ) { final AnnotationValue resultClass = getAnnotationValue( mirror, "resultClass" ); - //TODO: if there is no explicit result class, obtain the result class by - // type-checking the query (this is allowed but not required by JPA) + // if there is no explicit result class, we will infer it later by + // type checking the query (this is allowed but not required by JPA) + // and then we will replace this TypedMetaAttribute return new TypedMetaAttribute( this, name, prefix, resultClass == null ? JAVA_OBJECT : resultClass.getValue().toString(), "jakarta.persistence.TypedQueryReference" ); diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NameMetaAttribute.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NameMetaAttribute.java index d3fbf60e50..5ce3827cbb 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NameMetaAttribute.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/NameMetaAttribute.java @@ -54,7 +54,7 @@ class NameMetaAttribute implements MetaAttribute { .toString(); } - private String fieldName() { + String fieldName() { return nameToFieldName(name.charAt(0) == '#' ? name.substring(1) : name); } diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/TypedMetaAttribute.java b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/TypedMetaAttribute.java index 79d821bdf3..e622a72fbd 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/TypedMetaAttribute.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/processor/annotation/TypedMetaAttribute.java @@ -12,6 +12,7 @@ import static org.hibernate.processor.util.StringUtil.nameToMethodName; * @author Gavin King */ class TypedMetaAttribute extends NameMetaAttribute { + private final String prefix; private final String resultType; private final String referenceType; @@ -22,6 +23,7 @@ class TypedMetaAttribute extends NameMetaAttribute { String resultType, String referenceType) { super( annotationMetaEntity, name, prefix ); + this.prefix = prefix; this.resultType = resultType; this.referenceType = referenceType; } @@ -34,8 +36,13 @@ class TypedMetaAttribute extends NameMetaAttribute { @Override public String getAttributeDeclarationString() { final Metamodel entity = getHostingEntity(); - return new StringBuilder() - .append("\n/**\n * @see ") + final StringBuilder declaration = new StringBuilder(); + declaration + .append("\n/**") + .append("\n * The query named {@value ") + .append(prefix) + .append(fieldName()) + .append("}\n *\n * @see ") .append(entity.getQualifiedName()) .append("\n **/\n") .append("public static volatile ") @@ -45,8 +52,11 @@ class TypedMetaAttribute extends NameMetaAttribute { .append('>') .append(' ') .append('_') - .append(nameToMethodName(getPropertyName())) - .append(';') - .toString(); + .append(nameToMethodName(getPropertyName())); + if ( "QUERY_".equals(prefix) ) { //UGLY! + declaration.append('_'); + } + declaration.append(';'); + return declaration.toString(); } } diff --git a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/namedquery/Book.java b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/namedquery/Book.java index 2b6861eba9..bc6ff7c8b4 100644 --- a/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/namedquery/Book.java +++ b/tooling/metamodel-generator/src/test/java/org/hibernate/processor/test/namedquery/Book.java @@ -11,6 +11,12 @@ import jakarta.persistence.NamedQuery; @Entity @NamedEntityGraph(name = "entityGraph") +@NamedQuery(name="booksByTitle", + query = "from Book where title = ?1") +@NamedQuery(name="booksByTitleVerbose", + query = "select book from Book book where book.title = ?1") +@NamedQuery(name = "titlesWithIsbns", + query = "select title, isbn from Book") @NamedQuery(name = "#findByTitle", query = "from Book where title like :titlePattern") @NamedQuery(name = "#findByTitleAndType",