mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#1221 - ENH: Add a merge(bean) and merge(bean, paths)
This commit is contained in:
@@ -638,6 +638,16 @@ public final class Ebean {
|
||||
serverMgr.getDefaultServer().updateAll(beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
public static void merge(Object bean, MergeOptions options) {
|
||||
serverMgr.getDefaultServer().merge(bean, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save all the beans from a Collection.
|
||||
*/
|
||||
|
||||
@@ -1615,6 +1615,22 @@ public interface EbeanServer {
|
||||
*/
|
||||
void updateAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
void merge(Object bean, MergeOptions options);
|
||||
|
||||
/**
|
||||
* Merge the bean using the given merge options and a transaction.
|
||||
*
|
||||
* @param bean The bean to merge
|
||||
* @param options The options to control the merge
|
||||
*/
|
||||
void merge(Object bean, MergeOptions options, Transaction transaction);
|
||||
|
||||
/**
|
||||
* Insert the bean.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Options used to control a merge. Use MergeOptionsBuilder to create an instance.
|
||||
* <p>
|
||||
* Instances of MergeOptions are thread safe and safe to share across threads.
|
||||
*/
|
||||
public interface MergeOptions {
|
||||
|
||||
/**
|
||||
* Returns true if Id values are supplied by the client.
|
||||
* <p>
|
||||
* This would be the case when for example a mobile creates data in it's own local database
|
||||
* and then sync's. In this case often the id values are UUID.
|
||||
*/
|
||||
boolean isClientGeneratedIds();
|
||||
|
||||
/**
|
||||
* Return true if delete permanent should be used and false for 'normal' delete that allows soft deletes.
|
||||
*/
|
||||
boolean isDeletePermanent();
|
||||
|
||||
/**
|
||||
* Return the paths included in the merge.
|
||||
*/
|
||||
Set<String> paths();
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Builds a MergeOptions which is immutable and thread safe.
|
||||
*/
|
||||
public class MergeOptionsBuilder {
|
||||
|
||||
private Set<String> paths = new LinkedHashSet<>();
|
||||
|
||||
private boolean clientGeneratedIds;
|
||||
|
||||
private boolean deletePermanent = true;
|
||||
|
||||
/**
|
||||
* Add a path that will included in the merge.
|
||||
*
|
||||
* @param path The path relative to the root type.
|
||||
* @return The builder to chain another addPath() or build().
|
||||
*/
|
||||
public MergeOptionsBuilder addPath(String path) {
|
||||
paths.add(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if Id values are supplied by the client.
|
||||
* <p>
|
||||
* This would be the case when for example a mobile creates data in it's own local database
|
||||
* and then sync's. In this case often the id values are UUID.
|
||||
*/
|
||||
public MergeOptionsBuilder setClientGeneratedIds() {
|
||||
this.clientGeneratedIds = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set that deletions should use delete permanent (rather than default which allows soft deletes).
|
||||
*/
|
||||
public MergeOptionsBuilder setDeletePermanent() {
|
||||
this.deletePermanent = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the MergeOptions instance.
|
||||
*/
|
||||
public MOptions build() {
|
||||
return new MOptions(paths, clientGeneratedIds, deletePermanent);
|
||||
}
|
||||
|
||||
private static class MOptions implements MergeOptions {
|
||||
|
||||
private final boolean clientGeneratedIds;
|
||||
private final boolean deletePermanent;
|
||||
private final Set<String> paths;
|
||||
|
||||
private MOptions(Set<String> paths, boolean clientGeneratedIds, boolean deletePermanent) {
|
||||
this.paths = paths;
|
||||
this.clientGeneratedIds = clientGeneratedIds;
|
||||
this.deletePermanent = deletePermanent;
|
||||
}
|
||||
|
||||
public Set<String> paths() {
|
||||
return paths;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClientGeneratedIds() {
|
||||
return clientGeneratedIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeletePermanent() {
|
||||
return deletePermanent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,8 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
*/
|
||||
private int state;
|
||||
|
||||
private boolean forceUpdate;
|
||||
|
||||
private boolean readOnly;
|
||||
|
||||
private boolean dirty;
|
||||
@@ -292,6 +294,20 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bean to be updated when persisted (for merge).
|
||||
*/
|
||||
public void setForceUpdate(boolean forceUpdate) {
|
||||
this.forceUpdate = forceUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the entity should be updated.
|
||||
*/
|
||||
public boolean isUpdate() {
|
||||
return forceUpdate || state == STATE_LOADED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the entity has been loaded.
|
||||
*/
|
||||
|
||||
@@ -263,4 +263,9 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
|
||||
* Visit all the metrics (typically reporting them).
|
||||
*/
|
||||
void visitMetrics(MetricVisitor visitor);
|
||||
|
||||
/**
|
||||
* Return true if a row for the bean type and id exists.
|
||||
*/
|
||||
boolean exists(Class<?> beanType, Object beanId, Transaction transaction);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.ebean.Filter;
|
||||
import io.ebean.FutureIds;
|
||||
import io.ebean.FutureList;
|
||||
import io.ebean.FutureRowCount;
|
||||
import io.ebean.MergeOptions;
|
||||
import io.ebean.PagedList;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.ProfileLocation;
|
||||
@@ -915,6 +916,20 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return new DefaultUpdateQuery<>(createQuery(beanType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(Object bean, MergeOptions options) {
|
||||
merge(bean, options, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(Object bean, MergeOptions options, Transaction transaction) {
|
||||
BeanDescriptor<?> desc = getBeanDescriptor(bean.getClass());
|
||||
if (desc == null) {
|
||||
throw new PersistenceException(bean.getClass().getName() + " is NOT an Entity Bean registered with this server?");
|
||||
}
|
||||
executeInTrans((txn) -> persister.merge(desc, checkEntityBean(bean), options, txn), transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Query<T> find(Class<T> beanType) {
|
||||
return createQuery(beanType);
|
||||
@@ -1271,6 +1286,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(Class<?> beanType, Object beanId, Transaction transaction) {
|
||||
List<Object> ids = findIds(find(beanType).setId(beanId), transaction);
|
||||
return !ids.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A, T> List<A> findIds(Query<T> query, Transaction t) {
|
||||
|
||||
@@ -2234,8 +2255,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
/**
|
||||
* Returns a set of properties if saving the bean will violate the unique constraints (defined by given properties).
|
||||
*/
|
||||
private Set<Property> checkUniqueness(EntityBean entityBean, BeanDescriptor<?> beanDesc, BeanProperty[] props,
|
||||
Transaction transaction) {
|
||||
private Set<Property> checkUniqueness(EntityBean entityBean, BeanDescriptor<?> beanDesc, BeanProperty[] props, Transaction transaction) {
|
||||
BeanProperty idProperty = beanDesc.getIdProperty();
|
||||
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
|
||||
ExpressionList<?> exprList = query.where();
|
||||
|
||||
@@ -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.Flags;
|
||||
import io.ebeaninternal.server.persist.PersistExecute;
|
||||
import io.ebeaninternal.server.transaction.BeanPersistIdMap;
|
||||
import io.ebeanservice.docstore.api.DocStoreUpdate;
|
||||
@@ -75,6 +76,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
|
||||
private final boolean publish;
|
||||
|
||||
private int flags;
|
||||
|
||||
private DocStoreMode docStoreMode;
|
||||
|
||||
private ConcurrencyMode concurrencyMode;
|
||||
@@ -152,7 +155,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
private boolean getterCallback;
|
||||
|
||||
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
|
||||
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse, boolean publish) {
|
||||
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
|
||||
|
||||
super(server, t, persistExecute);
|
||||
this.entityBean = (EntityBean) bean;
|
||||
@@ -165,7 +168,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
this.controller = beanDescriptor.getPersistController();
|
||||
this.type = type;
|
||||
this.docStoreMode = calcDocStoreMode(transaction, type);
|
||||
if (saveRecurse) {
|
||||
this.flags = flags;
|
||||
if (Flags.isRecurse(flags)) {
|
||||
this.persistCascade = t.isPersistCascade();
|
||||
}
|
||||
|
||||
@@ -179,7 +183,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
beanDescriptor.checkMutableProperties(intercept);
|
||||
}
|
||||
this.concurrencyMode = beanDescriptor.getConcurrencyMode(intercept);
|
||||
this.publish = publish;
|
||||
this.publish = Flags.isPublish(flags);
|
||||
if (isMarkDraftDirty(publish)) {
|
||||
beanDescriptor.setDraftDirty(entityBean, true);
|
||||
}
|
||||
@@ -231,6 +235,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
if (createImplicitTransIfRequired()) {
|
||||
docStoreMode = calcDocStoreMode(transaction, type);
|
||||
}
|
||||
checkBatchEscalationOnCascade();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for batch escalation on cascade.
|
||||
*/
|
||||
public void checkBatchEscalationOnCascade() {
|
||||
if (transaction.checkBatchEscalationOnCascade(this)) {
|
||||
// we escalated to use batch mode so flush when done
|
||||
// but if createdTransaction then commit will flush it
|
||||
@@ -1076,6 +1087,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
return requestUpdateAllLoadedProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the flags set on this persist request.
|
||||
*/
|
||||
public int getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this request is a 'publish' action.
|
||||
*/
|
||||
@@ -1226,4 +1244,25 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
public void profile() {
|
||||
profileBase(type.profileEventId, profileOffset, beanDescriptor.getProfileId(), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the request flags indicating this is an insert.
|
||||
*/
|
||||
public void flagInsert() {
|
||||
flags = Flags.setInsert(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the request insert flag indicating this is an update.
|
||||
*/
|
||||
public void flagUpdate() {
|
||||
flags = Flags.unsetInsert(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this request is an insert.
|
||||
*/
|
||||
public boolean isInsertedParent() {
|
||||
return Flags.isInsert(flags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.CallableSql;
|
||||
import io.ebean.MergeOptions;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.Update;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -16,6 +19,11 @@ import java.util.List;
|
||||
*/
|
||||
public interface Persister {
|
||||
|
||||
/**
|
||||
* Merge the bean.
|
||||
*/
|
||||
int merge(BeanDescriptor<?> desc, EntityBean entityBean, MergeOptions options, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Update the bean.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
abstract class BaseCollectionHelp<T> implements BeanCollectionHelp<T> {
|
||||
|
||||
@Override
|
||||
public Collection underlying(Object value) {
|
||||
if (value instanceof BeanCollection) {
|
||||
return ((BeanCollection)value).getActualDetails();
|
||||
} else {
|
||||
return (Collection)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.text.json.SpiJsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Helper functions for performing tasks on Lists Sets or Maps.
|
||||
@@ -21,6 +22,11 @@ public interface BeanCollectionHelp<T> {
|
||||
*/
|
||||
void setLoader(BeanCollectionLoader loader);
|
||||
|
||||
/**
|
||||
* Return the underlying collection of beans.
|
||||
*/
|
||||
Collection underlying(Object value);
|
||||
|
||||
/**
|
||||
* Return the mechanism to add beans to the underlying collection.
|
||||
* <p>
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.List;
|
||||
/**
|
||||
* Helper object for dealing with Lists.
|
||||
*/
|
||||
public final class BeanListHelp<T> implements BeanCollectionHelp<T> {
|
||||
public final class BeanListHelp<T> extends BaseCollectionHelp<T> {
|
||||
|
||||
private final BeanPropertyAssocMany<T> many;
|
||||
private final BeanDescriptor<T> targetDescriptor;
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.Map.Entry;
|
||||
/**
|
||||
* Helper specifically for dealing with Maps.
|
||||
*/
|
||||
public final class BeanMapHelp<T> implements BeanCollectionHelp<T> {
|
||||
public final class BeanMapHelp<T> extends BaseCollectionHelp<T> {
|
||||
|
||||
private final BeanPropertyAssocMany<T> many;
|
||||
private final BeanDescriptor<T> targetDescriptor;
|
||||
|
||||
@@ -205,6 +205,13 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
// by default not including "Many" properties in document store
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying collection of beans.
|
||||
*/
|
||||
public Collection getRawCollection(EntityBean bean) {
|
||||
return help.underlying(getVal(bean));
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy collection value if existing is empty.
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,7 @@ import java.util.Set;
|
||||
/**
|
||||
* Helper specifically for dealing with Sets.
|
||||
*/
|
||||
public final class BeanSetHelp<T> implements BeanCollectionHelp<T> {
|
||||
public final class BeanSetHelp<T> extends BaseCollectionHelp<T> {
|
||||
|
||||
private final BeanPropertyAssocMany<T> many;
|
||||
private final BeanDescriptor<T> targetDescriptor;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.persist;
|
||||
|
||||
import io.ebean.CallableSql;
|
||||
import io.ebean.MergeOptions;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
@@ -53,8 +54,6 @@ import java.util.Set;
|
||||
* <li>Handles cascading of save and delete</li>
|
||||
* <li>Handles the batching and queueing</li>
|
||||
* </p>
|
||||
*
|
||||
* @see io.ebeaninternal.server.persist.DefaultPersistExecute
|
||||
*/
|
||||
public final class DefaultPersister implements Persister {
|
||||
|
||||
@@ -161,7 +160,7 @@ public final class DefaultPersister implements Persister {
|
||||
draftHandler.resetDraft(draftBean);
|
||||
|
||||
PUB.trace("draftRestore bean [{}] id[{}]", desc.getName(), draftHandler.getId());
|
||||
update(createRequest(draftBean, transaction, null, mgr, Type.UPDATE, true, false));
|
||||
update(createRequest(draftBean, transaction, null, mgr, Type.UPDATE, Flags.RECURSE));
|
||||
}
|
||||
|
||||
PUB.debug("draftRestore - complete for [{}]", desc.getName());
|
||||
@@ -211,7 +210,7 @@ public final class DefaultPersister implements Persister {
|
||||
Type persistType = draftHandler.isInsert() ? Type.INSERT : Type.UPDATE;
|
||||
PUB.trace("publish bean [{}] id[{}] type[{}]", desc.getName(), draftHandler.getId(), persistType);
|
||||
|
||||
PersistRequestBean<T> request = createRequest(liveBean, transaction, null, mgr, persistType, true, true);
|
||||
PersistRequestBean<T> request = createRequest(liveBean, transaction, null, mgr, persistType, Flags.PUBLISH_RECURSE);
|
||||
if (persistType == Type.INSERT) {
|
||||
insert(request);
|
||||
} else {
|
||||
@@ -281,7 +280,7 @@ public final class DefaultPersister implements Persister {
|
||||
// update the dirty status on the drafts that have been published
|
||||
PUB.debug("publish - update dirty status on [{}] drafts", draftUpdates.size());
|
||||
for (T draftUpdate : draftUpdates) {
|
||||
update(createRequest(draftUpdate, transaction, null, mgr, Type.UPDATE, false, false));
|
||||
update(createRequest(draftUpdate, transaction, null, mgr, Type.UPDATE, Flags.ZERO));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -346,6 +345,28 @@ public final class DefaultPersister implements Persister {
|
||||
return deleteRequest(createRequest(detailBean, t, deleteType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int merge(BeanDescriptor<?> desc, EntityBean bean, MergeOptions options, SpiTransaction transaction) {
|
||||
|
||||
MergeHandler merge = new MergeHandler(server, desc, bean, options, transaction);
|
||||
List<EntityBean> deleteBeans = merge.merge();
|
||||
if (!deleteBeans.isEmpty()) {
|
||||
// all detected deletes for the merge paths
|
||||
for (EntityBean deleteBean : deleteBeans) {
|
||||
delete(deleteBean, transaction, options.isDeletePermanent());
|
||||
}
|
||||
}
|
||||
|
||||
// cascade save as normal with forceUpdate flags set
|
||||
PersistRequestBean<?> request = createRequestRecurse(bean, transaction, null, Flags.MERGE);
|
||||
request.checkBatchEscalationOnCascade();
|
||||
saveRecurse(request);
|
||||
request.flushBatchOnCascade();
|
||||
|
||||
// lambda expects a return
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the bean.
|
||||
*/
|
||||
@@ -368,7 +389,7 @@ public final class DefaultPersister implements Persister {
|
||||
if (req.isReference()) {
|
||||
// its a reference so see if there are manys to save...
|
||||
if (req.isPersistCascade()) {
|
||||
saveAssocMany(false, req, false);
|
||||
saveAssocMany(req);
|
||||
}
|
||||
req.checkUpdatedManysOnly();
|
||||
} else {
|
||||
@@ -389,7 +410,7 @@ public final class DefaultPersister implements Persister {
|
||||
*/
|
||||
@Override
|
||||
public void save(EntityBean bean, Transaction t) {
|
||||
if (bean._ebean_getIntercept().isLoaded()) {
|
||||
if (bean._ebean_getIntercept().isUpdate()) {
|
||||
// deleteMissingChildren is false when using 'save' on 'loaded' beans
|
||||
update(bean, t, false);
|
||||
} else {
|
||||
@@ -416,19 +437,22 @@ public final class DefaultPersister implements Persister {
|
||||
}
|
||||
}
|
||||
|
||||
void saveRecurse(EntityBean bean, Transaction t, Object parentBean, boolean insertMode, boolean publish) {
|
||||
void saveRecurse(EntityBean bean, Transaction t, Object parentBean, int flags) {
|
||||
|
||||
// determine insert or update taking into account stateless updates
|
||||
PersistRequestBean<?> request = createRequestRecurse(bean, t, parentBean, insertMode, publish);
|
||||
PersistRequestBean<?> request = createRequestRecurse(bean, t, parentBean, flags);
|
||||
saveRecurse(request);
|
||||
}
|
||||
|
||||
private void saveRecurse(PersistRequestBean<?> request) {
|
||||
if (request.isReference()) {
|
||||
// its a reference...
|
||||
if (request.isPersistCascade()) {
|
||||
// save any associated List held beans
|
||||
saveAssocMany(false, request, insertMode);
|
||||
request.flagUpdate();
|
||||
saveAssocMany(request);
|
||||
}
|
||||
request.checkUpdatedManysOnly();
|
||||
|
||||
} else {
|
||||
if (request.isInsert()) {
|
||||
insert(request);
|
||||
@@ -447,11 +471,11 @@ public final class DefaultPersister implements Persister {
|
||||
// skip as already inserted/updated in this request (recursive cascading)
|
||||
return;
|
||||
}
|
||||
|
||||
request.flagInsert();
|
||||
try {
|
||||
if (request.isPersistCascade()) {
|
||||
// save associated One beans recursively first
|
||||
saveAssocOne(request, true);
|
||||
saveAssocOne(request);
|
||||
}
|
||||
|
||||
// set the IDGenerated value if required
|
||||
@@ -460,7 +484,7 @@ public final class DefaultPersister implements Persister {
|
||||
|
||||
if (request.isPersistCascade()) {
|
||||
// save any associated List held beans
|
||||
saveAssocMany(true, request, true);
|
||||
saveAssocMany(request);
|
||||
}
|
||||
} finally {
|
||||
request.unRegisterBean();
|
||||
@@ -476,11 +500,11 @@ public final class DefaultPersister implements Persister {
|
||||
// skip as already inserted/updated in this request (recursive cascading)
|
||||
return;
|
||||
}
|
||||
|
||||
request.flagUpdate();
|
||||
try {
|
||||
if (request.isPersistCascade()) {
|
||||
// save associated One beans recursively first
|
||||
saveAssocOne(request, false);
|
||||
saveAssocOne(request);
|
||||
}
|
||||
|
||||
if (request.isDirty()) {
|
||||
@@ -495,7 +519,7 @@ public final class DefaultPersister implements Persister {
|
||||
|
||||
if (request.isPersistCascade()) {
|
||||
// save all the beans in assocMany's after
|
||||
saveAssocMany(false, request, false);
|
||||
saveAssocMany(request);
|
||||
}
|
||||
|
||||
request.checkUpdatedManysOnly();
|
||||
@@ -518,7 +542,7 @@ public final class DefaultPersister implements Persister {
|
||||
if (originalRequest.isHardDeleteDraft()) {
|
||||
// a hard delete of a draftable bean so first we need to delete the associated 'live' bean
|
||||
// due to FK constraint and then after that execute the original delete of the draft bean
|
||||
return deleteRequest(createPublishRequest(originalRequest.createReference(), t, Type.DELETE_PERMANENT, true), originalRequest);
|
||||
return deleteRequest(createPublishRequest(originalRequest.createReference(), t, Type.DELETE_PERMANENT, Flags.PUBLISH), originalRequest);
|
||||
|
||||
} else {
|
||||
// normal delete or soft delete
|
||||
@@ -610,7 +634,7 @@ public final class DefaultPersister implements Persister {
|
||||
int rowCount = deleteRecurse(bean, transaction, permanent);
|
||||
if (rowCount == -1) {
|
||||
total = -1;
|
||||
} else if (total != -1){
|
||||
} else if (total != -1) {
|
||||
total += rowCount;
|
||||
}
|
||||
}
|
||||
@@ -838,7 +862,7 @@ public final class DefaultPersister implements Persister {
|
||||
* bean to the child beans.
|
||||
* </p>
|
||||
*/
|
||||
private void saveAssocMany(boolean insertedParent, PersistRequestBean<?> request, boolean insertMode) {
|
||||
private void saveAssocMany(PersistRequestBean<?> request) {
|
||||
|
||||
EntityBean parentBean = request.getEntityBean();
|
||||
BeanDescriptor<?> desc = request.getBeanDescriptor();
|
||||
@@ -854,7 +878,7 @@ public final class DefaultPersister implements Persister {
|
||||
if (!prop.isSaveRecurseSkippable(detailBean)) {
|
||||
t.depth(+1);
|
||||
prop.setParentBeanToChild(parentBean, detailBean);
|
||||
saveRecurse(detailBean, t, parentBean, insertMode, request.isPublish());
|
||||
saveRecurse(detailBean, t, parentBean, request.getFlags());
|
||||
t.depth(-1);
|
||||
}
|
||||
}
|
||||
@@ -863,10 +887,11 @@ public final class DefaultPersister implements Persister {
|
||||
|
||||
// many's with cascade save
|
||||
BeanPropertyAssocMany<?>[] manys = desc.propertiesManySave();
|
||||
boolean insertedParent = request.isInsertedParent();
|
||||
for (BeanPropertyAssocMany<?> many : manys) {
|
||||
// check that property is loaded and collection should be cascaded to
|
||||
if (request.isLoadedProperty(many) && !many.isSkipSaveBeanCollection(parentBean, insertedParent)) {
|
||||
saveMany(new SaveManyPropRequest(insertedParent, many, parentBean, request), insertMode);
|
||||
saveMany(new SaveManyPropRequest(insertedParent, many, parentBean, request));
|
||||
if (!insertedParent) {
|
||||
request.addUpdatedManyProperty(many);
|
||||
}
|
||||
@@ -874,7 +899,7 @@ public final class DefaultPersister implements Persister {
|
||||
}
|
||||
}
|
||||
|
||||
private void saveMany(SaveManyPropRequest saveMany, boolean insertMode) {
|
||||
private void saveMany(SaveManyPropRequest saveMany) {
|
||||
|
||||
if (saveMany.getMany().hasJoinTable()) {
|
||||
|
||||
@@ -882,7 +907,7 @@ public final class DefaultPersister implements Persister {
|
||||
// we only allow one direction based on first traversed basis
|
||||
boolean saveIntersectionFromThisDirection = saveMany.isSaveIntersection();
|
||||
if (saveMany.isCascade()) {
|
||||
saveAssocManyDetails(saveMany, false, insertMode);
|
||||
saveAssocManyDetails(saveMany, false);
|
||||
}
|
||||
// for ManyToMany save the 'relationship' via inserts/deletes
|
||||
// into/from the intersection table
|
||||
@@ -900,7 +925,7 @@ public final class DefaultPersister implements Persister {
|
||||
}
|
||||
if (saveMany.isCascade()) {
|
||||
// potentially deletes 'missing children' for 'stateless update'
|
||||
saveAssocManyDetails(saveMany, saveMany.isDeleteMissingChildren(), insertMode);
|
||||
saveAssocManyDetails(saveMany, saveMany.isDeleteMissingChildren());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -922,7 +947,7 @@ public final class DefaultPersister implements Persister {
|
||||
EntityBean eb = (EntityBean) removedBean;
|
||||
if (eb._ebean_getIntercept().isLoaded()) {
|
||||
// only delete if the bean was loaded meaning that it is known to exist in the DB
|
||||
deleteRequest(createPublishRequest(removedBean, saveMany.getTransaction(), PersistRequest.Type.DELETE, saveMany.isPublish()));
|
||||
deleteRequest(createPublishRequest(removedBean, saveMany.getTransaction(), PersistRequest.Type.DELETE, saveMany.getFlags()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -933,9 +958,9 @@ public final class DefaultPersister implements Persister {
|
||||
/**
|
||||
* Save the details from a OneToMany collection.
|
||||
*/
|
||||
private void saveAssocManyDetails(SaveManyPropRequest saveMany, boolean deleteMissingChildren, boolean insertMode) {
|
||||
private void saveAssocManyDetails(SaveManyPropRequest saveMany, boolean deleteMissingChildren) {
|
||||
|
||||
saveMany.saveDetails(this, deleteMissingChildren, insertMode);
|
||||
saveMany.saveDetails(this, deleteMissingChildren);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1056,13 +1081,12 @@ public final class DefaultPersister implements Persister {
|
||||
return false;
|
||||
}
|
||||
|
||||
private int deleteAssocManyIntersection(EntityBean bean, BeanPropertyAssocMany<?> many, Transaction t, boolean publish) {
|
||||
private void deleteAssocManyIntersection(EntityBean bean, BeanPropertyAssocMany<?> many, Transaction t, boolean publish) {
|
||||
|
||||
// delete all intersection rows for this bean
|
||||
IntersectionRow intRow = many.buildManyToManyDeleteChildren(bean, publish);
|
||||
SqlUpdate sqlDelete = intRow.createDeleteChildren(server);
|
||||
|
||||
return executeSqlUpdate(sqlDelete, t);
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1154,7 +1178,7 @@ 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, boolean softDelete) {
|
||||
|
||||
if (many.getCascadeInfo().isDelete()) {
|
||||
// cascade delete the beans in the collection
|
||||
@@ -1202,7 +1226,7 @@ public final class DefaultPersister implements Persister {
|
||||
/**
|
||||
* Save any associated one beans.
|
||||
*/
|
||||
private void saveAssocOne(PersistRequestBean<?> request, boolean insertMode) {
|
||||
private void saveAssocOne(PersistRequestBean<?> request) {
|
||||
|
||||
BeanDescriptor<?> desc = request.getBeanDescriptor();
|
||||
|
||||
@@ -1219,7 +1243,7 @@ public final class DefaultPersister implements Persister {
|
||||
&& !request.isParent(detailBean)) {
|
||||
SpiTransaction t = request.getTransaction();
|
||||
t.depth(-1);
|
||||
saveRecurse(detailBean, t, null, insertMode, request.isPublish());
|
||||
saveRecurse(detailBean, t, null, request.getFlags());
|
||||
t.depth(+1);
|
||||
}
|
||||
}
|
||||
@@ -1315,25 +1339,25 @@ public final class DefaultPersister implements Persister {
|
||||
* perform an insert, update or delete.
|
||||
*/
|
||||
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, PersistRequest.Type type) {
|
||||
return createRequestInternal(bean, t, type, false, false);
|
||||
return createRequestInternal(bean, t, type, Flags.ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Persist Request Object additionally specifying the publish status.
|
||||
*/
|
||||
private <T> PersistRequestBean<T> createPublishRequest(T bean, Transaction t, PersistRequest.Type type, boolean publish) {
|
||||
return createRequestInternal(bean, t, type, false, publish);
|
||||
private <T> PersistRequestBean<T> createPublishRequest(T bean, Transaction t, PersistRequest.Type type, int flags) {
|
||||
return createRequestInternal(bean, t, type, Flags.unsetRecuse(flags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Persist Request Object additionally specifying the publish status.
|
||||
*/
|
||||
private <T> PersistRequestBean<T> createRequestInternal(T bean, Transaction t, PersistRequest.Type type, boolean saveRecurse, boolean publish) {
|
||||
private <T> PersistRequestBean<T> createRequestInternal(T bean, Transaction t, PersistRequest.Type type, int flags) {
|
||||
BeanManager<T> mgr = getBeanManager(bean);
|
||||
if (mgr == null) {
|
||||
throw new PersistenceException(errNotRegistered(bean.getClass()));
|
||||
}
|
||||
return createRequest(bean, t, null, mgr, type, saveRecurse, publish);
|
||||
return createRequest(bean, t, null, mgr, type, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1341,7 +1365,7 @@ public final class DefaultPersister implements Persister {
|
||||
* <p>
|
||||
* This call determines the PersistRequest.Type based on bean state and the insert flag (root persist type).
|
||||
*/
|
||||
private <T> PersistRequestBean<T> createRequestRecurse(T bean, Transaction t, Object parentBean, boolean insertMode, boolean publish) {
|
||||
private <T> PersistRequestBean<T> createRequestRecurse(T bean, Transaction t, Object parentBean, int flags) {
|
||||
BeanManager<T> mgr = getBeanManager(bean);
|
||||
if (mgr == null) {
|
||||
throw new PersistenceException(errNotRegistered(bean.getClass()));
|
||||
@@ -1349,14 +1373,15 @@ public final class DefaultPersister implements Persister {
|
||||
BeanDescriptor<T> desc = mgr.getBeanDescriptor();
|
||||
EntityBean entityBean = (EntityBean) bean;
|
||||
PersistRequest.Type type;
|
||||
if (publish) {
|
||||
if (Flags.isPublishOrMerge(flags)) {
|
||||
// insert if it is a new bean (as publish created it)
|
||||
type = entityBean._ebean_getIntercept().isNew() ? Type.INSERT : Type.UPDATE;
|
||||
type = entityBean._ebean_getIntercept().isUpdate() ? Type.UPDATE : Type.INSERT;
|
||||
} else {
|
||||
// determine Insert or Update based on bean state and insert flag
|
||||
boolean insertMode = Flags.isInsert(flags);
|
||||
type = desc.isInsertMode(entityBean._ebean_getIntercept(), insertMode) ? Type.INSERT : Type.UPDATE;
|
||||
}
|
||||
return createRequest(bean, t, parentBean, mgr, type, true, publish);
|
||||
return createRequest(bean, t, parentBean, mgr, type, Flags.setRecurse(flags));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1365,7 +1390,7 @@ public final class DefaultPersister implements Persister {
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, Object parentBean, BeanManager<?> mgr,
|
||||
PersistRequest.Type type, boolean saveRecurse, boolean publish) {
|
||||
PersistRequest.Type type, int flags) {
|
||||
|
||||
if (type == Type.DELETE_PERMANENT) {
|
||||
type = Type.DELETE;
|
||||
@@ -1374,7 +1399,7 @@ public final class DefaultPersister implements Persister {
|
||||
type = Type.SOFT_DELETE;
|
||||
}
|
||||
|
||||
return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type, saveRecurse, publish);
|
||||
return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type, flags);
|
||||
}
|
||||
|
||||
private String errNotRegistered(Class<?> beanClass) {
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package io.ebeaninternal.server.persist;
|
||||
|
||||
/**
|
||||
* Flags used in persistence.
|
||||
* <p>
|
||||
* Allows passing of flag state when recursively persisting.
|
||||
*/
|
||||
public class Flags {
|
||||
|
||||
/**
|
||||
* Indicates the bean is being inserted.
|
||||
*/
|
||||
public static final int INSERT = 0x00000001;
|
||||
|
||||
/**
|
||||
* Indicates persist cascade.
|
||||
*/
|
||||
public static final int RECURSE = 0x00000002;
|
||||
|
||||
/**
|
||||
* Indicates Publish mode.
|
||||
*/
|
||||
public static final int PUBLISH = 0x00000004;
|
||||
|
||||
/**
|
||||
* Indicates Merge mode.
|
||||
*/
|
||||
public static final int MERGE = 0x00000008;
|
||||
|
||||
/**
|
||||
* No flags set.
|
||||
*/
|
||||
public static final int ZERO = 0;
|
||||
|
||||
public static final int PUBLISH_RECURSE = PUBLISH + RECURSE;
|
||||
|
||||
private static final int PUBLISH_MERGE = PUBLISH + MERGE;
|
||||
|
||||
/**
|
||||
* Return true if the bean is being inserted.
|
||||
*/
|
||||
public static boolean isInsert(int state) {
|
||||
return isSet(state, INSERT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if persist cascading.
|
||||
*/
|
||||
public static boolean isRecurse(int state) {
|
||||
return isSet(state, RECURSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if part of a Publish.
|
||||
*/
|
||||
public static boolean isPublish(int state) {
|
||||
return isSet(state, PUBLISH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if part of a Merge.
|
||||
*/
|
||||
public static boolean isMerge(int state) {
|
||||
return isSet(state, PUBLISH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if part of a Merge or Publish.
|
||||
*/
|
||||
public static boolean isPublishOrMerge(long state) {
|
||||
return (state & PUBLISH_MERGE) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the given flag is set.
|
||||
*/
|
||||
public static boolean isSet(int state, int flag) {
|
||||
return (state & flag) == flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Insert flag.
|
||||
*/
|
||||
public static int setInsert(int state) {
|
||||
return set(state, INSERT, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent was not inserted.
|
||||
*/
|
||||
public static int unsetInsert(int state) {
|
||||
return set(state, INSERT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Recurse flag.
|
||||
*/
|
||||
public static int setRecurse(int state) {
|
||||
return set(state, RECURSE, true);
|
||||
}
|
||||
|
||||
public static int unsetRecuse(int state) {
|
||||
return set(state, RECURSE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Publish flag.
|
||||
*/
|
||||
public static int setPublish(int state) {
|
||||
return set(state, PUBLISH, true);
|
||||
}
|
||||
|
||||
public static int unsetPublish(int state) {
|
||||
return set(state, PUBLISH, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Merge flag.
|
||||
*/
|
||||
public static int setMerge(int state) {
|
||||
return set(state, MERGE, true);
|
||||
}
|
||||
|
||||
public static int unsetMerge(int state) {
|
||||
return set(state, MERGE, false);
|
||||
}
|
||||
|
||||
private static int set(int state, int flag, boolean setFlag) {
|
||||
if (setFlag) {
|
||||
return (state |= flag);
|
||||
} else {
|
||||
return state &= ~flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.ebeaninternal.server.persist;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Context used for merge processing.
|
||||
*/
|
||||
class MergeContext {
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final SpiTransaction transaction;
|
||||
|
||||
private final List<EntityBean> deleteBeans = new ArrayList<>();
|
||||
|
||||
private boolean clientGeneratedIds;
|
||||
|
||||
MergeContext(SpiEbeanServer server, SpiTransaction transaction, boolean clientGeneratedIds) {
|
||||
this.server = server;
|
||||
this.transaction = transaction;
|
||||
this.clientGeneratedIds = clientGeneratedIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to the list of beans to delete.
|
||||
*/
|
||||
void addDelete(EntityBean deleteBean) {
|
||||
deleteBeans.add(deleteBean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if Ids must be checked against the DB.
|
||||
*/
|
||||
boolean isClientGeneratedIds() {
|
||||
return clientGeneratedIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Id / bean exists in the database.
|
||||
*/
|
||||
boolean idExists(Class<?> beanType, Object beanId) {
|
||||
return server.exists(beanType, beanId, transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of beans to delete.
|
||||
*/
|
||||
List<EntityBean> getDeletedBeans() {
|
||||
return deleteBeans;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package io.ebeaninternal.server.persist;
|
||||
|
||||
import io.ebean.CacheMode;
|
||||
import io.ebean.MergeOptions;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Drives the merge processing.
|
||||
*/
|
||||
class MergeHandler {
|
||||
|
||||
private final Pattern PATH_SPLIT = Pattern.compile("\\.");
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
private final BeanDescriptor<?> desc;
|
||||
private final EntityBean bean;
|
||||
private final MergeOptions options;
|
||||
private final SpiTransaction transaction;
|
||||
|
||||
private final Map<String, MergeNode> nodes = new LinkedHashMap<>();
|
||||
|
||||
|
||||
MergeHandler(SpiEbeanServer server, BeanDescriptor<?> desc, EntityBean bean, MergeOptions options, SpiTransaction transaction) {
|
||||
this.server = server;
|
||||
this.desc = desc;
|
||||
this.bean = bean;
|
||||
this.options = options;
|
||||
this.transaction = transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the Ids for the graph and use them to determine inserts, updates and deletes for the merge paths.
|
||||
*/
|
||||
List<EntityBean> merge() {
|
||||
|
||||
Set<String> paths = options.paths();
|
||||
if (paths.isEmpty() && !options.isClientGeneratedIds()) {
|
||||
// just do a single insert or update based on Id value present
|
||||
Object id = desc.getId(bean);
|
||||
if (id != null) {
|
||||
bean._ebean_getIntercept().setForceUpdate(true);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
EntityBean outline = fetchOutline(paths);
|
||||
if (outline == null) {
|
||||
// considered an insert ...
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// the top level bean is an update
|
||||
bean._ebean_getIntercept().setForceUpdate(true);
|
||||
|
||||
// detect what beans are updates and recursively set forceUpdate as needed
|
||||
// and outline beans not in the merge graph are generally considered as deletes
|
||||
MergeContext context = new MergeContext(server, transaction, options.isClientGeneratedIds());
|
||||
MergeRequest request = new MergeRequest(context, bean, outline);
|
||||
for (MergeNode value : nodes.values()) {
|
||||
value.merge(request);
|
||||
}
|
||||
|
||||
return context.getDeletedBeans();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the outline bean with associated one and associated many beans loaded with Id values only.
|
||||
* <p>
|
||||
* We use the Id values to determine what are inserts, updates and deletes as part of the merge.
|
||||
*/
|
||||
private EntityBean fetchOutline(Set<String> paths) {
|
||||
|
||||
Query<?> query = server.find(desc.getBeanType());
|
||||
|
||||
query.setBeanCacheMode(CacheMode.OFF);
|
||||
query.setPersistenceContextScope(PersistenceContextScope.QUERY);
|
||||
query.setId(desc.getId(bean));
|
||||
query.select(desc.getIdProperty().getName());
|
||||
|
||||
for (String path : paths) {
|
||||
MergeNode node = buildNode(path);
|
||||
node.addSelectId(query);
|
||||
}
|
||||
return (EntityBean) server.findOne(query, transaction);
|
||||
}
|
||||
|
||||
private MergeNode buildNode(String path) {
|
||||
String[] split = PATH_SPLIT.split(path);
|
||||
if (split.length == 1) {
|
||||
return addRootLevelNode(split[0]);
|
||||
} else {
|
||||
return addSubNode(path, split);
|
||||
}
|
||||
}
|
||||
|
||||
private MergeNode addSubNode(String fullPath, String[] split) {
|
||||
MergeNode parent = nodes.get(split[0]);
|
||||
if (parent == null) {
|
||||
throw new PersistenceException("Unable to find parent path " + split[0] + " in merge paths?");
|
||||
}
|
||||
|
||||
for (int i = 1; i < split.length - 1; i++) {
|
||||
parent = parent.get(split[i]);
|
||||
if (parent == null) {
|
||||
throw new PersistenceException("Unable to find parent path " + split[0] + " in merge paths?");
|
||||
}
|
||||
}
|
||||
return parent.addChild(fullPath, split[split.length - 1]);
|
||||
}
|
||||
|
||||
private MergeNode addRootLevelNode(String rootPath) {
|
||||
|
||||
MergeNode node = createMergeNode(rootPath, desc, rootPath);
|
||||
nodes.put(rootPath, node);
|
||||
return node;
|
||||
}
|
||||
|
||||
static MergeNode createMergeNode(String fullPath, BeanDescriptor<?> targetDesc, String path) {
|
||||
|
||||
BeanProperty prop = targetDesc.getBeanProperty(path);
|
||||
if (prop == null || !(prop instanceof BeanPropertyAssoc)) {
|
||||
throw new PersistenceException("merge path [" + path + "] is not a ToMany or ToOne property of " + targetDesc.getFullName());
|
||||
}
|
||||
if (prop instanceof BeanPropertyAssocMany<?>) {
|
||||
return new MergeNodeAssocMany(fullPath, (BeanPropertyAssocMany<?>) prop);
|
||||
} else {
|
||||
return new MergeNodeAssocOne(fullPath, (BeanPropertyAssocOne<?>) prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package io.ebeaninternal.server.persist;
|
||||
|
||||
import io.ebean.Query;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Base class for merge nodes.
|
||||
*/
|
||||
abstract class MergeNode {
|
||||
|
||||
protected final String fullPath;
|
||||
protected final BeanDescriptor<?> targetDescriptor;
|
||||
protected Map<String,MergeNode> children;
|
||||
|
||||
MergeNode(String fullPath, BeanPropertyAssoc<?> property) {
|
||||
this.fullPath = fullPath;
|
||||
this.targetDescriptor = property.getTargetDescriptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the merge processing.
|
||||
*/
|
||||
abstract void merge(MergeRequest request);
|
||||
|
||||
/**
|
||||
* Add a child node given the fullPath and relative path.
|
||||
*/
|
||||
MergeNode addChild(String fullPath, String path) {
|
||||
MergeNode childNode = MergeHandler.createMergeNode(fullPath, targetDescriptor, path);
|
||||
if (children == null) {
|
||||
children = new LinkedHashMap<>();
|
||||
}
|
||||
children.put(path, childNode);
|
||||
return childNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the node given the relative path.
|
||||
*/
|
||||
MergeNode get(String path) {
|
||||
if (children != null) {
|
||||
return children.get(path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to the query to fetch the Ids values for the foreign keys basically.
|
||||
*/
|
||||
void addSelectId(Query<?> query) {
|
||||
|
||||
BeanProperty idProperty = targetDescriptor.getIdProperty();
|
||||
query.fetch(fullPath, idProperty.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cascade the merge processing if this has child nodes.
|
||||
*/
|
||||
protected void cascade(EntityBean entityBean, EntityBean outlineBean, MergeRequest request) {
|
||||
|
||||
if (children != null && !children.isEmpty()) {
|
||||
MergeRequest sub = request.sub(entityBean, outlineBean);
|
||||
for (MergeNode node : children.values()) {
|
||||
node.merge(sub);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.ebeaninternal.server.persist;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Node for processing merge on ToMany properties.
|
||||
*/
|
||||
class MergeNodeAssocMany extends MergeNode {
|
||||
|
||||
private final BeanPropertyAssocMany<?> many;
|
||||
|
||||
MergeNodeAssocMany(String fullPath, BeanPropertyAssocMany<?> property) {
|
||||
super(fullPath, property);
|
||||
this.many = property;
|
||||
}
|
||||
|
||||
public void merge(MergeRequest request) {
|
||||
|
||||
Collection beans = many.getRawCollection(request.getBean());
|
||||
Collection outlines = many.getRawCollection(request.getOutline());
|
||||
|
||||
Map<Object, EntityBean> outlineIds = new HashMap<>();
|
||||
if (outlines != null) {
|
||||
for (Object outline : outlines) {
|
||||
EntityBean outlineBean = (EntityBean) outline;
|
||||
Object outlineId = targetDescriptor.getId(outlineBean);
|
||||
outlineIds.put(outlineId, outlineBean);
|
||||
}
|
||||
}
|
||||
|
||||
if (beans != null) {
|
||||
for (Object bean : beans) {
|
||||
EntityBean entityBean = (EntityBean) bean;
|
||||
Object beanId = targetDescriptor.getId(entityBean);
|
||||
if (beanId != null) {
|
||||
EntityBean outlineBean = outlineIds.remove(beanId);
|
||||
if (outlineBean != null) {
|
||||
// must be an update
|
||||
entityBean._ebean_getIntercept().setForceUpdate(true);
|
||||
cascade(entityBean, outlineBean, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// any remaining are considered deletes
|
||||
for (EntityBean outlineBean : outlineIds.values()) {
|
||||
request.addDelete(outlineBean);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package io.ebeaninternal.server.persist;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Node for processing merge on ToOne properties.
|
||||
*/
|
||||
class MergeNodeAssocOne extends MergeNode {
|
||||
|
||||
private final BeanPropertyAssocOne<?> one;
|
||||
|
||||
MergeNodeAssocOne(String fullPath, BeanPropertyAssocOne<?> property) {
|
||||
super(fullPath, property);
|
||||
this.one = property;
|
||||
}
|
||||
|
||||
public void merge(MergeRequest request) {
|
||||
|
||||
EntityBean entityBean = getEntityBean(request.getBean());
|
||||
if (entityBean == null) {
|
||||
checkOrphanRemoval(request);
|
||||
|
||||
} else {
|
||||
Object beanId = targetDescriptor.getId(entityBean);
|
||||
if (beanId == null) {
|
||||
checkOrphanRemoval(request);
|
||||
|
||||
} else {
|
||||
EntityBean outlineBean = getEntityBean(request.getOutline());
|
||||
Object outlineId = (outlineBean == null) ? null : targetDescriptor.getId(outlineBean);
|
||||
if (isUpdate(beanId, outlineId, request)) {
|
||||
entityBean._ebean_getIntercept().setForceUpdate(true);
|
||||
cascade(entityBean, outlineBean, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkOrphanRemoval(MergeRequest request) {
|
||||
if (one.isOrphanRemoval()) {
|
||||
EntityBean outlineBean = getEntityBean(request.getOutline());
|
||||
if (outlineBean != null) {
|
||||
request.addDelete(outlineBean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUpdate(Object beanId, Object outlineId, MergeRequest request) {
|
||||
return Objects.equals(beanId, outlineId)
|
||||
|| !request.isClientGeneratedIds()
|
||||
|| request.idExists(targetDescriptor.getBeanType(), beanId);
|
||||
}
|
||||
|
||||
private EntityBean getEntityBean(Object bean) {
|
||||
return (EntityBean) one.getVal(bean);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.ebeaninternal.server.persist;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
/**
|
||||
* Request object used for processing the merge.
|
||||
*/
|
||||
class MergeRequest {
|
||||
|
||||
private final EntityBean bean;
|
||||
private final EntityBean outline;
|
||||
|
||||
private final MergeContext context;
|
||||
|
||||
MergeRequest(MergeContext context, EntityBean bean, EntityBean outline) {
|
||||
this.context = context;
|
||||
this.bean = bean;
|
||||
this.outline = outline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a sub request with the given beans (to cascade the processing).
|
||||
*/
|
||||
public MergeRequest sub(EntityBean entityBean, EntityBean outlineBean) {
|
||||
return new MergeRequest(context, entityBean, outlineBean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the entity bean being merged.
|
||||
*/
|
||||
public EntityBean getBean() {
|
||||
return bean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the outline bean (only has Id property).
|
||||
*/
|
||||
public EntityBean getOutline() {
|
||||
return outline;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bean to the deletion list.
|
||||
*/
|
||||
public void addDelete(EntityBean deleteRemain) {
|
||||
context.addDelete(deleteRemain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the Ids are generated by the client. This means we can't know if a bean
|
||||
* should be inserted or updated based on having an Id value.
|
||||
*/
|
||||
public boolean isClientGeneratedIds() {
|
||||
return context.isClientGeneratedIds();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if a bean of the type with the given Id exists in the database.
|
||||
*/
|
||||
public boolean idExists(Class<?> beanType, Object beanId) {
|
||||
return context.idExists(beanType, beanId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import java.util.Map;
|
||||
*/
|
||||
class SaveManyPropRequest {
|
||||
|
||||
private final PersistRequestBean<?> request;
|
||||
private final boolean insertedParent;
|
||||
private final BeanPropertyAssocMany<?> many;
|
||||
private final EntityBean parentBean;
|
||||
@@ -37,10 +38,10 @@ class SaveManyPropRequest {
|
||||
private Collection<?> collection;
|
||||
private DefaultPersister persister;
|
||||
private boolean deleteMissing;
|
||||
private boolean insertMode;
|
||||
private int sortOrder;
|
||||
|
||||
SaveManyPropRequest(boolean insertedParent, BeanPropertyAssocMany<?> many, EntityBean parentBean, PersistRequestBean<?> request) {
|
||||
this.request = request;
|
||||
this.insertedParent = insertedParent;
|
||||
this.many = many;
|
||||
this.cascade = many.getCascadeInfo().isSave();
|
||||
@@ -70,6 +71,10 @@ class SaveManyPropRequest {
|
||||
return deleteMissingChildren;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return request.getFlags();
|
||||
}
|
||||
|
||||
boolean isInsertedParent() {
|
||||
return insertedParent;
|
||||
}
|
||||
@@ -108,11 +113,10 @@ class SaveManyPropRequest {
|
||||
}
|
||||
}
|
||||
|
||||
void saveDetails(DefaultPersister persister, boolean deleteMissing, boolean insertMode) {
|
||||
void saveDetails(DefaultPersister persister, boolean deleteMissing) {
|
||||
|
||||
this.persister = persister;
|
||||
this.deleteMissing = deleteMissing;
|
||||
this.insertMode = insertMode;
|
||||
|
||||
// check that the list is not null and if it is a BeanCollection
|
||||
// check that is has been populated (don't trigger lazy loading)
|
||||
@@ -198,7 +202,7 @@ class SaveManyPropRequest {
|
||||
}
|
||||
|
||||
if (!skipSavingThisBean) {
|
||||
persister.saveRecurse(detail, transaction, parentBean, insertMode, publish);
|
||||
persister.saveRecurse(detail, transaction, parentBean, request.getFlags());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user