mirror of
https://github.com/hibernate/hibernate-orm
synced 2025-02-09 12:44:49 +00:00
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:
parent
348217c899
commit
9d515dd182
@ -64,27 +64,17 @@
|
|||||||
* <p/>
|
* <p/>
|
||||||
* Default depends on {@linkplain #trackActive()} - {@code deleted} if {@code false} and
|
* Default depends on {@linkplain #trackActive()} - {@code deleted} if {@code false} and
|
||||||
* {@code active} if {@code true}.
|
* {@code active} if {@code true}.
|
||||||
|
*
|
||||||
|
* @see SoftDeleteType#getDefaultColumnName()
|
||||||
*/
|
*/
|
||||||
String columnName() default "";
|
String columnName() default "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the database value indicates active/inactive, as opposed to the
|
* The strategy to use for storing/reading values to/from the database.
|
||||||
* default of tracking deleted/not-deleted
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* By default, the database values are interpreted as <ul>
|
* The strategy also affects the default {@linkplain #columnName() column name}.
|
||||||
* <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.
|
|
||||||
*/
|
*/
|
||||||
boolean trackActive() default false;
|
SoftDeleteType strategy() default SoftDeleteType.DELETED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* (Optional) Conversion to apply to determine the appropriate value to
|
* (Optional) Conversion to apply to determine the appropriate value to
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -41,10 +41,6 @@
|
|||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
public class SoftDeleteHelper {
|
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
|
* Creates and binds the column and value for modeling the soft-delete in the database
|
||||||
*
|
*
|
||||||
@ -80,7 +76,7 @@ private static BasicValue createSoftDeleteIndicatorValue(
|
|||||||
);
|
);
|
||||||
|
|
||||||
final BasicValue softDeleteIndicatorValue = new BasicValue( context, table );
|
final BasicValue softDeleteIndicatorValue = new BasicValue( context, table );
|
||||||
softDeleteIndicatorValue.makeSoftDelete( softDeleteConfig.trackActive() );
|
softDeleteIndicatorValue.makeSoftDelete( softDeleteConfig.strategy() );
|
||||||
softDeleteIndicatorValue.setJpaAttributeConverterDescriptor( converterDescriptor );
|
softDeleteIndicatorValue.setJpaAttributeConverterDescriptor( converterDescriptor );
|
||||||
softDeleteIndicatorValue.setImplicitJavaTypeAccess( (typeConfiguration) -> converterDescriptor.getRelationalValueResolvedType().getErasedType() );
|
softDeleteIndicatorValue.setImplicitJavaTypeAccess( (typeConfiguration) -> converterDescriptor.getRelationalValueResolvedType().getErasedType() );
|
||||||
return softDeleteIndicatorValue;
|
return softDeleteIndicatorValue;
|
||||||
@ -112,7 +108,7 @@ private static void applyColumnName(
|
|||||||
final Database database = context.getMetadataCollector().getDatabase();
|
final Database database = context.getMetadataCollector().getDatabase();
|
||||||
final PhysicalNamingStrategy namingStrategy = context.getBuildingOptions().getPhysicalNamingStrategy();
|
final PhysicalNamingStrategy namingStrategy = context.getBuildingOptions().getPhysicalNamingStrategy();
|
||||||
final String logicalColumnName = coalesce(
|
final String logicalColumnName = coalesce(
|
||||||
softDeleteConfig.trackActive() ? DEFAULT_REVERSED_COLUMN_NAME : DEFAULT_COLUMN_NAME,
|
softDeleteConfig.strategy().getDefaultColumnName(),
|
||||||
softDeleteConfig.columnName()
|
softDeleteConfig.columnName()
|
||||||
);
|
);
|
||||||
final Identifier physicalColumnName = namingStrategy.toPhysicalColumnName(
|
final Identifier physicalColumnName = namingStrategy.toPhysicalColumnName(
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import org.hibernate.MappingException;
|
import org.hibernate.MappingException;
|
||||||
import org.hibernate.TimeZoneStorageStrategy;
|
import org.hibernate.TimeZoneStorageStrategy;
|
||||||
import org.hibernate.annotations.SoftDelete;
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.annotations.SoftDeleteType;
|
||||||
import org.hibernate.annotations.TimeZoneStorageType;
|
import org.hibernate.annotations.TimeZoneStorageType;
|
||||||
import org.hibernate.boot.model.TypeDefinition;
|
import org.hibernate.boot.model.TypeDefinition;
|
||||||
import org.hibernate.boot.model.convert.internal.AutoApplicableConverterDescriptorBypassedImpl;
|
import org.hibernate.boot.model.convert.internal.AutoApplicableConverterDescriptorBypassedImpl;
|
||||||
@ -100,7 +101,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
|
|||||||
private TemporalType temporalPrecision;
|
private TemporalType temporalPrecision;
|
||||||
private TimeZoneStorageType timeZoneStorageType;
|
private TimeZoneStorageType timeZoneStorageType;
|
||||||
private boolean isSoftDelete;
|
private boolean isSoftDelete;
|
||||||
private boolean isSoftDeleteReversed;
|
private SoftDeleteType softDeleteStrategy;
|
||||||
|
|
||||||
private java.lang.reflect.Type resolvedJavaType;
|
private java.lang.reflect.Type resolvedJavaType;
|
||||||
|
|
||||||
@ -150,13 +151,13 @@ public boolean isSoftDelete() {
|
|||||||
return isSoftDelete;
|
return isSoftDelete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSoftDeleteReversed() {
|
public SoftDeleteType getSoftDeleteStrategy() {
|
||||||
return isSoftDeleteReversed;
|
return softDeleteStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void makeSoftDelete(boolean reversed) {
|
public void makeSoftDelete(SoftDeleteType strategy) {
|
||||||
isSoftDelete = true;
|
isSoftDelete = true;
|
||||||
isSoftDeleteReversed = reversed;
|
softDeleteStrategy = strategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -476,7 +477,7 @@ else if ( jdbcType.isString() ) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( isSoftDeleteReversed() ) {
|
if ( getSoftDeleteStrategy() == SoftDeleteType.ACTIVE ) {
|
||||||
attributeConverterDescriptor = new ReversedConverterDescriptor<>( attributeConverterDescriptor );
|
attributeConverterDescriptor = new ReversedConverterDescriptor<>( attributeConverterDescriptor );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
package org.hibernate.orm.test.softdelete;
|
package org.hibernate.orm.test.softdelete;
|
||||||
|
|
||||||
import org.hibernate.annotations.SoftDelete;
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.annotations.SoftDeleteType;
|
||||||
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
|
||||||
import org.hibernate.type.NumericBooleanConverter;
|
import org.hibernate.type.NumericBooleanConverter;
|
||||||
import org.hibernate.type.TrueFalseConverter;
|
import org.hibernate.type.TrueFalseConverter;
|
||||||
@ -115,7 +116,7 @@ public static class YesNoEntity {
|
|||||||
|
|
||||||
@Entity(name="ReversedYesNoEntity")
|
@Entity(name="ReversedYesNoEntity")
|
||||||
@Table(name="reversed_yes_no_entity")
|
@Table(name="reversed_yes_no_entity")
|
||||||
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
|
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
|
||||||
public static class ReversedYesNoEntity {
|
public static class ReversedYesNoEntity {
|
||||||
@Id
|
@Id
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
import org.hibernate.ObjectNotFoundException;
|
import org.hibernate.ObjectNotFoundException;
|
||||||
import org.hibernate.annotations.BatchSize;
|
import org.hibernate.annotations.BatchSize;
|
||||||
import org.hibernate.annotations.SoftDelete;
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.annotations.SoftDeleteType;
|
||||||
import org.hibernate.type.YesNoConverter;
|
import org.hibernate.type.YesNoConverter;
|
||||||
|
|
||||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
@ -214,7 +215,7 @@ void testRestrictedDeleteMutationQuery(SessionFactoryScope scope) {
|
|||||||
@Entity(name="BatchLoadable")
|
@Entity(name="BatchLoadable")
|
||||||
@Table(name="batch_loadable")
|
@Table(name="batch_loadable")
|
||||||
@BatchSize(size = 5)
|
@BatchSize(size = 5)
|
||||||
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
|
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
|
||||||
public static class BatchLoadable {
|
public static class BatchLoadable {
|
||||||
@Id
|
@Id
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
import org.hibernate.annotations.Fetch;
|
import org.hibernate.annotations.Fetch;
|
||||||
import org.hibernate.annotations.FetchMode;
|
import org.hibernate.annotations.FetchMode;
|
||||||
import org.hibernate.annotations.SoftDelete;
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.annotations.SoftDeleteType;
|
||||||
import org.hibernate.type.YesNoConverter;
|
import org.hibernate.type.YesNoConverter;
|
||||||
|
|
||||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||||
@ -144,7 +145,7 @@ public Issue(Integer id, String description, User reporter, User assignee) {
|
|||||||
|
|
||||||
@Entity(name="User")
|
@Entity(name="User")
|
||||||
@Table(name="users")
|
@Table(name="users")
|
||||||
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
|
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
|
||||||
public static class User {
|
public static class User {
|
||||||
@Id
|
@Id
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
import org.hibernate.SessionFactory;
|
import org.hibernate.SessionFactory;
|
||||||
import org.hibernate.annotations.SQLDelete;
|
import org.hibernate.annotations.SQLDelete;
|
||||||
import org.hibernate.annotations.SoftDelete;
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.annotations.SoftDeleteType;
|
||||||
import org.hibernate.boot.Metadata;
|
import org.hibernate.boot.Metadata;
|
||||||
import org.hibernate.boot.MetadataSources;
|
import org.hibernate.boot.MetadataSources;
|
||||||
import org.hibernate.metamodel.UnsupportedMappingException;
|
import org.hibernate.metamodel.UnsupportedMappingException;
|
||||||
@ -65,7 +66,7 @@ public static class Person {
|
|||||||
|
|
||||||
@Entity(name="Address")
|
@Entity(name="Address")
|
||||||
@Table(name="addresses")
|
@Table(name="addresses")
|
||||||
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
|
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
|
||||||
public static class Address {
|
public static class Address {
|
||||||
@Id
|
@Id
|
||||||
private Integer id;
|
private Integer id;
|
||||||
@ -74,7 +75,7 @@ public static class Address {
|
|||||||
|
|
||||||
@Entity(name="NoNo")
|
@Entity(name="NoNo")
|
||||||
@Table(name="nonos")
|
@Table(name="nonos")
|
||||||
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
|
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
|
||||||
@SQLDelete( sql = "delete from nonos" )
|
@SQLDelete( sql = "delete from nonos" )
|
||||||
public static class NoNo {
|
public static class NoNo {
|
||||||
@Id
|
@Id
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
import org.hibernate.annotations.Fetch;
|
import org.hibernate.annotations.Fetch;
|
||||||
import org.hibernate.annotations.FetchMode;
|
import org.hibernate.annotations.FetchMode;
|
||||||
import org.hibernate.annotations.SoftDelete;
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.annotations.SoftDeleteType;
|
||||||
import org.hibernate.type.NumericBooleanConverter;
|
import org.hibernate.type.NumericBooleanConverter;
|
||||||
import org.hibernate.type.YesNoConverter;
|
import org.hibernate.type.YesNoConverter;
|
||||||
|
|
||||||
@ -35,13 +36,13 @@ public class CollectionOwner2 {
|
|||||||
@ElementCollection
|
@ElementCollection
|
||||||
@CollectionTable(name="batch_loadables", joinColumns = @JoinColumn(name="owner_fk"))
|
@CollectionTable(name="batch_loadables", joinColumns = @JoinColumn(name="owner_fk"))
|
||||||
@BatchSize(size = 5)
|
@BatchSize(size = 5)
|
||||||
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
|
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
|
||||||
private Set<String> batchLoadable;
|
private Set<String> batchLoadable;
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
@CollectionTable(name="subselect_loadables", joinColumns = @JoinColumn(name="owner_fk"))
|
@CollectionTable(name="subselect_loadables", joinColumns = @JoinColumn(name="owner_fk"))
|
||||||
@Fetch(FetchMode.SUBSELECT)
|
@Fetch(FetchMode.SUBSELECT)
|
||||||
@SoftDelete(converter = NumericBooleanConverter.class, trackActive = true)
|
@SoftDelete(converter = NumericBooleanConverter.class, strategy = SoftDeleteType.ACTIVE)
|
||||||
private Set<String> subSelectLoadable;
|
private Set<String> subSelectLoadable;
|
||||||
|
|
||||||
public CollectionOwner2() {
|
public CollectionOwner2() {
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
package org.hibernate.orm.test.softdelete.converter.reversed;
|
package org.hibernate.orm.test.softdelete.converter.reversed;
|
||||||
|
|
||||||
import org.hibernate.annotations.SoftDelete;
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.annotations.SoftDeleteType;
|
||||||
import org.hibernate.type.YesNoConverter;
|
import org.hibernate.type.YesNoConverter;
|
||||||
|
|
||||||
import jakarta.persistence.Basic;
|
import jakarta.persistence.Basic;
|
||||||
@ -20,7 +21,7 @@
|
|||||||
@Table(name = "the_entity")
|
@Table(name = "the_entity")
|
||||||
//tag::example-soft-delete-reverse[]
|
//tag::example-soft-delete-reverse[]
|
||||||
@Entity
|
@Entity
|
||||||
@SoftDelete(converter = YesNoConverter.class, trackActive = true)
|
@SoftDelete(converter = YesNoConverter.class, strategy = SoftDeleteType.ACTIVE)
|
||||||
public class TheEntity {
|
public class TheEntity {
|
||||||
// ...
|
// ...
|
||||||
//end::example-soft-delete-reverse[]
|
//end::example-soft-delete-reverse[]
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
package org.hibernate.orm.test.softdelete.converter.reversed;
|
package org.hibernate.orm.test.softdelete.converter.reversed;
|
||||||
|
|
||||||
import org.hibernate.annotations.SoftDelete;
|
import org.hibernate.annotations.SoftDelete;
|
||||||
|
import org.hibernate.annotations.SoftDeleteType;
|
||||||
|
|
||||||
import jakarta.persistence.Basic;
|
import jakarta.persistence.Basic;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
@ -19,7 +20,7 @@
|
|||||||
@Table(name = "the_entity2")
|
@Table(name = "the_entity2")
|
||||||
//tag::example-soft-delete-reverse[]
|
//tag::example-soft-delete-reverse[]
|
||||||
@Entity
|
@Entity
|
||||||
@SoftDelete(trackActive = true)
|
@SoftDelete(strategy = SoftDeleteType.ACTIVE)
|
||||||
public class TheEntity2 {
|
public class TheEntity2 {
|
||||||
// ...
|
// ...
|
||||||
//end::example-soft-delete-reverse[]
|
//end::example-soft-delete-reverse[]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user