HHH-16633 introduce native query methods to JPA metamodel generator

This commit is contained in:
Gavin King 2023-06-17 02:25:51 +02:00
parent 698b245753
commit cfe545ec3d
6 changed files with 139 additions and 40 deletions

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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 SQL query}, and is
* generated automatically by the Hibernate Metamodel
* Generator.
* <p>
* For example:
* <pre>
* public interface Books {
* &#64;Sql("select * from Book where isbn = :isbn")
* Book findBookByIsbn(String isbn);
*
* &#64;Sql("select * from Book where title like ?1 order by title offset ?3 fetch first ?2 rows only")
* List&lt;Book&gt; findBooksByTitleWithPagination(String title, int max, int start);
*
* &#64;Sql("select * from Book where title like ?1")
* Query findBooksByTitle(String title);
* }
* </pre>
* <p>
* The Metamodel Generator automatically creates an
* "implementation" of these methods in the static metamodel
* class {@code Books_}.
* <pre>
* Book book = Books_.findBookByIsbn(session, isbn);
* List&lt;Book&gt; books = Books_.findBooksByTitleWithPagination(session, pattern, 10, 0);
* </pre>
* <p>
* The return type of an annotated method must be:
* <ul>
* <li>an entity type,
* <li>{@link java.util.List},
* <li>{@link org.hibernate.query.Query},
* <li>{@link jakarta.persistence.Query}, or
* <li>{@link org.hibernate.query.NativeQuery}.
* </ul>
* <p>
* The method parameters must match the parameters of the
* SQL query, either by name or by position.
*
* @author Gavin King
* @since 6.3
*/
@Target(METHOD)
@Retention(CLASS)
@Incubating
public @interface Sql {
String value();
}

View File

@ -39,7 +39,8 @@ import org.hibernate.jpamodelgen.xml.JpaDescriptorParser;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import static org.hibernate.jpamodelgen.util.Constants.QUERY_METHOD; import static org.hibernate.jpamodelgen.util.Constants.HQL;
import static org.hibernate.jpamodelgen.util.Constants.SQL;
import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation; import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation;
/** /**
@ -142,7 +143,7 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
} }
else if ( element instanceof TypeElement ) { else if ( element instanceof TypeElement ) {
for ( Element enclosedElement : element.getEnclosedElements() ) { for ( Element enclosedElement : element.getEnclosedElements() ) {
if ( containsAnnotation( enclosedElement, QUERY_METHOD ) ) { if ( containsAnnotation( enclosedElement, HQL, SQL ) ) {
AnnotationMetaEntity metaEntity = AnnotationMetaEntity metaEntity =
AnnotationMetaEntity.create( (TypeElement) element, context, false ); AnnotationMetaEntity.create( (TypeElement) element, context, false );
context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity ); context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity );

View File

@ -228,7 +228,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
gettersAndSettersOfClass.add( rawMethodOfClass ); gettersAndSettersOfClass.add( rawMethodOfClass );
} }
else if ( rawMethodOfClass instanceof ExecutableElement else if ( rawMethodOfClass instanceof ExecutableElement
&& containsAnnotation( rawMethodOfClass, Constants.QUERY_METHOD ) ) { && containsAnnotation( rawMethodOfClass, Constants.HQL, Constants.SQL ) ) {
queryMethods.add( (ExecutableElement) rawMethodOfClass ); queryMethods.add( (ExecutableElement) rawMethodOfClass );
} }
} }
@ -348,33 +348,53 @@ public class AnnotationMetaEntity extends AnnotationMeta {
private void addQueryMethod( private void addQueryMethod(
ExecutableElement method, ExecutableElement method,
String methodName, String methodName,
@Nullable String returnTypeName, @Nullable String containerTypeName) { @Nullable String returnTypeName,
final AnnotationMirror mirror = getAnnotationMirror(method, Constants.QUERY_METHOD ); @Nullable String containerTypeName) {
if ( mirror != null ) { final AnnotationMirror hql = getAnnotationMirror(method, Constants.HQL);
final Object queryString = getAnnotationValue( mirror, "value" ); if ( hql != null ) {
if ( queryString instanceof String ) { addQueryMethod(method, methodName, returnTypeName, containerTypeName, hql, false);
final List<String> paramNames = }
method.getParameters().stream() final AnnotationMirror sql = getAnnotationMirror(method, Constants.SQL);
.map(param -> param.getSimpleName().toString()) if ( sql != null ) {
.collect(toList()); addQueryMethod(method, methodName, returnTypeName, containerTypeName, sql, true);
final List<String> 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); private void addQueryMethod(
ExecutableElement method,
String methodName,
@Nullable
String returnTypeName,
@Nullable
String containerTypeName,
AnnotationMirror mirror,
boolean isNative) {
final Object queryString = getAnnotationValue(mirror, "value" );
if ( queryString instanceof String ) {
final List<String> paramNames =
method.getParameters().stream()
.map(param -> param.getSimpleName().toString())
.collect(toList());
final List<String> 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,
isNative
);
putMember( attribute.getPropertyName(), attribute );
checkParameters(method, paramNames, mirror, hql);
if (!isNative) {
checkHqlSyntax(method, mirror, hql); checkHqlSyntax(method, mirror, hql);
} }
} }

View File

@ -25,6 +25,7 @@ public class QueryMethod implements MetaAttribute {
private final @Nullable String containerTypeName; private final @Nullable String containerTypeName;
private final List<String> paramNames; private final List<String> paramNames;
private final List<String> paramTypes; private final List<String> paramTypes;
private final boolean isNative;
public QueryMethod( public QueryMethod(
Metamodel annotationMetaEntity, Metamodel annotationMetaEntity,
@ -35,8 +36,8 @@ public class QueryMethod implements MetaAttribute {
@Nullable @Nullable
String containerTypeName, String containerTypeName,
List<String> paramNames, List<String> paramNames,
List<String> paramTypes List<String> paramTypes,
) { boolean isNative) {
this.annotationMetaEntity = annotationMetaEntity; this.annotationMetaEntity = annotationMetaEntity;
this.methodName = methodName; this.methodName = methodName;
this.queryString = queryString; this.queryString = queryString;
@ -44,6 +45,7 @@ public class QueryMethod implements MetaAttribute {
this.containerTypeName = containerTypeName; this.containerTypeName = containerTypeName;
this.paramNames = paramNames; this.paramNames = paramNames;
this.paramTypes = paramTypes; this.paramTypes = paramTypes;
this.isNative = isNative;
} }
@Override @Override
@ -55,20 +57,18 @@ public class QueryMethod implements MetaAttribute {
public String getAttributeDeclarationString() { public String getAttributeDeclarationString() {
StringBuilder declaration = new StringBuilder(); StringBuilder declaration = new StringBuilder();
declaration.append("public static "); declaration.append("public static ");
StringBuilder type = new StringBuilder();
if (containerTypeName != null) { if (containerTypeName != null) {
declaration type.append(annotationMetaEntity.importType(containerTypeName));
.append(annotationMetaEntity.importType(containerTypeName));
if (returnTypeName != null) { if (returnTypeName != null) {
declaration type.append("<").append(annotationMetaEntity.importType(returnTypeName)).append(">");
.append("<")
.append(annotationMetaEntity.importType(returnTypeName))
.append(">");
} }
} }
else if (returnTypeName != null) { else if (returnTypeName != null) {
declaration.append(annotationMetaEntity.importType(returnTypeName)); type.append(annotationMetaEntity.importType(returnTypeName));
} }
declaration declaration
.append(type)
.append(" ") .append(" ")
.append(methodName) .append(methodName)
.append("(") .append("(")
@ -85,7 +85,14 @@ public class QueryMethod implements MetaAttribute {
declaration declaration
.append(")") .append(")")
.append(" {") .append(" {")
.append("\n return entityManager.createQuery(") .append("\n return ");
if ( isNative && returnTypeName != null ) {
declaration.append("(").append(type).append(") ");
}
declaration
.append("entityManager.")
.append(isNative ? "createNativeQuery" :"createQuery")
.append("(")
.append(getUpperUnderscoreCaseFromLowerCamelCase(methodName)); .append(getUpperUnderscoreCaseFromLowerCamelCase(methodName));
if (returnTypeName != null) { if (returnTypeName != null) {
declaration declaration

View File

@ -50,7 +50,8 @@ public final class Constants {
public static final String HIB_FILTER_DEF = "org.hibernate.annotations.FilterDef"; public static final String HIB_FILTER_DEF = "org.hibernate.annotations.FilterDef";
public static final String HIB_FILTER_DEFS = "org.hibernate.annotations.FilterDefs"; public static final String HIB_FILTER_DEFS = "org.hibernate.annotations.FilterDefs";
public static final String QUERY_METHOD = "org.hibernate.annotations.Hql"; public static final String HQL = "org.hibernate.annotations.Hql";
public static final String SQL = "org.hibernate.annotations.Sql";
public static final Map<String, String> COLLECTIONS = allCollectionTypes(); public static final Map<String, String> COLLECTIONS = allCollectionTypes();

View File

@ -2,6 +2,7 @@ package org.hibernate.jpamodelgen.test.namedquery;
import jakarta.persistence.TypedQuery; import jakarta.persistence.TypedQuery;
import org.hibernate.annotations.Hql; import org.hibernate.annotations.Hql;
import org.hibernate.annotations.Sql;
import java.util.List; import java.util.List;
@ -14,4 +15,7 @@ public interface Dao {
@Hql("from Book where isbn = :isbn") @Hql("from Book where isbn = :isbn")
Book findByIsbn(String isbn); Book findByIsbn(String isbn);
@Sql("select * from Book where isbn = :isbn")
Book findByIsbnNative(String isbn);
} }