From 0ad52cf6a7fa3cdbd6cdfd420f614f94af209ed4 Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Tue, 1 Dec 2015 23:21:11 +1300
Subject: [PATCH] #118 - ENH: Add support for for Soft Deletes (Logical
Deletion) ... - initial
---
src/main/java/com/avaje/ebean/Query.java | 5 +
.../avaje/ebean/annotation/SoftDelete.java | 27 +++
.../avaje/ebean/bean/EntityBeanIntercept.java | 2 +-
.../com/avaje/ebeaninternal/api/SpiQuery.java | 13 ++
.../server/core/PersistRequest.java | 2 +-
.../server/core/PersistRequestBean.java | 20 ++
.../server/deploy/BeanDescriptor.java | 58 +++++-
.../server/deploy/BeanProperty.java | 34 ++++
.../server/deploy/BeanPropertyAssocMany.java | 2 +-
.../server/deploy/IntersectionRow.java | 37 ++--
.../deploy/meta/DeployBeanProperty.java | 42 ++++
.../deploy/meta/DeployBeanPropertyLists.java | 10 +
.../server/deploy/parse/AnnotationFields.java | 4 +
.../server/persist/DefaultPersister.java | 188 ++++++++++--------
.../server/query/CQueryBuilder.java | 19 ++
.../ebeaninternal/server/query/SqlTree.java | 7 +
.../server/query/SqlTreeNode.java | 5 +
.../server/query/SqlTreeNodeBean.java | 10 +
.../server/query/SqlTreeNodeExtraJoin.java | 5 +
.../query/SqlTreeNodeManyWhereJoin.java | 5 +
.../server/querydefn/DefaultOrmQuery.java | 26 +++
.../model/softdelete/BaseSoftDelete.java | 45 +++++
.../model/softdelete/EBasicSoftDelete.java | 27 +++
.../tests/softdelete/TestSoftDeleteBasic.java | 43 ++++
24 files changed, 531 insertions(+), 105 deletions(-)
create mode 100644 src/main/java/com/avaje/ebean/annotation/SoftDelete.java
create mode 100644 src/test/java/com/avaje/tests/model/softdelete/BaseSoftDelete.java
create mode 100644 src/test/java/com/avaje/tests/model/softdelete/EBasicSoftDelete.java
create mode 100644 src/test/java/com/avaje/tests/softdelete/TestSoftDeleteBasic.java
diff --git a/src/main/java/com/avaje/ebean/Query.java b/src/main/java/com/avaje/ebean/Query.java
index 00367523e..7043fd0f7 100644
--- a/src/main/java/com/avaje/ebean/Query.java
+++ b/src/main/java/com/avaje/ebean/Query.java
@@ -302,6 +302,11 @@ public interface Query extends Serializable {
*/
Query asDraft();
+ /**
+ * Execute the query including soft deleted rows.
+ */
+ Query includeSoftDeletes();
+
/**
* Cancel the query execution if supported by the underlying database and
* driver.
diff --git a/src/main/java/com/avaje/ebean/annotation/SoftDelete.java b/src/main/java/com/avaje/ebean/annotation/SoftDelete.java
new file mode 100644
index 000000000..56fde390e
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/SoftDelete.java
@@ -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').
+ *
+ * The property should be of type boolean, int or short.
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface SoftDelete {
+
+ /**
+ * Specify the bind value that matches 'deleted' state.
+ *
+ * If not specified then for boolean this is true and
+ * for int and short this value is 1.
+ *
+ */
+ String value() default "";
+}
diff --git a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java
index e1e2845a1..c67f16616 100644
--- a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java
+++ b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java
@@ -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];
}
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
index 05b1a7136..912dc6354 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
@@ -96,6 +96,10 @@ public interface SpiQuery extends Query {
}
enum TemporalMode {
+ /**
+ * Includes soft deletes rows in the result.
+ */
+ SOFT_DELETED,
/**
* Query runs against draft tables.
*/
@@ -183,6 +187,11 @@ public interface SpiQuery extends Query {
*/
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 extends Query {
*/
List getAsOfTableAlias();
+ void addSoftDeletePredicate(String softDeletePredicate);
+
+ List getSoftDeletePredicates();
+
/**
* Return a listener that wants to be notified when the bean collection is
* first used.
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java
index baecf7470..0d1eeb9ae 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java
@@ -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;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
index 324fee809..00b69ba60 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
@@ -479,6 +479,11 @@ public final class PersistRequestBean 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 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 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;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
index 3f0320b30..30fc2d8ac 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -149,6 +149,9 @@ public class BeanDescriptor implements MetaBeanInfo, SpiBeanType {
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 implements MetaBeanInfo, SpiBeanType {
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 implements MetaBeanInfo, SpiBeanType {
// 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 implements MetaBeanInfo, SpiBeanType {
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 implements MetaBeanInfo, SpiBeanType {
cacheHelp.initialise();
}
- public SqlUpdate deleteById(Object id, List
*/
private void deleteManyDetails(SpiTransaction t, BeanDescriptor> desc, EntityBean parentBean,
- BeanPropertyAssocMany> many, ArrayList excludeDetailIds) {
+ BeanPropertyAssocMany> many, ArrayList 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 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 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 {
*
* Will use delete by object if the child entity has manyToMany relationships.
*/
- private void deleteChildrenById(SpiTransaction t, BeanDescriptor> targetDesc, List childIds) {
+ private void deleteChildrenById(SpiTransaction t, BeanDescriptor> targetDesc, List 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 PersistRequestBean 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 PersistRequestBean createRequest(T bean, Transaction t, PersistRequest.Type type, boolean publish) {
+ private PersistRequestBean 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 PersistRequestBean createRequestInternal(T bean, Transaction t, PersistRequest.Type type, boolean saveRecurse, boolean publish) {
BeanManager 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 PersistRequestBean 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);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java
index c507de345..2919e12d3 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java
@@ -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 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);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java
index 06b2e6e40..aaca0a912 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTree.java
@@ -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.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java
index dc32662f1..6700f8e16 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java
@@ -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.
*
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java
index 3f68e5f63..a0a20e710 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java
@@ -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
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java
index f0574d3ce..804b66d8b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java
@@ -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.
*
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java
index 396d01891..eaa0996c0 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java
@@ -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.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
index 2dfe91490..b3f5fdda2 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
@@ -180,6 +180,8 @@ public class DefaultOrmQuery implements SpiQuery {
private Timestamp versionsStart;
private Timestamp versionsEnd;
+ private List softDeletePredicates;
+
private boolean disableReadAudit;
private int bufferFetchSizeHint;
@@ -287,6 +289,19 @@ public class DefaultOrmQuery implements SpiQuery {
return this;
}
+ @Override
+ public void addSoftDeletePredicate(String softDeletePredicate) {
+ if (softDeletePredicates == null) {
+ softDeletePredicates = new ArrayList();
+ }
+ softDeletePredicates.add(softDeletePredicate);
+ }
+
+ @Override
+ public List 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 implements SpiQuery {
return this;
}
+ @Override
+ public Query 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 implements SpiQuery {
return TemporalMode.DRAFT == temporalMode;
}
+ @Override
+ public boolean isIncludeSoftDeletes() {
+ return TemporalMode.SOFT_DELETED == temporalMode;
+ }
+
public void setMode(Mode mode) {
this.mode = mode;
}
diff --git a/src/test/java/com/avaje/tests/model/softdelete/BaseSoftDelete.java b/src/test/java/com/avaje/tests/model/softdelete/BaseSoftDelete.java
new file mode 100644
index 000000000..829dc171c
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/softdelete/BaseSoftDelete.java
@@ -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;
+ }
+}
diff --git a/src/test/java/com/avaje/tests/model/softdelete/EBasicSoftDelete.java b/src/test/java/com/avaje/tests/model/softdelete/EBasicSoftDelete.java
new file mode 100644
index 000000000..9058410ee
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/softdelete/EBasicSoftDelete.java
@@ -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;
+ }
+}
diff --git a/src/test/java/com/avaje/tests/softdelete/TestSoftDeleteBasic.java b/src/test/java/com/avaje/tests/softdelete/TestSoftDeleteBasic.java
new file mode 100644
index 000000000..efade738c
--- /dev/null
+++ b/src/test/java/com/avaje/tests/softdelete/TestSoftDeleteBasic.java
@@ -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();
+
+ }
+}