diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 945d395cbd..9aa98b3139 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -3479,6 +3479,8 @@ public abstract class AbstractEntityPersister private final Expectation deleteExpectation; private final String customDeleteSql; private final boolean deleteCallable; + private final boolean dynamicUpdate; + private final boolean dynamicInsert; private final List attributeIndexes = new ArrayList<>(); @@ -3498,7 +3500,9 @@ public abstract class AbstractEntityPersister boolean cascadeDeleteEnabled, Expectation deleteExpectation, String customDeleteSql, - boolean deleteCallable) { + boolean deleteCallable, + boolean dynamicUpdate, + boolean dynamicInsert) { this.tableName = tableName; this.relativePosition = relativePosition; this.keyMapping = keyMapping; @@ -3515,6 +3519,8 @@ public abstract class AbstractEntityPersister this.deleteExpectation = deleteExpectation; this.customDeleteSql = customDeleteSql; this.deleteCallable = deleteCallable; + this.dynamicUpdate = dynamicUpdate; + this.dynamicInsert = dynamicInsert; } private EntityTableMapping build() { @@ -3535,7 +3541,9 @@ public abstract class AbstractEntityPersister cascadeDeleteEnabled, deleteExpectation, customDeleteSql, - deleteCallable + deleteCallable, + dynamicUpdate, + dynamicInsert ); } } @@ -3592,7 +3600,9 @@ public abstract class AbstractEntityPersister isTableCascadeDeleteEnabled( relativePosition ), deleteExpectations[ relativePosition ], customDeleteSql, - deleteCallable[ relativePosition ] + deleteCallable[ relativePosition ], + entityMetamodel.isDynamicUpdate(), + entityMetamodel.isDynamicInsert() ); tableBuilderMap.put( tableExpression, tableMappingBuilder ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/EntityTableMapping.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/EntityTableMapping.java index 24d8599f03..74a2215147 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/EntityTableMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/EntityTableMapping.java @@ -65,14 +65,33 @@ public class EntityTableMapping implements TableMapping { boolean cascadeDeleteEnabled, Expectation deleteExpectation, String deleteCustomSql, - boolean deleteCallable) { + boolean deleteCallable, + boolean dynamicUpdate, + boolean dynamicInsert) { this.tableName = tableName; this.relativePosition = relativePosition; this.keyMapping = keyMapping; this.attributeIndexes = attributeIndexes; - this.insertDetails = new MutationDetails( MutationType.INSERT, insertExpectation, insertCustomSql, insertCallable ); - this.updateDetails = new MutationDetails( MutationType.UPDATE, updateExpectation, updateCustomSql, updateCallable ); - this.deleteDetails = new MutationDetails( MutationType.DELETE, deleteExpectation, deleteCustomSql, deleteCallable ); + this.insertDetails = new MutationDetails( + MutationType.INSERT, + insertExpectation, + insertCustomSql, + insertCallable, + dynamicInsert + ); + this.updateDetails = new MutationDetails( + MutationType.UPDATE, + updateExpectation, + updateCustomSql, + updateCallable, + dynamicUpdate + ); + this.deleteDetails = new MutationDetails( + MutationType.DELETE, + deleteExpectation, + deleteCustomSql, + deleteCallable + ); if ( isOptional ) { flags.set( Flag.OPTIONAL.ordinal() ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java index 29ee5ac2d5..716eaa4835 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java @@ -305,7 +305,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple ); //noinspection StatementWithEmptyBody - if ( valuesAnalysis.tablesNeedingUpdate.isEmpty() ) { + if ( valuesAnalysis.tablesNeedingUpdate.isEmpty() && valuesAnalysis.tablesNeedingDynamicUpdate.isEmpty() ) { // nothing to do } else if ( valuesAnalysis.needsDynamicUpdate() ) { @@ -969,7 +969,9 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple if ( tableMapping.isOptional() && !valuesAnalysis.tablesWithNonNullValues.contains( tableMapping ) ) { // the table is optional, and we have null values for all of its columns - // todo (6.0) : technically we might need to delete row here + if ( valuesAnalysis.dirtyAttributeIndexes.length > 0 ) { + return true; + } return false; } else { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateValuesAnalysis.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateValuesAnalysis.java index 9b5eff3533..02e75326ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateValuesAnalysis.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateValuesAnalysis.java @@ -40,6 +40,8 @@ public interface UpdateValuesAnalysis extends ValuesAnalysis { */ TableSet getTablesWithPreviousNonNullValues(); + TableSet getTablesNeedingDynamicUpdate(); + /** * Descriptors for the analysis of each attribute */ diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/TableMapping.java b/hibernate-core/src/main/java/org/hibernate/sql/model/TableMapping.java index 60de5d041c..50eebe1b27 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/TableMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/TableMapping.java @@ -81,16 +81,33 @@ public interface TableMapping extends TableDetails { private final Expectation expectation; private final String customSql; private final boolean callable; + private final boolean dynamicMutation; public MutationDetails( MutationType mutationType, Expectation expectation, String customSql, boolean callable) { + this( + mutationType, + expectation, + customSql, + callable, + false + ); + } + + public MutationDetails( + MutationType mutationType, + Expectation expectation, + String customSql, + boolean callable, + boolean dynamicMutation) { this.mutationType = mutationType; this.expectation = expectation; this.customSql = customSql; this.callable = callable; + this.dynamicMutation = dynamicMutation; } /** @@ -122,5 +139,10 @@ public interface TableMapping extends TableDetails { public boolean isCallable() { return callable; } + + public boolean isDynamicMutation() { + return dynamicMutation; + } } + } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/internal/OptionalTableUpdate.java b/hibernate-core/src/main/java/org/hibernate/sql/model/internal/OptionalTableUpdate.java index fd429a8102..f2e7770812 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/internal/OptionalTableUpdate.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/internal/OptionalTableUpdate.java @@ -128,9 +128,15 @@ public class OptionalTableUpdate @Override public MutationOperation createMutationOperation(ValuesAnalysis valuesAnalysis, SessionFactoryImplementor factory) { - if ( getMutatingTable().getTableMapping().getInsertDetails().getCustomSql() != null - || getMutatingTable().getTableMapping().getDeleteDetails().getCustomSql() != null ) { + final TableMapping tableMapping = getMutatingTable().getTableMapping(); + if ( tableMapping.getInsertDetails().getCustomSql() != null + || tableMapping.getInsertDetails().isDynamicMutation() + || tableMapping.getDeleteDetails().getCustomSql() != null + || tableMapping.getUpdateDetails().getCustomSql() != null + || tableMapping.getUpdateDetails().isDynamicMutation() ) { // Fallback to the optional table mutation operation because we have to execute user specified SQL + // and with dynamic update insert we have to avoid using merge for optional table because + // it can cause the deletion of the row when an attribute is set to null return new OptionalTableUpdateOperation( getMutationTarget(), this, factory ); } return factory.getJdbcServices().getDialect().createOptionalTableUpdateOperation( diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java index bc0d018f62..25fa3ef0cf 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/jdbc/OptionalTableUpdateOperation.java @@ -127,7 +127,8 @@ public class OptionalTableUpdateOperation implements SelfExecutingUpdateOperatio ValuesAnalysis incomingValuesAnalysis, SharedSessionContractImplementor session) { final UpdateValuesAnalysis valuesAnalysis = (UpdateValuesAnalysis) incomingValuesAnalysis; - if ( !valuesAnalysis.getTablesNeedingUpdate().contains( tableMapping ) ) { + if ( !valuesAnalysis.getTablesNeedingUpdate().contains( tableMapping ) + && !valuesAnalysis.getTablesNeedingDynamicUpdate().contains( tableMapping ) ) { return; }