Fix for #167 - Ebean try to do update child nodes instead of inserting them on cascading save resulting in OptimisticLockException

This commit is contained in:
rbygrave
2014-07-15 22:05:56 +12:00
parent aee46955a7
commit 6d1a41f979
6 changed files with 228 additions and 46 deletions
@@ -92,7 +92,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private List<BeanPropertyAssocMany<?>> updatedManys;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type) {
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse) {
super(server, t, persistExecute);
this.entityBean = (EntityBean) bean;
@@ -103,12 +103,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
this.bean = bean;
this.parentBean = parentBean;
this.controller = beanDescriptor.getPersistController();
if (PersistRequest.Type.DETERMINE != type) {
this.type = type;
} else {
// determine mode during cascade save (supporting stateless update)
this.type = beanDescriptor.isInsertMode(intercept) ? Type.INSERT : Type.UPDATE;
this.type = type;
if (saveRecurse) {
this.persistCascade = t.isPersistCascade();
}
@@ -1886,21 +1886,31 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
return idProperty;
}
public boolean isInsertMode(EntityBeanIntercept ebi) {
/**
* Return true if this bean should be inserted rather than updated.
*
* @param ebi
* The entity bean intercept
* @param insertMode
* true if the 'root request' was an insert rather than an update
*/
public boolean isInsertMode(EntityBeanIntercept ebi, boolean insertMode) {
if (ebi.isLoaded()) {
// must be an update as the bean is loaded
return false;
}
// determine based on Id property
if (idProperty.isEmbedded()) {
// not using Id generator so just base on isLoaded()
return !ebi.isLoaded();
}
//if (idGenerator == null) {
// return !ebi.isLoaded();
//} else {
return !hasIdProperty(ebi);
//}
if (!hasIdProperty(ebi)) {
// No Id property means it must be an insert
return true;
}
// same as the 'root request'
return insertMode;
}
public boolean isReference(EntityBeanIntercept ebi) {
@@ -32,6 +32,7 @@ import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate;
import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql;
import com.avaje.ebeaninternal.server.core.Persister;
import com.avaje.ebeaninternal.server.core.PstmtBatch;
import com.avaje.ebeaninternal.server.core.PersistRequest.Type;
import com.avaje.ebeaninternal.server.deploy.BeanCollectionUtil;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
@@ -172,7 +173,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);
saveAssocMany(false, req, false);
}
req.checkUpdatedManysOnly();
@@ -218,16 +219,16 @@ public final class DefaultPersister implements Persister {
}
}
private void saveRecurse(EntityBean bean, Transaction t, Object parentBean) {
private void saveRecurse(EntityBean bean, Transaction t, Object parentBean, boolean insertMode) {
// determine insert or update taking into account stateless updates
PersistRequestBean<?> request = createRequest(bean, t, parentBean, PersistRequest.Type.DETERMINE);
PersistRequestBean<?> request = createRequest(bean, t, parentBean, insertMode);
if (request.isReference()) {
// its a reference...
if (request.isPersistCascade()) {
// save any associated List held beans
saveAssocMany(false, request);
saveAssocMany(false, request, insertMode);
}
request.checkUpdatedManysOnly();
@@ -253,7 +254,7 @@ public final class DefaultPersister implements Persister {
try {
if (request.isPersistCascade()) {
// save associated One beans recursively first
saveAssocOne(request);
saveAssocOne(request, true);
}
// set the IDGenerated value if required
@@ -262,7 +263,7 @@ public final class DefaultPersister implements Persister {
if (request.isPersistCascade()) {
// save any associated List held beans
saveAssocMany(true, request);
saveAssocMany(true, request, true);
}
} finally {
request.unRegisterBean();
@@ -282,7 +283,7 @@ public final class DefaultPersister implements Persister {
try {
if (request.isPersistCascade()) {
// save associated One beans recursively first
saveAssocOne(request);
saveAssocOne(request, false);
}
if (request.isDirty()) {
@@ -297,7 +298,7 @@ public final class DefaultPersister implements Persister {
if (request.isPersistCascade()) {
// save all the beans in assocMany's after
saveAssocMany(false, request);
saveAssocMany(false, request, false);
}
request.checkUpdatedManysOnly();
@@ -539,7 +540,7 @@ public final class DefaultPersister implements Persister {
* bean to the child beans.
* </p>
*/
private void saveAssocMany(boolean insertedParent, PersistRequestBean<?> request) {
private void saveAssocMany(boolean insertedParent, PersistRequestBean<?> request, boolean insertMode) {
EntityBean parentBean = request.getEntityBean();
BeanDescriptor<?> desc = request.getBeanDescriptor();
@@ -559,7 +560,7 @@ public final class DefaultPersister implements Persister {
} else {
t.depth(+1);
prop.setParentBeanToChild(parentBean, detailBean);
saveRecurse(detailBean, t, parentBean);
saveRecurse(detailBean, t, parentBean, insertMode);
t.depth(-1);
}
}
@@ -571,7 +572,7 @@ public final class DefaultPersister implements Persister {
for (int i = 0; i < manys.length; i++) {
// check that property is loaded and not empty uninitialised collection
if (request.isLoadedProperty(manys[i]) && !manys[i].isEmptyBeanCollection(parentBean)) {
saveMany(new SaveManyPropRequest(insertedParent, manys[i], parentBean, request));
saveMany(new SaveManyPropRequest(insertedParent, manys[i], parentBean, request), insertMode);
if (!insertedParent) {
request.addUpdatedManyProperty(manys[i]);
}
@@ -646,7 +647,7 @@ public final class DefaultPersister implements Persister {
}
}
private void saveMany(SaveManyPropRequest saveMany) {
private void saveMany(SaveManyPropRequest saveMany, boolean insertMode) {
if (saveMany.getMany().isManyToMany()) {
@@ -654,7 +655,7 @@ public final class DefaultPersister implements Persister {
boolean saveIntersectionFromThisDirection = saveMany.isSaveIntersection();
if (saveMany.isCascade()) {
// Need explicit Cascade to save the beans on other side
saveAssocManyDetails(saveMany, false);
saveAssocManyDetails(saveMany, false, insertMode);
}
// for ManyToMany save the 'relationship' via inserts/deletes
// into/from the intersection table
@@ -670,7 +671,7 @@ public final class DefaultPersister implements Persister {
}
if (saveMany.isCascade()) {
// potentially deletes 'missing children' for 'stateless update'
saveAssocManyDetails(saveMany, saveMany.isDeleteMissingChildren());
saveAssocManyDetails(saveMany, saveMany.isDeleteMissingChildren(), insertMode);
}
}
}
@@ -708,7 +709,7 @@ public final class DefaultPersister implements Persister {
/**
* Save the details from a OneToMany collection.
*/
private void saveAssocManyDetails(SaveManyPropRequest saveMany, boolean deleteMissingChildren) {
private void saveAssocManyDetails(SaveManyPropRequest saveMany, boolean deleteMissingChildren, boolean insertMode) {
BeanPropertyAssocMany<?> prop = saveMany.getMany();
@@ -791,7 +792,7 @@ public final class DefaultPersister implements Persister {
} else {
// normal save recurse
saveRecurse(detail, t, parentBean);
saveRecurse(detail, t, parentBean, insertMode);
}
if (detailIds != null) {
// remember the Id (other details not in the collection) will be removed
@@ -839,7 +840,7 @@ public final class DefaultPersister implements Persister {
if (prop instanceof BeanPropertyAssocMany<?>) {
BeanPropertyAssocMany<?> manyProp = (BeanPropertyAssocMany<?>) prop;
saveMany(new SaveManyPropRequest(manyProp, parentBean, (SpiTransaction) t));
saveMany(new SaveManyPropRequest(manyProp, parentBean, (SpiTransaction) t), true);
} else if (prop instanceof BeanPropertyAssocOne<?>) {
BeanPropertyAssocOne<?> oneProp = (BeanPropertyAssocOne<?>) prop;
@@ -849,7 +850,7 @@ public final class DefaultPersister implements Persister {
int revertDepth = -1 * depth;
trans.depth(depth);
saveRecurse(assocBean, t, parentBean);
saveRecurse(assocBean, t, parentBean, true);
trans.depth(revertDepth);
} else {
@@ -1087,7 +1088,7 @@ public final class DefaultPersister implements Persister {
/**
* Save any associated one beans.
*/
private void saveAssocOne(PersistRequestBean<?> request) {
private void saveAssocOne(PersistRequestBean<?> request, boolean insertMode) {
BeanDescriptor<?> desc = request.getBeanDescriptor();
@@ -1111,7 +1112,7 @@ public final class DefaultPersister implements Persister {
} else {
SpiTransaction t = request.getTransaction();
t.depth(-1);
saveRecurse(detailBean, t, null);
saveRecurse(detailBean, t, null, insertMode);
t.depth(+1);
}
}
@@ -1196,38 +1197,52 @@ public final class DefaultPersister implements Persister {
}
}
/**
* Create the Persist Request Object that wraps all the objects used to
* perform an insert, update or delete.
*/
@SuppressWarnings("unchecked")
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, Object parentBean, PersistRequest.Type type) {
BeanManager<T> mgr = getBeanManager(bean);
if (mgr == null) {
throw new PersistenceException(errNotRegistered(bean.getClass()));
}
return (PersistRequestBean<T>) createRequest(bean, t, parentBean, mgr, type);
return createRequest(bean, t, parentBean, mgr, type, false);
}
private String errNotRegistered(Class<?> beanClass) {
String msg = "The type [" + beanClass + "] is not a registered entity?";
msg += " If you don't explicitly list the entity classes to use Ebean will search for them in the classpath.";
msg += " If the entity is in a Jar check the ebean.search.jars property in ebean.properties file or check ServerConfig.addJar().";
return msg;
}
/**
* Create an Insert or Update PersistRequestBean when cascading.
* <p>
* This call determines the PersistRequest.Type based on bean state and the insert flag (root persist type).
*/
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, Object parentBean, boolean insertMode) {
BeanManager<T> mgr = getBeanManager(bean);
if (mgr == null) {
throw new PersistenceException(errNotRegistered(bean.getClass()));
}
BeanDescriptor<T> desc = mgr.getBeanDescriptor();
EntityBean entityBean = (EntityBean)bean;
// determine Insert or Update based on bean state and insert flag
PersistRequest.Type type = desc.isInsertMode(entityBean._ebean_getIntercept(), insertMode) ? Type.INSERT : Type.UPDATE;
return createRequest(bean, t, parentBean, mgr, type, true);
}
/**
* Create the Persist Request Object that wraps all the objects used to
* perform an insert, update or delete.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private PersistRequestBean<?> createRequest(Object bean, Transaction t, Object parentBean, BeanManager<?> mgr, PersistRequest.Type type) {
private <T> PersistRequestBean<T> createRequest(T bean, Transaction t, Object parentBean, BeanManager<?> mgr, PersistRequest.Type type, boolean saveRecurse) {
return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type);
return new PersistRequestBean(server, bean, parentBean, mgr, (SpiTransaction) t, persistExecute, type, saveRecurse);
}
private String errNotRegistered(Class<?> beanClass) {
String msg = "The type [" + beanClass + "] is not a registered entity?";
msg += " If you don't explicitly list the entity classes to use Ebean will search for them in the classpath.";
msg += " If the entity is in a Jar check the ebean.search.jars property in ebean.properties file or check ServerConfig.addJar().";
return msg;
}
/**
* Return the BeanDescriptor for a bean that is being persisted.
* <p>