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