#1425 - Stateless update with OneToMany with SoftDelete and deleteMissingChildren = true ... does a hard delete

This commit is contained in:
rob bygrave
2018-06-14 16:09:45 +12:00
parent e21839de91
commit 81c6d4601d
13 changed files with 259 additions and 75 deletions
@@ -13,7 +13,7 @@ public interface TxnProfileEventCodes {
String EVT_INSERT = "i";
String EVT_UPDATE = "u";
String EVT_DELETE = "d";
String EVT_SOFT_DELETE = "ds";
String EVT_DELETE_SOFT = "ds";
String EVT_DELETE_PERMANENT = "dp";
String EVT_ORMUPDATE = "uo";
String FIND_UPDATE = "uq";
@@ -17,7 +17,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
INSERT(EVT_INSERT),
UPDATE(EVT_UPDATE),
DELETE(EVT_DELETE),
SOFT_DELETE(EVT_SOFT_DELETE),
DELETE_SOFT(EVT_DELETE_SOFT),
DELETE_PERMANENT(EVT_DELETE_PERMANENT),
UPDATESQL(EVT_UPDATESQL),
CALLABLESQL(EVT_CALLABLESQL);
@@ -23,6 +23,7 @@ import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.BatchedSqlException;
import io.ebeaninternal.server.persist.DeleteMode;
import io.ebeaninternal.server.persist.Flags;
import io.ebeaninternal.server.persist.PersistExecute;
import io.ebeaninternal.server.transaction.BeanPersistIdMap;
@@ -271,7 +272,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
onUpdateGeneratedProperties();
}
break;
case SOFT_DELETE:
case DELETE_SOFT:
onUpdateGeneratedProperties();
break;
}
@@ -455,7 +456,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
beanDescriptor.cacheHandleUpdate(idValue, this, changeSet);
break;
case DELETE:
case SOFT_DELETE:
case DELETE_SOFT:
beanDescriptor.cacheHandleDelete(idValue, this, changeSet);
break;
default:
@@ -475,7 +476,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
beanDescriptor.docStoreInsert(idValue, this, txn);
break;
case UPDATE:
case SOFT_DELETE:
case DELETE_SOFT:
beanDescriptor.docStoreUpdate(idValue, this, txn);
break;
case DELETE:
@@ -496,7 +497,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
break;
case UPDATE:
case SOFT_DELETE:
case DELETE_SOFT:
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
break;
case DELETE:
@@ -527,7 +528,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
beanPersistListener.deleted(bean);
break;
case SOFT_DELETE:
case DELETE_SOFT:
beanPersistListener.softDeleted(bean);
break;
@@ -772,7 +773,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
executeUpdate();
return -1;
case SOFT_DELETE:
case DELETE_SOFT:
prepareForSoftDelete();
executeSoftDelete();
return -1;
@@ -855,7 +856,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
switch (type) {
case DELETE:
case SOFT_DELETE:
case DELETE_SOFT:
postDelete();
break;
case UPDATE:
@@ -968,7 +969,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
pendingPostUpdateNotify++;
}
break;
case SOFT_DELETE:
case DELETE_SOFT:
controller.postSoftDelete(this);
break;
case DELETE:
@@ -993,7 +994,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
case DELETE:
transaction.logSummary("Deleted [" + name + "] [" + idValue + "]" + draft);
break;
case SOFT_DELETE:
case DELETE_SOFT:
transaction.logSummary("SoftDelete [" + name + "] [" + idValue + "]" + draft);
break;
default:
@@ -1184,10 +1185,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
/**
* Return true if this is a soft delete request.
* Return the delete mode - Soft or Hard.
*/
public boolean isSoftDelete() {
return Type.SOFT_DELETE == type;
public DeleteMode deleteMode() {
return Type.DELETE_SOFT == type ? DeleteMode.SOFT : DeleteMode.HARD;
}
/**
@@ -62,6 +62,7 @@ import io.ebeaninternal.server.el.ElComparatorProperty;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.DeleteMode;
import io.ebeaninternal.server.persist.DmlUtil;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.ExtraJoin;
@@ -907,7 +908,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
case INSERT:
return changeLogFilter.includeInsert(request) ? insertBeanChange(request) : null;
case UPDATE:
case SOFT_DELETE:
case DELETE_SOFT:
return changeLogFilter.includeUpdate(request) ? updateBeanChange(request) : null;
case DELETE:
return changeLogFilter.includeDelete(request) ? deleteBeanChange(request) : null;
@@ -991,11 +992,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
jsonWriter.writeEndObject();
}
public SqlUpdate deleteById(Object id, List<Object> idList, boolean softDelete) {
public SqlUpdate deleteById(Object id, List<Object> idList, DeleteMode mode) {
if (id != null) {
return deleteById(id, softDelete);
return deleteById(id, mode);
} else {
return deleteByIdList(idList, softDelete);
return deleteByIdList(idList, mode);
}
}
@@ -1017,9 +1018,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return SQL that can be used to delete a list of Id's without any optimistic
* concurrency checking.
*/
private SqlUpdate deleteByIdList(List<Object> idList, boolean softDelete) {
private SqlUpdate deleteByIdList(List<Object> idList, DeleteMode mode) {
String baseSql = softDelete ? softDeleteByIdInSql : deleteByIdInSql;
String baseSql = mode.isHard() ? deleteByIdInSql : softDeleteByIdInSql;
StringBuilder sb = new StringBuilder(baseSql);
String inClause = idBinder.getIdInValueExprDelete(idList.size());
sb.append(inClause);
@@ -1033,9 +1034,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return SQL that can be used to delete by Id without any optimistic
* concurrency checking.
*/
private SqlUpdate deleteById(Object id, boolean softDelete) {
private SqlUpdate deleteById(Object id, DeleteMode mode) {
String baseSql = softDelete ? softDeleteByIdSql : deleteByIdSql;
String baseSql = mode.isHard() ? deleteByIdSql : softDeleteByIdSql;
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(baseSql);
Object[] bindValues = idBinder.getBindValues(id);
@@ -6,6 +6,7 @@ import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.expression.DefaultExpressionRequest;
import io.ebeaninternal.server.expression.IdInExpression;
import io.ebeaninternal.server.persist.DeleteMode;
import java.util.LinkedHashMap;
import java.util.List;
@@ -72,16 +73,16 @@ public class IntersectionRow {
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
}
public SqlUpdate createDelete(SpiEbeanServer server, boolean softDelete) {
public SqlUpdate createDelete(SpiEbeanServer server, DeleteMode deleteMode) {
BindParams bindParams = new BindParams();
StringBuilder sb = new StringBuilder();
if (softDelete) {
if (deleteMode.isHard()) {
sb.append("delete from ").append(tableName);
} else {
sb.append("update ").append(tableName).append(" set ");
sb.append(targetDescriptor.getSoftDeleteDbSet());
} else {
sb.append("delete from ").append(tableName);
}
sb.append(" where ");
@@ -142,7 +142,7 @@ public class BatchedBeanHolder {
return inserts.size();
case UPDATE:
case SOFT_DELETE:
case DELETE_SOFT:
if (updates == null) {
updates = new ArrayList<>();
}
@@ -357,9 +357,8 @@ public final class DefaultPersister implements Persister {
/**
* Recursively delete the bean. This calls back to the EbeanServer.
*/
private int deleteRecurse(EntityBean detailBean, Transaction t, boolean softDelete) {
Type deleteType = softDelete ? Type.SOFT_DELETE : Type.DELETE_PERMANENT;
return deleteRequest(createRequest(detailBean, t, deleteType));
private int deleteRecurse(EntityBean detailBean, Transaction t, DeleteMode deleteMode) {
return deleteRequest(createRequest(detailBean, t, deleteMode.persistType()));
}
@Override
@@ -605,13 +604,13 @@ public final class DefaultPersister implements Persister {
}
}
private void deleteList(List<?> beanList, SpiTransaction t, boolean softDelete, boolean children) {
private void deleteList(List<?> beanList, SpiTransaction t, DeleteMode deleteMode, boolean children) {
if (children) {
t.depth(-1);
t.checkBatchEscalationOnCollection();
}
for (Object aBeanList : beanList) {
deleteRecurse((EntityBean) aBeanList, t, softDelete);
deleteRecurse((EntityBean) aBeanList, t, deleteMode);
}
if (children) {
t.flushBatchOnCollection();
@@ -630,9 +629,9 @@ public final class DefaultPersister implements Persister {
}
BeanDescriptor<?> descriptor = beanDescriptorManager.getBeanDescriptor(beanType);
boolean softDelete = !permanent && descriptor.isSoftDelete();
DeleteMode deleteMode = (permanent || !descriptor.isSoftDelete()) ? DeleteMode.HARD : DeleteMode.SOFT;
if (descriptor.isMultiTenant()) {
return deleteAsBeans(ids, transaction, permanent, descriptor);
return deleteAsBeans(ids, transaction, deleteMode, descriptor);
}
ArrayList<Object> idList = new ArrayList<>(ids.size());
@@ -641,19 +640,19 @@ public final class DefaultPersister implements Persister {
idList.add(descriptor.convertId(id));
}
return delete(descriptor, null, idList, transaction, softDelete);
return delete(descriptor, null, idList, transaction, deleteMode);
}
/**
* Convert delete by Ids into delete many beans.
*/
private int deleteAsBeans(Collection<?> ids, Transaction transaction, boolean permanent, BeanDescriptor<?> descriptor) {
private int deleteAsBeans(Collection<?> ids, Transaction transaction, DeleteMode deleteMode, BeanDescriptor<?> descriptor) {
// convert to a delete by bean
int total = 0;
for (Object id : ids) {
EntityBean bean = descriptor.createEntityBean();
descriptor.convertSetId(id, bean);
int rowCount = deleteRecurse(bean, transaction, permanent);
int rowCount = deleteRecurse(bean, transaction, deleteMode);
if (rowCount == -1) {
total = -1;
} else if (total != -1) {
@@ -677,14 +676,14 @@ public final class DefaultPersister implements Persister {
}
id = descriptor.convertId(id);
boolean softDelete = !permanent && descriptor.isSoftDelete();
return delete(descriptor, id, null, transaction, softDelete);
DeleteMode deleteMode = (permanent || !descriptor.isSoftDelete()) ? DeleteMode.HARD : DeleteMode.SOFT;
return delete(descriptor, id, null, transaction, deleteMode);
}
/**
* Delete by Id or a List of Id's.
*/
private int delete(BeanDescriptor<?> descriptor, Object id, List<Object> idList, Transaction transaction, boolean softDelete) {
private int delete(BeanDescriptor<?> descriptor, Object id, List<Object> idList, Transaction transaction, DeleteMode deleteMode) {
SpiTransaction t = (SpiTransaction) transaction;
if (t.isPersistCascade()) {
@@ -693,14 +692,14 @@ public final class DefaultPersister implements Persister {
// We actually need to execute a query to get the foreign key values
// as they are required for the delete cascade. Query back just the
// Id and the appropriate foreign key values
Query<?> q = deleteRequiresQuery(descriptor, propImportDelete, softDelete);
Query<?> q = deleteRequiresQuery(descriptor, propImportDelete, deleteMode);
if (idList != null) {
q.where().idIn(idList);
if (t.isLogSummary()) {
t.logSummary("-- DeleteById of " + descriptor.getName() + " ids[" + idList + "] requires fetch of foreign key values");
}
List<?> beanList = server.findList(q, t);
deleteList(beanList, t, softDelete, false);
deleteList(beanList, t, deleteMode, false);
return beanList.size();
} else {
@@ -712,7 +711,7 @@ public final class DefaultPersister implements Persister {
if (bean == null) {
return 0;
} else {
return deleteRecurse(bean, t, softDelete);
return deleteRecurse(bean, t, deleteMode);
}
}
}
@@ -724,14 +723,14 @@ public final class DefaultPersister implements Persister {
for (BeanPropertyAssocOne<?> expOne : expOnes) {
BeanDescriptor<?> targetDesc = expOne.getTargetDescriptor();
// only cascade soft deletes when supported by target
if (!softDelete || targetDesc.isSoftDelete()) {
if (!softDelete && targetDesc.isDeleteByStatement()) {
if (deleteMode.isHard() || targetDesc.isSoftDelete()) {
if (deleteMode.isHard() && targetDesc.isDeleteByStatement()) {
SqlUpdate sqlDelete = expOne.deleteByParentId(id, idList);
executeSqlUpdate(sqlDelete, t);
} else {
List<Object> childIds = expOne.findIdsByParentId(id, idList, t);
if (childIds != null && !childIds.isEmpty()) {
deleteChildrenById(t, targetDesc, childIds, softDelete);
deleteChildrenById(t, targetDesc, childIds, deleteMode);
}
}
}
@@ -742,8 +741,8 @@ public final class DefaultPersister implements Persister {
for (BeanPropertyAssocMany<?> many : manys) {
BeanDescriptor<?> targetDesc = many.getTargetDescriptor();
// only cascade soft deletes when supported by target
if (!softDelete || targetDesc.isSoftDelete()) {
if (!softDelete && targetDesc.isDeleteByStatement()) {
if (deleteMode.isHard() || targetDesc.isSoftDelete()) {
if (deleteMode.isHard() && targetDesc.isDeleteByStatement()) {
// we can just delete children with a single statement
SqlUpdate sqlDelete = many.deleteByParentId(id, idList);
executeSqlUpdate(sqlDelete, t);
@@ -751,14 +750,14 @@ public final class DefaultPersister implements Persister {
// we need to fetch the Id's to delete (recurse or notify L2 cache)
List<Object> childIds = many.findIdsByParentId(id, idList, t, null);
if (!childIds.isEmpty()) {
delete(targetDesc, null, childIds, t, softDelete);
delete(targetDesc, null, childIds, t, deleteMode);
}
}
}
}
}
if (!softDelete) {
if (deleteMode.isHard()) {
// ManyToMany's ... delete from intersection table
BeanPropertyAssocMany<?>[] manys = descriptor.propertiesManyToMany();
for (BeanPropertyAssocMany<?> many : manys) {
@@ -771,7 +770,7 @@ public final class DefaultPersister implements Persister {
}
// delete the bean(s)
SqlUpdate deleteById = descriptor.deleteById(id, idList, softDelete);
SqlUpdate deleteById = descriptor.deleteById(id, idList, deleteMode);
if (t.isLogSummary()) {
if (idList != null) {
t.logSummary("-- Deleting " + descriptor.getName() + " Ids: " + idList);
@@ -822,7 +821,7 @@ public final class DefaultPersister implements Persister {
* We need to create and execute a query to get the foreign key values as
* the delete cascades to them (foreign keys).
*/
private Query<?> deleteRequiresQuery(BeanDescriptor<?> desc, BeanPropertyAssocOne<?>[] propImportDelete, boolean softDelete) {
private Query<?> deleteRequiresQuery(BeanDescriptor<?> desc, BeanPropertyAssocOne<?>[] propImportDelete, DeleteMode deleteMode) {
Query<?> q = server.createQuery(desc.getBeanType());
StringBuilder sb = new StringBuilder(30);
@@ -831,7 +830,7 @@ public final class DefaultPersister implements Persister {
}
q.setAutoTune(false);
q.select(sb.toString());
if (!softDelete) {
if (deleteMode.isHard() && desc.isSoftDelete()) {
// hard delete so we want this query to include logically deleted rows (if any)
q.setIncludeSoftDeletes();
}
@@ -956,7 +955,7 @@ public final class DefaultPersister implements Persister {
BeanDescriptor<?> desc = request.getBeanDescriptor();
EntityBean parentBean = request.getEntityBean();
boolean softDelete = request.isSoftDelete();
DeleteMode deleteMode = request.deleteMode();
BeanPropertyAssocOne<?>[] expOnes = desc.propertiesOneExportedDelete();
if (expOnes.length > 0) {
@@ -964,11 +963,11 @@ public final class DefaultPersister implements Persister {
DeleteUnloadedForeignKeys unloaded = null;
for (BeanPropertyAssocOne<?> prop : expOnes) {
// for soft delete check cascade type also supports soft delete
if (!softDelete || prop.isTargetSoftDelete()) {
if (deleteMode.isHard() || prop.isTargetSoftDelete()) {
if (request.isLoadedProperty(prop)) {
Object detailBean = prop.getValue(parentBean);
if (detailBean != null) {
deleteRecurse((EntityBean) detailBean, t, softDelete);
deleteRecurse((EntityBean) detailBean, t, deleteMode);
}
} else {
if (unloaded == null) {
@@ -987,7 +986,7 @@ public final class DefaultPersister implements Persister {
// Many's with delete cascade
for (BeanPropertyAssocMany<?> many : desc.propertiesManyDelete()) {
if (many.hasJoinTable()) {
if (!softDelete) {
if (deleteMode.isHard()) {
// delete associated rows from intersection table (but not during soft delete)
deleteAssocManyIntersection(parentBean, many, t, request.isPublish());
}
@@ -996,7 +995,7 @@ public final class DefaultPersister implements Persister {
if (ModifyListenMode.REMOVALS == many.getModifyListenMode()) {
// PrivateOwned ...
// if soft delete then check target also supports soft delete
if (!softDelete || many.isTargetSoftDelete()) {
if (deleteMode.isHard() || many.isTargetSoftDelete()) {
Object details = many.getValue(parentBean);
if (details instanceof BeanCollection<?>) {
Set<?> modifyRemovals = ((BeanCollection<?>) details).getModifyRemovals();
@@ -1006,7 +1005,7 @@ public final class DefaultPersister implements Persister {
for (Object detail : modifyRemovals) {
EntityBean detailBean = (EntityBean) detail;
if (many.hasId(detailBean)) {
deleteRecurse(detailBean, t, softDelete);
deleteRecurse(detailBean, t, deleteMode);
}
}
}
@@ -1014,7 +1013,7 @@ public final class DefaultPersister implements Persister {
}
}
deleteManyDetails(t, desc, parentBean, many, null, softDelete);
deleteManyDetails(t, desc, parentBean, many, null, deleteMode);
}
}
@@ -1031,16 +1030,16 @@ public final class DefaultPersister implements Persister {
* </p>
*/
void deleteManyDetails(SpiTransaction t, BeanDescriptor<?> desc, EntityBean parentBean,
BeanPropertyAssocMany<?> many, List<Object> excludeDetailIds, boolean softDelete) {
BeanPropertyAssocMany<?> many, List<Object> excludeDetailIds, DeleteMode deleteMode) {
if (many.getCascadeInfo().isDelete()) {
// cascade delete the beans in the collection
BeanDescriptor<?> targetDesc = many.getTargetDescriptor();
if (!softDelete || targetDesc.isSoftDelete()) {
if (deleteMode.isHard() || targetDesc.isSoftDelete()) {
if (targetDesc.isDeleteByStatement()) {
// Just delete all the children with one statement
IntersectionRow intRow = many.buildManyDeleteChildren(parentBean, excludeDetailIds);
SqlUpdate sqlDelete = intRow.createDelete(server, softDelete);
SqlUpdate sqlDelete = intRow.createDelete(server, deleteMode);
executeSqlUpdate(sqlDelete, t);
} else {
@@ -1048,7 +1047,7 @@ public final class DefaultPersister implements Persister {
Object parentId = desc.getId(parentBean);
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds);
if (!idsByParentId.isEmpty()) {
deleteChildrenById(t, targetDesc, idsByParentId, softDelete);
deleteChildrenById(t, targetDesc, idsByParentId, deleteMode);
}
}
}
@@ -1060,7 +1059,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, boolean softDelete) {
private void deleteChildrenById(SpiTransaction t, BeanDescriptor<?> targetDesc, List<Object> childIds, DeleteMode deleteMode) {
if (!targetDesc.isDeleteByBulk()) {
// convert into a list of reference objects and perform delete by object
@@ -1068,11 +1067,11 @@ public final class DefaultPersister implements Persister {
for (Object id : childIds) {
refList.add(targetDesc.createReference(id, null));
}
deleteList(refList, t, softDelete, true);
deleteList(refList, t, deleteMode, true);
} else {
// perform delete by statement if possible
delete(targetDesc, null, childIds, t, softDelete);
delete(targetDesc, null, childIds, t, deleteMode);
}
}
@@ -1137,16 +1136,16 @@ public final class DefaultPersister implements Persister {
*/
private void deleteAssocOne(PersistRequestBean<?> request) {
boolean softDelete = request.isSoftDelete();
DeleteMode deleteMode = request.deleteMode();
for (BeanPropertyAssocOne<?> prop : request.getBeanDescriptor().propertiesOneImportedDelete()) {
if (!softDelete || prop.isTargetSoftDelete()) {
if (deleteMode.isHard() || prop.isTargetSoftDelete()) {
if (request.isLoadedProperty(prop)) {
Object detailBean = prop.getValue(request.getEntityBean());
if (detailBean != null) {
EntityBean detail = (EntityBean) detailBean;
if (prop.hasId(detail)) {
deleteRecurse(detail, request.getTransaction(), request.isSoftDelete());
deleteRecurse(detail, request.getTransaction(), deleteMode);
}
}
}
@@ -1245,7 +1244,7 @@ public final class DefaultPersister implements Persister {
type = Type.DELETE;
} else if (type == Type.DELETE && mgr.getBeanDescriptor().isSoftDelete()) {
// automatically convert to soft delete for types that support it
type = Type.SOFT_DELETE;
type = Type.DELETE_SOFT;
}
return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type, flags);
@@ -0,0 +1,38 @@
package io.ebeaninternal.server.persist;
import io.ebeaninternal.server.core.PersistRequest;
/**
* The delete mode of the persistence request. The mode is determined at the beginning of the request
* as being a Hard or Soft delete based and then the mode is used for that request and any cascading.
*/
public enum DeleteMode {
/**
* Soft/logical delete.
*/
SOFT(PersistRequest.Type.DELETE_SOFT, false),
/**
* Hard/permanent delete.
*/
HARD(PersistRequest.Type.DELETE_PERMANENT, true);
private boolean hard;
private PersistRequest.Type persistType;
DeleteMode(PersistRequest.Type persistType, boolean hard) {
this.persistType = persistType;
this.hard = hard;
}
public PersistRequest.Type persistType() {
return persistType;
}
public boolean isHard() {
return hard;
}
}
@@ -33,6 +33,7 @@ class SaveManyBeans extends SaveManyBase {
private final BeanDescriptor<?> targetDescriptor;
private final boolean isMap;
private final boolean saveRecurseSkippable;
private final DeleteMode deleteMode;
private Collection<?> collection;
private DefaultPersister persister;
@@ -47,6 +48,7 @@ class SaveManyBeans extends SaveManyBase {
this.targetDescriptor = many.getTargetDescriptor();
this.isMap = many.getManyType().isMap();
this.saveRecurseSkippable = many.isSaveRecurseSkippable();
this.deleteMode = targetDescriptor.isSoftDelete() ? DeleteMode.SOFT : DeleteMode.HARD;
}
@Override
@@ -58,7 +60,7 @@ class SaveManyBeans extends SaveManyBase {
// we only allow one direction based on first traversed basis
boolean saveIntersectionFromThisDirection = isSaveIntersection();
if (cascade) {
saveAssocManyDetails( false);
saveAssocManyDetails(false);
}
// for ManyToMany save the 'relationship' via inserts/deletes
// into/from the intersection table
@@ -125,7 +127,7 @@ class SaveManyBeans extends SaveManyBase {
// collect the Id's (to exclude from deleteManyDetails)
List<Object> detailIds = collectIds(collection, targetDescriptor, isMap);
// deleting missing children - children not in our collected detailIds
persister.deleteManyDetails(transaction, many.getBeanDescriptor(), parentBean, many, detailIds, false);
persister.deleteManyDetails(transaction, many.getBeanDescriptor(), parentBean, many, detailIds, deleteMode);
}
transaction.depth(+1);
@@ -320,7 +322,7 @@ class SaveManyBeans extends SaveManyBase {
// the object from the 'other' side of the ManyToMany
// build a intersection row for 'delete'
IntersectionRow intRow = many.buildManyToManyMapBean(parentBean, otherDelete, publish);
SqlUpdate sqlDelete = intRow.createDelete(server, false);
SqlUpdate sqlDelete = intRow.createDelete(server, DeleteMode.HARD);
persister.executeSqlUpdate(sqlDelete, transaction);
}
}
@@ -176,7 +176,7 @@ public class BeanPersistIds {
addUpdateId(id);
break;
case DELETE:
case SOFT_DELETE:
case DELETE_SOFT:
addDeleteId(id);
break;
@@ -0,0 +1,33 @@
package org.tests.model.softdelete;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
@Entity
public class EsdDetail extends BaseSoftDelete {
String name;
@ManyToOne(optional = false)
EsdMaster master;
public EsdDetail(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public EsdMaster getMaster() {
return master;
}
public void setMaster(EsdMaster master) {
this.master = master;
}
}
@@ -0,0 +1,35 @@
package org.tests.model.softdelete;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.OneToMany;
import java.util.List;
@Entity
public class EsdMaster extends BaseSoftDelete {
String name;
@OneToMany(mappedBy = "master", cascade = CascadeType.ALL)
List<EsdDetail> details;
public EsdMaster(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<EsdDetail> getDetails() {
return details;
}
public void setDetails(List<EsdDetail> details) {
this.details = details;
}
}
@@ -0,0 +1,74 @@
package org.tests.softdelete;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebeantest.LoggedSql;
import org.junit.Test;
import org.tests.model.softdelete.EsdDetail;
import org.tests.model.softdelete.EsdMaster;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestSoftDeleteStatelessUpdate extends BaseTestCase {
@Test
public void test() {
EsdMaster master = new EsdMaster("m1");
master.getDetails().add(new EsdDetail("d1"));
master.getDetails().add(new EsdDetail("d2"));
master.getDetails().add(new EsdDetail("d3"));
Ebean.save(master);
EsdMaster upd = new EsdMaster("m1-modified");
upd.setId(master.getId());
EsdDetail d1 = new EsdDetail("d1");
d1.setId(master.getDetails().get(0).getId());
upd.getDetails().add(d1);
EsdDetail d3 = new EsdDetail("d3-mod");
d3.setId(master.getDetails().get(2).getId());
upd.getDetails().add(d3);
LoggedSql.start();
Ebean.getDefaultServer().update(upd, null, true);
List<String> sql = LoggedSql.collect();
assertThat(sql).hasSize(4);
assertThat(sql.get(0)).contains("update esd_master set name=? where id=?");
assertThat(sql.get(1)).contains("update esd_detail set deleted=true where master_id = ? and not");
EsdMaster fetchedWithSoftDeletes = Ebean.find(EsdMaster.class)
.setId(master.getId())
.setIncludeSoftDeletes()
.fetch("details")
.findOne();
assertThat(fetchedWithSoftDeletes.getDetails()).hasSize(3);
sql = LoggedSql.collect();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("left join esd_detail t1 on t1.master_id = t0.id where t0.id = ?");
EsdMaster fetchedWithOutSoftDeletes = Ebean.find(EsdMaster.class)
.setId(master.getId())
.fetch("details")
.findOne();
assertThat(fetchedWithOutSoftDeletes.getDetails()).hasSize(2);
sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("left join esd_detail t1 on t1.master_id = t0.id and t1.deleted = false where t0.id = ?");
}
}