HHH-17587 Setting to null a property from a @SecondaryTable and @DynamicUpdate deletes the whole entry from database
This commit is contained in:
parent
663e5c0206
commit
225740bce5
|
@ -3508,6 +3508,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<>();
|
||||
|
||||
|
@ -3527,7 +3529,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;
|
||||
|
@ -3544,6 +3548,8 @@ public abstract class AbstractEntityPersister
|
|||
this.deleteExpectation = deleteExpectation;
|
||||
this.customDeleteSql = customDeleteSql;
|
||||
this.deleteCallable = deleteCallable;
|
||||
this.dynamicUpdate = dynamicUpdate;
|
||||
this.dynamicInsert = dynamicInsert;
|
||||
}
|
||||
|
||||
private EntityTableMapping build() {
|
||||
|
@ -3564,7 +3570,9 @@ public abstract class AbstractEntityPersister
|
|||
cascadeDeleteEnabled,
|
||||
deleteExpectation,
|
||||
customDeleteSql,
|
||||
deleteCallable
|
||||
deleteCallable,
|
||||
dynamicUpdate,
|
||||
dynamicInsert
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3621,7 +3629,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() );
|
||||
|
|
|
@ -153,7 +153,7 @@ public class InsertCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
return foundStateDependentGenerator;
|
||||
}
|
||||
|
||||
protected static class InsertValuesAnalysis implements ValuesAnalysis {
|
||||
public static class InsertValuesAnalysis implements ValuesAnalysis {
|
||||
private final List<TableMapping> tablesWithNonNullValues = new ArrayList<>();
|
||||
|
||||
public InsertValuesAnalysis(EntityMutationTarget mutationTarget, Object[] values) {
|
||||
|
|
|
@ -308,7 +308,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
|||
);
|
||||
|
||||
//noinspection StatementWithEmptyBody
|
||||
if ( valuesAnalysis.tablesNeedingUpdate.isEmpty() ) {
|
||||
if ( valuesAnalysis.tablesNeedingUpdate.isEmpty() && valuesAnalysis.tablesNeedingDynamicUpdate.isEmpty() ) {
|
||||
// nothing to do
|
||||
return null;
|
||||
}
|
||||
|
@ -972,7 +972,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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.function.Consumer;
|
|||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.jdbc.Expectation;
|
||||
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
|
||||
import org.hibernate.persister.entity.mutation.InsertCoordinatorStandard;
|
||||
import org.hibernate.sql.ast.SqlAstWalker;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
|
||||
import org.hibernate.sql.model.MutationOperation;
|
||||
|
@ -128,9 +129,16 @@ 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();
|
||||
assert ! (valuesAnalysis instanceof InsertCoordinatorStandard.InsertValuesAnalysis );
|
||||
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