mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#1656 #1824 Stateless updates - Remove update deleteMissingChildren option, instead always use orphanRemoval
This commit is contained in:
@@ -396,15 +396,6 @@ public class DB {
|
||||
* called then no optimistic locking is performed (internally ConcurrencyMode.NONE is used).
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>{@link DatabaseConfig#setUpdatesDeleteMissingChildren(boolean)}: </b> When cascade saving to a
|
||||
* OneToMany or ManyToMany the updatesDeleteMissingChildren setting controls if any other children
|
||||
* that are in the database but are not in the collection are deleted.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>{@link DatabaseConfig#setUpdateChangesOnly(boolean)}: </b> The updateChangesOnly setting
|
||||
* controls if only the changed properties are included in the update or if all the loaded
|
||||
* properties are included instead.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // A 'stateless update' example
|
||||
@@ -414,9 +405,6 @@ public class DB {
|
||||
* database.update(customer);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see DatabaseConfig#setUpdatesDeleteMissingChildren(boolean)
|
||||
* @see DatabaseConfig#setUpdateChangesOnly(boolean)
|
||||
*/
|
||||
public static void update(Object bean) throws OptimisticLockException {
|
||||
getDefault().update(bean);
|
||||
|
||||
@@ -1216,9 +1216,6 @@ public interface Database {
|
||||
* database.update(customer);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see ServerConfig#setUpdatesDeleteMissingChildren(boolean)
|
||||
* @see ServerConfig#setUpdateChangesOnly(boolean)
|
||||
*/
|
||||
void update(Object bean) throws OptimisticLockException;
|
||||
|
||||
@@ -1227,16 +1224,6 @@ public interface Database {
|
||||
*/
|
||||
void update(Object bean, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Update a bean additionally specifying a transaction and the deleteMissingChildren setting.
|
||||
*
|
||||
* @param bean the bean to update
|
||||
* @param transaction the transaction to use (can be null).
|
||||
* @param deleteMissingChildren specify false if you do not want 'missing children' of a OneToMany
|
||||
* or ManyToMany to be automatically deleted.
|
||||
*/
|
||||
void update(Object bean, Transaction transaction, boolean deleteMissingChildren) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Update a collection of beans. If there is no current transaction one is created and used to
|
||||
* update all the beans in the collection.
|
||||
|
||||
@@ -355,11 +355,6 @@ public class ServerConfig {
|
||||
*/
|
||||
private boolean updateAllPropertiesInBatch;
|
||||
|
||||
/**
|
||||
* Default behaviour for updates when cascade save on a O2M or M2M to delete any missing children.
|
||||
*/
|
||||
private boolean updatesDeleteMissingChildren = true;
|
||||
|
||||
/**
|
||||
* Database platform configuration.
|
||||
*/
|
||||
@@ -2423,22 +2418,6 @@ public class ServerConfig {
|
||||
this.updateAllPropertiesInBatch = updateAllPropertiesInBatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if updates by default delete missing children when cascading save to a OneToMany or
|
||||
* ManyToMany. When not set this defaults to true.
|
||||
*/
|
||||
public boolean isUpdatesDeleteMissingChildren() {
|
||||
return updatesDeleteMissingChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if updates by default delete missing children when cascading save to a OneToMany or
|
||||
* ManyToMany. When not set this defaults to true.
|
||||
*/
|
||||
public void setUpdatesDeleteMissingChildren(boolean updatesDeleteMissingChildren) {
|
||||
this.updatesDeleteMissingChildren = updatesDeleteMissingChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if query statistics should be collected by ObjectGraphNode.
|
||||
*/
|
||||
@@ -2948,9 +2927,6 @@ public class ServerConfig {
|
||||
skipCacheAfterWrite = p.getBoolean("skipCacheAfterWrite", skipCacheAfterWrite);
|
||||
updateAllPropertiesInBatch = p.getBoolean("updateAllPropertiesInBatch", updateAllPropertiesInBatch);
|
||||
|
||||
boolean defaultDeleteMissingChildren = p.getBoolean("defaultDeleteMissingChildren", updatesDeleteMissingChildren);
|
||||
updatesDeleteMissingChildren = p.getBoolean("updatesDeleteMissingChildren", defaultDeleteMissingChildren);
|
||||
|
||||
if (p.get("batch.mode") != null || p.get("persistBatching") != null) {
|
||||
throw new IllegalArgumentException("Property 'batch.mode' or 'persistBatching' is being set but no longer used. Please change to use 'persistBatchMode'");
|
||||
}
|
||||
|
||||
@@ -1736,30 +1736,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
((EntityBean) bean)._ebean_getIntercept().setDirty(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the bean using the default 'updatesDeleteMissingChildren' setting.
|
||||
*/
|
||||
@Override
|
||||
public void update(Object bean) {
|
||||
update(bean, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the bean using the default 'updatesDeleteMissingChildren' setting.
|
||||
*/
|
||||
@Override
|
||||
public void update(Object bean, Transaction t) {
|
||||
persister.update(checkEntityBean(bean), t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the bean specifying the deleteMissingChildren option.
|
||||
*/
|
||||
@Override
|
||||
public void update(Object bean, Transaction t, boolean deleteMissingChildren) {
|
||||
persister.update(checkEntityBean(bean), t, deleteMissingChildren);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAll(Collection<?> beans) throws OptimisticLockException {
|
||||
updateAll(beans, null);
|
||||
|
||||
@@ -104,8 +104,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
|
||||
private boolean notifyCache;
|
||||
|
||||
private boolean deleteMissingChildren;
|
||||
|
||||
/**
|
||||
* Flag used to detect when only many properties where updated via a cascade. Used to ensure
|
||||
* appropriate caches are updated in that case.
|
||||
@@ -629,20 +627,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
return beanDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a stateless update should also delete any missing details beans.
|
||||
*/
|
||||
public boolean isDeleteMissingChildren() {
|
||||
return deleteMissingChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if deleteMissingChildren occurs on cascade save to OneToMany or ManyToMany.
|
||||
*/
|
||||
public void setDeleteMissingChildren(boolean deleteMissingChildren) {
|
||||
this.deleteMissingChildren = deleteMissingChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the update after potential modifications in a BeanPersistController.
|
||||
*/
|
||||
@@ -1476,4 +1460,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
this.saveManyIntersections.add(saveManyIntersection);
|
||||
}
|
||||
|
||||
public boolean isForcedUpdate() {
|
||||
return Flags.isUpdateForce(flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,6 @@ public interface Persister {
|
||||
*/
|
||||
void update(EntityBean entityBean, Transaction t);
|
||||
|
||||
/**
|
||||
* Update the bean specifying deleteMissingChildren.
|
||||
*/
|
||||
void update(EntityBean entityBean, Transaction t, boolean deleteMissingChildren);
|
||||
|
||||
/**
|
||||
* Force an Insert using the given bean.
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
private final TableJoin intersectionJoin;
|
||||
private final String intersectionPublishTable;
|
||||
private final String intersectionDraftTable;
|
||||
private final boolean orphanRemoval;
|
||||
|
||||
private IntersectionTable intersectionTable;
|
||||
|
||||
@@ -123,6 +124,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
public BeanPropertyAssocMany(BeanDescriptor<?> descriptor, DeployBeanPropertyAssocMany<T> deploy) {
|
||||
super(descriptor, deploy);
|
||||
this.unidirectional = deploy.isUnidirectional();
|
||||
this.orphanRemoval = deploy.isOrphanRemoval();
|
||||
this.o2mJoinTable = deploy.isO2mJoinTable();
|
||||
this.hasOrderColumn = deploy.hasOrderColumn();
|
||||
this.manyToMany = deploy.isManyToMany();
|
||||
@@ -510,6 +512,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
return hasOrderColumn;
|
||||
}
|
||||
|
||||
public boolean isOrphanRemoval() {
|
||||
return orphanRemoval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAssocMany() {
|
||||
return true;
|
||||
|
||||
@@ -39,11 +39,8 @@ import java.util.Map;
|
||||
public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STreePropertyAssocOne {
|
||||
|
||||
private final boolean oneToOne;
|
||||
|
||||
private final boolean oneToOneExported;
|
||||
|
||||
private final boolean orphanRemoval;
|
||||
|
||||
private final boolean primaryKeyExport;
|
||||
private final boolean primaryKeyJoin;
|
||||
|
||||
@@ -89,7 +86,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
for (BeanProperty embeddedProp : embeddedProps) {
|
||||
embeddedPropsMap.put(embeddedProp.getName(), embeddedProp);
|
||||
}
|
||||
|
||||
} else {
|
||||
embeddedProps = null;
|
||||
embeddedPropsMap = null;
|
||||
|
||||
@@ -45,6 +45,8 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
|
||||
|
||||
private PropertyForeignKey foreignKey;
|
||||
|
||||
boolean orphanRemoval;
|
||||
|
||||
/**
|
||||
* Construct the property.
|
||||
*/
|
||||
@@ -148,6 +150,14 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
|
||||
}
|
||||
}
|
||||
|
||||
public void setOrphanRemoval() {
|
||||
orphanRemoval = true;
|
||||
}
|
||||
|
||||
public boolean isOrphanRemoval() {
|
||||
return orphanRemoval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set DocStoreEmbedded deployment information.
|
||||
*/
|
||||
|
||||
@@ -21,8 +21,6 @@ public class DeployBeanPropertyAssocOne<T> extends DeployBeanPropertyAssoc<T> {
|
||||
|
||||
private String columnPrefix;
|
||||
|
||||
private boolean orphanRemoval;
|
||||
|
||||
/**
|
||||
* Create the property.
|
||||
*/
|
||||
@@ -145,14 +143,6 @@ public class DeployBeanPropertyAssocOne<T> extends DeployBeanPropertyAssoc<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public void setOrphanRemoval(boolean orphanRemoval) {
|
||||
this.orphanRemoval = orphanRemoval;
|
||||
}
|
||||
|
||||
public boolean isOrphanRemoval() {
|
||||
return orphanRemoval;
|
||||
}
|
||||
|
||||
public void setJoinType(boolean outerJoin) {
|
||||
tableJoin.setType(outerJoin ? SqlJoinType.OUTER : SqlJoinType.INNER);
|
||||
}
|
||||
|
||||
@@ -84,11 +84,13 @@ class AnnotationAssocManys extends AnnotationParser {
|
||||
if (oneToMany != null) {
|
||||
readToOne(oneToMany, prop);
|
||||
if (readOrphanRemoval(oneToMany)) {
|
||||
prop.setOrphanRemoval();
|
||||
prop.setModifyListenMode(ModifyListenMode.REMOVALS);
|
||||
prop.getCascadeInfo().setDelete(true);
|
||||
}
|
||||
PrivateOwned privateOwned = get(prop, PrivateOwned.class);
|
||||
if (privateOwned != null) {
|
||||
prop.setOrphanRemoval();
|
||||
prop.setModifyListenMode(ModifyListenMode.REMOVALS);
|
||||
prop.getCascadeInfo().setDelete(privateOwned.cascadeRemove());
|
||||
}
|
||||
|
||||
@@ -225,7 +225,9 @@ public class AnnotationAssocOnes extends AnnotationParser {
|
||||
prop.setNullable(propAnn.optional());
|
||||
prop.setFetchType(propAnn.fetch());
|
||||
prop.setMappedBy(propAnn.mappedBy());
|
||||
prop.setOrphanRemoval(readOrphanRemoval(propAnn));
|
||||
if (readOrphanRemoval(propAnn)) {
|
||||
prop.setOrphanRemoval();
|
||||
}
|
||||
if (!"".equals(propAnn.mappedBy())) {
|
||||
prop.setOneToOneExported();
|
||||
}
|
||||
|
||||
@@ -74,11 +74,8 @@ public final class DefaultPersister implements Persister {
|
||||
|
||||
private final BeanDescriptorManager beanDescriptorManager;
|
||||
|
||||
private final boolean updatesDeleteMissingChildren;
|
||||
|
||||
public DefaultPersister(SpiEbeanServer server, Binder binder, BeanDescriptorManager descMgr) {
|
||||
this.server = server;
|
||||
this.updatesDeleteMissingChildren = server.getServerConfig().isUpdatesDeleteMissingChildren();
|
||||
this.beanDescriptorManager = descMgr;
|
||||
this.persistExecute = new DefaultPersistExecute(binder, server.getServerConfig().getPersistBatchSize());
|
||||
}
|
||||
@@ -407,17 +404,7 @@ public final class DefaultPersister implements Persister {
|
||||
*/
|
||||
@Override
|
||||
public void update(EntityBean entityBean, Transaction t) {
|
||||
update(entityBean, t, updatesDeleteMissingChildren);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the bean specifying deleteMissingChildren.
|
||||
*/
|
||||
@Override
|
||||
public void update(EntityBean entityBean, Transaction t, boolean deleteMissingChildren) {
|
||||
|
||||
PersistRequestBean<?> req = createRequest(entityBean, t, PersistRequest.Type.UPDATE);
|
||||
req.setDeleteMissingChildren(deleteMissingChildren);
|
||||
req.checkDraft();
|
||||
try {
|
||||
req.initTransIfRequiredWithBatchCascade();
|
||||
@@ -448,8 +435,7 @@ public final class DefaultPersister implements Persister {
|
||||
@Override
|
||||
public void save(EntityBean bean, Transaction t) {
|
||||
if (bean._ebean_getIntercept().isUpdate()) {
|
||||
// deleteMissingChildren is false when using 'save' on 'loaded' beans
|
||||
update(bean, t, false);
|
||||
update(bean, t);
|
||||
} else {
|
||||
insert(bean, t);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,10 @@ public final class Flags {
|
||||
return (state & PUBLISH_MERGE_NORMAL) != 0;
|
||||
}
|
||||
|
||||
public static boolean isUpdateForce(int state) {
|
||||
return !isSet(state, INSERT) && !isSet(state, NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given flag is set.
|
||||
*/
|
||||
@@ -149,10 +153,10 @@ public final class Flags {
|
||||
}
|
||||
|
||||
private static int set(int state, int flag) {
|
||||
return (state |= flag);
|
||||
return state | flag;
|
||||
}
|
||||
|
||||
private static int unset(int state, int flag) {
|
||||
return state &= ~flag;
|
||||
return state & ~flag;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
|
||||
private Collection<?> collection;
|
||||
private final DefaultPersister persister;
|
||||
private boolean deleteMissing;
|
||||
private int sortOrder;
|
||||
|
||||
SaveManyBeans(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request, DefaultPersister persister) {
|
||||
@@ -56,18 +55,17 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
void save() {
|
||||
|
||||
if (many.hasJoinTable()) {
|
||||
|
||||
// check if we can save the m2m intersection in this direction
|
||||
// we only allow one direction based on first traversed basis
|
||||
boolean saveIntersectionFromThisDirection = isSaveIntersection();
|
||||
if (cascade) {
|
||||
saveAssocManyDetails(false);
|
||||
saveAssocManyDetails();
|
||||
}
|
||||
// for ManyToMany save the 'relationship' via inserts/deletes
|
||||
// into/from the intersection table
|
||||
if (saveIntersectionFromThisDirection) {
|
||||
// only allowed on one direction of a m2m based on beanName
|
||||
saveAssocManyIntersection(request.isDeleteMissingChildren());
|
||||
saveAssocManyIntersection();
|
||||
} else {
|
||||
resetModifyState();
|
||||
}
|
||||
@@ -79,7 +77,7 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
}
|
||||
if (cascade) {
|
||||
// potentially deletes 'missing children' for 'stateless update'
|
||||
saveAssocManyDetails(request.isDeleteMissingChildren());
|
||||
saveAssocManyDetails();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,13 +96,9 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
/**
|
||||
* Save the details from a OneToMany collection.
|
||||
*/
|
||||
private void saveAssocManyDetails(boolean deleteMissingChildren) {
|
||||
|
||||
this.deleteMissing = deleteMissingChildren;
|
||||
|
||||
private void saveAssocManyDetails() {
|
||||
// check that the list is not null and if it is a BeanCollection
|
||||
// check that is has been populated (don't trigger lazy loading)
|
||||
|
||||
collection = BeanCollectionUtil.getActualEntries(value);
|
||||
if (collection != null) {
|
||||
processDetails();
|
||||
@@ -127,7 +121,7 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
targetDescriptor.preAllocateIds(collection.size());
|
||||
}
|
||||
|
||||
if (deleteMissing) {
|
||||
if (!insertedParent && many.isOrphanRemoval() && request.isForcedUpdate()) {
|
||||
// collect the Id's (to exclude from deleteManyDetails)
|
||||
List<Object> detailIds = collectIds(collection, targetDescriptor, isMap);
|
||||
// deleting missing children - children not in our collected detailIds
|
||||
@@ -142,7 +136,6 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
transaction.depth(-1);
|
||||
}
|
||||
|
||||
|
||||
private void saveAllBeans(BeanProperty orderColumn) {
|
||||
|
||||
// if a map, then we get the key value and
|
||||
@@ -240,17 +233,16 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
* This is done via MapBeans.
|
||||
* </p>
|
||||
*/
|
||||
private void saveAssocManyIntersection(boolean deleteMissingChildren) {
|
||||
private void saveAssocManyIntersection() {
|
||||
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (request.isQueueManyIntersection()) {
|
||||
// queue/delay until bean persist request is flushed
|
||||
this.deleteMissing = deleteMissingChildren;
|
||||
request.addManyIntersection(this);
|
||||
} else {
|
||||
saveAssocManyIntersection(deleteMissingChildren, false);
|
||||
saveAssocManyIntersection(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,13 +250,14 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
* Push intersection table changes onto batch flush queue.
|
||||
*/
|
||||
public void saveIntersectionBatch() {
|
||||
saveAssocManyIntersection(deleteMissing, true);
|
||||
saveAssocManyIntersection(true);
|
||||
}
|
||||
|
||||
private void saveAssocManyIntersection(boolean deleteMissingChildren, boolean queue) {
|
||||
private void saveAssocManyIntersection(boolean queue) {
|
||||
|
||||
boolean forcedUpdate = request.isForcedUpdate();
|
||||
boolean vanillaCollection = !(value instanceof BeanCollection<?>);
|
||||
if (vanillaCollection || deleteMissingChildren) {
|
||||
if (vanillaCollection || forcedUpdate) {
|
||||
// delete all intersection rows and then treat all
|
||||
// beans in the collection as additions
|
||||
persister.deleteManyIntersection(parentBean, many, transaction, publish, queue);
|
||||
@@ -273,7 +266,7 @@ public class SaveManyBeans extends SaveManyBase {
|
||||
Collection<?> deletions = null;
|
||||
Collection<?> additions;
|
||||
|
||||
if (insertedParent || vanillaCollection || deleteMissingChildren) {
|
||||
if (insertedParent || vanillaCollection || forcedUpdate) {
|
||||
// treat everything in the list/set/map as an intersection addition
|
||||
if (value instanceof Map<?, ?>) {
|
||||
additions = ((Map<?, ?>) value).values();
|
||||
|
||||
@@ -809,10 +809,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
|
||||
public void update(Object bean, Transaction t) throws OptimisticLockException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Object bean, Transaction transaction, boolean deleteMissingChildren) throws OptimisticLockException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insert(Object bean) {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package org.tests.cascade;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import java.util.List;
|
||||
|
||||
import static javax.persistence.CascadeType.ALL;
|
||||
|
||||
@Entity
|
||||
public class COOne {
|
||||
|
||||
@Id
|
||||
private long id;
|
||||
|
||||
private final String name;
|
||||
|
||||
@OneToMany(cascade = ALL, orphanRemoval = true)
|
||||
private List<COOneMany> children;
|
||||
|
||||
public COOne(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<COOneMany> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.tests.cascade;
|
||||
|
||||
import io.ebean.annotation.SoftDelete;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class COOneMany {
|
||||
|
||||
@Id
|
||||
private long id;
|
||||
|
||||
@SoftDelete
|
||||
private boolean deleted;
|
||||
|
||||
private String name;
|
||||
|
||||
public COOneMany(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.tests.cascade;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
import static javax.persistence.CascadeType.ALL;
|
||||
|
||||
@Entity
|
||||
public class CORoot {
|
||||
|
||||
@Id
|
||||
private long id;
|
||||
|
||||
private final String name;
|
||||
|
||||
@OneToOne(cascade = ALL, orphanRemoval = true)
|
||||
private COOne one;
|
||||
|
||||
public CORoot(String name, COOne one) {
|
||||
this.name = name;
|
||||
this.one = one;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public COOne getOne() {
|
||||
return one;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.tests.cascade;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class TestCascadeOrphanStatelessUpdate extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void update() {
|
||||
|
||||
final CORoot orig = setup();
|
||||
|
||||
CORoot root = DB.find(CORoot.class, orig.getId());
|
||||
assertNotNull(root);
|
||||
assertThat(root.getOne().getChildren()).hasSize(3);
|
||||
|
||||
final JsonContext jsonContext = DB.json();
|
||||
String asJson = jsonContext.toJson(root);
|
||||
|
||||
final CORoot deserialized = jsonContext.toBean(CORoot.class, asJson);
|
||||
|
||||
final COOne one = deserialized.getOne();
|
||||
COOneMany removed0 = one.getChildren().remove(0);
|
||||
COOneMany removed1 = one.getChildren().remove(1);
|
||||
|
||||
one.getChildren().add(new COOneMany("m3"));
|
||||
one.getChildren().add(new COOneMany("m4"));
|
||||
|
||||
DB.update(deserialized);
|
||||
|
||||
CORoot saved = DB.find(CORoot.class, orig.getId());
|
||||
assertNotNull(saved);
|
||||
assertThat(saved.getOne().getChildren()).hasSize(3);
|
||||
|
||||
List<COOneMany> softDeleted = DB.find(COOneMany.class)
|
||||
.setIncludeSoftDeletes()
|
||||
.where()
|
||||
.idIn(asList(removed0.getId(), removed1.getId()))
|
||||
.findList();
|
||||
|
||||
assertThat(softDeleted).hasSize(2);
|
||||
assertThat(softDeleted.stream().map(COOneMany::getId).collect(toList()))
|
||||
.contains(removed0.getId(), removed1.getId());
|
||||
}
|
||||
|
||||
private CORoot setup() {
|
||||
COOne one = new COOne("one");
|
||||
one.getChildren().add(new COOneMany("m0"));
|
||||
one.getChildren().add(new COOneMany("m1"));
|
||||
one.getChildren().add(new COOneMany("m2"));
|
||||
|
||||
CORoot root = new CORoot("r0", one);
|
||||
DB.save(root);
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
package org.tests.model.softdelete;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.OneToMany;
|
||||
import java.util.List;
|
||||
|
||||
import static javax.persistence.CascadeType.ALL;
|
||||
|
||||
@Entity
|
||||
public class EsdMaster extends BaseSoftDelete {
|
||||
|
||||
String name;
|
||||
|
||||
@OneToMany(mappedBy = "master", cascade = CascadeType.ALL)
|
||||
@OneToMany(mappedBy = "master", cascade = ALL, orphanRemoval = true)
|
||||
List<EsdDetail> details;
|
||||
|
||||
public EsdMaster(String name) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.tests.softdelete;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.DB;
|
||||
import io.ebeantest.LoggedSql;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.softdelete.EsdDetail;
|
||||
@@ -21,8 +21,7 @@ public class TestSoftDeleteStatelessUpdate extends BaseTestCase {
|
||||
master.getDetails().add(new EsdDetail("d2"));
|
||||
master.getDetails().add(new EsdDetail("d3"));
|
||||
|
||||
|
||||
Ebean.save(master);
|
||||
DB.save(master);
|
||||
|
||||
EsdMaster upd = new EsdMaster("m1-modified");
|
||||
upd.setId(master.getId());
|
||||
@@ -38,7 +37,7 @@ public class TestSoftDeleteStatelessUpdate extends BaseTestCase {
|
||||
|
||||
LoggedSql.start();
|
||||
|
||||
Ebean.getDefaultServer().update(upd, null, true);
|
||||
DB.update(upd);
|
||||
|
||||
List<String> sql = LoggedSql.collect();
|
||||
assertThat(sql).hasSize(5);
|
||||
@@ -47,7 +46,7 @@ public class TestSoftDeleteStatelessUpdate extends BaseTestCase {
|
||||
assertThat(sql.get(1)).contains("update esd_detail set deleted=true where master_id = ? and not");
|
||||
}
|
||||
|
||||
EsdMaster fetchedWithSoftDeletes = Ebean.find(EsdMaster.class)
|
||||
EsdMaster fetchedWithSoftDeletes = DB.find(EsdMaster.class)
|
||||
.setId(master.getId())
|
||||
.setIncludeSoftDeletes()
|
||||
.fetch("details")
|
||||
@@ -59,7 +58,7 @@ public class TestSoftDeleteStatelessUpdate extends BaseTestCase {
|
||||
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)
|
||||
EsdMaster fetchedWithOutSoftDeletes = DB.find(EsdMaster.class)
|
||||
.setId(master.getId())
|
||||
.fetch("details")
|
||||
.findOne();
|
||||
|
||||
@@ -48,7 +48,7 @@ public class TestStatelessUpdate extends TransactionalTestCase {
|
||||
updateAll.setId(e.getId());
|
||||
updateAll.setName("updAllProps");
|
||||
|
||||
server.update(updateAll, null, false);
|
||||
server.update(updateAll);
|
||||
|
||||
eBasic = server.find(EBasic.class, e.getId());
|
||||
assertEquals(e.getStatus(), eBasic.getStatus());
|
||||
@@ -383,9 +383,7 @@ public class TestStatelessUpdate extends TransactionalTestCase {
|
||||
updateCustomer.getContacts().add(updateContact3);
|
||||
|
||||
// not adding contact2 but it won't be deleted in this case
|
||||
boolean deleteMissingChildren = false;
|
||||
server.update(updateCustomer, null, deleteMissingChildren);
|
||||
|
||||
server.update(updateCustomer);
|
||||
|
||||
// assert
|
||||
Customer assCustomer = server.find(Customer.class, customer.getId());
|
||||
|
||||
Reference in New Issue
Block a user