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 Expectation deleteExpectation;
|
||||||
private final String customDeleteSql;
|
private final String customDeleteSql;
|
||||||
private final boolean deleteCallable;
|
private final boolean deleteCallable;
|
||||||
|
private final boolean dynamicUpdate;
|
||||||
|
private final boolean dynamicInsert;
|
||||||
|
|
||||||
private final List<Integer> attributeIndexes = new ArrayList<>();
|
private final List<Integer> attributeIndexes = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -3527,7 +3529,9 @@ public abstract class AbstractEntityPersister
|
||||||
boolean cascadeDeleteEnabled,
|
boolean cascadeDeleteEnabled,
|
||||||
Expectation deleteExpectation,
|
Expectation deleteExpectation,
|
||||||
String customDeleteSql,
|
String customDeleteSql,
|
||||||
boolean deleteCallable) {
|
boolean deleteCallable,
|
||||||
|
boolean dynamicUpdate,
|
||||||
|
boolean dynamicInsert) {
|
||||||
this.tableName = tableName;
|
this.tableName = tableName;
|
||||||
this.relativePosition = relativePosition;
|
this.relativePosition = relativePosition;
|
||||||
this.keyMapping = keyMapping;
|
this.keyMapping = keyMapping;
|
||||||
|
@ -3544,6 +3548,8 @@ public abstract class AbstractEntityPersister
|
||||||
this.deleteExpectation = deleteExpectation;
|
this.deleteExpectation = deleteExpectation;
|
||||||
this.customDeleteSql = customDeleteSql;
|
this.customDeleteSql = customDeleteSql;
|
||||||
this.deleteCallable = deleteCallable;
|
this.deleteCallable = deleteCallable;
|
||||||
|
this.dynamicUpdate = dynamicUpdate;
|
||||||
|
this.dynamicInsert = dynamicInsert;
|
||||||
}
|
}
|
||||||
|
|
||||||
private EntityTableMapping build() {
|
private EntityTableMapping build() {
|
||||||
|
@ -3564,7 +3570,9 @@ public abstract class AbstractEntityPersister
|
||||||
cascadeDeleteEnabled,
|
cascadeDeleteEnabled,
|
||||||
deleteExpectation,
|
deleteExpectation,
|
||||||
customDeleteSql,
|
customDeleteSql,
|
||||||
deleteCallable
|
deleteCallable,
|
||||||
|
dynamicUpdate,
|
||||||
|
dynamicInsert
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3621,7 +3629,9 @@ public abstract class AbstractEntityPersister
|
||||||
isTableCascadeDeleteEnabled( relativePosition ),
|
isTableCascadeDeleteEnabled( relativePosition ),
|
||||||
deleteExpectations[ relativePosition ],
|
deleteExpectations[ relativePosition ],
|
||||||
customDeleteSql,
|
customDeleteSql,
|
||||||
deleteCallable[ relativePosition ]
|
deleteCallable[ relativePosition ],
|
||||||
|
entityMetamodel.isDynamicUpdate(),
|
||||||
|
entityMetamodel.isDynamicInsert()
|
||||||
);
|
);
|
||||||
|
|
||||||
tableBuilderMap.put( tableExpression, tableMappingBuilder );
|
tableBuilderMap.put( tableExpression, tableMappingBuilder );
|
||||||
|
|
|
@ -65,14 +65,33 @@ public class EntityTableMapping implements TableMapping {
|
||||||
boolean cascadeDeleteEnabled,
|
boolean cascadeDeleteEnabled,
|
||||||
Expectation deleteExpectation,
|
Expectation deleteExpectation,
|
||||||
String deleteCustomSql,
|
String deleteCustomSql,
|
||||||
boolean deleteCallable) {
|
boolean deleteCallable,
|
||||||
|
boolean dynamicUpdate,
|
||||||
|
boolean dynamicInsert) {
|
||||||
this.tableName = tableName;
|
this.tableName = tableName;
|
||||||
this.relativePosition = relativePosition;
|
this.relativePosition = relativePosition;
|
||||||
this.keyMapping = keyMapping;
|
this.keyMapping = keyMapping;
|
||||||
this.attributeIndexes = attributeIndexes;
|
this.attributeIndexes = attributeIndexes;
|
||||||
this.insertDetails = new MutationDetails( MutationType.INSERT, insertExpectation, insertCustomSql, insertCallable );
|
this.insertDetails = new MutationDetails(
|
||||||
this.updateDetails = new MutationDetails( MutationType.UPDATE, updateExpectation, updateCustomSql, updateCallable );
|
MutationType.INSERT,
|
||||||
this.deleteDetails = new MutationDetails( MutationType.DELETE, deleteExpectation, deleteCustomSql, deleteCallable );
|
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 ) {
|
if ( isOptional ) {
|
||||||
flags.set( Flag.OPTIONAL.ordinal() );
|
flags.set( Flag.OPTIONAL.ordinal() );
|
||||||
|
|
|
@ -153,7 +153,7 @@ public class InsertCoordinatorStandard extends AbstractMutationCoordinator imple
|
||||||
return foundStateDependentGenerator;
|
return foundStateDependentGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class InsertValuesAnalysis implements ValuesAnalysis {
|
public static class InsertValuesAnalysis implements ValuesAnalysis {
|
||||||
private final List<TableMapping> tablesWithNonNullValues = new ArrayList<>();
|
private final List<TableMapping> tablesWithNonNullValues = new ArrayList<>();
|
||||||
|
|
||||||
public InsertValuesAnalysis(EntityMutationTarget mutationTarget, Object[] values) {
|
public InsertValuesAnalysis(EntityMutationTarget mutationTarget, Object[] values) {
|
||||||
|
|
|
@ -308,7 +308,7 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
||||||
);
|
);
|
||||||
|
|
||||||
//noinspection StatementWithEmptyBody
|
//noinspection StatementWithEmptyBody
|
||||||
if ( valuesAnalysis.tablesNeedingUpdate.isEmpty() ) {
|
if ( valuesAnalysis.tablesNeedingUpdate.isEmpty() && valuesAnalysis.tablesNeedingDynamicUpdate.isEmpty() ) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -972,7 +972,9 @@ public class UpdateCoordinatorStandard extends AbstractMutationCoordinator imple
|
||||||
if ( tableMapping.isOptional()
|
if ( tableMapping.isOptional()
|
||||||
&& !valuesAnalysis.tablesWithNonNullValues.contains( tableMapping ) ) {
|
&& !valuesAnalysis.tablesWithNonNullValues.contains( tableMapping ) ) {
|
||||||
// the table is optional, and we have null values for all of its columns
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -40,6 +40,8 @@ public interface UpdateValuesAnalysis extends ValuesAnalysis {
|
||||||
*/
|
*/
|
||||||
TableSet getTablesWithPreviousNonNullValues();
|
TableSet getTablesWithPreviousNonNullValues();
|
||||||
|
|
||||||
|
TableSet getTablesNeedingDynamicUpdate();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Descriptors for the analysis of each attribute
|
* Descriptors for the analysis of each attribute
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -81,16 +81,33 @@ public interface TableMapping extends TableDetails {
|
||||||
private final Expectation expectation;
|
private final Expectation expectation;
|
||||||
private final String customSql;
|
private final String customSql;
|
||||||
private final boolean callable;
|
private final boolean callable;
|
||||||
|
private final boolean dynamicMutation;
|
||||||
|
|
||||||
public MutationDetails(
|
public MutationDetails(
|
||||||
MutationType mutationType,
|
MutationType mutationType,
|
||||||
Expectation expectation,
|
Expectation expectation,
|
||||||
String customSql,
|
String customSql,
|
||||||
boolean callable) {
|
boolean callable) {
|
||||||
|
this(
|
||||||
|
mutationType,
|
||||||
|
expectation,
|
||||||
|
customSql,
|
||||||
|
callable,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutationDetails(
|
||||||
|
MutationType mutationType,
|
||||||
|
Expectation expectation,
|
||||||
|
String customSql,
|
||||||
|
boolean callable,
|
||||||
|
boolean dynamicMutation) {
|
||||||
this.mutationType = mutationType;
|
this.mutationType = mutationType;
|
||||||
this.expectation = expectation;
|
this.expectation = expectation;
|
||||||
this.customSql = customSql;
|
this.customSql = customSql;
|
||||||
this.callable = callable;
|
this.callable = callable;
|
||||||
|
this.dynamicMutation = dynamicMutation;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -122,5 +139,10 @@ public interface TableMapping extends TableDetails {
|
||||||
public boolean isCallable() {
|
public boolean isCallable() {
|
||||||
return callable;
|
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.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.jdbc.Expectation;
|
import org.hibernate.jdbc.Expectation;
|
||||||
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
|
import org.hibernate.persister.entity.mutation.EntityMutationTarget;
|
||||||
|
import org.hibernate.persister.entity.mutation.InsertCoordinatorStandard;
|
||||||
import org.hibernate.sql.ast.SqlAstWalker;
|
import org.hibernate.sql.ast.SqlAstWalker;
|
||||||
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
|
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
|
||||||
import org.hibernate.sql.model.MutationOperation;
|
import org.hibernate.sql.model.MutationOperation;
|
||||||
|
@ -128,9 +129,16 @@ public class OptionalTableUpdate
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MutationOperation createMutationOperation(ValuesAnalysis valuesAnalysis, SessionFactoryImplementor factory) {
|
public MutationOperation createMutationOperation(ValuesAnalysis valuesAnalysis, SessionFactoryImplementor factory) {
|
||||||
if ( getMutatingTable().getTableMapping().getInsertDetails().getCustomSql() != null
|
final TableMapping tableMapping = getMutatingTable().getTableMapping();
|
||||||
|| getMutatingTable().getTableMapping().getDeleteDetails().getCustomSql() != null ) {
|
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
|
// 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 new OptionalTableUpdateOperation( getMutationTarget(), this, factory );
|
||||||
}
|
}
|
||||||
return factory.getJdbcServices().getDialect().createOptionalTableUpdateOperation(
|
return factory.getJdbcServices().getDialect().createOptionalTableUpdateOperation(
|
||||||
|
|
|
@ -127,7 +127,8 @@ public class OptionalTableUpdateOperation implements SelfExecutingUpdateOperatio
|
||||||
ValuesAnalysis incomingValuesAnalysis,
|
ValuesAnalysis incomingValuesAnalysis,
|
||||||
SharedSessionContractImplementor session) {
|
SharedSessionContractImplementor session) {
|
||||||
final UpdateValuesAnalysis valuesAnalysis = (UpdateValuesAnalysis) incomingValuesAnalysis;
|
final UpdateValuesAnalysis valuesAnalysis = (UpdateValuesAnalysis) incomingValuesAnalysis;
|
||||||
if ( !valuesAnalysis.getTablesNeedingUpdate().contains( tableMapping ) ) {
|
if ( !valuesAnalysis.getTablesNeedingUpdate().contains( tableMapping )
|
||||||
|
&& !valuesAnalysis.getTablesNeedingDynamicUpdate().contains( tableMapping ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue