HHH-17164 - Proper, first-class soft-delete support

HHH-17311 - Reversed soft delete support

https://hibernate.atlassian.net/browse/HHH-17164
https://hibernate.atlassian.net/browse/HHH-17311
This commit is contained in:
Steve Ebersole 2023-10-18 12:03:53 -05:00
parent 348217c899
commit 9d515dd182
11 changed files with 78 additions and 36 deletions

View File

@ -64,27 +64,17 @@ public @interface SoftDelete {
* <p/>
* Default depends on {@linkplain #trackActive()} - {@code deleted} if {@code false} and
* {@code active} if {@code true}.
*
* @see SoftDeleteType#getDefaultColumnName()
*/
String columnName() default "";
/**
* Whether the database value indicates active/inactive, as opposed to the
* default of tracking deleted/not-deleted
* The strategy to use for storing/reading values to/from the database.
* <p/>
* By default, the database values are interpreted as <ul>
* <li>{@code true} means the row is considered deleted</li>
* <li>{@code false} means the row is considered NOT deleted</li>
* </ul>
* <p/>
* Setting this {@code true} reverses the interpretation of the database value <ul>
* <li>{@code true} means the row is active (NOT deleted)</li>
* <li>{@code false} means the row is inactive (deleted)</li>
* </ul>
*
* @implNote Causes the {@linkplain #converter() conversion} to be wrapped in
* a negated conversion.
* The strategy also affects the default {@linkplain #columnName() column name}.
*/
boolean trackActive() default false;
SoftDeleteType strategy() default SoftDeleteType.DELETED;
/**
* (Optional) Conversion to apply to determine the appropriate value to

View File

@ -0,0 +1,48 @@
/*
* 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.util.Locale;
/**
* Enumeration of defines styles of soft-delete
*
* @author Steve Ebersole
*/
public enum SoftDeleteType {
/**
* Tracks rows which are active. The values stored in the database:<dl>
* <dt>{@code true}</dt>
* <dd>indicates that the row is active (non-deleted)</dd>
* <dt>{@code false}</dt>
* <dd>indicates that the row is inactive (deleted)</dd>
* </dl>
*
* @implNote Causes the {@linkplain SoftDelete#converter() conversion} to be wrapped in a negation.
*/
ACTIVE,
/**
* Tracks rows which are deleted. The values stored in the database:<dl>
* <dt>{@code true}</dt>
* <dd>indicates that the row is deleted</dd>
* <dt>{@code false}</dt>
* <dd>indicates that the row is non-deleted</dd>
* </dl>
*/
DELETED;
private final String defaultColumnName;
SoftDeleteType() {
this.defaultColumnName = name().toLowerCase( Locale.ROOT );
}
public String getDefaultColumnName() {
return defaultColumnName;
}
}

View File

@ -41,10 +41,6 @@ import static org.hibernate.query.sqm.ComparisonOperator.EQUAL;
* @author Steve Ebersole
*/
public class SoftDeleteHelper {
public static final String DEFAULT_COLUMN_NAME = "deleted";
public static final String DEFAULT_REVERSED_COLUMN_NAME = "active";
/**
* Creates and binds the column and value for modeling the soft-delete in the database
*
@ -80,7 +76,7 @@ public class SoftDeleteHelper {
);
final BasicValue softDeleteIndicatorValue = new BasicValue( context, table );
softDeleteIndicatorValue.makeSoftDelete( softDeleteConfig.trackActive() );
softDeleteIndicatorValue.makeSoftDelete( softDeleteConfig.strategy() );
softDeleteIndicatorValue.setJpaAttributeConverterDescriptor( converterDescriptor );
softDeleteIndicatorValue.setImplicitJavaTypeAccess( (typeConfiguration) -> converterDescriptor.getRelationalValueResolvedType().getErasedType() );
return softDeleteIndicatorValue;
@ -112,7 +108,7 @@ public class SoftDeleteHelper {
final Database database = context.getMetadataCollector().getDatabase();
final PhysicalNamingStrategy namingStrategy = context.getBuildingOptions().getPhysicalNamingStrategy();
final String logicalColumnName = coalesce(
softDeleteConfig.trackActive() ? DEFAULT_REVERSED_COLUMN_NAME : DEFAULT_COLUMN_NAME,
softDeleteConfig.strategy().getDefaultColumnName(),
softDeleteConfig.columnName()
);
final Identifier physicalColumnName = namingStrategy.toPhysicalColumnName(

View File

@ -17,6 +17,7 @@ import org.hibernate.Internal;
import org.hibernate.MappingException;
import org.hibernate.TimeZoneStorageStrategy;
import org.hibernate.annotations.SoftDelete;
import org.hibernate.annotations.SoftDeleteType;
import org.hibernate.annotations.TimeZoneStorageType;
import org.hibernate.boot.model.TypeDefinition;
import org.hibernate.boot.model.convert.internal.AutoApplicableConverterDescriptorBypassedImpl;
@ -100,7 +101,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
private TemporalType temporalPrecision;
private TimeZoneStorageType timeZoneStorageType;
private boolean isSoftDelete;
private boolean isSoftDeleteReversed;
private SoftDeleteType softDeleteStrategy;
private java.lang.reflect.Type resolvedJavaType;
@ -150,13 +151,13 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
return isSoftDelete;
}
public boolean isSoftDeleteReversed() {
return isSoftDeleteReversed;
public SoftDeleteType getSoftDeleteStrategy() {
return softDeleteStrategy;
}
public void makeSoftDelete(boolean reversed) {
public void makeSoftDelete(SoftDeleteType strategy) {
isSoftDelete = true;
isSoftDeleteReversed = reversed;
softDeleteStrategy = strategy;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -476,7 +477,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
}
}
if ( isSoftDeleteReversed() ) {
if ( getSoftDeleteStrategy() == SoftDeleteType.ACTIVE ) {
attributeConverterDescriptor = new ReversedConverterDescriptor<>( attributeConverterDescriptor );
}
}

View File

@ -7,6 +7,7 @@
package org.hibernate.orm.test.softdelete;
import org.hibernate.annotations.SoftDelete;
import org.hibernate.annotations.SoftDeleteType;
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
import org.hibernate.type.NumericBooleanConverter;
import org.hibernate.type.TrueFalseConverter;
@ -115,7 +116,7 @@ public class MappingTests {
@Entity(name="ReversedYesNoEntity")
@Table(name="reversed_yes_no_entity")
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
public static class ReversedYesNoEntity {
@Id
private Integer id;

View File

@ -13,6 +13,7 @@ import org.hibernate.Hibernate;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.SoftDelete;
import org.hibernate.annotations.SoftDeleteType;
import org.hibernate.type.YesNoConverter;
import org.hibernate.testing.jdbc.SQLStatementInspector;
@ -214,7 +215,7 @@ public class SimpleSoftDeleteTests {
@Entity(name="BatchLoadable")
@Table(name="batch_loadable")
@BatchSize(size = 5)
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
public static class BatchLoadable {
@Id
private Integer id;

View File

@ -11,6 +11,7 @@ import java.sql.Statement;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.SoftDelete;
import org.hibernate.annotations.SoftDeleteType;
import org.hibernate.type.YesNoConverter;
import org.hibernate.testing.jdbc.SQLStatementInspector;
@ -144,7 +145,7 @@ public class ToOneTests {
@Entity(name="User")
@Table(name="users")
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
public static class User {
@Id
private Integer id;

View File

@ -9,6 +9,7 @@ package org.hibernate.orm.test.softdelete;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SoftDelete;
import org.hibernate.annotations.SoftDeleteType;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.metamodel.UnsupportedMappingException;
@ -65,7 +66,7 @@ public class ValidationTests {
@Entity(name="Address")
@Table(name="addresses")
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
public static class Address {
@Id
private Integer id;
@ -74,7 +75,7 @@ public class ValidationTests {
@Entity(name="NoNo")
@Table(name="nonos")
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
@SQLDelete( sql = "delete from nonos" )
public static class NoNo {
@Id

View File

@ -12,6 +12,7 @@ import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.SoftDelete;
import org.hibernate.annotations.SoftDeleteType;
import org.hibernate.type.NumericBooleanConverter;
import org.hibernate.type.YesNoConverter;
@ -35,13 +36,13 @@ public class CollectionOwner2 {
@ElementCollection
@CollectionTable(name="batch_loadables", joinColumns = @JoinColumn(name="owner_fk"))
@BatchSize(size = 5)
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
private Set<String> batchLoadable;
@ElementCollection
@CollectionTable(name="subselect_loadables", joinColumns = @JoinColumn(name="owner_fk"))
@Fetch(FetchMode.SUBSELECT)
@SoftDelete(converter = NumericBooleanConverter.class, trackActive = true)
@SoftDelete(converter = NumericBooleanConverter.class, strategy = SoftDeleteType.ACTIVE)
private Set<String> subSelectLoadable;
public CollectionOwner2() {

View File

@ -7,6 +7,7 @@
package org.hibernate.orm.test.softdelete.converter.reversed;
import org.hibernate.annotations.SoftDelete;
import org.hibernate.annotations.SoftDeleteType;
import org.hibernate.type.YesNoConverter;
import jakarta.persistence.Basic;
@ -20,7 +21,7 @@ import jakarta.persistence.Table;
@Table(name = "the_entity")
//tag::example-soft-delete-reverse[]
@Entity
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
public class TheEntity {
// ...
//end::example-soft-delete-reverse[]

View File

@ -7,6 +7,7 @@
package org.hibernate.orm.test.softdelete.converter.reversed;
import org.hibernate.annotations.SoftDelete;
import org.hibernate.annotations.SoftDeleteType;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
@ -19,7 +20,7 @@ import jakarta.persistence.Table;
@Table(name = "the_entity2")
//tag::example-soft-delete-reverse[]
@Entity
@SoftDelete(trackActive = true)
@SoftDelete(strategy = SoftDeleteType.ACTIVE)
public class TheEntity2 {
// ...
//end::example-soft-delete-reverse[]