HHH-17587 Setting to null a property from a @SecondaryTable and @DynamicUpdate deletes the whole entry from database
This commit is contained in:
parent
74f018182e
commit
3844a34760
|
@ -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<Integer> 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 );
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -40,6 +40,8 @@ public interface UpdateValuesAnalysis extends ValuesAnalysis {
|
|||
*/
|
||||
TableSet getTablesWithPreviousNonNullValues();
|
||||
|
||||
TableSet getTablesNeedingDynamicUpdate();
|
||||
|
||||
/**
|
||||
* Descriptors for the analysis of each attribute
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue