diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
index 67c7a63d8..210cc03c3 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
@@ -92,7 +92,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
private List> updatedManys;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager 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 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();
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
index 5143748d3..5e30d00d8 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -1886,21 +1886,31 @@ public class BeanDescriptor 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) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java
index 5edfc43ba..b77f424b7 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java
@@ -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.
*
*/
- 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 PersistRequestBean createRequest(T bean, Transaction t, Object parentBean, PersistRequest.Type type) {
BeanManager mgr = getBeanManager(bean);
if (mgr == null) {
throw new PersistenceException(errNotRegistered(bean.getClass()));
}
- return (PersistRequestBean) 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.
+ *
+ * This call determines the PersistRequest.Type based on bean state and the insert flag (root persist type).
+ */
+ private PersistRequestBean createRequest(T bean, Transaction t, Object parentBean, boolean insertMode) {
+ BeanManager mgr = getBeanManager(bean);
+ if (mgr == null) {
+ throw new PersistenceException(errNotRegistered(bean.getClass()));
+ }
+ BeanDescriptor 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 PersistRequestBean 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.
*
diff --git a/src/test/java/com/avaje/tests/insert/TestInsertOnStringKey.java b/src/test/java/com/avaje/tests/insert/TestInsertOnStringKey.java
new file mode 100644
index 000000000..a41cb8cee
--- /dev/null
+++ b/src/test/java/com/avaje/tests/insert/TestInsertOnStringKey.java
@@ -0,0 +1,40 @@
+package com.avaje.tests.insert;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.tests.model.orderentity.OrderEntity;
+import com.avaje.tests.model.orderentity.OrderItemEntity;
+
+public class TestInsertOnStringKey extends BaseTestCase {
+
+ @Test
+ public void test() {
+
+
+ OrderEntity orderEntity = new OrderEntity();
+ orderEntity.setId("anyOrderId");
+
+ OrderItemEntity orderItemEntity = new OrderItemEntity();
+ orderItemEntity.setId("anyOrderItemId");
+ orderItemEntity.setVariantId("anyVariantId");
+ orderItemEntity.setAmount(BigDecimal.ONE);
+
+ orderEntity.setItems(toList(orderItemEntity));
+
+ Ebean.save(orderEntity);
+
+ }
+
+ private List toList(OrderItemEntity orderItemEntity) {
+ List list = new ArrayList();
+ list.add(orderItemEntity);
+ return list;
+ }
+
+}
diff --git a/src/test/java/com/avaje/tests/model/orderentity/OrderEntity.java b/src/test/java/com/avaje/tests/model/orderentity/OrderEntity.java
new file mode 100644
index 000000000..c8ce0d361
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/orderentity/OrderEntity.java
@@ -0,0 +1,45 @@
+package com.avaje.tests.model.orderentity;
+
+import java.util.List;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "s_orders")
+public class OrderEntity {
+
+ /**
+ * Rob Note: Ideally this would be a UUID rather than a String type - then Ebean would automatically
+ * assign a UUID based id generator and 'do the right thing'.
+ */
+ @Id
+ @Column(name = "uuid")
+ private String id;
+
+ @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "order")
+ private List items;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public List getItems() {
+ return items;
+ }
+
+ public void setItems(List items) {
+ this.items = items;
+ }
+
+
+}
diff --git a/src/test/java/com/avaje/tests/model/orderentity/OrderItemEntity.java b/src/test/java/com/avaje/tests/model/orderentity/OrderItemEntity.java
new file mode 100644
index 000000000..897a9fe6b
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/orderentity/OrderItemEntity.java
@@ -0,0 +1,75 @@
+package com.avaje.tests.model.orderentity;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "s_order_items")
+public class OrderItemEntity {
+
+ /**
+ * Rob Note: Ideally this would be a UUID rather than a String type - then Ebean would automatically
+ * assign a UUID based id generator and 'do the right thing'.
+ */
+ @Id
+ @Column(name = "uuid")
+ private String id;
+
+ @Column(name = "product_variant_uuid")
+ private String variantId;
+
+ @JoinColumn(name = "order_uuid")
+ @ManyToOne
+ private OrderEntity order;
+
+ private int quantity;
+
+ private BigDecimal amount;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getVariantId() {
+ return variantId;
+ }
+
+ public void setVariantId(String variantId) {
+ this.variantId = variantId;
+ }
+
+ public OrderEntity getOrder() {
+ return order;
+ }
+
+ public void setOrder(OrderEntity order) {
+ this.order = order;
+ }
+
+ public int getQuantity() {
+ return quantity;
+ }
+
+ public void setQuantity(int quantity) {
+ this.quantity = quantity;
+ }
+
+ public BigDecimal getAmount() {
+ return amount;
+ }
+
+ public void setAmount(BigDecimal amount) {
+ this.amount = amount;
+ }
+
+}
\ No newline at end of file