mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-23 23:28:55 +00:00
#1425 - Stateless update with OneToMany with SoftDelete and deleteMissingChildren = true ... does a hard delete
This commit is contained in:
@@ -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 = ?");
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user