HHH-15570 allow @SqlInsert, @SqlUpdate, @SqlDelete for secondary tables

This commit is contained in:
Gavin King 2022-10-03 14:26:36 +02:00
parent 28b253512e
commit aef9ab2425
17 changed files with 453 additions and 185 deletions

View File

@ -909,7 +909,7 @@ You can see the expected order by enabling debug logging, so Hibernate can print
To see the expected sequence, remember to not include your custom SQL through annotations or mapping files as that will override the Hibernate generated static SQL.
====
Overriding SQL statements for secondary tables is also possible using `@org.hibernate.annotations.Table` and the `sqlInsert`, `sqlUpdate`, `sqlDelete` attributes.
Overriding SQL statements for secondary tables is also possible.
[[sql-custom-crud-secondary-table-example]]
.Overriding SQL statements for secondary tables

View File

@ -90,24 +90,24 @@ public class CustomSQLSecondaryTableTest extends BaseEntityManagerFunctionalTest
//tag::sql-custom-crud-secondary-table-example[]
@Entity(name = "Person")
@Table(name = "person")
@SecondaryTable(name = "person_details",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id"))
@SQLInsert(
sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) "
)
@SQLDelete(
sql = "UPDATE person SET valid = false WHERE id = ? "
)
@SecondaryTable(name = "person_details",
pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id"))
@org.hibernate.annotations.Table(
appliesTo = "person_details",
sqlInsert = @SQLInsert(
sql = "INSERT INTO person_details (image, person_id, valid) VALUES (?, ?, true) ",
check = ResultCheckStyle.COUNT
),
sqlDelete = @SQLDelete(
sql = "UPDATE person_details SET valid = false WHERE person_id = ? "
)
)
)
@SQLInsert(
table = "person_details",
sql = "INSERT INTO person_details (image, person_id, valid) VALUES (?, ?, true) ",
check = ResultCheckStyle.COUNT
)
@SQLDelete(
table = "person_details",
sql = "UPDATE person_details SET valid = false WHERE person_id = ? "
)
@Loader(namedQuery = "find_valid_person")
@NamedNativeQueries({
@NamedNativeQuery(

View File

@ -7,30 +7,36 @@
package org.hibernate.annotations;
/**
* Possible styles of checking return codes on SQL INSERT, UPDATE and DELETE queries.
* Enumerates strategies for checking JDBC return codes for custom SQL DML queries.
* <p>
* Return code checking is used to verify that a SQL statement actually had the
* intended effect, for example, that an {@code UPDATE} statement actually changed
* the expected number of rows.
*
* @author L<EFBFBD>szl<EFBFBD> Benke
*/
public enum ResultCheckStyle {
/**
* Do not perform checking. Might mean that the user really just does not want any checking. Might
* also mean that the user is expecting a failure to be indicated by a {@link java.sql.SQLException} being
* thrown (presumably from a {@link java.sql.CallableStatement} which is performing explicit checks and
* propagating failures back through the driver).
* No return code checking. Might mean that no checks are required, or that
* failure is indicated by a {@link java.sql.SQLException} being thrown, for
* example, by a {@link java.sql.CallableStatement stored procedure} which
* performs explicit checks.
*/
NONE,
/**
* Perform row-count checking. Row counts are the int values returned by both
* {@link java.sql.PreparedStatement#executeUpdate()} and
* {@link java.sql.Statement#executeBatch()}. These values are checked
* against some expected count.
* Row count checking. A row count is an integer value returned by
* {@link java.sql.PreparedStatement#executeUpdate()} or
* {@link java.sql.Statement#executeBatch()}. The row count is checked
* against an expected value. For example, the expected row count for
* an {@code INSERT} statement is always 1.
*/
COUNT,
/**
* Essentially the same as {@link #COUNT} except that the row count actually
* comes from an output parameter registered as part of a
* {@link java.sql.CallableStatement}. This style explicitly prohibits
* statement batching from being used...
* Essentially identical to {@link #COUNT} except that the row count is
* obtained via an output parameter of a {@link java.sql.CallableStatement
* stored procedure}.
* <p>
* Statement batching is disabled when {@code PARAM} is selected.
*/
PARAM
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.annotations;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@ -15,12 +16,14 @@ import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Custom SQL statement for delete of an entity/collection.
* Specifies a custom SQL DML statement to be used in place of the default SQL generated by
* Hibernate when an entity or collection row is deleted from the database.
*
* @author L<EFBFBD>szl<EFBFBD> Benke
*/
@Target( {TYPE, FIELD, METHOD} )
@Retention( RUNTIME )
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
@Repeatable(SQLDeletes.class)
public @interface SQLDelete {
/**
* Procedure name or SQL DELETE statement.
@ -36,4 +39,14 @@ public @interface SQLDelete {
* For persistence operation what style of determining results (success/failure) is to be used.
*/
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.
*
* @return the name of the table
*
* @since 6.2
*/
String table() default "";
}

View File

@ -7,20 +7,21 @@
package org.hibernate.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
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;
/**
* Custom SQL statement for delete of all of a collection's elements.
* Specifies a custom SQL DML statement to be used in place of the default SQL generated by
* Hibernate when an entire collection is deleted from the database.
*
* @author L<EFBFBD>szl<EFBFBD> Benke
*/
@Target( {TYPE, FIELD, METHOD} )
@Retention( RetentionPolicy.RUNTIME )
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
public @interface SQLDeleteAll {
/**
* Procedure name or SQL DELETE statement.
@ -36,4 +37,14 @@ public @interface SQLDeleteAll {
* For persistence operation what style of determining results (success/failure) is to be used.
*/
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.
*
* @return the name of the table
*
* @since 6.2
*/
String table() default "";
}

View File

@ -0,0 +1,25 @@
/*
* 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 java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* A grouping of {@link SQLDelete}s.
*
* @since 6.2
* @author Gavin King
*/
@Target(TYPE)
@Retention(RUNTIME)
public @interface SQLDeletes {
SQLDelete[] value();
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.annotations;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@ -15,12 +16,14 @@ import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Custom SQL statement for insertion of an entity/collection.
* Specifies a custom SQL DML statement to be used in place of the default SQL generated by
* Hibernate when an entity or collection row is inserted in the database.
*
* @author L<EFBFBD>szl<EFBFBD> Benke
*/
@Target( {TYPE, FIELD, METHOD} )
@Retention( RUNTIME )
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
@Repeatable(SQLInserts.class)
public @interface SQLInsert {
/**
* Procedure name or SQL INSERT statement.
@ -36,4 +39,14 @@ public @interface SQLInsert {
* For persistence operation what style of determining results (success/failure) is to be used.
*/
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.
*
* @return the name of the secondary table
*
* @since 6.2
*/
String table() default "";
}

View File

@ -0,0 +1,25 @@
/*
* 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 java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* A grouping of {@link SQLInsert}s.
*
* @since 6.2
* @author Gavin King
*/
@Target(TYPE)
@Retention(RUNTIME)
public @interface SQLInserts {
SQLInsert[] value();
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.annotations;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@ -15,12 +16,14 @@ import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* Custom SQL statement for update of an entity/collection.
* Specifies a custom SQL DML statement to be used in place of the default SQL generated by
* Hibernate when an entity or collection row is updated in the database.
*
* @author L<EFBFBD>szl<EFBFBD> Benke
*/
@Target( {TYPE, FIELD, METHOD} )
@Retention( RUNTIME )
@Target({TYPE, FIELD, METHOD})
@Retention(RUNTIME)
@Repeatable(SQLUpdates.class)
public @interface SQLUpdate {
/**
* Procedure name or SQL UPDATE statement.
@ -36,4 +39,14 @@ public @interface SQLUpdate {
* For persistence operation what style of determining results (success/failure) is to be used.
*/
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.
*
* @return the name of the table
*
* @since 6.2
*/
String table() default "";
}

View File

@ -0,0 +1,25 @@
/*
* 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 java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* A grouping of {@link SQLUpdate}s.
*
* @since 6.2
* @author Gavin King
*/
@Target(TYPE)
@Retention(RUNTIME)
public @interface SQLUpdates {
SQLUpdate[] value();
}

View File

@ -18,6 +18,7 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
*
* @see jakarta.persistence.SecondaryTable
*
* @since 6.2
* @author Gavin King
*/
@Target(TYPE)

View File

@ -13,8 +13,9 @@ import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* A grouping of {@link SecondaryRows}.
* A grouping of {@link SecondaryRow}s.
*
* @since 6.2
* @author Gavin King
*/
@Target(TYPE)

View File

@ -100,8 +100,9 @@ public @interface Table {
* <p>
* <em>Only applies to secondary tables.</em>
*
* @see SQLInsert
* @deprecated use {@link SQLInsert#table()} to specify the secondary table
*/
@Deprecated(since="6.2")
SQLInsert sqlInsert() default @SQLInsert(sql="");
/**
@ -109,8 +110,9 @@ public @interface Table {
* <p>
* <em>Only applies to secondary tables.</em>
*
* @see SQLUpdate
* @deprecated use {@link SQLInsert#table()} to specify the secondary table
*/
@Deprecated(since="6.2")
SQLUpdate sqlUpdate() default @SQLUpdate(sql="");
/**
@ -118,7 +120,8 @@ public @interface Table {
* <p>
* <em>Only applies to secondary tables.</em>
*
* @see SQLDelete
* @deprecated use {@link SQLInsert#table()} to specify the secondary table
*/
@Deprecated(since="6.2")
SQLDelete sqlDelete() default @SQLDelete(sql="");
}

View File

@ -114,7 +114,6 @@ import org.hibernate.cfg.InheritanceState;
import org.hibernate.cfg.NotYetImplementedException;
import org.hibernate.cfg.PropertyData;
import org.hibernate.cfg.PropertyHolder;
import org.hibernate.cfg.PropertyHolderBuilder;
import org.hibernate.cfg.PropertyInferredData;
import org.hibernate.cfg.PropertyPreloadedData;
import org.hibernate.cfg.SecondPass;
@ -158,9 +157,18 @@ import static org.hibernate.cfg.AnnotatedColumn.buildFormulaFromAnnotation;
import static org.hibernate.cfg.AnnotatedJoinColumns.buildJoinColumnsWithDefaultColumnSuffix;
import static org.hibernate.cfg.AnnotatedJoinColumns.buildJoinTableJoinColumns;
import static org.hibernate.cfg.AnnotationBinder.fillComponent;
import static org.hibernate.cfg.BinderHelper.*;
import static org.hibernate.cfg.BinderHelper.buildAnyValue;
import static org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference;
import static org.hibernate.cfg.BinderHelper.getCascadeStrategy;
import static org.hibernate.cfg.BinderHelper.getFetchMode;
import static org.hibernate.cfg.BinderHelper.getOverridableAnnotation;
import static org.hibernate.cfg.BinderHelper.getPath;
import static org.hibernate.cfg.BinderHelper.isEmptyAnnotationValue;
import static org.hibernate.cfg.BinderHelper.isPrimitive;
import static org.hibernate.cfg.BinderHelper.toAliasEntityMap;
import static org.hibernate.cfg.BinderHelper.toAliasTableMap;
import static org.hibernate.cfg.PropertyHolderBuilder.buildPropertyHolder;
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromExternalName;
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
import static org.hibernate.internal.util.StringHelper.getNonEmptyOrConjunctionIfBothNonEmpty;
import static org.hibernate.internal.util.StringHelper.isEmpty;
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
@ -1294,34 +1302,35 @@ public abstract class CollectionBinder {
collection.setCustomSQLInsert(
sqlInsert.sql().trim(),
sqlInsert.callable(),
fromExternalName( sqlInsert.check().toString().toLowerCase(Locale.ROOT) )
fromResultCheckStyle( sqlInsert.check() )
);
}
final SQLUpdate sqlUpdate = property.getAnnotation( SQLUpdate.class );
if ( sqlUpdate != null ) {
collection.setCustomSQLUpdate(
sqlUpdate.sql(),
sqlUpdate.sql().trim(),
sqlUpdate.callable(),
fromExternalName( sqlUpdate.check().toString().toLowerCase(Locale.ROOT) )
fromResultCheckStyle( sqlUpdate.check() )
);
}
final SQLDelete sqlDelete = property.getAnnotation( SQLDelete.class );
if ( sqlDelete != null ) {
collection.setCustomSQLDelete(
sqlDelete.sql(),
sqlDelete.sql().trim(),
sqlDelete.callable(),
fromExternalName( sqlDelete.check().toString().toLowerCase(Locale.ROOT) )
fromResultCheckStyle( sqlDelete.check() )
);
}
final SQLDeleteAll sqlDeleteAll = property.getAnnotation( SQLDeleteAll.class );
if ( sqlDeleteAll != null ) {
collection.setCustomSQLDeleteAll(
sqlDeleteAll.sql(),
sqlDeleteAll.sql().trim(),
sqlDeleteAll.callable(),
fromExternalName( sqlDeleteAll.check().toString().toLowerCase(Locale.ROOT) )
fromResultCheckStyle( sqlDeleteAll.check() )
);
}
@ -2111,7 +2120,7 @@ public abstract class CollectionBinder {
parentPropertyHolder.startingProperty( property );
}
final CollectionPropertyHolder holder = PropertyHolderBuilder.buildPropertyHolder(
final CollectionPropertyHolder holder = buildPropertyHolder(
collection,
collection.getRole(),
elementClass,

View File

@ -38,6 +38,7 @@ import jakarta.persistence.SecondaryTable;
import jakarta.persistence.SecondaryTables;
import jakarta.persistence.SharedCacheMode;
import jakarta.persistence.UniqueConstraint;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.MappingException;
@ -67,8 +68,11 @@ import org.hibernate.annotations.Proxy;
import org.hibernate.annotations.RowId;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLDeleteAll;
import org.hibernate.annotations.SQLDeletes;
import org.hibernate.annotations.SQLInsert;
import org.hibernate.annotations.SQLInserts;
import org.hibernate.annotations.SQLUpdate;
import org.hibernate.annotations.SQLUpdates;
import org.hibernate.annotations.SecondaryRow;
import org.hibernate.annotations.SecondaryRows;
import org.hibernate.annotations.SelectBeforeUpdate;
@ -136,10 +140,17 @@ import org.jboss.logging.Logger;
import static org.hibernate.cfg.AnnotatedDiscriminatorColumn.buildDiscriminatorColumn;
import static org.hibernate.cfg.AnnotatedJoinColumn.buildJoinColumn;
import static org.hibernate.cfg.BinderHelper.*;
import static org.hibernate.cfg.BinderHelper.getMappedSuperclassOrNull;
import static org.hibernate.cfg.BinderHelper.getOverridableAnnotation;
import static org.hibernate.cfg.BinderHelper.hasToOneAnnotation;
import static org.hibernate.cfg.BinderHelper.isEmptyAnnotationValue;
import static org.hibernate.cfg.BinderHelper.isEmptyOrNullAnnotationValue;
import static org.hibernate.cfg.BinderHelper.makeIdGenerator;
import static org.hibernate.cfg.BinderHelper.toAliasEntityMap;
import static org.hibernate.cfg.BinderHelper.toAliasTableMap;
import static org.hibernate.cfg.InheritanceState.getInheritanceStateOfSuperEntity;
import static org.hibernate.cfg.PropertyHolderBuilder.buildPropertyHolder;
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromExternalName;
import static org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle.fromResultCheckStyle;
import static org.hibernate.internal.util.StringHelper.isEmpty;
import static org.hibernate.internal.util.StringHelper.isNotEmpty;
import static org.hibernate.internal.util.StringHelper.nullIfEmpty;
@ -1141,41 +1152,40 @@ public class EntityBinder {
private void bindCustomSql() {
//SQL overriding
final SQLInsert sqlInsert = annotatedClass.getAnnotation( SQLInsert.class );
//TODO: tolerate non-empty table() member here
final SQLInsert sqlInsert = findMatchingSqlAnnotation( "", SQLInsert.class, SQLInserts.class );
if ( sqlInsert != null ) {
persistentClass.setCustomSQLInsert(
sqlInsert.sql().trim(),
sqlInsert.callable(),
fromExternalName( sqlInsert.check().toString().toLowerCase(Locale.ROOT) )
fromResultCheckStyle( sqlInsert.check() )
);
}
final SQLUpdate sqlUpdate = annotatedClass.getAnnotation( SQLUpdate.class );
final SQLUpdate sqlUpdate = findMatchingSqlAnnotation( "", SQLUpdate.class, SQLUpdates.class );
if ( sqlUpdate != null ) {
persistentClass.setCustomSQLUpdate(
sqlUpdate.sql().trim(),
sqlUpdate.callable(),
fromExternalName( sqlUpdate.check().toString().toLowerCase(Locale.ROOT) )
fromResultCheckStyle( sqlUpdate.check() )
);
}
final SQLDelete sqlDelete = annotatedClass.getAnnotation( SQLDelete.class );
final SQLDelete sqlDelete = findMatchingSqlAnnotation( "", SQLDelete.class, SQLDeletes.class );
if ( sqlDelete != null ) {
persistentClass.setCustomSQLDelete(
sqlDelete.sql().trim(),
sqlDelete.callable(),
fromExternalName( sqlDelete.check().toString().toLowerCase(Locale.ROOT) )
fromResultCheckStyle( sqlDelete.check() )
);
}
final SQLDeleteAll sqlDeleteAll = annotatedClass.getAnnotation( SQLDeleteAll.class );
if ( sqlDeleteAll != null ) {
persistentClass.setCustomSQLDelete(
sqlDeleteAll.sql().trim(),
sqlDeleteAll.callable(),
fromExternalName( sqlDeleteAll.check().toString().toLowerCase(Locale.ROOT) )
);
throw new AnnotationException("@SQLDeleteAll does not apply to entities: "
+ persistentClass.getEntityName());
}
final Loader loader = annotatedClass.getAnnotation( Loader.class );
@ -1724,7 +1734,8 @@ public class EntityBinder {
private void setForeignKeyNameIfDefined(Join join) {
// just awful..
org.hibernate.annotations.Table matchingTable = findMatchingComplementaryTableAnnotation( join );
final String tableName = join.getTable().getQuotedName();
final org.hibernate.annotations.Table matchingTable = findMatchingComplementaryTableAnnotation( tableName );
final SimpleValue key = (SimpleValue) join.getKey();
if ( matchingTable != null && !isEmptyAnnotationValue( matchingTable.foreignKey().name() ) ) {
key.setForeignKeyName( matchingTable.foreignKey().name() );
@ -1762,14 +1773,13 @@ public class EntityBinder {
return null;
}
private org.hibernate.annotations.Table findMatchingComplementaryTableAnnotation(Join join) {
final String tableName = join.getTable().getQuotedName();
private org.hibernate.annotations.Table findMatchingComplementaryTableAnnotation(String tableName) {
final org.hibernate.annotations.Table table = annotatedClass.getAnnotation( org.hibernate.annotations.Table.class );
if ( table != null && tableName.equals( table.appliesTo() ) ) {
return table;
}
else {
Tables tables = annotatedClass.getAnnotation( Tables.class );
final Tables tables = annotatedClass.getAnnotation( Tables.class );
if ( tables != null ) {
for (org.hibernate.annotations.Table current : tables.value()) {
if ( tableName.equals( current.appliesTo() ) ) {
@ -1781,14 +1791,13 @@ public class EntityBinder {
}
}
private SecondaryRow findMatchingComplementarySecondaryRowAnnotation(Join join) {
final String tableName = join.getTable().getQuotedName();
private SecondaryRow findMatchingSecondaryRowAnnotation(String tableName) {
final SecondaryRow row = annotatedClass.getAnnotation( SecondaryRow.class );
if ( row != null && ( row.table().isEmpty() || tableName.equals( row.table() ) ) ) {
return row;
}
else {
SecondaryRows tables = annotatedClass.getAnnotation( SecondaryRows.class );
final SecondaryRows tables = annotatedClass.getAnnotation( SecondaryRows.class );
if ( tables != null ) {
for ( SecondaryRow current : tables.value() ) {
if ( tableName.equals( current.table() ) ) {
@ -1800,139 +1809,163 @@ public class EntityBinder {
}
}
private <T extends Annotation,R extends Annotation> T findMatchingSqlAnnotation(
String tableName,
Class<T> annotationType,
Class<R> repeatableType) {
final T sqlAnnotation = annotatedClass.getAnnotation( annotationType );
if ( sqlAnnotation != null ) {
if ( tableName.equals( tableMember( annotationType, sqlAnnotation ) ) ) {
return sqlAnnotation;
}
}
final R repeatable = annotatedClass.getAnnotation(repeatableType);
if ( repeatable != null ) {
for ( Annotation current : valueMember( repeatableType, repeatable ) ) {
@SuppressWarnings("unchecked")
final T sqlAnn = (T) current;
if ( tableName.equals( tableMember( annotationType, sqlAnn ) ) ) {
return sqlAnn;
}
}
}
return null;
}
private static <T extends Annotation> String tableMember(Class<T> annotationType, T sqlAnnotation) {
if (SQLInsert.class.equals(annotationType)) {
return ((SQLInsert) sqlAnnotation).table();
}
else if (SQLUpdate.class.equals(annotationType)) {
return ((SQLUpdate) sqlAnnotation).table();
}
else if (SQLDelete.class.equals(annotationType)) {
return ((SQLDelete) sqlAnnotation).table();
}
else if (SQLDeleteAll.class.equals(annotationType)) {
return ((SQLDeleteAll) sqlAnnotation).table();
}
else {
throw new AssertionFailure("Unknown annotation type");
}
}
private static <T extends Annotation> Annotation[] valueMember(Class<T> repeatableType, T sqlAnnotation) {
if (SQLInserts.class.equals(repeatableType)) {
return ((SQLInserts) sqlAnnotation).value();
}
else if (SQLUpdates.class.equals(repeatableType)) {
return ((SQLUpdates) sqlAnnotation).value();
}
else if (SQLDeletes.class.equals(repeatableType)) {
return ((SQLDeletes) sqlAnnotation).value();
}
else {
throw new AssertionFailure("Unknown annotation type");
}
}
//Used for @*ToMany @JoinTable
public Join addJoin(JoinTable joinTable, PropertyHolder holder, boolean noDelayInPkColumnCreation) {
return addJoin( null, joinTable, holder, noDelayInPkColumnCreation );
return addJoin(
holder,
noDelayInPkColumnCreation,
false,
joinTable.name(),
joinTable.schema(),
joinTable.catalog(),
joinTable.joinColumns(),
joinTable.uniqueConstraints()
);
}
public Join addJoin(SecondaryTable secondaryTable, PropertyHolder holder, boolean noDelayInPkColumnCreation) {
return addJoin( secondaryTable, null, holder, noDelayInPkColumnCreation );
Join join = addJoin(
holder,
noDelayInPkColumnCreation,
true,
secondaryTable.name(),
secondaryTable.schema(),
secondaryTable.catalog(),
secondaryTable.pkJoinColumns(),
secondaryTable.uniqueConstraints()
);
TableBinder.addIndexes( join.getTable(), secondaryTable.indexes(), context );
return join;
}
private Join addJoin(
SecondaryTable secondaryTable,
JoinTable joinTable,
PropertyHolder propertyHolder,
boolean noDelayInPkColumnCreation) {
// A non-null propertyHolder means than we process the Pk creation without delay
final Join join = new Join();
join.setPersistentClass( persistentClass );
final String schema;
final String catalog;
final Object joinColumns;
final List<UniqueConstraintHolder> uniqueConstraintHolders;
final QualifiedTableName logicalName;
if ( secondaryTable != null ) {
schema = secondaryTable.schema();
catalog = secondaryTable.catalog();
logicalName = new QualifiedTableName(
Identifier.toIdentifier( catalog ),
Identifier.toIdentifier( schema ),
context.getMetadataCollector()
.getDatabase()
.getJdbcEnvironment()
.getIdentifierHelper()
.toIdentifier( secondaryTable.name() )
);
joinColumns = secondaryTable.pkJoinColumns();
uniqueConstraintHolders = TableBinder.buildUniqueConstraintHolders( secondaryTable.uniqueConstraints() );
}
else if ( joinTable != null ) {
schema = joinTable.schema();
catalog = joinTable.catalog();
logicalName = new QualifiedTableName(
Identifier.toIdentifier( catalog ),
Identifier.toIdentifier( schema ),
boolean noDelayInPkColumnCreation,
boolean secondaryTable,
String name,
String schema,
String catalog,
Object joinColumns,
UniqueConstraint[] uniqueConstraints) {
final QualifiedTableName logicalName = new QualifiedTableName(
Identifier.toIdentifier(catalog),
Identifier.toIdentifier(schema),
context.getMetadataCollector()
.getDatabase()
.getJdbcEnvironment()
.getIdentifierHelper()
.toIdentifier( joinTable.name() )
);
joinColumns = joinTable.joinColumns();
uniqueConstraintHolders = TableBinder.buildUniqueConstraintHolders( joinTable.uniqueConstraints() );
}
else {
throw new AssertionFailure( "Both JoinTable and SecondaryTable are null" );
}
final Table table = TableBinder.buildAndFillTable(
schema,
catalog,
logicalName.getTableName(),
false,
uniqueConstraintHolders,
null,
null,
context,
null,
null
.toIdentifier(name)
);
return createJoin(
propertyHolder,
noDelayInPkColumnCreation,
secondaryTable,
joinColumns,
logicalName,
TableBinder.buildAndFillTable(
schema,
catalog,
logicalName.getTableName(),
false,
TableBinder.buildUniqueConstraintHolders( uniqueConstraints ),
null,
null,
context,
null,
null
)
);
}
private Join createJoin(
PropertyHolder propertyHolder,
boolean noDelayInPkColumnCreation,
boolean secondaryTable,
Object joinColumns,
QualifiedTableName logicalName,
Table table) {
final Join join = new Join();
join.setPersistentClass( persistentClass );
final InFlightMetadataCollector.EntityTableXref tableXref
= context.getMetadataCollector().getEntityTableXref( persistentClass.getEntityName() );
assert tableXref != null : "Could not locate EntityTableXref for entity [" + persistentClass.getEntityName() + "]";
tableXref.addSecondaryTable( logicalName, join );
if ( secondaryTable != null ) {
TableBinder.addIndexes( table, secondaryTable.indexes(), context );
}
//no check constraints available on joins
// No check constraints available on joins
join.setTable( table );
//somehow keep joins() for later.
//Has to do the work later because it needs persistentClass id!
// Somehow keep joins() for later.
// Has to do the work later because it needs PersistentClass id!
LOG.debugf( "Adding secondary table to entity %s -> %s",
persistentClass.getEntityName(), join.getTable().getName() );
final SecondaryRow matchingRow = findMatchingComplementarySecondaryRowAnnotation( join );
final org.hibernate.annotations.Table matchingTable = findMatchingComplementaryTableAnnotation( join );
if ( matchingRow != null ) {
join.setInverse( !matchingRow.owned() );
join.setOptional( matchingRow.optional() );
}
else if ( matchingTable != null ) {
join.setInverse( matchingTable.inverse() );
join.setOptional( matchingTable.optional() );
String insertSql = matchingTable.sqlInsert().sql();
if ( !isEmptyAnnotationValue(insertSql) ) {
join.setCustomSQLInsert(
insertSql.trim(),
matchingTable.sqlInsert().callable(),
fromExternalName( matchingTable.sqlInsert().check().toString().toLowerCase(Locale.ROOT) )
);
}
String updateSql = matchingTable.sqlUpdate().sql();
if ( !isEmptyAnnotationValue(updateSql) ) {
join.setCustomSQLUpdate(
updateSql.trim(),
matchingTable.sqlUpdate().callable(),
fromExternalName( matchingTable.sqlUpdate().check().toString().toLowerCase(Locale.ROOT) )
);
}
String deleteSql = matchingTable.sqlDelete().sql();
if ( !isEmptyAnnotationValue(deleteSql) ) {
join.setCustomSQLDelete(
deleteSql.trim(),
matchingTable.sqlDelete().callable(),
fromExternalName( matchingTable.sqlDelete().check().toString().toLowerCase(Locale.ROOT) )
);
}
}
else {
//default
join.setInverse( false );
join.setOptional( true ); //perhaps not quite per-spec, but a Good Thing anyway
}
handleSecondaryRowManagement( join );
processSecondaryTableCustomSql( join );
if ( noDelayInPkColumnCreation ) {
// A non-null propertyHolder means than we process the Pk creation without delay
createPrimaryColumnsToSecondaryTable( joinColumns, propertyHolder, join );
}
else {
final String quotedName = table.getQuotedName();
if ( secondaryTable != null ) {
if ( secondaryTable ) {
secondaryTablesFromAnnotation.put( quotedName, join );
secondaryTableFromAnnotationJoins.put( quotedName, joinColumns );
}
@ -1945,6 +1978,86 @@ public class EntityBinder {
return join;
}
private void handleSecondaryRowManagement(Join join) {
final String tableName = join.getTable().getQuotedName();
final org.hibernate.annotations.Table matchingTable = findMatchingComplementaryTableAnnotation( tableName );
final SecondaryRow matchingRow = findMatchingSecondaryRowAnnotation( tableName );
if ( matchingRow != null ) {
join.setInverse( !matchingRow.owned() );
join.setOptional( matchingRow.optional() );
}
else if ( matchingTable != null ) {
join.setInverse( matchingTable.inverse() );
join.setOptional( matchingTable.optional() );
}
else {
//default
join.setInverse( false );
join.setOptional( true ); //perhaps not quite per-spec, but a Good Thing anyway
}
}
private void processSecondaryTableCustomSql(Join join) {
final String tableName = join.getTable().getQuotedName();
final org.hibernate.annotations.Table matchingTable = findMatchingComplementaryTableAnnotation( tableName );
final SQLInsert sqlInsert = findMatchingSqlAnnotation( tableName, SQLInsert.class, SQLInserts.class );
if ( sqlInsert != null ) {
join.setCustomSQLInsert(
sqlInsert.sql().trim(),
sqlInsert.callable(),
fromResultCheckStyle( sqlInsert.check() )
);
}
else if ( matchingTable != null ) {
final String insertSql = matchingTable.sqlInsert().sql();
if ( !isEmptyAnnotationValue(insertSql) ) {
join.setCustomSQLInsert(
insertSql.trim(),
matchingTable.sqlInsert().callable(),
fromResultCheckStyle( matchingTable.sqlInsert().check() )
);
}
}
final SQLUpdate sqlUpdate = findMatchingSqlAnnotation( tableName, SQLUpdate.class, SQLUpdates.class );
if ( sqlUpdate != null ) {
join.setCustomSQLUpdate(
sqlUpdate.sql().trim(),
sqlUpdate.callable(),
fromResultCheckStyle( sqlUpdate.check() )
);
}
else if ( matchingTable != null ) {
final String updateSql = matchingTable.sqlUpdate().sql();
if ( !isEmptyAnnotationValue(updateSql) ) {
join.setCustomSQLUpdate(
updateSql.trim(),
matchingTable.sqlUpdate().callable(),
fromResultCheckStyle( matchingTable.sqlUpdate().check() )
);
}
}
final SQLDelete sqlDelete = findMatchingSqlAnnotation( tableName, SQLDelete.class, SQLDeletes.class );
if ( sqlDelete != null ) {
join.setCustomSQLDelete(
sqlDelete.sql().trim(),
sqlDelete.callable(),
fromResultCheckStyle( sqlDelete.check() )
);
}
else if ( matchingTable != null ) {
final String deleteSql = matchingTable.sqlDelete().sql();
if ( !isEmptyAnnotationValue(deleteSql) ) {
join.setCustomSQLDelete(
deleteSql.trim(),
matchingTable.sqlDelete().callable(),
fromResultCheckStyle( matchingTable.sqlDelete().check() )
);
}
}
}
public java.util.Map<String, Join> getSecondaryTables() {
return secondaryTables;
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.engine.spi;
import org.hibernate.annotations.ResultCheckStyle;
/**
* For persistence operations (INSERT, UPDATE, DELETE) what style of
* determining results (success/failure) is to be used.
@ -47,6 +49,19 @@ public enum ExecuteUpdateResultCheckStyle {
return name;
}
public static ExecuteUpdateResultCheckStyle fromResultCheckStyle(ResultCheckStyle style) {
switch (style) {
case NONE:
return NONE;
case COUNT:
return COUNT;
case PARAM:
return PARAM;
default:
return null;
}
}
public static ExecuteUpdateResultCheckStyle fromExternalName(String name) {
if ( name.equalsIgnoreCase( NONE.name ) ) {
return NONE;
@ -63,11 +78,6 @@ public enum ExecuteUpdateResultCheckStyle {
}
public static ExecuteUpdateResultCheckStyle determineDefault(String customSql, boolean callable) {
if ( customSql == null ) {
return COUNT;
}
else {
return callable ? PARAM : COUNT;
}
return customSql != null && callable ? PARAM : COUNT;
}
}

View File

@ -31,7 +31,6 @@ import org.hibernate.annotations.SQLUpdate;
@SQLInsert( sql="INSERT INTO CHAOS(name, nick_name, chaos_size, id) VALUES(upper(?),?,?,?)")
@SQLUpdate( sql="UPDATE CHAOS SET name = upper(?), nick_name = ?, chaos_size = ? WHERE id = ?")
@SQLDelete( sql="DELETE CHAOS WHERE id = ?")
@SQLDeleteAll( sql="DELETE CHAOS")
@Loader(namedQuery = "chaos")
@NamedNativeQuery(name="chaos", query="select id, chaos_size, name, lower( nick_name ) as nick_name from CHAOS where id= ?", resultClass = Chaos.class)
public class Chaos {
@ -47,6 +46,7 @@ public class Chaos {
@JoinColumn(name="chaos_fk")
@SQLInsert( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = ? where id = ?")
@SQLDelete( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = null where id = ?")
@SQLDeleteAll( sql="UPDATE CASIMIR_PARTICULE SET chaos_fk = null where chaos_fk = ?")
private Set<CasimirParticle> particles = new HashSet<CasimirParticle>();
public Long getId() {