HHH-16000 Add @SQLSelect annotation as abbreviation for @NamedNativeQuery + @Loader
This commit is contained in:
parent
bfdd7f648b
commit
3ceb91d280
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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.userguide.sql;
|
||||
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import org.hibernate.annotations.SQLDelete;
|
||||
import org.hibernate.annotations.SQLDeleteAll;
|
||||
import org.hibernate.annotations.SQLInsert;
|
||||
import org.hibernate.annotations.SQLSelect;
|
||||
import org.hibernate.annotations.SQLUpdate;
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
import org.hibernate.metamodel.CollectionClassification;
|
||||
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
|
||||
import org.hibernate.testing.RequiresDialect;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.sql.Statement;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hibernate.annotations.ResultCheckStyle.COUNT;
|
||||
import static org.hibernate.cfg.AvailableSettings.DEFAULT_LIST_SEMANTICS;
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* This test is for replicating the HHH-10557 issue.
|
||||
*
|
||||
* @author Vlad Mihalcea
|
||||
*/
|
||||
@RequiresDialect(H2Dialect.class)
|
||||
@RequiresDialect(PostgreSQLDialect.class)
|
||||
public class SQLSelectTest extends BaseEntityManagerFunctionalTestCase {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] {
|
||||
Person.class
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addConfigOptions(Map options) {
|
||||
super.addConfigOptions( options );
|
||||
options.put( DEFAULT_LIST_SEMANTICS, CollectionClassification.BAG.name() );
|
||||
}
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
SessionImplementor session = entityManager.unwrap( SessionImplementor.class);
|
||||
DdlTypeRegistry ddlTypeRegistry = session.getTypeConfiguration().getDdlTypeRegistry();
|
||||
session.doWork(connection -> {
|
||||
try(Statement statement = connection.createStatement();) {
|
||||
statement.executeUpdate(String.format( "ALTER TABLE person %s valid %s",
|
||||
getDialect().getAddColumnString(),
|
||||
ddlTypeRegistry.getTypeName( Types.BOOLEAN, getDialect())));
|
||||
statement.executeUpdate(String.format( "ALTER TABLE Person_phones %s valid %s",
|
||||
getDialect().getAddColumnString(),
|
||||
ddlTypeRegistry.getTypeName( Types.BOOLEAN, getDialect())));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test @TestForIssue(jiraKey = "HHH-10557")
|
||||
public void test_HHH10557() {
|
||||
|
||||
Person _person = doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
Person person = new Person();
|
||||
person.setName("John Doe");
|
||||
entityManager.persist(person);
|
||||
person.getPhones().add("123-456-7890");
|
||||
person.getPhones().add("123-456-0987");
|
||||
return person;
|
||||
});
|
||||
|
||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
Long postId = _person.getId();
|
||||
Person person = entityManager.find(Person.class, postId);
|
||||
assertEquals(2, person.getPhones().size());
|
||||
person.getPhones().remove(0);
|
||||
person.setName("Mr. John Doe");
|
||||
});
|
||||
|
||||
|
||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||
Long postId = _person.getId();
|
||||
Person person = entityManager.find(Person.class, postId);
|
||||
assertEquals(1, person.getPhones().size());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//tag::sql-custom-crud-example[]
|
||||
@Entity(name = "Person")
|
||||
@SQLInsert(sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) ", check = COUNT)
|
||||
@SQLUpdate(sql = "UPDATE person SET name = ? where id = ? ")
|
||||
@SQLDelete(sql = "UPDATE person SET valid = false WHERE id = ? ")
|
||||
@SQLSelect(sql = "SELECT id, name FROM person WHERE id = ? and valid = true")
|
||||
public static class Person {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@ElementCollection
|
||||
@SQLInsert(sql = "INSERT INTO person_phones (person_id, phones, valid) VALUES (?, ?, true) ")
|
||||
@SQLDeleteAll(sql = "UPDATE person_phones SET valid = false WHERE person_id = ?")
|
||||
@SQLSelect(sql = "SELECT phones FROM Person_phones WHERE person_id = ? and valid = true ")
|
||||
private List<String> phones = new ArrayList<>();
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<String> getPhones() {
|
||||
return phones;
|
||||
}
|
||||
}
|
||||
//end::sql-custom-crud-example[]
|
||||
|
||||
}
|
|
@ -32,7 +32,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
@Repeatable(SQLDeletes.class)
|
||||
public @interface SQLDelete {
|
||||
/**
|
||||
* Procedure name or SQL DELETE statement.
|
||||
* Procedure name or SQL {@code DELETE} statement.
|
||||
*/
|
||||
String sql();
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
@Retention(RUNTIME)
|
||||
public @interface SQLDeleteAll {
|
||||
/**
|
||||
* Procedure name or SQL DELETE statement.
|
||||
* Procedure name or SQL {@code DELETE} statement.
|
||||
*/
|
||||
String sql();
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
@Repeatable(SQLInserts.class)
|
||||
public @interface SQLInsert {
|
||||
/**
|
||||
* Procedure name or SQL INSERT statement.
|
||||
* Procedure name or SQL {@code INSERT} statement.
|
||||
*/
|
||||
String sql();
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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 jakarta.persistence.SqlResultSetMapping;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Specifies a custom SQL query to be used in place of the default SQL
|
||||
* generated by Hibernate when an entity or collection is loaded from the
|
||||
* database.
|
||||
* <p>
|
||||
* The given {@linkplain #sql SQL statement} must have exactly the number
|
||||
* of JDBC {@code ?} parameters that Hibernate expects, that is, one for
|
||||
* each column of:
|
||||
* <ol>
|
||||
* <li>the {@linkplain jakarta.persistence.Id primary key}, in the case of
|
||||
* an entity, or
|
||||
* <li>the foreign key, in the case of a collection.
|
||||
* </ol>
|
||||
* <p>
|
||||
* Optionally, an explicit {@linkplain #resultSetMapping result set mapping}
|
||||
* may be specified. It should have:
|
||||
* <ol>
|
||||
* <li>a single {@link jakarta.persistence.EntityResult}, if the SQL query
|
||||
* loads an {@linkplain jakarta.persistence.Entity entity},
|
||||
* {@linkplain jakarta.persistence.OneToMany one-to-many} association,
|
||||
* or {@linkplain jakarta.persistence.ManyToMany many-to-many} association,
|
||||
* or
|
||||
* <li>a single {@link jakarta.persistence.ColumnResult} or
|
||||
* {@link jakarta.persistence.ConstructorResult}, if the SQL query
|
||||
* loads an {@linkplain jakarta.persistence.ElementCollection collection}
|
||||
* of basic-typed values.
|
||||
* </ol>
|
||||
*
|
||||
* @author Gavin King
|
||||
*
|
||||
* @since 6.2
|
||||
*
|
||||
* @implNote This annotation is just an abbreviation for {@link Loader}
|
||||
* together with {@link NamedNativeQuery}.
|
||||
*/
|
||||
@Target({TYPE, FIELD, METHOD})
|
||||
@Retention(RUNTIME)
|
||||
public @interface SQLSelect {
|
||||
/**
|
||||
* The SQL {@code SELECT} statement.
|
||||
*/
|
||||
String sql();
|
||||
|
||||
/**
|
||||
* A {@link SqlResultSetMapping} with a single
|
||||
* {@link jakarta.persistence.ColumnResult} or
|
||||
* {@link jakarta.persistence.EntityResult}.
|
||||
*/
|
||||
SqlResultSetMapping resultSetMapping() default @SqlResultSetMapping(name="");
|
||||
|
||||
/**
|
||||
* The query spaces involved in this query.
|
||||
*
|
||||
* @see org.hibernate.query.SynchronizeableQuery
|
||||
*/
|
||||
String[] querySpaces() default {};
|
||||
|
||||
}
|
|
@ -51,7 +51,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
@Repeatable(SQLUpdates.class)
|
||||
public @interface SQLUpdate {
|
||||
/**
|
||||
* Procedure name or SQL UPDATE statement.
|
||||
* Procedure name or SQL {@code UPDATE} statement.
|
||||
*/
|
||||
String sql();
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ import org.hibernate.annotations.Persister;
|
|||
import org.hibernate.annotations.SQLDelete;
|
||||
import org.hibernate.annotations.SQLDeleteAll;
|
||||
import org.hibernate.annotations.SQLInsert;
|
||||
import org.hibernate.annotations.SQLSelect;
|
||||
import org.hibernate.annotations.SQLUpdate;
|
||||
import org.hibernate.annotations.SortComparator;
|
||||
import org.hibernate.annotations.SortNatural;
|
||||
|
@ -1292,7 +1293,7 @@ public abstract class CollectionBinder {
|
|||
binder.setUpdatable( updatable );
|
||||
Property prop = binder.makeProperty();
|
||||
//we don't care about the join stuffs because the column is on the association table.
|
||||
if (! declaringClassSet) {
|
||||
if ( !declaringClassSet ) {
|
||||
throw new AssertionFailure( "DeclaringClass is not set in CollectionBinder while binding" );
|
||||
}
|
||||
propertyHolder.addProperty( prop, declaringClass );
|
||||
|
@ -1338,6 +1339,14 @@ public abstract class CollectionBinder {
|
|||
);
|
||||
}
|
||||
|
||||
final SQLSelect sqlSelect = property.getAnnotation( SQLSelect.class );
|
||||
if ( sqlSelect != null ) {
|
||||
final String loaderName = collection.getRole() + "$SQLSelect";
|
||||
collection.setLoaderName( loaderName );
|
||||
// TODO: pass in the collection element type here
|
||||
QueryBinder.bindNativeQuery( loaderName, sqlSelect, null, buildingContext );
|
||||
}
|
||||
|
||||
final Loader loader = property.getAnnotation( Loader.class );
|
||||
if ( loader != null ) {
|
||||
collection.setLoaderName( loader.namedQuery() );
|
||||
|
|
|
@ -69,6 +69,7 @@ import org.hibernate.annotations.SQLDeleteAll;
|
|||
import org.hibernate.annotations.SQLDeletes;
|
||||
import org.hibernate.annotations.SQLInsert;
|
||||
import org.hibernate.annotations.SQLInserts;
|
||||
import org.hibernate.annotations.SQLSelect;
|
||||
import org.hibernate.annotations.SQLUpdate;
|
||||
import org.hibernate.annotations.SQLUpdates;
|
||||
import org.hibernate.annotations.SecondaryRow;
|
||||
|
@ -1275,6 +1276,13 @@ public class EntityBinder {
|
|||
+ persistentClass.getEntityName());
|
||||
}
|
||||
|
||||
final SQLSelect sqlSelect = annotatedClass.getAnnotation( SQLSelect.class );
|
||||
if ( sqlSelect != null ) {
|
||||
final String loaderName = persistentClass.getEntityName() + "$SQLSelect";
|
||||
persistentClass.setLoaderName( loaderName );
|
||||
QueryBinder.bindNativeQuery( loaderName, sqlSelect, annotatedClass, context );
|
||||
}
|
||||
|
||||
final Loader loader = annotatedClass.getAnnotation( Loader.class );
|
||||
if ( loader != null ) {
|
||||
persistentClass.setLoaderName( loader.namedQuery() );
|
||||
|
|
|
@ -17,14 +17,18 @@ import org.hibernate.FlushMode;
|
|||
import org.hibernate.Remove;
|
||||
import org.hibernate.annotations.CacheModeType;
|
||||
import org.hibernate.annotations.FlushModeType;
|
||||
import org.hibernate.annotations.HQLSelect;
|
||||
import org.hibernate.annotations.SQLSelect;
|
||||
import org.hibernate.annotations.common.annotationfactory.AnnotationDescriptor;
|
||||
import org.hibernate.annotations.common.annotationfactory.AnnotationFactory;
|
||||
import org.hibernate.annotations.common.reflection.XClass;
|
||||
import org.hibernate.boot.internal.NamedHqlQueryDefinitionImpl;
|
||||
import org.hibernate.boot.internal.NamedProcedureCallDefinitionImpl;
|
||||
import org.hibernate.boot.query.NamedHqlQueryDefinition;
|
||||
import org.hibernate.boot.query.NamedNativeQueryDefinition;
|
||||
import org.hibernate.boot.query.NamedNativeQueryDefinitionBuilder;
|
||||
import org.hibernate.boot.query.NamedProcedureCallDefinition;
|
||||
import org.hibernate.boot.query.SqlResultSetMappingDescriptor;
|
||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.internal.log.DeprecationLogger;
|
||||
|
@ -155,6 +159,32 @@ public abstract class QueryBinder {
|
|||
}
|
||||
}
|
||||
|
||||
public static void bindNativeQuery(
|
||||
String name,
|
||||
SQLSelect sqlSelect,
|
||||
XClass annotatedClass,
|
||||
MetadataBuildingContext context) {
|
||||
final NamedNativeQueryDefinitionBuilder builder = new NamedNativeQueryDefinitionBuilder( name )
|
||||
.setFlushMode( FlushMode.MANUAL )
|
||||
.setSqlString( sqlSelect.sql() )
|
||||
.setQuerySpaces( setOf( sqlSelect.querySpaces() ) );
|
||||
|
||||
if ( annotatedClass != null ) {
|
||||
builder.setResultSetMappingClassName( annotatedClass.getName() );
|
||||
}
|
||||
|
||||
final SqlResultSetMapping resultSetMapping = sqlSelect.resultSetMapping();
|
||||
if ( resultSetMapping.columns().length != 0
|
||||
|| resultSetMapping.entities().length != 0
|
||||
|| resultSetMapping.classes().length != 0) {
|
||||
context.getMetadataCollector()
|
||||
.addResultSetMapping( SqlResultSetMappingDescriptor.from( resultSetMapping, name ) );
|
||||
builder.setResultSetMappingName( name );
|
||||
}
|
||||
|
||||
context.getMetadataCollector().addNamedNativeQuery( builder.build() );
|
||||
}
|
||||
|
||||
public static void bindNativeQuery(
|
||||
org.hibernate.annotations.NamedNativeQuery namedNativeQuery,
|
||||
MetadataBuildingContext context) {
|
||||
|
@ -178,16 +208,15 @@ public abstract class QueryBinder {
|
|||
.setSqlString( namedNativeQuery.query() )
|
||||
.setResultSetMappingName( resultSetMappingName )
|
||||
.setResultSetMappingClassName( resultSetMappingClassName )
|
||||
.setQuerySpaces( null )
|
||||
.setCacheable( namedNativeQuery.cacheable() )
|
||||
.setCacheRegion(nullIfEmpty(namedNativeQuery.cacheRegion()))
|
||||
.setCacheRegion( nullIfEmpty( namedNativeQuery.cacheRegion() ) )
|
||||
.setCacheMode( getCacheMode( namedNativeQuery ) )
|
||||
.setTimeout( namedNativeQuery.timeout() < 0 ? null : namedNativeQuery.timeout() )
|
||||
.setFetchSize( namedNativeQuery.fetchSize() < 0 ? null : namedNativeQuery.fetchSize() )
|
||||
.setFlushMode( getFlushMode( namedNativeQuery.flushMode() ) )
|
||||
.setReadOnly( namedNativeQuery.readOnly() )
|
||||
.setQuerySpaces( setOf( namedNativeQuery.querySpaces() ) )
|
||||
.setComment(nullIfEmpty(namedNativeQuery.comment()));
|
||||
.setComment( nullIfEmpty( namedNativeQuery.comment() ) );
|
||||
|
||||
if ( namedNativeQuery.callable() ) {
|
||||
final NamedProcedureCallDefinition definition =
|
||||
|
@ -320,6 +349,18 @@ public abstract class QueryBinder {
|
|||
}
|
||||
}
|
||||
|
||||
public static void bindQuery(
|
||||
String name,
|
||||
HQLSelect hqlSelect,
|
||||
MetadataBuildingContext context) {
|
||||
final NamedHqlQueryDefinition hqlQueryDefinition = new NamedHqlQueryDefinition.Builder( name )
|
||||
.setFlushMode( FlushMode.MANUAL )
|
||||
.setHqlString( hqlSelect.query() )
|
||||
.build();
|
||||
|
||||
context.getMetadataCollector().addNamedQuery( hqlQueryDefinition );
|
||||
}
|
||||
|
||||
public static void bindQuery(
|
||||
org.hibernate.annotations.NamedQuery namedQuery,
|
||||
MetadataBuildingContext context) {
|
||||
|
|
|
@ -21,23 +21,23 @@ import jakarta.persistence.SqlResultSetMapping;
|
|||
public class ResultSetMappingSecondPass implements QuerySecondPass {
|
||||
// private static final CoreMessageLogger LOG = CoreLogging.messageLogger( ResultsetMappingSecondPass.class );
|
||||
|
||||
private final SqlResultSetMapping ann;
|
||||
private final SqlResultSetMapping annotation;
|
||||
private final MetadataBuildingContext context;
|
||||
private final boolean isDefault;
|
||||
|
||||
public ResultSetMappingSecondPass(SqlResultSetMapping ann, MetadataBuildingContext context, boolean isDefault) {
|
||||
this.ann = ann;
|
||||
public ResultSetMappingSecondPass(SqlResultSetMapping annotation, MetadataBuildingContext context, boolean isDefault) {
|
||||
this.annotation = annotation;
|
||||
this.context = context;
|
||||
this.isDefault = isDefault;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSecondPass(Map<String, PersistentClass> persistentClasses) throws MappingException {
|
||||
if ( ann == null ) {
|
||||
if ( annotation == null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SqlResultSetMappingDescriptor mappingDefinition = SqlResultSetMappingDescriptor.from( ann, context );
|
||||
final SqlResultSetMappingDescriptor mappingDefinition = SqlResultSetMappingDescriptor.from( annotation );
|
||||
|
||||
if ( isDefault ) {
|
||||
context.getMetadataCollector().addDefaultResultSetMapping( mappingDefinition );
|
||||
|
|
|
@ -14,9 +14,7 @@ import java.util.Map;
|
|||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.metamodel.RuntimeMetamodels;
|
||||
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
|
||||
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
|
||||
|
@ -47,6 +45,8 @@ import jakarta.persistence.EntityResult;
|
|||
import jakarta.persistence.FieldResult;
|
||||
import jakarta.persistence.SqlResultSetMapping;
|
||||
|
||||
import static org.hibernate.internal.util.collections.CollectionHelper.arrayList;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -66,54 +66,44 @@ public class SqlResultSetMappingDescriptor implements NamedResultSetMappingDescr
|
|||
// (`org.hibernate.query.results.ResultBuilder`) as part of the
|
||||
// memento for its resolution
|
||||
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
public static SqlResultSetMappingDescriptor from(
|
||||
SqlResultSetMapping mappingAnnotation,
|
||||
MetadataBuildingContext context) {
|
||||
|
||||
public static SqlResultSetMappingDescriptor from(SqlResultSetMapping mappingAnnotation, String name) {
|
||||
final EntityResult[] entityResults = mappingAnnotation.entities();
|
||||
final ConstructorResult[] constructorResults = mappingAnnotation.classes();
|
||||
final ColumnResult[] columnResults = mappingAnnotation.columns();
|
||||
|
||||
final List<ResultDescriptor> resultDescriptors = CollectionHelper.arrayList(
|
||||
final List<ResultDescriptor> resultDescriptors = arrayList(
|
||||
entityResults.length + columnResults.length + columnResults.length
|
||||
);
|
||||
|
||||
for ( int i = 0; i < entityResults.length; i++ ) {
|
||||
final EntityResult entityResult = entityResults[i];
|
||||
for ( final EntityResult entityResult : entityResults ) {
|
||||
resultDescriptors.add(
|
||||
new EntityResultDescriptor( entityResult, mappingAnnotation, context )
|
||||
new EntityResultDescriptor( entityResult )
|
||||
);
|
||||
}
|
||||
|
||||
for ( int i = 0; i < constructorResults.length; i++ ) {
|
||||
final ConstructorResult constructorResult = constructorResults[i];
|
||||
for ( final ConstructorResult constructorResult : constructorResults ) {
|
||||
resultDescriptors.add(
|
||||
new ConstructorResultDescriptor( constructorResult, mappingAnnotation )
|
||||
);
|
||||
}
|
||||
|
||||
for ( int i = 0; i < columnResults.length; i++ ) {
|
||||
final ColumnResult columnResult = columnResults[i];
|
||||
for ( final ColumnResult columnResult : columnResults ) {
|
||||
resultDescriptors.add(
|
||||
new JpaColumnResultDescriptor( columnResult, mappingAnnotation )
|
||||
);
|
||||
}
|
||||
|
||||
return new SqlResultSetMappingDescriptor(
|
||||
mappingAnnotation.name(),
|
||||
resultDescriptors,
|
||||
context
|
||||
);
|
||||
return new SqlResultSetMappingDescriptor( name, resultDescriptors );
|
||||
}
|
||||
|
||||
public static SqlResultSetMappingDescriptor from(SqlResultSetMapping mappingAnnotation) {
|
||||
return from( mappingAnnotation, mappingAnnotation.name() );
|
||||
}
|
||||
|
||||
private final String mappingName;
|
||||
private final List<ResultDescriptor> resultDescriptors;
|
||||
|
||||
private SqlResultSetMappingDescriptor(
|
||||
String mappingName,
|
||||
List<ResultDescriptor> resultDescriptors,
|
||||
MetadataBuildingContext context) {
|
||||
private SqlResultSetMappingDescriptor(String mappingName, List<ResultDescriptor> resultDescriptors) {
|
||||
this.mappingName = mappingName;
|
||||
this.resultDescriptors = resultDescriptors;
|
||||
}
|
||||
|
@ -125,7 +115,7 @@ public class SqlResultSetMappingDescriptor implements NamedResultSetMappingDescr
|
|||
|
||||
@Override
|
||||
public NamedResultSetMappingMemento resolve(ResultSetMappingResolutionContext resolutionContext) {
|
||||
final List<ResultMemento> resultMementos = CollectionHelper.arrayList( resultDescriptors.size() );
|
||||
final List<ResultMemento> resultMementos = arrayList( resultDescriptors.size() );
|
||||
|
||||
resultDescriptors.forEach(
|
||||
resultDescriptor -> resultMementos.add( resultDescriptor.resolve( resolutionContext ) )
|
||||
|
@ -142,9 +132,7 @@ public class SqlResultSetMappingDescriptor implements NamedResultSetMappingDescr
|
|||
private final ColumnResult columnResult;
|
||||
private final String mappingName;
|
||||
|
||||
public JpaColumnResultDescriptor(
|
||||
ColumnResult columnResult,
|
||||
SqlResultSetMapping mappingAnnotation) {
|
||||
public JpaColumnResultDescriptor(ColumnResult columnResult, SqlResultSetMapping mappingAnnotation) {
|
||||
this.columnResult = columnResult;
|
||||
this.mappingName = mappingAnnotation.name();
|
||||
}
|
||||
|
@ -182,9 +170,7 @@ public class SqlResultSetMappingDescriptor implements NamedResultSetMappingDescr
|
|||
private final Class<?> targetJavaType;
|
||||
private final List<ArgumentDescriptor> argumentResultDescriptors;
|
||||
|
||||
public ConstructorResultDescriptor(
|
||||
ConstructorResult constructorResult,
|
||||
SqlResultSetMapping mappingAnnotation) {
|
||||
public ConstructorResultDescriptor(ConstructorResult constructorResult, SqlResultSetMapping mappingAnnotation) {
|
||||
this.mappingName = mappingAnnotation.name();
|
||||
this.targetJavaType = constructorResult.targetClass();
|
||||
|
||||
|
@ -193,15 +179,13 @@ public class SqlResultSetMappingDescriptor implements NamedResultSetMappingDescr
|
|||
throw new IllegalArgumentException( "ConstructorResult did not define any ColumnResults" );
|
||||
}
|
||||
|
||||
this.argumentResultDescriptors = CollectionHelper.arrayList( columnResults.length );
|
||||
//noinspection ForLoopReplaceableByForEach
|
||||
for ( int i = 0; i < columnResults.length; i++ ) {
|
||||
final ColumnResult columnResult = columnResults[i];
|
||||
this.argumentResultDescriptors = arrayList( columnResults.length );
|
||||
for ( final ColumnResult columnResult : columnResults ) {
|
||||
final JpaColumnResultDescriptor argumentResultDescriptor = new JpaColumnResultDescriptor(
|
||||
columnResult,
|
||||
mappingAnnotation
|
||||
);
|
||||
argumentResultDescriptors.add( new ArgumentDescriptor( argumentResultDescriptor ) );
|
||||
argumentResultDescriptors.add(new ArgumentDescriptor(argumentResultDescriptor));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,20 +216,13 @@ public class SqlResultSetMappingDescriptor implements NamedResultSetMappingDescr
|
|||
* @see jakarta.persistence.EntityResult
|
||||
*/
|
||||
public static class EntityResultDescriptor implements ResultDescriptor {
|
||||
@SuppressWarnings( { "FieldCanBeLocal", "FieldMayBeFinal", "unused" } )
|
||||
private String resultSetMappingName;
|
||||
|
||||
private final NavigablePath navigablePath;
|
||||
private final String entityName;
|
||||
private final String discriminatorColumn;
|
||||
|
||||
private final Map<String, AttributeFetchDescriptor> explicitFetchMappings;
|
||||
|
||||
public EntityResultDescriptor(
|
||||
EntityResult entityResult,
|
||||
SqlResultSetMapping mappingAnnotation,
|
||||
MetadataBuildingContext context) {
|
||||
this.resultSetMappingName = mappingAnnotation.name();
|
||||
public EntityResultDescriptor(EntityResult entityResult) {
|
||||
this.entityName = entityResult.entityClass().getName();
|
||||
this.discriminatorColumn = entityResult.discriminatorColumn();
|
||||
|
||||
|
@ -261,7 +238,7 @@ public class SqlResultSetMappingDescriptor implements NamedResultSetMappingDescr
|
|||
else {
|
||||
explicitFetchMappings.put(
|
||||
fieldResult.name(),
|
||||
AttributeFetchDescriptor.from( navigablePath, entityName, fieldResult, context )
|
||||
AttributeFetchDescriptor.from( navigablePath, entityName, fieldResult )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -320,8 +297,7 @@ public class SqlResultSetMappingDescriptor implements NamedResultSetMappingDescr
|
|||
private static AttributeFetchDescriptor from(
|
||||
NavigablePath entityPath,
|
||||
String entityName,
|
||||
FieldResult fieldResult,
|
||||
MetadataBuildingContext context) {
|
||||
FieldResult fieldResult) {
|
||||
return new AttributeFetchDescriptor(
|
||||
entityPath,
|
||||
entityName,
|
||||
|
@ -367,9 +343,7 @@ public class SqlResultSetMappingDescriptor implements NamedResultSetMappingDescr
|
|||
}
|
||||
|
||||
@Override
|
||||
public ResultMemento asResultMemento(
|
||||
NavigablePath path,
|
||||
ResultSetMappingResolutionContext resolutionContext) {
|
||||
public ResultMemento asResultMemento(NavigablePath path, ResultSetMappingResolutionContext resolutionContext) {
|
||||
final RuntimeMetamodels runtimeMetamodels = resolutionContext.getSessionFactory().getRuntimeMetamodels();
|
||||
final EntityMappingType entityMapping = runtimeMetamodels.getEntityMappingType( entityName );
|
||||
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package org.hibernate.orm.test.loaders;
|
||||
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ColumnResult;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.SqlResultSetMapping;
|
||||
import jakarta.persistence.Table;
|
||||
import org.hibernate.annotations.SQLSelect;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@SessionFactory
|
||||
@DomainModel(annotatedClasses = {SqlSelectTest.WithSqlSelect.class})
|
||||
public class SqlSelectTest {
|
||||
|
||||
@Test
|
||||
void test(SessionFactoryScope scope) {
|
||||
WithSqlSelect withSqlSelect = new WithSqlSelect();
|
||||
withSqlSelect.name = "Hibernate";
|
||||
withSqlSelect.uuids.add( UUID.randomUUID() );
|
||||
withSqlSelect.uuids.add( UUID.randomUUID() );
|
||||
withSqlSelect.uuids.add( UUID.randomUUID() );
|
||||
|
||||
scope.inTransaction( s -> s.persist( withSqlSelect ) );
|
||||
|
||||
scope.inSession( s -> {
|
||||
WithSqlSelect wss = s.get( WithSqlSelect.class, withSqlSelect.id );
|
||||
assertEquals( "Hibernate", wss.name );
|
||||
assertEquals( 3, wss.uuids.size() );
|
||||
});
|
||||
}
|
||||
|
||||
@Entity
|
||||
@Table(name = "With_Sql_Select")
|
||||
@SQLSelect(sql = "select * from With_Sql_Select where Sql_Select_id = ?",
|
||||
querySpaces = "With_Sql_Select")
|
||||
static class WithSqlSelect {
|
||||
@Id @GeneratedValue
|
||||
@Column(name = "Sql_Select_id")
|
||||
Long id;
|
||||
String name;
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "With_Uuids",
|
||||
joinColumns = @JoinColumn(name = "Sql_Select_id", referencedColumnName = "Sql_Select_id"))
|
||||
@SQLSelect(sql = "select Random_Uuids as uuid from With_Uuids where Sql_Select_id = ?",
|
||||
resultSetMapping = @SqlResultSetMapping(name = "",
|
||||
columns = @ColumnResult(name = "uuid", type = UUID.class)),
|
||||
querySpaces = "With_Uuids")
|
||||
@Column(name = "Random_Uuids")
|
||||
List<UUID> uuids = new ArrayList<>();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue