HHH-17848 tolerate primary table name in @SqlXxxx annotations
just cleaning up a TODO I left behind a while ago
This commit is contained in:
parent
6c4aa400d4
commit
557a4f16da
|
@ -26,6 +26,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
* {@code ?} parameters that Hibernate expects, in the exact order Hibernate
|
* {@code ?} parameters that Hibernate expects, in the exact order Hibernate
|
||||||
* expects. The primary key columns come before the version column if the
|
* expects. The primary key columns come before the version column if the
|
||||||
* entity is versioned.
|
* entity is versioned.
|
||||||
|
* <p>
|
||||||
|
* If an entity has {@linkplain jakarta.persistence.SecondaryTable secondary
|
||||||
|
* tables}, it may have a {@code @SQLDelete} annotation for each secondary table.
|
||||||
|
* The {@link #table} member must specify the name of the secondary table.
|
||||||
*
|
*
|
||||||
* @author Laszlo Benke
|
* @author Laszlo Benke
|
||||||
*/
|
*/
|
||||||
|
@ -61,10 +65,12 @@ public @interface SQLDelete {
|
||||||
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the table in the case of an entity with {@link jakarta.persistence.SecondaryTable
|
* The name of the table affected by the delete statement. Required when the
|
||||||
* secondary tables}, defaults to the primary table.
|
* statement affects a {@linkplain jakarta.persistence.SecondaryTable secondary
|
||||||
|
* table} of an entity. Not required for collections nor when the insert statement
|
||||||
|
* affects the primary table of an entity.
|
||||||
*
|
*
|
||||||
* @return the name of the table
|
* @return the name of the secondary table
|
||||||
*
|
*
|
||||||
* @since 6.2
|
* @since 6.2
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -53,10 +53,9 @@ public @interface SQLDeleteAll {
|
||||||
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the table in the case of an entity with {@link jakarta.persistence.SecondaryTable
|
* The name of the table affected. Never required.
|
||||||
* secondary tables}, defaults to the primary table.
|
|
||||||
*
|
*
|
||||||
* @return the name of the table
|
* @return the name of the secondary table
|
||||||
*
|
*
|
||||||
* @since 6.2
|
* @since 6.2
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -42,6 +42,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
* loses synchronization with the database after the insert is executed unless
|
* loses synchronization with the database after the insert is executed unless
|
||||||
* {@link Generated @Generated(writable=true)} is specified, again forcing
|
* {@link Generated @Generated(writable=true)} is specified, again forcing
|
||||||
* Hibernate to reread the state of the entity after each insert.
|
* Hibernate to reread the state of the entity after each insert.
|
||||||
|
* <p>
|
||||||
|
* If an entity has {@linkplain jakarta.persistence.SecondaryTable secondary
|
||||||
|
* tables}, it may have a {@code @SQLInsert} annotation for each secondary table.
|
||||||
|
* The {@link #table} member must specify the name of the secondary table.
|
||||||
*
|
*
|
||||||
* @author Laszlo Benke
|
* @author Laszlo Benke
|
||||||
*/
|
*/
|
||||||
|
@ -77,8 +81,10 @@ public @interface SQLInsert {
|
||||||
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the table in the case of an entity with {@link jakarta.persistence.SecondaryTable
|
* The name of the table affected by the insert statement. Required when the
|
||||||
* secondary tables}, defaults to the primary table.
|
* statement affects a {@linkplain jakarta.persistence.SecondaryTable secondary
|
||||||
|
* table} of an entity. Not required for collections nor when the insert statement
|
||||||
|
* affects the primary table of an entity.
|
||||||
*
|
*
|
||||||
* @return the name of the secondary table
|
* @return the name of the secondary table
|
||||||
*
|
*
|
||||||
|
|
|
@ -45,6 +45,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
* loses synchronization with the database after the update is executed unless
|
* loses synchronization with the database after the update is executed unless
|
||||||
* {@link Generated @Generated(event=UPDATE, writable=true)} is specified, again
|
* {@link Generated @Generated(event=UPDATE, writable=true)} is specified, again
|
||||||
* forcing Hibernate to reread the state of the entity after each update.
|
* forcing Hibernate to reread the state of the entity after each update.
|
||||||
|
* <p>
|
||||||
|
* If an entity has {@linkplain jakarta.persistence.SecondaryTable secondary
|
||||||
|
* tables}, it may have a {@code @SQLUpdate} annotation for each secondary table.
|
||||||
|
* The {@link #table} member must specify the name of the secondary table.
|
||||||
*
|
*
|
||||||
* @author Laszlo Benke
|
* @author Laszlo Benke
|
||||||
*/
|
*/
|
||||||
|
@ -80,10 +84,12 @@ public @interface SQLUpdate {
|
||||||
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the table in the case of an entity with {@link jakarta.persistence.SecondaryTable
|
* The name of the table affected by the update statement. Required when the
|
||||||
* secondary tables}, defaults to the primary table.
|
* statement affects a {@linkplain jakarta.persistence.SecondaryTable secondary
|
||||||
|
* table} of an entity. Not required for collections nor when the insert statement
|
||||||
|
* affects the primary table of an entity.
|
||||||
*
|
*
|
||||||
* @return the name of the table
|
* @return the name of the secondary table
|
||||||
*
|
*
|
||||||
* @since 6.2
|
* @since 6.2
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -230,8 +230,11 @@ public class EntityBinder {
|
||||||
|
|
||||||
final EntityBinder entityBinder = new EntityBinder( clazzToProcess, persistentClass, context );
|
final EntityBinder entityBinder = new EntityBinder( clazzToProcess, persistentClass, context );
|
||||||
entityBinder.bindEntity();
|
entityBinder.bindEntity();
|
||||||
entityBinder.handleClassTable( inheritanceState, superEntity );
|
entityBinder.bindSubselect(); // has to happen before table binding
|
||||||
entityBinder.handleSecondaryTables();
|
entityBinder.bindTables( inheritanceState, superEntity );
|
||||||
|
entityBinder.bindCustomSql(); // has to happen after table binding
|
||||||
|
entityBinder.bindSynchronize();
|
||||||
|
entityBinder.bindFilters();
|
||||||
entityBinder.handleCheckConstraints();
|
entityBinder.handleCheckConstraints();
|
||||||
final PropertyHolder holder = buildPropertyHolder(
|
final PropertyHolder holder = buildPropertyHolder(
|
||||||
clazzToProcess,
|
clazzToProcess,
|
||||||
|
@ -246,7 +249,7 @@ public class EntityBinder {
|
||||||
final InFlightMetadataCollector collector = context.getMetadataCollector();
|
final InFlightMetadataCollector collector = context.getMetadataCollector();
|
||||||
if ( persistentClass instanceof RootClass ) {
|
if ( persistentClass instanceof RootClass ) {
|
||||||
collector.addSecondPass( new CreateKeySecondPass( (RootClass) persistentClass ) );
|
collector.addSecondPass( new CreateKeySecondPass( (RootClass) persistentClass ) );
|
||||||
bindSoftDelete( clazzToProcess, (RootClass) persistentClass, inheritanceState, context );
|
bindSoftDelete( clazzToProcess, (RootClass) persistentClass, context );
|
||||||
}
|
}
|
||||||
if ( persistentClass instanceof Subclass) {
|
if ( persistentClass instanceof Subclass) {
|
||||||
assert superEntity != null;
|
assert superEntity != null;
|
||||||
|
@ -262,6 +265,11 @@ public class EntityBinder {
|
||||||
entityBinder.callTypeBinders( persistentClass );
|
entityBinder.callTypeBinders( persistentClass );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void bindTables(InheritanceState inheritanceState, PersistentClass superEntity) {
|
||||||
|
handleClassTable( inheritanceState, superEntity );
|
||||||
|
handleSecondaryTables();
|
||||||
|
}
|
||||||
|
|
||||||
private static void checkOverrides(XClass clazzToProcess, PersistentClass superEntity) {
|
private static void checkOverrides(XClass clazzToProcess, PersistentClass superEntity) {
|
||||||
if ( superEntity != null ) {
|
if ( superEntity != null ) {
|
||||||
//TODO: correctly handle compound paths (embeddables)
|
//TODO: correctly handle compound paths (embeddables)
|
||||||
|
@ -312,28 +320,24 @@ public class EntityBinder {
|
||||||
private static void bindSoftDelete(
|
private static void bindSoftDelete(
|
||||||
XClass xClass,
|
XClass xClass,
|
||||||
RootClass rootClass,
|
RootClass rootClass,
|
||||||
InheritanceState inheritanceState,
|
|
||||||
MetadataBuildingContext context) {
|
MetadataBuildingContext context) {
|
||||||
// todo (soft-delete) : do we assume all package-level registrations are already available?
|
// todo (soft-delete) : do we assume all package-level registrations are already available?
|
||||||
// or should this be a "second pass"?
|
// or should this be a "second pass"?
|
||||||
|
|
||||||
final SoftDelete softDelete = extractSoftDelete( xClass, rootClass, inheritanceState, context );
|
final SoftDelete softDelete = extractSoftDelete( xClass, rootClass, context );
|
||||||
if ( softDelete == null ) {
|
if ( softDelete != null ) {
|
||||||
return;
|
SoftDeleteHelper.bindSoftDeleteIndicator(
|
||||||
|
softDelete,
|
||||||
|
rootClass,
|
||||||
|
rootClass.getRootTable(),
|
||||||
|
context
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SoftDeleteHelper.bindSoftDeleteIndicator(
|
|
||||||
softDelete,
|
|
||||||
rootClass,
|
|
||||||
rootClass.getRootTable(),
|
|
||||||
context
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SoftDelete extractSoftDelete(
|
private static SoftDelete extractSoftDelete(
|
||||||
XClass xClass,
|
XClass xClass,
|
||||||
RootClass rootClass,
|
RootClass rootClass,
|
||||||
InheritanceState inheritanceState,
|
|
||||||
MetadataBuildingContext context) {
|
MetadataBuildingContext context) {
|
||||||
final SoftDelete fromClass = xClass.getAnnotation( SoftDelete.class );
|
final SoftDelete fromClass = xClass.getAnnotation( SoftDelete.class );
|
||||||
if ( fromClass != null ) {
|
if ( fromClass != null ) {
|
||||||
|
@ -1332,9 +1336,7 @@ public class EntityBinder {
|
||||||
ensureNoMutabilityPlan();
|
ensureNoMutabilityPlan();
|
||||||
|
|
||||||
bindCustomPersister();
|
bindCustomPersister();
|
||||||
bindCustomSql();
|
bindCustomLoader();
|
||||||
bindSynchronize();
|
|
||||||
bindFilters();
|
|
||||||
|
|
||||||
registerImportName();
|
registerImportName();
|
||||||
|
|
||||||
|
@ -1381,9 +1383,12 @@ public class EntityBinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindCustomSql() {
|
private void bindCustomSql() {
|
||||||
//TODO: tolerate non-empty table() member here if it explicitly names the main table
|
final String primaryTableName = persistentClass.getTable().getName();
|
||||||
|
|
||||||
final SQLInsert sqlInsert = findMatchingSqlAnnotation( "", SQLInsert.class, SQLInserts.class );
|
SQLInsert sqlInsert = findMatchingSqlAnnotation( primaryTableName, SQLInsert.class, SQLInserts.class );
|
||||||
|
if ( sqlInsert == null ) {
|
||||||
|
sqlInsert = findMatchingSqlAnnotation( "", SQLInsert.class, SQLInserts.class );
|
||||||
|
}
|
||||||
if ( sqlInsert != null ) {
|
if ( sqlInsert != null ) {
|
||||||
persistentClass.setCustomSQLInsert(
|
persistentClass.setCustomSQLInsert(
|
||||||
sqlInsert.sql().trim(),
|
sqlInsert.sql().trim(),
|
||||||
|
@ -1395,7 +1400,10 @@ public class EntityBinder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final SQLUpdate sqlUpdate = findMatchingSqlAnnotation( "", SQLUpdate.class, SQLUpdates.class );
|
SQLUpdate sqlUpdate = findMatchingSqlAnnotation( primaryTableName, SQLUpdate.class, SQLUpdates.class );
|
||||||
|
if ( sqlUpdate == null ) {
|
||||||
|
sqlUpdate = findMatchingSqlAnnotation( "", SQLUpdate.class, SQLUpdates.class );
|
||||||
|
}
|
||||||
if ( sqlUpdate != null ) {
|
if ( sqlUpdate != null ) {
|
||||||
persistentClass.setCustomSQLUpdate(
|
persistentClass.setCustomSQLUpdate(
|
||||||
sqlUpdate.sql().trim(),
|
sqlUpdate.sql().trim(),
|
||||||
|
@ -1407,7 +1415,10 @@ public class EntityBinder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final SQLDelete sqlDelete = findMatchingSqlAnnotation( "", SQLDelete.class, SQLDeletes.class );
|
SQLDelete sqlDelete = findMatchingSqlAnnotation( primaryTableName, SQLDelete.class, SQLDeletes.class );
|
||||||
|
if ( sqlDelete == null ) {
|
||||||
|
sqlDelete = findMatchingSqlAnnotation( "", SQLDelete.class, SQLDeletes.class );
|
||||||
|
}
|
||||||
if ( sqlDelete != null ) {
|
if ( sqlDelete != null ) {
|
||||||
persistentClass.setCustomSQLDelete(
|
persistentClass.setCustomSQLDelete(
|
||||||
sqlDelete.sql().trim(),
|
sqlDelete.sql().trim(),
|
||||||
|
@ -1438,12 +1449,16 @@ public class EntityBinder {
|
||||||
persistentClass.setLoaderName( loaderName );
|
persistentClass.setLoaderName( loaderName );
|
||||||
QueryBinder.bindQuery( loaderName, hqlSelect, context );
|
QueryBinder.bindQuery( loaderName, hqlSelect, context );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindCustomLoader() {
|
||||||
final Loader loader = annotatedClass.getAnnotation( Loader.class );
|
final Loader loader = annotatedClass.getAnnotation( Loader.class );
|
||||||
if ( loader != null ) {
|
if ( loader != null ) {
|
||||||
persistentClass.setLoaderName( loader.namedQuery() );
|
persistentClass.setLoaderName( loader.namedQuery() );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindSubselect() {
|
||||||
final Subselect subselect = annotatedClass.getAnnotation( Subselect.class );
|
final Subselect subselect = annotatedClass.getAnnotation( Subselect.class );
|
||||||
if ( subselect != null ) {
|
if ( subselect != null ) {
|
||||||
this.subselect = subselect.value();
|
this.subselect = subselect.value();
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
package org.hibernate.orm.test.customsql;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.SecondaryTable;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.Version;
|
||||||
|
import org.hibernate.annotations.SQLDelete;
|
||||||
|
import org.hibernate.annotations.SQLInsert;
|
||||||
|
import org.hibernate.annotations.SQLUpdate;
|
||||||
|
import org.hibernate.jdbc.Expectation;
|
||||||
|
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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
@SessionFactory
|
||||||
|
@DomainModel(annotatedClasses = CustomSqlPrimaryTableExplicitTest.Custom.class)
|
||||||
|
public class CustomSqlPrimaryTableExplicitTest {
|
||||||
|
@Test
|
||||||
|
public void testCustomSql(SessionFactoryScope scope) {
|
||||||
|
Custom c = new Custom();
|
||||||
|
c.name = "name";
|
||||||
|
c.text = "text";
|
||||||
|
scope.inTransaction(s->{
|
||||||
|
s.persist(c);
|
||||||
|
s.flush();
|
||||||
|
s.clear();
|
||||||
|
Custom cc = s.find(Custom.class, c.id);
|
||||||
|
assertEquals(cc.text, "TEXT");
|
||||||
|
assertEquals(cc.name, "NAME");
|
||||||
|
cc.name = "eman";
|
||||||
|
cc.text = "more text";
|
||||||
|
s.flush();
|
||||||
|
s.clear();
|
||||||
|
cc = s.find(Custom.class, c.id);
|
||||||
|
assertEquals(cc.text, "MORE TEXT");
|
||||||
|
assertEquals(cc.name, "EMAN");
|
||||||
|
s.remove(cc);
|
||||||
|
s.flush();
|
||||||
|
s.clear();
|
||||||
|
cc = s.find(Custom.class, c.id);
|
||||||
|
assertEquals(cc.text, "DELETED");
|
||||||
|
assertEquals(cc.name, "DELETED");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@Entity
|
||||||
|
@Table(name = "CustomPrimary")
|
||||||
|
@SecondaryTable(name = "CustomSecondary")
|
||||||
|
@SQLInsert(table = "CustomPrimary",
|
||||||
|
sql="insert into CustomPrimary (name, revision, id) values (upper(?),?,?)",
|
||||||
|
verify = Expectation.RowCount.class)
|
||||||
|
@SQLInsert(table = "CustomSecondary",
|
||||||
|
sql="insert into CustomSecondary (text, id) values (upper(?),?)",
|
||||||
|
verify = Expectation.None.class)
|
||||||
|
@SQLUpdate(table = "CustomPrimary",
|
||||||
|
sql="update CustomPrimary set name = upper(?), revision = ? where id = ? and revision = ?",
|
||||||
|
verify = Expectation.RowCount.class)
|
||||||
|
@SQLUpdate(table = "CustomSecondary",
|
||||||
|
sql="update CustomSecondary set text = upper(?) where id = ?",
|
||||||
|
verify = Expectation.None.class)
|
||||||
|
@SQLDelete(table = "CustomPrimary",
|
||||||
|
sql="update CustomPrimary set name = 'DELETED' where id = ? and revision = ?",
|
||||||
|
verify = Expectation.RowCount.class)
|
||||||
|
@SQLDelete(table = "CustomSecondary",
|
||||||
|
sql="update CustomSecondary set text = 'DELETED' where id = ?",
|
||||||
|
verify = Expectation.None.class)
|
||||||
|
static class Custom {
|
||||||
|
@Id @GeneratedValue
|
||||||
|
Long id;
|
||||||
|
@Version @Column(name = "revision")
|
||||||
|
int version;
|
||||||
|
String name;
|
||||||
|
@Column(table = "CustomSecondary")
|
||||||
|
String text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue