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) ... - initial
This commit is contained in:
@@ -302,6 +302,11 @@ public interface Query<T> extends Serializable {
|
||||
*/
|
||||
Query<T> asDraft();
|
||||
|
||||
/**
|
||||
* Execute the query including soft deleted rows.
|
||||
*/
|
||||
Query<T> includeSoftDeletes();
|
||||
|
||||
/**
|
||||
* Cancel the query execution if supported by the underlying database and
|
||||
* driver.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
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.
|
||||
* </p>
|
||||
*/
|
||||
@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 "";
|
||||
}
|
||||
@@ -507,7 +507,7 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
setDirty(true);
|
||||
}
|
||||
|
||||
private void setChangedProperty(int propertyIndex) {
|
||||
public void setChangedProperty(int propertyIndex) {
|
||||
if (changedProps == null) {
|
||||
changedProps = new boolean[owner._ebean_getPropertyNames().length];
|
||||
}
|
||||
|
||||
@@ -96,6 +96,10 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
}
|
||||
|
||||
enum TemporalMode {
|
||||
/**
|
||||
* Includes soft deletes rows in the result.
|
||||
*/
|
||||
SOFT_DELETED,
|
||||
/**
|
||||
* Query runs against draft tables.
|
||||
*/
|
||||
@@ -183,6 +187,11 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
*/
|
||||
boolean isAsDraft();
|
||||
|
||||
/**
|
||||
* Return true if this query includes soft deleted rows.
|
||||
*/
|
||||
boolean isIncludeSoftDeletes();
|
||||
|
||||
/**
|
||||
* Return the asOf Timestamp which the query should run as.
|
||||
*/
|
||||
@@ -198,6 +207,10 @@ public interface SpiQuery<T> extends Query<T> {
|
||||
*/
|
||||
List<String> getAsOfTableAlias();
|
||||
|
||||
void addSoftDeletePredicate(String softDeletePredicate);
|
||||
|
||||
List<String> getSoftDeletePredicates();
|
||||
|
||||
/**
|
||||
* Return a listener that wants to be notified when the bean collection is
|
||||
* first used.
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.avaje.ebeaninternal.server.persist.PersistExecute;
|
||||
public abstract class PersistRequest extends BeanRequest implements BatchPostExecute {
|
||||
|
||||
public enum Type {
|
||||
INSERT, UPDATE, DELETE, UPDATESQL, CALLABLESQL
|
||||
INSERT, UPDATE, DELETE, SOFT_DELETE, UPDATESQL, CALLABLESQL
|
||||
}
|
||||
|
||||
protected boolean persistCascade;
|
||||
|
||||
@@ -479,6 +479,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
persistExecute.executeUpdateBean(this);
|
||||
return -1;
|
||||
|
||||
case SOFT_DELETE:
|
||||
prepareForSoftDelete();
|
||||
persistExecute.executeUpdateBean(this);
|
||||
return -1;
|
||||
|
||||
case DELETE:
|
||||
return persistExecute.executeDeleteBean(this);
|
||||
|
||||
@@ -487,6 +492,14 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Soft delete is executed as update so we want to set deleted=true property.
|
||||
*/
|
||||
private void prepareForSoftDelete() {
|
||||
|
||||
beanDescriptor.setSoftDeleteValue(entityBean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeOrQueue() {
|
||||
|
||||
@@ -801,4 +814,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
public String getUpdateTable() {
|
||||
return publish ? beanDescriptor.getBaseTable() : beanDescriptor.getDraftTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a soft delete request.
|
||||
*/
|
||||
public boolean isSoftDelete() {
|
||||
return Type.SOFT_DELETE == type;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,9 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
private final String baseTableVersionsBetween;
|
||||
private final boolean historySupport;
|
||||
|
||||
private final BeanProperty softDeleteProperty;
|
||||
private final boolean softDelete;
|
||||
|
||||
private final String draftTable;
|
||||
|
||||
/**
|
||||
@@ -316,8 +319,9 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
private String idBinderIdSql;
|
||||
|
||||
private String deleteByIdSql;
|
||||
|
||||
private String deleteByIdInSql;
|
||||
private String softDeleteByIdSql;
|
||||
private String softDeleteByIdInSql;
|
||||
|
||||
private final String name;
|
||||
|
||||
@@ -395,6 +399,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
// helper object used to derive lists of properties
|
||||
DeployBeanPropertyLists listHelper = new DeployBeanPropertyLists(owner, this, deploy);
|
||||
|
||||
this.softDeleteProperty = listHelper.getSoftDeleteProperty();
|
||||
this.softDelete = (softDeleteProperty != null);
|
||||
this.idProperty = listHelper.getId();
|
||||
this.versionProperty = listHelper.getVersionProperty();
|
||||
this.draftDirty = listHelper.getDraftDirty();
|
||||
@@ -621,6 +627,14 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
deleteByIdSql = "delete from " + baseTable + " where " + idEqualsSql;
|
||||
deleteByIdInSql = "delete from " + baseTable + " where " + idBinderInLHSSqlNoAlias + " ";
|
||||
|
||||
if (softDelete) {
|
||||
softDeleteByIdSql = "update " + baseTable + " set " + getSoftDeleteDbSet() + " where " + idEqualsSql;
|
||||
softDeleteByIdInSql = "update " + baseTable + " set " + getSoftDeleteDbSet() + " where " + idBinderInLHSSqlNoAlias + " ";
|
||||
} else {
|
||||
softDeleteByIdSql = null;
|
||||
softDeleteByIdInSql = null;
|
||||
}
|
||||
|
||||
if (!isEmbedded()) {
|
||||
// parse every named update up front into sql dml
|
||||
for (DeployNamedUpdate namedUpdate : namedUpdates.values()) {
|
||||
@@ -710,11 +724,11 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
cacheHelp.initialise();
|
||||
}
|
||||
|
||||
public SqlUpdate deleteById(Object id, List<Object> idList) {
|
||||
public SqlUpdate deleteById(Object id, List<Object> idList, boolean softDelete) {
|
||||
if (id != null) {
|
||||
return deleteById(id);
|
||||
return deleteById(id, softDelete);
|
||||
} else {
|
||||
return deleteByIdList(idList);
|
||||
return deleteByIdList(idList, softDelete);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,9 +743,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
* Return SQL that can be used to delete a list of Id's without any optimistic
|
||||
* concurrency checking.
|
||||
*/
|
||||
private SqlUpdate deleteByIdList(List<Object> idList) {
|
||||
private SqlUpdate deleteByIdList(List<Object> idList, boolean softDelete) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(deleteByIdInSql);
|
||||
String baseSql = softDelete ? softDeleteByIdInSql : deleteByIdInSql;
|
||||
StringBuilder sb = new StringBuilder(baseSql);
|
||||
String inClause = idBinder.getIdInValueExprDelete(idList.size());
|
||||
sb.append(inClause);
|
||||
|
||||
@@ -746,9 +761,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
* Return SQL that can be used to delete by Id without any optimistic
|
||||
* concurrency checking.
|
||||
*/
|
||||
private SqlUpdate deleteById(Object id) {
|
||||
private SqlUpdate deleteById(Object id, boolean softDelete) {
|
||||
|
||||
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByIdSql);
|
||||
String baseSql = softDelete ? softDeleteByIdSql : deleteByIdSql;
|
||||
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(baseSql);
|
||||
|
||||
Object[] bindValues = idBinder.getBindValues(id);
|
||||
for (int i = 0; i < bindValues.length; i++) {
|
||||
@@ -1175,6 +1191,16 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
return deleteRecurseSkippable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if delete can use a single SQL statement.
|
||||
*
|
||||
* This implies cascade delete does not continue depth wise and that this is no
|
||||
* associated L2 bean caching.
|
||||
*/
|
||||
public boolean isDeleteByStatement() {
|
||||
return deleteRecurseSkippable && !isBeanCaching();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a property annotated with @WhenCreated or @CreatedTimestamp.
|
||||
*/
|
||||
@@ -1932,6 +1958,22 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
|
||||
return readAuditing;
|
||||
}
|
||||
|
||||
public boolean isSoftDelete() {
|
||||
return softDelete;
|
||||
}
|
||||
|
||||
public void setSoftDeleteValue(EntityBean bean) {
|
||||
softDeleteProperty.setSoftDeleteValue(bean);
|
||||
}
|
||||
|
||||
public String getSoftDeleteDbSet() {
|
||||
return softDeleteProperty.getSoftDeleteDbSet();
|
||||
}
|
||||
|
||||
public String getSoftDeletePredicate(String tableAlias) {
|
||||
return softDeleteProperty.getSoftDeleteDbPredicate(tableAlias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this entity type is draftable.
|
||||
*/
|
||||
|
||||
@@ -229,6 +229,10 @@ public class BeanProperty implements ElPropertyValue {
|
||||
|
||||
final boolean draftReset;
|
||||
|
||||
final boolean softDelete;
|
||||
|
||||
final Object softDeleteValue;
|
||||
|
||||
final boolean indexed;
|
||||
|
||||
final String indexName;
|
||||
@@ -258,6 +262,8 @@ 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) {
|
||||
@@ -345,6 +351,8 @@ public class BeanProperty implements ElPropertyValue {
|
||||
this.draftDirty = source.draftDirty;
|
||||
this.draftOnly = source.draftOnly;
|
||||
this.draftReset = source.draftReset;
|
||||
this.softDelete = source.softDelete;
|
||||
this.softDeleteValue = source.softDeleteValue;
|
||||
this.fetchEager = source.fetchEager;
|
||||
this.unidirectionalShadow = source.unidirectionalShadow;
|
||||
this.discriminator = source.discriminator;
|
||||
@@ -616,6 +624,25 @@ public class BeanProperty implements ElPropertyValue {
|
||||
}
|
||||
}
|
||||
|
||||
public String getSoftDeleteDbSet() {
|
||||
return dbColumn +"=true";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB literal predicate used to filter out soft deleted rows from a query.
|
||||
*/
|
||||
public String getSoftDeleteDbPredicate(String tableAlias) {
|
||||
return tableAlias+"."+dbColumn+"=false";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the soft delete property value on the bean without invoking lazy loading.
|
||||
*/
|
||||
public void setSoftDeleteValue(EntityBean bean) {
|
||||
setValue(bean, softDeleteValue);
|
||||
bean._ebean_getIntercept().setChangedProperty(propertyIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of the property without interception or
|
||||
* PropertyChangeSupport.
|
||||
@@ -1056,6 +1083,13 @@ public class BeanProperty implements ElPropertyValue {
|
||||
return draftReset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is the soft delete property.
|
||||
*/
|
||||
public boolean isSoftDelete() {
|
||||
return softDelete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property should be included in an Insert.
|
||||
*/
|
||||
|
||||
@@ -800,7 +800,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
|
||||
public IntersectionRow buildManyDeleteChildren(EntityBean parentBean, ArrayList<Object> excludeDetailIds) {
|
||||
|
||||
IntersectionRow row = new IntersectionRow(tableJoin.getTable());
|
||||
IntersectionRow row = new IntersectionRow(tableJoin.getTable(), targetDescriptor);
|
||||
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
|
||||
row.setExcludeIds(excludeDetailIds, getTargetDescriptor());
|
||||
}
|
||||
|
||||
@@ -15,13 +15,21 @@ public class IntersectionRow {
|
||||
|
||||
private final String tableName;
|
||||
|
||||
private final BeanDescriptor<?> targetDescriptor;
|
||||
|
||||
private final LinkedHashMap<String, Object> values = new LinkedHashMap<String, Object>();
|
||||
|
||||
private ArrayList<Object> excludeIds;
|
||||
private BeanDescriptor<?> excludeDescriptor;
|
||||
|
||||
public IntersectionRow(String tableName, BeanDescriptor<?> targetDescriptor) {
|
||||
this.tableName = tableName;
|
||||
this.targetDescriptor = targetDescriptor;
|
||||
}
|
||||
|
||||
public IntersectionRow(String tableName) {
|
||||
this.tableName = tableName;
|
||||
this.targetDescriptor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,22 +72,20 @@ public class IntersectionRow {
|
||||
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
|
||||
}
|
||||
|
||||
public SqlUpdate createDelete(EbeanServer server) {
|
||||
public SqlUpdate createDelete(EbeanServer server, boolean softDelete) {
|
||||
|
||||
BindParams bindParams = new BindParams();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("delete from ").append(tableName).append(" where ");
|
||||
|
||||
int count = 0;
|
||||
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
||||
if (count++ > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
sb.append(entry.getKey());
|
||||
sb.append(" = ?");
|
||||
bindParams.setParameter(count, entry.getValue());
|
||||
if (softDelete) {
|
||||
sb.append("update ").append(tableName).append(" set ");
|
||||
sb.append(targetDescriptor.getSoftDeleteDbSet());
|
||||
} else {
|
||||
sb.append("delete from ").append(tableName);
|
||||
}
|
||||
sb.append(" where ");
|
||||
|
||||
int count = setBindParams(bindParams, sb);
|
||||
|
||||
if (excludeIds != null) {
|
||||
IdInExpression idIn = new IdInExpression(excludeIds);
|
||||
@@ -108,6 +114,13 @@ public class IntersectionRow {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("delete from ").append(tableName).append(" where ");
|
||||
|
||||
setBindParams(bindParams, sb);
|
||||
|
||||
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
|
||||
}
|
||||
|
||||
private int setBindParams(BindParams bindParams, StringBuilder sb) {
|
||||
|
||||
int count = 0;
|
||||
for (Map.Entry<String, Object> entry : values.entrySet()) {
|
||||
if (count++ > 0) {
|
||||
@@ -120,6 +133,6 @@ public class IntersectionRow {
|
||||
bindParams.setParameter(count, entry.getValue());
|
||||
}
|
||||
|
||||
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,9 @@ public class DeployBeanProperty {
|
||||
private boolean draftDirty;
|
||||
private boolean draftReset;
|
||||
|
||||
private boolean softDelete;
|
||||
private String softDeleteValue = "";
|
||||
|
||||
public DeployBeanProperty(DeployBeanDescriptor<?> desc, Class<?> propertyType, ScalarType<?> scalarType, ScalarTypeConverter<?, ?> typeConverter) {
|
||||
this.desc = desc;
|
||||
this.propertyType = propertyType;
|
||||
@@ -872,4 +875,43 @@ public class DeployBeanProperty {
|
||||
public boolean isDraftReset() {
|
||||
return draftReset;
|
||||
}
|
||||
|
||||
public void setSoftDelete(String softDeleteValue) {
|
||||
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+"]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,6 +293,16 @@ public class DeployBeanPropertyLists {
|
||||
return draftDirty;
|
||||
}
|
||||
|
||||
public BeanProperty getSoftDeleteProperty() {
|
||||
|
||||
for (BeanProperty prop: nonManys) {
|
||||
if (prop.isSoftDelete()) {
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mode used to determine which BeanPropertyAssoc to include.
|
||||
*/
|
||||
|
||||
@@ -158,6 +158,10 @@ public class AnnotationFields extends AnnotationParser {
|
||||
if (get(prop, DraftReset.class) != null) {
|
||||
prop.setDraftReset();
|
||||
}
|
||||
SoftDelete softDelete = get(prop, SoftDelete.class);
|
||||
if (softDelete != null) {
|
||||
prop.setSoftDelete(softDelete.value());
|
||||
}
|
||||
|
||||
DbJson dbJson = get(prop, DbJson.class);
|
||||
if (dbJson != null) {
|
||||
|
||||
@@ -33,7 +33,6 @@ import com.avaje.ebeaninternal.server.deploy.ManyType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -60,8 +59,6 @@ import java.util.Set;
|
||||
*/
|
||||
public final class DefaultPersister implements Persister {
|
||||
|
||||
private static final Logger SUM = LoggerFactory.getLogger("org.avaje.ebean.SUM");
|
||||
|
||||
private static final Logger PUB = LoggerFactory.getLogger("org.avaje.ebean.PUB");
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultPersister.class);
|
||||
@@ -337,9 +334,10 @@ public final class DefaultPersister implements Persister {
|
||||
/**
|
||||
* Recursively delete the bean. This calls back to the EbeanServer.
|
||||
*/
|
||||
private void deleteRecurse(Object detailBean, Transaction t) {
|
||||
// NB: a new PersistRequest is made
|
||||
server.delete(detailBean, t);
|
||||
private void deleteRecurse(EntityBean detailBean, Transaction t, boolean softDelete) {
|
||||
|
||||
Type deleteType = softDelete ? Type.SOFT_DELETE : Type.DELETE;
|
||||
deleteRequest(createRequest(detailBean, t, deleteType));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -508,7 +506,7 @@ public final class DefaultPersister implements Persister {
|
||||
if (request.isDraftable()) {
|
||||
// we have just deleting a draft bean so now we need to delete the
|
||||
// associated 'live' bean. This is effectively an 'automatic publish'.
|
||||
deleteRequest(createRequest(request.createReference(), t, Type.DELETE, true));
|
||||
deleteRequest(createPublishRequest(request.createReference(), t, Type.DELETE, true));
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
@@ -541,10 +539,10 @@ public final class DefaultPersister implements Persister {
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteList(List<?> beanList, Transaction t) {
|
||||
private void deleteList(List<?> beanList, Transaction t, boolean softDelete) {
|
||||
for (int i = 0; i < beanList.size(); i++) {
|
||||
EntityBean bean = (EntityBean) beanList.get(i);
|
||||
delete(bean, t);
|
||||
// deleteRecurse((EntityBean) beanList.get(i), t, softDelete);
|
||||
delete((EntityBean) beanList.get(i), t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,7 +563,7 @@ public final class DefaultPersister implements Persister {
|
||||
idList.add(descriptor.convertId(id));
|
||||
}
|
||||
|
||||
delete(descriptor, null, idList, transaction);
|
||||
delete(descriptor, null, idList, transaction, descriptor.isSoftDelete());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -577,13 +575,13 @@ public final class DefaultPersister implements Persister {
|
||||
|
||||
// convert to appropriate type if required
|
||||
id = descriptor.convertId(id);
|
||||
return delete(descriptor, id, null, transaction);
|
||||
return delete(descriptor, id, null, transaction, descriptor.isSoftDelete());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete by Id or a List of Id's.
|
||||
*/
|
||||
private int delete(BeanDescriptor<?> descriptor, Object id, List<Object> idList, Transaction transaction) {
|
||||
private int delete(BeanDescriptor<?> descriptor, Object id, List<Object> idList, Transaction transaction, boolean softDelete) {
|
||||
|
||||
SpiTransaction t = (SpiTransaction) transaction;
|
||||
if (t.isPersistCascade()) {
|
||||
@@ -599,7 +597,7 @@ public final class DefaultPersister implements Persister {
|
||||
t.logSummary("-- DeleteById of " + descriptor.getName() + " ids[" + idList + "] requires fetch of foreign key values");
|
||||
}
|
||||
List<?> beanList = server.findList(q, t);
|
||||
deleteList(beanList, t);
|
||||
deleteList(beanList, t, softDelete);
|
||||
return beanList.size();
|
||||
|
||||
} else {
|
||||
@@ -611,7 +609,7 @@ public final class DefaultPersister implements Persister {
|
||||
if (bean == null) {
|
||||
return 0;
|
||||
} else {
|
||||
delete(bean, t);
|
||||
deleteRecurse(bean, t, softDelete);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -623,12 +621,15 @@ public final class DefaultPersister implements Persister {
|
||||
BeanPropertyAssocOne<?>[] expOnes = descriptor.propertiesOneExportedDelete();
|
||||
for (int i = 0; i < expOnes.length; i++) {
|
||||
BeanDescriptor<?> targetDesc = expOnes[i].getTargetDescriptor();
|
||||
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
|
||||
SqlUpdate sqlDelete = expOnes[i].deleteByParentId(id, idList);
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
} else {
|
||||
List<Object> childIds = expOnes[i].findIdsByParentId(id, idList, t);
|
||||
deleteChildrenById(t, targetDesc, childIds);
|
||||
// only cascade soft deletes when supported by target
|
||||
if (!softDelete || targetDesc.isSoftDelete()) {
|
||||
if (!softDelete && targetDesc.isDeleteByStatement()) {
|
||||
SqlUpdate sqlDelete = expOnes[i].deleteByParentId(id, idList);
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
} else {
|
||||
List<Object> childIds = expOnes[i].findIdsByParentId(id, idList, t);
|
||||
deleteChildrenById(t, targetDesc, childIds, softDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,32 +637,37 @@ public final class DefaultPersister implements Persister {
|
||||
BeanPropertyAssocMany<?>[] manys = descriptor.propertiesManyDelete();
|
||||
for (int i = 0; i < manys.length; i++) {
|
||||
BeanDescriptor<?> targetDesc = manys[i].getTargetDescriptor();
|
||||
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
|
||||
// we can just delete children with a single statement
|
||||
SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList);
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
} else {
|
||||
// we need to fetch the Id's to delete (recurse or notify L2 cache)
|
||||
List<Object> childIds = manys[i].findIdsByParentId(id, idList, t, null);
|
||||
if (!childIds.isEmpty()) {
|
||||
delete(targetDesc, null, childIds, t);
|
||||
// only cascade soft deletes when supported by target
|
||||
if (!softDelete || targetDesc.isSoftDelete()) {
|
||||
if (!softDelete && targetDesc.isDeleteByStatement()) {
|
||||
// we can just delete children with a single statement
|
||||
SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList);
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
} else {
|
||||
// we need to fetch the Id's to delete (recurse or notify L2 cache)
|
||||
List<Object> childIds = manys[i].findIdsByParentId(id, idList, t, null);
|
||||
if (!childIds.isEmpty()) {
|
||||
delete(targetDesc, null, childIds, t, softDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ManyToMany's ... delete from intersection table
|
||||
BeanPropertyAssocMany<?>[] manys = descriptor.propertiesManyToMany();
|
||||
for (int i = 0; i < manys.length; i++) {
|
||||
SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList);
|
||||
if (t.isLogSummary()) {
|
||||
t.logSummary("-- Deleting intersection table entries: " + manys[i].getFullBeanName());
|
||||
if (!softDelete) {
|
||||
// ManyToMany's ... delete from intersection table
|
||||
BeanPropertyAssocMany<?>[] manys = descriptor.propertiesManyToMany();
|
||||
for (int i = 0; i < manys.length; i++) {
|
||||
SqlUpdate sqlDelete = manys[i].deleteByParentId(id, idList);
|
||||
if (t.isLogSummary()) {
|
||||
t.logSummary("-- Deleting intersection table entries: " + manys[i].getFullBeanName());
|
||||
}
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
}
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
}
|
||||
|
||||
// delete the bean(s)
|
||||
SqlUpdate deleteById = descriptor.deleteById(id, idList);
|
||||
SqlUpdate deleteById = descriptor.deleteById(id, idList, softDelete);
|
||||
if (t.isLogSummary()) {
|
||||
if (idList != null) {
|
||||
t.logSummary("-- Deleting " + descriptor.getName() + " Ids: " + idList);
|
||||
@@ -924,7 +930,7 @@ public final class DefaultPersister implements Persister {
|
||||
EntityBean eb = (EntityBean) removedBean;
|
||||
if (eb._ebean_getIntercept().isLoaded()) {
|
||||
// only delete if the bean was loaded meaning that it is known to exist in the DB
|
||||
deleteRequest(createRequest(removedBean, t, PersistRequest.Type.DELETE, saveMany.isPublish()));
|
||||
deleteRequest(createPublishRequest(removedBean, t, PersistRequest.Type.DELETE, saveMany.isPublish()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1031,7 +1037,7 @@ public final class DefaultPersister implements Persister {
|
||||
}
|
||||
}
|
||||
// deleting missing children - children not in our collected detailIds
|
||||
deleteManyDetails(t, prop.getBeanDescriptor(), parentBean, prop, detailIds);
|
||||
deleteManyDetails(t, prop.getBeanDescriptor(), parentBean, prop, detailIds, false);
|
||||
}
|
||||
|
||||
t.depth(-1);
|
||||
@@ -1175,7 +1181,7 @@ public final class DefaultPersister implements Persister {
|
||||
// the object from the 'other' side of the ManyToMany
|
||||
// build a intersection row for 'delete'
|
||||
IntersectionRow intRow = prop.buildManyToManyMapBean(saveManyPropRequest.getParentBean(), otherDelete, saveManyPropRequest.isPublish());
|
||||
SqlUpdate sqlDelete = intRow.createDelete(server);
|
||||
SqlUpdate sqlDelete = intRow.createDelete(server, false);
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
}
|
||||
}
|
||||
@@ -1206,6 +1212,7 @@ public final class DefaultPersister implements Persister {
|
||||
|
||||
BeanDescriptor<?> desc = request.getBeanDescriptor();
|
||||
EntityBean parentBean = request.getEntityBean();
|
||||
boolean softDelete = request.isSoftDelete();
|
||||
|
||||
BeanPropertyAssocOne<?>[] expOnes = desc.propertiesOneExportedDelete();
|
||||
if (expOnes.length > 0) {
|
||||
@@ -1213,16 +1220,19 @@ public final class DefaultPersister implements Persister {
|
||||
DeleteUnloadedForeignKeys unloaded = null;
|
||||
for (int i = 0; i < expOnes.length; i++) {
|
||||
BeanPropertyAssocOne<?> prop = expOnes[i];
|
||||
if (request.isLoadedProperty(prop)) {
|
||||
Object detailBean = prop.getValue(parentBean);
|
||||
if (detailBean != null) {
|
||||
deleteRecurse(detailBean, t);
|
||||
// for soft delete check cascade type also supports soft delete
|
||||
if (!softDelete || prop.getTargetDescriptor().isSoftDelete()) {
|
||||
if (request.isLoadedProperty(prop)) {
|
||||
Object detailBean = prop.getValue(parentBean);
|
||||
if (detailBean != null) {
|
||||
deleteRecurse((EntityBean)detailBean, t, softDelete);
|
||||
}
|
||||
} else {
|
||||
if (unloaded == null) {
|
||||
unloaded = new DeleteUnloadedForeignKeys(server, request);
|
||||
}
|
||||
unloaded.add(prop);
|
||||
}
|
||||
} else {
|
||||
if (unloaded == null) {
|
||||
unloaded = new DeleteUnloadedForeignKeys(server, request);
|
||||
}
|
||||
unloaded.add(prop);
|
||||
}
|
||||
}
|
||||
if (unloaded != null) {
|
||||
@@ -1242,23 +1252,26 @@ public final class DefaultPersister implements Persister {
|
||||
|
||||
if (ModifyListenMode.REMOVALS.equals(manys[i].getModifyListenMode())) {
|
||||
// PrivateOwned ...
|
||||
Object details = manys[i].getValue(parentBean);
|
||||
if (details instanceof BeanCollection<?>) {
|
||||
Set<?> modifyRemovals = ((BeanCollection<?>) details).getModifyRemovals();
|
||||
if (modifyRemovals != null && !modifyRemovals.isEmpty()) {
|
||||
// if soft delete then check target also supports soft delete
|
||||
if (!softDelete || manys[i].getTargetDescriptor().isSoftDelete()) {
|
||||
Object details = manys[i].getValue(parentBean);
|
||||
if (details instanceof BeanCollection<?>) {
|
||||
Set<?> modifyRemovals = ((BeanCollection<?>) details).getModifyRemovals();
|
||||
if (modifyRemovals != null && !modifyRemovals.isEmpty()) {
|
||||
|
||||
// delete the orphans that have been removed from the collection
|
||||
for (Object detail : modifyRemovals) {
|
||||
EntityBean detailBean = (EntityBean) detail;
|
||||
if (manys[i].hasId(detailBean)) {
|
||||
deleteRecurse(detailBean, t);
|
||||
// delete the orphans that have been removed from the collection
|
||||
for (Object detail : modifyRemovals) {
|
||||
EntityBean detailBean = (EntityBean) detail;
|
||||
if (manys[i].hasId(detailBean)) {
|
||||
deleteRecurse(detailBean, t, softDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteManyDetails(t, desc, parentBean, manys[i], null);
|
||||
deleteManyDetails(t, desc, parentBean, manys[i], null, softDelete);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1275,23 +1288,25 @@ public final class DefaultPersister implements Persister {
|
||||
* </p>
|
||||
*/
|
||||
private void deleteManyDetails(SpiTransaction t, BeanDescriptor<?> desc, EntityBean parentBean,
|
||||
BeanPropertyAssocMany<?> many, ArrayList<Object> excludeDetailIds) {
|
||||
BeanPropertyAssocMany<?> many, ArrayList<Object> excludeDetailIds, boolean softDelete) {
|
||||
|
||||
if (many.getCascadeInfo().isDelete()) {
|
||||
// cascade delete the beans in the collection
|
||||
BeanDescriptor<?> targetDesc = many.getTargetDescriptor();
|
||||
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
|
||||
// Just delete all the children with one statement
|
||||
IntersectionRow intRow = many.buildManyDeleteChildren(parentBean, excludeDetailIds);
|
||||
SqlUpdate sqlDelete = intRow.createDelete(server);
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
if (!softDelete || targetDesc.isSoftDelete()) {
|
||||
if (targetDesc.isDeleteRecurseSkippable() && !targetDesc.isBeanCaching()) {
|
||||
// Just delete all the children with one statement
|
||||
IntersectionRow intRow = many.buildManyDeleteChildren(parentBean, excludeDetailIds);
|
||||
SqlUpdate sqlDelete = intRow.createDelete(server, softDelete);
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
|
||||
} else {
|
||||
// Delete recurse using the Id values of the children
|
||||
Object parentId = desc.getId(parentBean);
|
||||
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds);
|
||||
if (!idsByParentId.isEmpty()) {
|
||||
deleteChildrenById(t, targetDesc, idsByParentId);
|
||||
} else {
|
||||
// Delete recurse using the Id values of the children
|
||||
Object parentId = desc.getId(parentBean);
|
||||
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds);
|
||||
if (!idsByParentId.isEmpty()) {
|
||||
deleteChildrenById(t, targetDesc, idsByParentId, softDelete);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1302,7 +1317,7 @@ public final class DefaultPersister implements Persister {
|
||||
* <p>
|
||||
* Will use delete by object if the child entity has manyToMany relationships.
|
||||
*/
|
||||
private void deleteChildrenById(SpiTransaction t, BeanDescriptor<?> targetDesc, List<Object> childIds) {
|
||||
private void deleteChildrenById(SpiTransaction t, BeanDescriptor<?> targetDesc, List<Object> childIds, boolean softDelete) {
|
||||
|
||||
if (targetDesc.propertiesManyToMany().length > 0) {
|
||||
// convert into a list of reference objects and perform delete by object
|
||||
@@ -1310,11 +1325,11 @@ public final class DefaultPersister implements Persister {
|
||||
for (Object id : childIds) {
|
||||
refList.add(targetDesc.createReference(null, id));
|
||||
}
|
||||
deleteList(refList, t);
|
||||
deleteList(refList, t, softDelete);
|
||||
|
||||
} else {
|
||||
// perform delete by statement if possible
|
||||
delete(targetDesc, null, childIds, t);
|
||||
delete(targetDesc, null, childIds, t, softDelete);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1375,9 +1390,7 @@ public final class DefaultPersister implements Persister {
|
||||
*/
|
||||
private void deleteAssocOne(PersistRequestBean<?> request) {
|
||||
|
||||
BeanDescriptor<?> desc = request.getBeanDescriptor();
|
||||
BeanPropertyAssocOne<?>[] ones = desc.propertiesOneImportedDelete();
|
||||
|
||||
BeanPropertyAssocOne<?>[] ones = request.getBeanDescriptor().propertiesOneImportedDelete();
|
||||
for (int i = 0; i < ones.length; i++) {
|
||||
BeanPropertyAssocOne<?> prop = ones[i];
|
||||
if (request.isLoadedProperty(prop)) {
|
||||
@@ -1385,7 +1398,7 @@ public final class DefaultPersister implements Persister {
|
||||
if (detailBean != null) {
|
||||
EntityBean detail = (EntityBean) detailBean;
|
||||
if (prop.hasId(detail)) {
|
||||
deleteRecurse(detail, request.getTransaction());
|
||||
deleteRecurse(detail, request.getTransaction(), request.isSoftDelete());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1426,18 +1439,25 @@ public final class DefaultPersister implements Persister {
|
||||
* perform an insert, update or delete.
|
||||
*/
|
||||
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, PersistRequest.Type type) {
|
||||
return createRequest(bean, t, type, false);
|
||||
return createRequestInternal(bean, t, type, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Persist Request Object additionally specifying the publish status.
|
||||
*/
|
||||
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, PersistRequest.Type type, boolean publish) {
|
||||
private <T> PersistRequestBean<T> createPublishRequest(T bean, Transaction t, PersistRequest.Type type, boolean publish) {
|
||||
return createRequestInternal(bean, t, type, false, publish);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Persist Request Object additionally specifying the publish status.
|
||||
*/
|
||||
private <T> PersistRequestBean<T> createRequestInternal(T bean, Transaction t, PersistRequest.Type type, boolean saveRecurse, boolean publish) {
|
||||
BeanManager<T> mgr = getBeanManager(bean);
|
||||
if (mgr == null) {
|
||||
throw new PersistenceException(errNotRegistered(bean.getClass()));
|
||||
}
|
||||
return createRequest(bean, t, null, mgr, type, false, publish);
|
||||
return createRequest(bean, t, null, mgr, type, saveRecurse, publish);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1471,6 +1491,10 @@ public final class DefaultPersister implements Persister {
|
||||
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, Object parentBean, BeanManager<?> mgr,
|
||||
PersistRequest.Type type, boolean saveRecurse, boolean publish) {
|
||||
|
||||
if (type == Type.DELETE && mgr.getBeanDescriptor().isSoftDelete()) {
|
||||
type = Type.SOFT_DELETE;
|
||||
}
|
||||
|
||||
return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type, saveRecurse, publish);
|
||||
}
|
||||
|
||||
|
||||
@@ -268,6 +268,8 @@ public class CQueryBuilder {
|
||||
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
|
||||
if (query.isAsOfQuery()) {
|
||||
sqlTree.addAsOfTableAlias(query);
|
||||
} else if (SpiQuery.TemporalMode.CURRENT == query.getTemporalMode()) {
|
||||
sqlTree.addSoftDeletePredicate(query);
|
||||
}
|
||||
|
||||
SqlLimitResponse res = buildSql(null, request, predicates, sqlTree);
|
||||
@@ -478,6 +480,23 @@ public class CQueryBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
if (!query.isIncludeSoftDeletes()) {
|
||||
List<String> softDeletePredicates = query.getSoftDeletePredicates();
|
||||
if (softDeletePredicates != null) {
|
||||
if (!hasWhere) {
|
||||
sb.append(" where ");
|
||||
} else {
|
||||
sb.append("and ");
|
||||
}
|
||||
for (int i = 0; i < softDeletePredicates.size(); i++) {
|
||||
if (i > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
sb.append(softDeletePredicates.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dbOrderBy != null) {
|
||||
sb.append(" order by ").append(dbOrderBy);
|
||||
}
|
||||
|
||||
@@ -89,6 +89,13 @@ public class SqlTree {
|
||||
rootNode.addAsOfTableAlias(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurse through the tree adding soft delete predicates as necessary.
|
||||
*/
|
||||
public void addSoftDeletePredicate(SpiQuery<?> query) {
|
||||
rootNode.addSoftDeletePredicate(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a select expression chain for RawSql.
|
||||
*/
|
||||
|
||||
@@ -39,6 +39,11 @@ public interface SqlTreeNode {
|
||||
*/
|
||||
void addAsOfTableAlias(SpiQuery<?> query);
|
||||
|
||||
/**
|
||||
* Recurse through the tree adding soft delete predicates if necessary.
|
||||
*/
|
||||
void addSoftDeletePredicate(SpiQuery<?> query);
|
||||
|
||||
/**
|
||||
* Load the appropriate information from the SqlSelectReader.
|
||||
* <p>
|
||||
|
||||
@@ -480,6 +480,16 @@ public class SqlTreeNodeBean implements SqlTreeNode {
|
||||
ctx.popJoin();
|
||||
}
|
||||
|
||||
public void addSoftDeletePredicate(SpiQuery<?> query) {
|
||||
|
||||
if (desc.isSoftDelete()) {
|
||||
query.addSoftDeletePredicate(desc.getSoftDeletePredicate(baseTableAlias));
|
||||
}
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
children[i].addSoftDeletePredicate(query);
|
||||
}
|
||||
}
|
||||
|
||||
public void addAsOfTableAlias(SpiQuery<?> query) {
|
||||
// if history on this bean type add it's alias
|
||||
// for each alias we add an effect date predicate
|
||||
|
||||
@@ -47,6 +47,11 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSoftDeletePredicate(SpiQuery<?> query) {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the extra join is a many join.
|
||||
* <p>
|
||||
|
||||
@@ -44,6 +44,11 @@ public class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
|
||||
// do nothing here ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSoftDeletePredicate(SpiQuery<?> query) {
|
||||
// do nothing here ...
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to the FROM clause for this node.
|
||||
*/
|
||||
|
||||
@@ -180,6 +180,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
private Timestamp versionsStart;
|
||||
private Timestamp versionsEnd;
|
||||
|
||||
private List<String> softDeletePredicates;
|
||||
|
||||
private boolean disableReadAudit;
|
||||
|
||||
private int bufferFetchSizeHint;
|
||||
@@ -287,6 +289,19 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSoftDeletePredicate(String softDeletePredicate) {
|
||||
if (softDeletePredicates == null) {
|
||||
softDeletePredicates = new ArrayList<String>();
|
||||
}
|
||||
softDeletePredicates.add(softDeletePredicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getSoftDeletePredicates() {
|
||||
return softDeletePredicates;
|
||||
}
|
||||
|
||||
/**
|
||||
* This table alias is for a @History entity involved in the query and as
|
||||
* such we need to add a 'as of predicate' to the query using this alias.
|
||||
@@ -317,6 +332,12 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> includeSoftDeletes() {
|
||||
this.temporalMode = TemporalMode.SOFT_DELETED;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the BeanDescriptor for the root type of this query.
|
||||
*/
|
||||
@@ -660,6 +681,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return TemporalMode.DRAFT == temporalMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIncludeSoftDeletes() {
|
||||
return TemporalMode.SOFT_DELETED == temporalMode;
|
||||
}
|
||||
|
||||
public void setMode(Mode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.avaje.tests.model.softdelete;
|
||||
|
||||
import com.avaje.ebean.annotation.SoftDelete;
|
||||
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.persistence.Version;
|
||||
|
||||
@MappedSuperclass
|
||||
public class BaseSoftDelete {
|
||||
|
||||
@Id
|
||||
Long id;
|
||||
|
||||
@Version
|
||||
Long version;
|
||||
|
||||
@SoftDelete
|
||||
boolean deleted;
|
||||
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public void setDeleted(boolean deleted) {
|
||||
this.deleted = deleted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.avaje.tests.model.softdelete;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
public class EBasicSoftDelete extends BaseSoftDelete {
|
||||
|
||||
String name;
|
||||
|
||||
String description;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.avaje.tests.softdelete;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.SqlQuery;
|
||||
import com.avaje.ebean.SqlRow;
|
||||
import com.avaje.tests.model.softdelete.EBasicSoftDelete;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestSoftDeleteBasic extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
EBasicSoftDelete bean = new EBasicSoftDelete();
|
||||
bean.setName("one");
|
||||
|
||||
Ebean.save(bean);
|
||||
|
||||
Ebean.delete(bean);
|
||||
|
||||
SqlQuery sqlQuery = Ebean.createSqlQuery("select * from ebasic_soft_delete where id=?");
|
||||
sqlQuery.setParameter(1, bean.getId());
|
||||
SqlRow sqlRow = sqlQuery.findUnique();
|
||||
assertThat(sqlRow).isNotNull();
|
||||
|
||||
EBasicSoftDelete findNormal = Ebean.find(EBasicSoftDelete.class)
|
||||
.setId(bean.getId())
|
||||
.findUnique();
|
||||
|
||||
assertThat(findNormal).isNull();
|
||||
|
||||
EBasicSoftDelete findInclude = Ebean.find(EBasicSoftDelete.class)
|
||||
.setId(bean.getId())
|
||||
.includeSoftDeletes()
|
||||
.findUnique();
|
||||
|
||||
assertThat(findInclude).isNotNull();
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user