mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#118 - ENH: Add support for for Soft Deletes (Logical Deletion) ...
This commit is contained in:
@@ -9,19 +9,17 @@ import java.lang.annotation.Target;
|
||||
* Used to indicate a property on an entity bean used to control 'soft delete'
|
||||
* (also known as 'logical delete').
|
||||
* <p>
|
||||
* The property should be of type boolean, int or short.
|
||||
* The property should be of type boolean.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* @SoftDelete
|
||||
* boolean deleted;
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface SoftDelete {
|
||||
|
||||
/**
|
||||
* Specify the bind value that matches 'deleted' state.
|
||||
* <p>
|
||||
* If not specified then for boolean this is <code>true</code> and
|
||||
* for int and short this value is <code>1</code>.
|
||||
* </p>
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.avaje.ebeaninternal.server.text.json.ReadJson;
|
||||
import com.avaje.ebeaninternal.server.text.json.WriteJson;
|
||||
import com.avaje.ebeaninternal.server.type.DataBind;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarTypeBoolean;
|
||||
import com.avaje.ebeaninternal.util.ValueUtil;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import org.slf4j.Logger;
|
||||
@@ -231,7 +232,9 @@ public class BeanProperty implements ElPropertyValue {
|
||||
|
||||
final boolean softDelete;
|
||||
|
||||
final Object softDeleteValue;
|
||||
final String softDeleteDbSet;
|
||||
|
||||
final String softDeleteDbPredicate;
|
||||
|
||||
final boolean indexed;
|
||||
|
||||
@@ -262,9 +265,6 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.draftDirty = deploy.isDraftDirty();
|
||||
this.draftOnly = deploy.isDraftOnly();
|
||||
this.draftReset = deploy.isDraftReset();
|
||||
this.softDelete = deploy.isSoftDelete();
|
||||
this.softDeleteValue = deploy.getSoftDeleteValue();
|
||||
|
||||
this.secondaryTable = deploy.isSecondaryTable();
|
||||
if (secondaryTable) {
|
||||
this.secondaryTableJoin = new TableJoin(deploy.getSecondaryTableJoin());
|
||||
@@ -308,6 +308,16 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), false, null);
|
||||
this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), dbEncrypted, dbColumn);
|
||||
|
||||
this.softDelete = deploy.isSoftDelete();
|
||||
if (softDelete) {
|
||||
ScalarTypeBoolean.BooleanBase boolType = (ScalarTypeBoolean.BooleanBase)scalarType;
|
||||
this.softDeleteDbSet = dbColumn+"="+boolType.getDbTrueLiteral();
|
||||
this.softDeleteDbPredicate = dbColumn+"="+boolType.getDbFalseLiteral();
|
||||
} else {
|
||||
this.softDeleteDbSet = null;
|
||||
this.softDeleteDbPredicate = null;
|
||||
}
|
||||
|
||||
this.jsonSerialize = deploy.isJsonSerialize();
|
||||
this.jsonDeserialize = deploy.isJsonDeserialize();
|
||||
}
|
||||
@@ -352,7 +362,8 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.draftOnly = source.draftOnly;
|
||||
this.draftReset = source.draftReset;
|
||||
this.softDelete = source.softDelete;
|
||||
this.softDeleteValue = source.softDeleteValue;
|
||||
this.softDeleteDbSet = source.softDeleteDbSet;
|
||||
this.softDeleteDbPredicate = source.softDeleteDbPredicate;
|
||||
this.fetchEager = source.fetchEager;
|
||||
this.unidirectionalShadow = source.unidirectionalShadow;
|
||||
this.discriminator = source.discriminator;
|
||||
@@ -624,22 +635,26 @@ public class BeanProperty implements ElPropertyValue {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB literal expression to set the deleted state to true.
|
||||
*/
|
||||
public String getSoftDeleteDbSet() {
|
||||
return dbColumn +"=true";
|
||||
return softDeleteDbSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB literal predicate used to filter out soft deleted rows from a query.
|
||||
*/
|
||||
public String getSoftDeleteDbPredicate(String tableAlias) {
|
||||
return tableAlias+"."+dbColumn+"=false";
|
||||
return tableAlias+"."+softDeleteDbPredicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the soft delete property value on the bean without invoking lazy loading.
|
||||
*/
|
||||
public void setSoftDeleteValue(EntityBean bean) {
|
||||
setValue(bean, softDeleteValue);
|
||||
// assumes boolean deleted true being set which is ok limitation for now
|
||||
setValue(bean, true);
|
||||
bean._ebean_getIntercept().setChangedProperty(propertyIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.deploy.meta;
|
||||
|
||||
import com.avaje.ebean.annotation.CreatedTimestamp;
|
||||
import com.avaje.ebean.annotation.SoftDelete;
|
||||
import com.avaje.ebean.annotation.UpdatedTimestamp;
|
||||
import com.avaje.ebean.annotation.WhenCreated;
|
||||
import com.avaje.ebean.annotation.WhenModified;
|
||||
@@ -190,7 +191,6 @@ public class DeployBeanProperty {
|
||||
private boolean draftReset;
|
||||
|
||||
private boolean softDelete;
|
||||
private String softDeleteValue = "";
|
||||
|
||||
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
|
||||
this.desc = desc;
|
||||
@@ -224,6 +224,8 @@ public class DeployBeanProperty {
|
||||
return AUDITCOLUMN_ORDER;
|
||||
} else if (field.getAnnotation(Version.class) != null) {
|
||||
return VERSIONCOLUMN_ORDER;
|
||||
} else if (field.getAnnotation(SoftDelete.class) != null) {
|
||||
return VERSIONCOLUMN_ORDER;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -876,42 +878,12 @@ public class DeployBeanProperty {
|
||||
return draftReset;
|
||||
}
|
||||
|
||||
public void setSoftDelete(String softDeleteValue) {
|
||||
public void setSoftDelete() {
|
||||
this.softDelete = true;
|
||||
this.softDeleteValue = softDeleteValue;
|
||||
}
|
||||
|
||||
public boolean isSoftDelete() {
|
||||
return softDelete;
|
||||
}
|
||||
|
||||
public Object getSoftDeleteValue() {
|
||||
return !softDelete ? null : "".equals(softDeleteValue) ? defaultSoftDeleteValue() : parseSoftDeleteValue();
|
||||
}
|
||||
|
||||
private Object parseSoftDeleteValue() {
|
||||
if (Boolean.class.equals(propertyType) || boolean.class.equals(propertyType)) {
|
||||
return Boolean.parseBoolean(softDeleteValue);
|
||||
}
|
||||
if (Integer.class.equals(propertyType) || int.class.equals(propertyType)) {
|
||||
return Integer.parseInt(softDeleteValue);
|
||||
}
|
||||
if (Short.class.equals(propertyType) || short.class.equals(propertyType)) {
|
||||
return Short.parseShort(softDeleteValue);
|
||||
}
|
||||
throw new IllegalStateException("@SoftDelete on ["+getFullBeanName()+"] mapped to unsupported type propertyType["+propertyType+"]");
|
||||
}
|
||||
|
||||
private Object defaultSoftDeleteValue() {
|
||||
if (Boolean.class.equals(propertyType) || boolean.class.equals(propertyType)) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
if (Integer.class.equals(propertyType) || int.class.equals(propertyType)) {
|
||||
return Integer.valueOf(1);
|
||||
}
|
||||
if (Short.class.equals(propertyType) || short.class.equals(propertyType)) {
|
||||
return Short.valueOf("1");
|
||||
}
|
||||
throw new IllegalStateException("@SoftDelete on ["+getFullBeanName()+"] mapped to unsupported type propertyType["+propertyType+"]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ public class AnnotationFields extends AnnotationParser {
|
||||
}
|
||||
SoftDelete softDelete = get(prop, SoftDelete.class);
|
||||
if (softDelete != null) {
|
||||
prop.setSoftDelete(softDelete.value());
|
||||
prop.setSoftDelete();
|
||||
}
|
||||
|
||||
DbJson dbJson = get(prop, DbJson.class);
|
||||
|
||||
@@ -45,27 +45,12 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
|
||||
protected void configureQuery(SpiQuery<?> query, String lazyLoadProperty) {
|
||||
|
||||
// propagate the readOnly state
|
||||
if (parent.isReadOnly() != null) {
|
||||
query.setReadOnly(parent.isReadOnly());
|
||||
}
|
||||
// propagate the asOf and lazy loading mode
|
||||
query.setDisableLazyLoading(parent.isDisableLazyLoading());
|
||||
query.asOf(parent.getAsOf());
|
||||
parent.propagateQueryState(query);
|
||||
query.setParentNode(objectGraphNode);
|
||||
query.setLazyLoadProperty(lazyLoadProperty);
|
||||
if (parent.isAsDraft()) {
|
||||
query.asDraft();
|
||||
}
|
||||
if (parent.isDisableReadAudit()) {
|
||||
query.setDisableReadAuditing();
|
||||
}
|
||||
if (queryProps != null) {
|
||||
queryProps.configureBeanQuery(query);
|
||||
}
|
||||
if (parent.isUseAutoTune()) {
|
||||
query.setAutoTune(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected void register(EntityBeanIntercept ebi) {
|
||||
|
||||
@@ -43,6 +43,7 @@ public class DLoadContext implements LoadContext {
|
||||
private final int defaultBatchSize;
|
||||
private final boolean disableLazyLoading;
|
||||
private final boolean disableReadAudit;
|
||||
private final boolean includeSoftDeletes;
|
||||
|
||||
/**
|
||||
* The path relative to the root of the object graph.
|
||||
@@ -67,6 +68,7 @@ public class DLoadContext implements LoadContext {
|
||||
SpiQuery<?> query = request.getQuery();
|
||||
this.asOf = query.getAsOf();
|
||||
this.asDraft = query.isAsDraft();
|
||||
this.includeSoftDeletes = query.isIncludeSoftDeletes();
|
||||
this.readOnly = query.isReadOnly();
|
||||
this.disableReadAudit = query.isDisableReadAudit();
|
||||
this.disableLazyLoading = query.isDisableLazyLoading();
|
||||
@@ -201,10 +203,6 @@ public class DLoadContext implements LoadContext {
|
||||
return new ObjectGraphNode(origin, path);
|
||||
}
|
||||
|
||||
public boolean isUseAutoTune() {
|
||||
return useProfiling;
|
||||
}
|
||||
|
||||
protected String getFullPath(String path) {
|
||||
if (relativePath == null) {
|
||||
return path;
|
||||
@@ -225,34 +223,6 @@ public class DLoadContext implements LoadContext {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'as of' timestamp that should propagate to secondary queries.
|
||||
*/
|
||||
protected Timestamp getAsOf() {
|
||||
return asOf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the root query is a 'asDraft' query that should propagate to secondary queries.
|
||||
*/
|
||||
protected boolean isAsDraft() {
|
||||
return asDraft;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if disable read auditing should propagate to secondary queries.
|
||||
*/
|
||||
protected boolean isDisableReadAudit() {
|
||||
return disableReadAudit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if disable lazy loading should propagate to secondary queries.
|
||||
*/
|
||||
protected boolean isDisableLazyLoading() {
|
||||
return disableLazyLoading;
|
||||
}
|
||||
|
||||
public PersistenceContext getPersistenceContext() {
|
||||
return persistenceContext;
|
||||
}
|
||||
@@ -335,4 +305,26 @@ public class DLoadContext implements LoadContext {
|
||||
return desc.getBeanPropertyFromPath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagate the original query settings (draft, asOf etc) to the secondary queries.
|
||||
*/
|
||||
public void propagateQueryState(SpiQuery<?> query) {
|
||||
if (readOnly != null) {
|
||||
query.setReadOnly(readOnly);
|
||||
}
|
||||
query.setDisableLazyLoading(disableLazyLoading);
|
||||
query.asOf(asOf);
|
||||
if (asDraft) {
|
||||
query.asDraft();
|
||||
}
|
||||
if (includeSoftDeletes) {
|
||||
query.includeSoftDeletes();
|
||||
}
|
||||
if (disableReadAudit) {
|
||||
query.setDisableReadAuditing();
|
||||
}
|
||||
if (useProfiling) {
|
||||
query.setAutoTune(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,27 +56,11 @@ public class DLoadManyContext extends DLoadBaseContext implements LoadManyContex
|
||||
|
||||
public void configureQuery(SpiQuery<?> query) {
|
||||
|
||||
// propagate the readOnly state
|
||||
if (parent.isReadOnly() != null) {
|
||||
query.setReadOnly(parent.isReadOnly());
|
||||
}
|
||||
|
||||
// propagate the asOf and lazy loading mode
|
||||
query.setDisableLazyLoading(parent.isDisableLazyLoading());
|
||||
query.asOf(parent.getAsOf());
|
||||
parent.propagateQueryState(query);
|
||||
query.setParentNode(objectGraphNode);
|
||||
if (parent.isAsDraft()) {
|
||||
query.asDraft();
|
||||
}
|
||||
if (parent.isDisableReadAudit()) {
|
||||
query.setDisableReadAuditing();
|
||||
}
|
||||
if (queryProps != null) {
|
||||
queryProps.configureBeanQuery(query);
|
||||
}
|
||||
if (parent.isUseAutoTune()) {
|
||||
query.setAutoTune(true);
|
||||
}
|
||||
}
|
||||
|
||||
public BeanPropertyAssocMany<?> getBeanProperty() {
|
||||
|
||||
@@ -1245,9 +1245,10 @@ public final class DefaultPersister implements Persister {
|
||||
BeanPropertyAssocMany<?>[] manys = desc.propertiesManyDelete();
|
||||
for (int i = 0; i < manys.length; i++) {
|
||||
if (manys[i].isManyToMany()) {
|
||||
// delete associated rows from intersection table
|
||||
deleteAssocManyIntersection(parentBean, manys[i], t, request.isPublish());
|
||||
|
||||
if (!softDelete) {
|
||||
// delete associated rows from intersection table (but not during soft delete)
|
||||
deleteAssocManyIntersection(parentBean, manys[i], t, request.isPublish());
|
||||
}
|
||||
} else {
|
||||
|
||||
if (ModifyListenMode.REMOVALS.equals(manys[i].getModifyListenMode())) {
|
||||
|
||||
@@ -29,6 +29,16 @@ public class ScalarTypeBoolean {
|
||||
super(true, Types.BOOLEAN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDbFalseLiteral() {
|
||||
return "false";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDbTrueLiteral() {
|
||||
return "true";
|
||||
}
|
||||
|
||||
public Boolean toBeanType(Object value) {
|
||||
return BasicTypeConverter.toBoolean(value);
|
||||
}
|
||||
@@ -68,6 +78,16 @@ public class ScalarTypeBoolean {
|
||||
super(true, Types.BIT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDbFalseLiteral() {
|
||||
return "0";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDbTrueLiteral() {
|
||||
return "1";
|
||||
}
|
||||
|
||||
public Boolean toBeanType(Object value) {
|
||||
return BasicTypeConverter.toBoolean(value);
|
||||
}
|
||||
@@ -106,6 +126,16 @@ public class ScalarTypeBoolean {
|
||||
this.falseValue = falseValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDbFalseLiteral() {
|
||||
return falseValue.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDbTrueLiteral() {
|
||||
return trueValue.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return 1;
|
||||
@@ -179,6 +209,16 @@ public class ScalarTypeBoolean {
|
||||
this.falseValue = falseValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDbFalseLiteral() {
|
||||
return "'"+falseValue+"'";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDbTrueLiteral() {
|
||||
return "'"+trueValue+"'";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
// typically this will return 1
|
||||
@@ -245,6 +285,16 @@ public class ScalarTypeBoolean {
|
||||
super(Boolean.class, jdbcNative, jdbcType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB literal value for false.
|
||||
*/
|
||||
public abstract String getDbFalseLiteral();
|
||||
|
||||
/**
|
||||
* Return the DB literal value for true.
|
||||
*/
|
||||
public abstract String getDbTrueLiteral();
|
||||
|
||||
public String formatValue(Boolean t) {
|
||||
return t.toString();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user