HHH-16000 Add @SQLSelect annotation as abbreviation for @NamedNativeQuery + @Loader

This commit is contained in:
Gavin 2023-01-07 11:32:41 +01:00 committed by Gavin King
parent bfdd7f648b
commit 3ceb91d280
12 changed files with 387 additions and 63 deletions

View File

@ -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[]
}

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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 {};
}

View File

@ -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();

View File

@ -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;
@ -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() );

View File

@ -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() );

View File

@ -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,7 +208,6 @@ public abstract class QueryBinder {
.setSqlString( namedNativeQuery.query() )
.setResultSetMappingName( resultSetMappingName )
.setResultSetMappingClassName( resultSetMappingClassName )
.setQuerySpaces( null )
.setCacheable( namedNativeQuery.cacheable() )
.setCacheRegion( nullIfEmpty( namedNativeQuery.cacheRegion() ) )
.setCacheMode( getCacheMode( namedNativeQuery ) )
@ -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) {

View File

@ -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 );

View File

@ -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,10 +179,8 @@ 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
@ -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 );

View File

@ -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<>();
}
}