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
|
||||
* expects. The primary key columns come before the version column if the
|
||||
* 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
|
||||
*/
|
||||
|
@ -61,10 +65,12 @@ public @interface SQLDelete {
|
|||
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
||||
|
||||
/**
|
||||
* The name of the table in the case of an entity with {@link jakarta.persistence.SecondaryTable
|
||||
* secondary tables}, defaults to the primary table.
|
||||
* The name of the table affected by the delete statement. Required when the
|
||||
* 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
|
||||
*/
|
||||
|
|
|
@ -53,10 +53,9 @@ public @interface SQLDeleteAll {
|
|||
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
||||
|
||||
/**
|
||||
* The name of the table in the case of an entity with {@link jakarta.persistence.SecondaryTable
|
||||
* secondary tables}, defaults to the primary table.
|
||||
* The name of the table affected. Never required.
|
||||
*
|
||||
* @return the name of the table
|
||||
* @return the name of the secondary table
|
||||
*
|
||||
* @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
|
||||
* {@link Generated @Generated(writable=true)} is specified, again forcing
|
||||
* 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
|
||||
*/
|
||||
|
@ -77,8 +81,10 @@ public @interface SQLInsert {
|
|||
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
||||
|
||||
/**
|
||||
* The name of the table in the case of an entity with {@link jakarta.persistence.SecondaryTable
|
||||
* secondary tables}, defaults to the primary table.
|
||||
* The name of the table affected by the insert statement. Required when the
|
||||
* 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
|
||||
*
|
||||
|
|
|
@ -45,6 +45,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|||
* loses synchronization with the database after the update is executed unless
|
||||
* {@link Generated @Generated(event=UPDATE, writable=true)} is specified, again
|
||||
* 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
|
||||
*/
|
||||
|
@ -80,10 +84,12 @@ public @interface SQLUpdate {
|
|||
ResultCheckStyle check() default ResultCheckStyle.NONE;
|
||||
|
||||
/**
|
||||
* The name of the table in the case of an entity with {@link jakarta.persistence.SecondaryTable
|
||||
* secondary tables}, defaults to the primary table.
|
||||
* The name of the table affected by the update statement. Required when the
|
||||
* 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
|
||||
*/
|
||||
|
|
|
@ -230,8 +230,11 @@ public class EntityBinder {
|
|||
|
||||
final EntityBinder entityBinder = new EntityBinder( clazzToProcess, persistentClass, context );
|
||||
entityBinder.bindEntity();
|
||||
entityBinder.handleClassTable( inheritanceState, superEntity );
|
||||
entityBinder.handleSecondaryTables();
|
||||
entityBinder.bindSubselect(); // has to happen before table binding
|
||||
entityBinder.bindTables( inheritanceState, superEntity );
|
||||
entityBinder.bindCustomSql(); // has to happen after table binding
|
||||
entityBinder.bindSynchronize();
|
||||
entityBinder.bindFilters();
|
||||
entityBinder.handleCheckConstraints();
|
||||
final PropertyHolder holder = buildPropertyHolder(
|
||||
clazzToProcess,
|
||||
|
@ -246,7 +249,7 @@ public class EntityBinder {
|
|||
final InFlightMetadataCollector collector = context.getMetadataCollector();
|
||||
if ( persistentClass instanceof RootClass ) {
|
||||
collector.addSecondPass( new CreateKeySecondPass( (RootClass) persistentClass ) );
|
||||
bindSoftDelete( clazzToProcess, (RootClass) persistentClass, inheritanceState, context );
|
||||
bindSoftDelete( clazzToProcess, (RootClass) persistentClass, context );
|
||||
}
|
||||
if ( persistentClass instanceof Subclass) {
|
||||
assert superEntity != null;
|
||||
|
@ -262,6 +265,11 @@ public class EntityBinder {
|
|||
entityBinder.callTypeBinders( persistentClass );
|
||||
}
|
||||
|
||||
private void bindTables(InheritanceState inheritanceState, PersistentClass superEntity) {
|
||||
handleClassTable( inheritanceState, superEntity );
|
||||
handleSecondaryTables();
|
||||
}
|
||||
|
||||
private static void checkOverrides(XClass clazzToProcess, PersistentClass superEntity) {
|
||||
if ( superEntity != null ) {
|
||||
//TODO: correctly handle compound paths (embeddables)
|
||||
|
@ -312,28 +320,24 @@ public class EntityBinder {
|
|||
private static void bindSoftDelete(
|
||||
XClass xClass,
|
||||
RootClass rootClass,
|
||||
InheritanceState inheritanceState,
|
||||
MetadataBuildingContext context) {
|
||||
// todo (soft-delete) : do we assume all package-level registrations are already available?
|
||||
// or should this be a "second pass"?
|
||||
|
||||
final SoftDelete softDelete = extractSoftDelete( xClass, rootClass, inheritanceState, context );
|
||||
if ( softDelete == null ) {
|
||||
return;
|
||||
final SoftDelete softDelete = extractSoftDelete( xClass, rootClass, context );
|
||||
if ( softDelete != null ) {
|
||||
SoftDeleteHelper.bindSoftDeleteIndicator(
|
||||
softDelete,
|
||||
rootClass,
|
||||
rootClass.getRootTable(),
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
SoftDeleteHelper.bindSoftDeleteIndicator(
|
||||
softDelete,
|
||||
rootClass,
|
||||
rootClass.getRootTable(),
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
private static SoftDelete extractSoftDelete(
|
||||
XClass xClass,
|
||||
RootClass rootClass,
|
||||
InheritanceState inheritanceState,
|
||||
MetadataBuildingContext context) {
|
||||
final SoftDelete fromClass = xClass.getAnnotation( SoftDelete.class );
|
||||
if ( fromClass != null ) {
|
||||
|
@ -1332,9 +1336,7 @@ public class EntityBinder {
|
|||
ensureNoMutabilityPlan();
|
||||
|
||||
bindCustomPersister();
|
||||
bindCustomSql();
|
||||
bindSynchronize();
|
||||
bindFilters();
|
||||
bindCustomLoader();
|
||||
|
||||
registerImportName();
|
||||
|
||||
|
@ -1381,9 +1383,12 @@ public class EntityBinder {
|
|||
}
|
||||
|
||||
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 ) {
|
||||
persistentClass.setCustomSQLInsert(
|
||||
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 ) {
|
||||
persistentClass.setCustomSQLUpdate(
|
||||
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 ) {
|
||||
persistentClass.setCustomSQLDelete(
|
||||
sqlDelete.sql().trim(),
|
||||
|
@ -1438,12 +1449,16 @@ public class EntityBinder {
|
|||
persistentClass.setLoaderName( loaderName );
|
||||
QueryBinder.bindQuery( loaderName, hqlSelect, context );
|
||||
}
|
||||
}
|
||||
|
||||
private void bindCustomLoader() {
|
||||
final Loader loader = annotatedClass.getAnnotation( Loader.class );
|
||||
if ( loader != null ) {
|
||||
persistentClass.setLoaderName( loader.namedQuery() );
|
||||
}
|
||||
}
|
||||
|
||||
private void bindSubselect() {
|
||||
final Subselect subselect = annotatedClass.getAnnotation( Subselect.class );
|
||||
if ( subselect != null ) {
|
||||
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