From f39a684c8ef99b361a68069c8e2c50ff8ec99e25 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Wed, 2 Dec 2015 21:38:34 +1300 Subject: [PATCH] #118 - ENH: Add support for for Soft Deletes (Logical Deletion) ... --- .../avaje/ebean/annotation/SoftDelete.java | 16 ++-- .../server/deploy/BeanProperty.java | 31 +++++-- .../deploy/meta/DeployBeanProperty.java | 36 +------- .../server/deploy/parse/AnnotationFields.java | 2 +- .../server/loadcontext/DLoadBeanContext.java | 17 +--- .../server/loadcontext/DLoadContext.java | 56 ++++++------- .../server/loadcontext/DLoadManyContext.java | 18 +--- .../server/persist/DefaultPersister.java | 7 +- .../server/type/ScalarTypeBoolean.java | 50 +++++++++++ .../model/softdelete/EBasicNoSDChild.java | 72 ++++++++++++++++ .../tests/model/softdelete/EBasicSDChild.java | 48 +++++++++++ .../model/softdelete/EBasicSoftDelete.java | 34 ++++++++ .../tests/model/softdelete/ESoftDelRole.java | 35 ++++++++ .../tests/model/softdelete/ESoftDelUser.java | 38 +++++++++ .../tests/softdelete/TestSoftDeleteBasic.java | 84 +++++++++++++++++++ .../softdelete/TestSoftDeleteManyToMany.java | 43 ++++++++++ 16 files changed, 469 insertions(+), 118 deletions(-) create mode 100644 src/test/java/com/avaje/tests/model/softdelete/EBasicNoSDChild.java create mode 100644 src/test/java/com/avaje/tests/model/softdelete/EBasicSDChild.java create mode 100644 src/test/java/com/avaje/tests/model/softdelete/ESoftDelRole.java create mode 100644 src/test/java/com/avaje/tests/model/softdelete/ESoftDelUser.java create mode 100644 src/test/java/com/avaje/tests/softdelete/TestSoftDeleteManyToMany.java diff --git a/src/main/java/com/avaje/ebean/annotation/SoftDelete.java b/src/main/java/com/avaje/ebean/annotation/SoftDelete.java index 56fde390e..3cc7a1f35 100644 --- a/src/main/java/com/avaje/ebean/annotation/SoftDelete.java +++ b/src/main/java/com/avaje/ebean/annotation/SoftDelete.java @@ -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'). *

- * The property should be of type boolean, int or short. + * The property should be of type boolean. *

+ *
{@code
+ *
+ * @SoftDelete
+ * boolean deleted;
+ *
+ * }
*/ @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/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java index f7691b92d..0f2e02d52 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java @@ -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); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java index b4ab4b93d..30858aac0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java @@ -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+"]"); - } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java index 851fdd46f..fde8d0041 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -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); diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java index f591337c7..254fd6d78 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -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) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java index 99b563d14..db7f1c366 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadContext.java @@ -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); + } + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java index 6e5f8a763..6ed015ce0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadManyContext.java @@ -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() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java index ef6b4b61d..711147526 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java @@ -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())) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java index 94d85293e..154d4a331 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBoolean.java @@ -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(); } diff --git a/src/test/java/com/avaje/tests/model/softdelete/EBasicNoSDChild.java b/src/test/java/com/avaje/tests/model/softdelete/EBasicNoSDChild.java new file mode 100644 index 000000000..5b5c51f6a --- /dev/null +++ b/src/test/java/com/avaje/tests/model/softdelete/EBasicNoSDChild.java @@ -0,0 +1,72 @@ +package com.avaje.tests.model.softdelete; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Version; + +@Entity +public class EBasicNoSDChild { + + @Id + Long id; + + @Version + Long version; + + @ManyToOne(optional = false) + EBasicSoftDelete owner; + + String childName; + + long amount; + + public EBasicNoSDChild(EBasicSoftDelete owner, String childName, long amount) { + this.owner = owner; + this.childName = childName; + this.amount = amount; + } + + public EBasicNoSDChild() { + } + + 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 EBasicSoftDelete getOwner() { + return owner; + } + + public void setOwner(EBasicSoftDelete owner) { + this.owner = owner; + } + + public String getChildName() { + return childName; + } + + public void setChildName(String childName) { + this.childName = childName; + } + + public long getAmount() { + return amount; + } + + public void setAmount(long amount) { + this.amount = amount; + } +} diff --git a/src/test/java/com/avaje/tests/model/softdelete/EBasicSDChild.java b/src/test/java/com/avaje/tests/model/softdelete/EBasicSDChild.java new file mode 100644 index 000000000..ceee82f97 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/softdelete/EBasicSDChild.java @@ -0,0 +1,48 @@ +package com.avaje.tests.model.softdelete; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; + +@Entity +public class EBasicSDChild extends BaseSoftDelete { + + @ManyToOne(optional = false) + EBasicSoftDelete owner; + + String childName; + + long amount; + + public EBasicSDChild(EBasicSoftDelete owner, String childName, long amount) { + this.owner = owner; + this.childName = childName; + this.amount = amount; + } + + public EBasicSDChild() { + } + + public EBasicSoftDelete getOwner() { + return owner; + } + + public void setOwner(EBasicSoftDelete owner) { + this.owner = owner; + } + + public String getChildName() { + return childName; + } + + public void setChildName(String childName) { + this.childName = childName; + } + + public long getAmount() { + return amount; + } + + public void setAmount(long amount) { + this.amount = amount; + } +} diff --git a/src/test/java/com/avaje/tests/model/softdelete/EBasicSoftDelete.java b/src/test/java/com/avaje/tests/model/softdelete/EBasicSoftDelete.java index 9058410ee..640d4296a 100644 --- a/src/test/java/com/avaje/tests/model/softdelete/EBasicSoftDelete.java +++ b/src/test/java/com/avaje/tests/model/softdelete/EBasicSoftDelete.java @@ -1,6 +1,9 @@ package com.avaje.tests.model.softdelete; +import javax.persistence.CascadeType; import javax.persistence.Entity; +import javax.persistence.OneToMany; +import java.util.List; @Entity public class EBasicSoftDelete extends BaseSoftDelete { @@ -9,6 +12,13 @@ public class EBasicSoftDelete extends BaseSoftDelete { String description; + @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL) + List children; + + @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL) + List nosdChildren; + + public String getName() { return name; } @@ -24,4 +34,28 @@ public class EBasicSoftDelete extends BaseSoftDelete { public void setDescription(String description) { this.description = description; } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public void addChild(String childName, long amount) { + getChildren().add(new EBasicSDChild(this, childName, amount)); + } + + public List getNosdChildren() { + return nosdChildren; + } + + public void setNosdChildren(List nosdChildren) { + this.nosdChildren = nosdChildren; + } + + public void addNoSoftDeleteChild(String childName, long amount) { + getNosdChildren().add(new EBasicNoSDChild(this, childName, amount)); + } } diff --git a/src/test/java/com/avaje/tests/model/softdelete/ESoftDelRole.java b/src/test/java/com/avaje/tests/model/softdelete/ESoftDelRole.java new file mode 100644 index 000000000..44f29ca4f --- /dev/null +++ b/src/test/java/com/avaje/tests/model/softdelete/ESoftDelRole.java @@ -0,0 +1,35 @@ +package com.avaje.tests.model.softdelete; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.ManyToMany; +import java.util.List; + +@Entity +public class ESoftDelRole extends BaseSoftDelete { + + String roleName; + + @ManyToMany(cascade = CascadeType.ALL) + List users; + + public ESoftDelRole(String roleName) { + this.roleName = roleName; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } +} diff --git a/src/test/java/com/avaje/tests/model/softdelete/ESoftDelUser.java b/src/test/java/com/avaje/tests/model/softdelete/ESoftDelUser.java new file mode 100644 index 000000000..e1b41101d --- /dev/null +++ b/src/test/java/com/avaje/tests/model/softdelete/ESoftDelUser.java @@ -0,0 +1,38 @@ +package com.avaje.tests.model.softdelete; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.ManyToMany; +import java.util.List; + +@Entity +public class ESoftDelUser extends BaseSoftDelete { + + String userName; + + @ManyToMany(cascade = CascadeType.ALL) + List roles; + + public ESoftDelUser(String userName) { + this.userName = userName; + } + + public ESoftDelUser() { + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } +} diff --git a/src/test/java/com/avaje/tests/softdelete/TestSoftDeleteBasic.java b/src/test/java/com/avaje/tests/softdelete/TestSoftDeleteBasic.java index efade738c..4954ac54d 100644 --- a/src/test/java/com/avaje/tests/softdelete/TestSoftDeleteBasic.java +++ b/src/test/java/com/avaje/tests/softdelete/TestSoftDeleteBasic.java @@ -2,11 +2,15 @@ package com.avaje.tests.softdelete; import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; +import com.avaje.ebean.Query; import com.avaje.ebean.SqlQuery; import com.avaje.ebean.SqlRow; import com.avaje.tests.model.softdelete.EBasicSoftDelete; +import org.avaje.ebeantest.LoggedSqlCollector; import org.junit.Test; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; public class TestSoftDeleteBasic extends BaseTestCase { @@ -40,4 +44,84 @@ public class TestSoftDeleteBasic extends BaseTestCase { assertThat(findInclude).isNotNull(); } + + @Test + public void testCascadeSaveDelete() { + + EBasicSoftDelete bean = new EBasicSoftDelete(); + bean.setName("cascadeOne"); + bean.addChild("child1", 10); + bean.addChild("child2", 20); + bean.addChild("child3", 30); + bean.addNoSoftDeleteChild("nsd1", 101); + bean.addNoSoftDeleteChild("nsd2", 102); + + Ebean.save(bean); + + LoggedSqlCollector.start(); + + Ebean.delete(bean); + + List loggedSql = LoggedSqlCollector.stop(); + + // The children without SoftDelete are left as is (so no third statement) + assertThat(loggedSql).hasSize(2); + + // first statement is a single bulk update of the children with SoftDelete + assertThat(loggedSql.get(0)).contains("update ebasic_sdchild set deleted="); + assertThat(loggedSql.get(0)).contains("where owner_id = ?"); + + // second statement is the top level bean + assertThat(loggedSql.get(1)).contains("update ebasic_soft_delete set version=?, deleted=? where id=? and version=?"); + + } + + + @Test + public void testFetch() { + + EBasicSoftDelete bean = new EBasicSoftDelete(); + bean.setName("cascadeOne"); + bean.addChild("child1", 10); + bean.addChild("child2", 20); + bean.addChild("child3", 30); + bean.addNoSoftDeleteChild("nsd1", 101); + bean.addNoSoftDeleteChild("nsd2", 102); + + Ebean.save(bean); + + Ebean.delete(bean.getChildren().get(1)); + + LoggedSqlCollector.start(); + + Query query1 = + Ebean.find(EBasicSoftDelete.class) + .fetch("children") + .where().eq("id", bean.getId()) + .query(); + + List fetch1 = query1.findList(); + String generatedSql = query1.getGeneratedSql(); + + // first statement is a single bulk update of the children with SoftDelete + assertThat(generatedSql).contains("t0.deleted="); + assertThat(generatedSql).contains("t1.deleted="); + assertThat(fetch1.get(0).getChildren()).hasSize(2); + + assertThat(fetch1.get(0).getNosdChildren()).hasSize(2); + + + // fetch again using lazy loading + EBasicSoftDelete fetchWithLazy = Ebean.find(EBasicSoftDelete.class, bean.getId()); + assertThat(fetchWithLazy.getChildren()).hasSize(2); + + // fetch includeSoftDeletes using lazy loading + EBasicSoftDelete fetchAllWithLazy = + Ebean.find(EBasicSoftDelete.class) + .setId(bean.getId()) + .includeSoftDeletes() + .findUnique(); + + assertThat(fetchAllWithLazy.getChildren()).hasSize(3); + } } diff --git a/src/test/java/com/avaje/tests/softdelete/TestSoftDeleteManyToMany.java b/src/test/java/com/avaje/tests/softdelete/TestSoftDeleteManyToMany.java new file mode 100644 index 000000000..b6852d5ce --- /dev/null +++ b/src/test/java/com/avaje/tests/softdelete/TestSoftDeleteManyToMany.java @@ -0,0 +1,43 @@ +package com.avaje.tests.softdelete; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.tests.model.softdelete.ESoftDelRole; +import com.avaje.tests.model.softdelete.ESoftDelUser; +import org.avaje.ebeantest.LoggedSqlCollector; +import org.junit.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestSoftDeleteManyToMany extends BaseTestCase { + + @Test + public void test() { + + ESoftDelRole role1 = new ESoftDelRole("role1"); + ESoftDelRole role2 = new ESoftDelRole("role2"); + + Ebean.save(role1); + Ebean.save(role2); + + ESoftDelUser user1 = new ESoftDelUser("user1"); + user1.getRoles().add(role1); + user1.getRoles().add(role2); + + Ebean.save(user1); + + + LoggedSqlCollector.start(); + Ebean.delete(user1); + + List loggedSql = LoggedSqlCollector.stop(); + + // No Delete from the relationship table + assertThat(loggedSql).hasSize(1); + assertThat(loggedSql.get(0)).contains("update esoft_del_user set version=?, deleted=? where id=? and version=?;"); + + } + +}