query, Transaction transaction, int pageSize);
/**
* Execute the query returning a set of entity beans.
@@ -677,7 +693,7 @@ public interface EbeanServer {
/**
* Save all the beans in the collection.
*/
- public int save(Collection> it) throws OptimisticLockException;
+ public int save(Collection> beans) throws OptimisticLockException;
/**
* Delete the bean.
@@ -704,7 +720,7 @@ public interface EbeanServer {
/**
* Delete the bean given its type and id with an explicit transaction.
*/
- public int delete(Class> beanType, Object id, Transaction t);
+ public int delete(Class> beanType, Object id, Transaction transaction);
/**
* Delete several beans given their type and id values.
@@ -715,7 +731,7 @@ public interface EbeanServer {
* Delete several beans given their type and id values with an explicit
* transaction.
*/
- public void delete(Class> beanType, Collection> ids, Transaction t);
+ public void delete(Class> beanType, Collection> ids, Transaction transaction);
/**
* Execute a SQL Update Delete or Insert statement using the current
@@ -784,133 +800,107 @@ public interface EbeanServer {
/**
* Insert or update a bean with an explicit transaction.
*/
- public void save(Object bean, Transaction t) throws OptimisticLockException;
+ public void save(Object bean, Transaction transaction) throws OptimisticLockException;
/**
* Save all the beans in the iterator with an explicit transaction.
*/
- public int save(Iterator> it, Transaction t) throws OptimisticLockException;
+ public int save(Iterator> it, Transaction transaction) throws OptimisticLockException;
/**
- * Force an update using the bean.
+ * Save all the beans in the collection with an explicit transaction.
+ */
+ public int save(Collection> beans, Transaction transaction) throws OptimisticLockException;
+
+ /**
+ * Saves the bean using an update. If you know you are updating a bean then it is preferrable to
+ * use this update() method rather than save().
*
- * You can use this method to FORCE an update to occur (even on a bean that
- * has not been fetched but say built from JSON or XML). When
- * {@link EbeanServer#save(Object)} is used Ebean determines whether to use an
- * insert or an update based on the state of the bean. Using this method will
- * force an update to occur.
+ * Stateless updates: Note that the bean does not have to be previously fetched to call
+ * update().You can create a new instance and set some of its properties programmatically for via
+ * JSON/XML marshalling etc. This is described as a 'stateless update'.
*
*
- * It is expected that this method is most useful in stateless REST services
- * or web applications where you have the values you wish to update but no
- * existing bean.
+ * Optimistic Locking: Note that if the version property is not set when update() is
+ * called then no optimistic locking is performed (internally ConcurrencyMode.NONE is used).
*
*
- * For updates against beans that have not been fetched (say built from JSON
- * or XML) this will treat deleteMissingChildren=true and will delete any
- * 'missing children'. Refer to
- * {@link EbeanServer#update(Object, Set, Transaction, boolean, boolean)}.
+ * {@link ServerConfig#setUpdatesDeleteMissingChildren(boolean)}: When cascade saving to a
+ * OneToMany or ManyToMany the updatesDeleteMissingChildren setting controls if any other children
+ * that are in the database but are not in the collection are deleted.
+ *
+ *
+ * {@link ServerConfig#setUpdateChangesOnly(boolean)}: The updateChangesOnly setting
+ * controls if only the changed properties are included in the update or if all the loaded
+ * properties are included instead.
*
*
*
*
- * Customer c = new Customer();
- * c.setId(7);
- * c.setName("ModifiedNameNoOCC");
- *
- * // generally you should set the version property
- * // so that Optimistic Concurrency Checking is used.
- * // If a version property is not set then no Optimistic
- * // Concurrency Checking occurs for the update
- * // c.setLastUpdate(lastUpdateTime);
- *
- * // by default the Non-null properties
- * // are included in the update
- * ebeanServer.update(c);
+ * // A 'stateless update' example
+ * Customer customer = new Customer();
+ * customer.setId(7);
+ * customer.setName("ModifiedNameNoOCC");
+ * ebeanServer.update(customer);
*
*
+ *
+ * @see ServerConfig#setUpdatesDeleteMissingChildren(boolean)
+ * @see ServerConfig#setUpdateChangesOnly(boolean)
*/
- public void update(Object bean);
+ public void update(Object bean) throws OptimisticLockException;
/**
- * Force an update of the non-null properties of the bean with an explicit
- * transaction.
- *
- * You can use this method to FORCE an update to occur (even on a bean that
- * has not been fetched but say built from JSON or XML). When
- * {@link EbeanServer#save(Object)} is used Ebean determines whether to use an
- * insert or an update based on the state of the bean. Using this method will
- * force an update to occur.
- *
- *
- * It is expected that this method is most useful in stateless REST services
- * or web applications where you have the values you wish to update but no
- * existing bean.
- *
- *
- * For updates against beans that have not been fetched (say built from JSON
- * or XML) this will treat deleteMissingChildren=true and will delete any
- * 'missing children'. Refer to
- * {@link EbeanServer#update(Object, Set, Transaction, boolean, boolean)}.
- *
+ * Update a bean additionally specifying a transaction.
*/
- public void update(Object bean, Transaction t);
+ public void update(Object bean, Transaction t) throws OptimisticLockException;
/**
- * Force an update additionally specifying whether to 'deleteMissingChildren'
- * when the update cascades to a OneToMany or ManyToMany.
- *
- * By default the deleteMissingChildren is true and it is assumed that when
- * cascade saving a O2M or M2M relationship that the relationship is 'fully
- * loaded' and any child beans that are no longer on the relationship will be
- * deleted.
- *
- *
- * You can use this method to FORCE an update to occur (even on a bean that
- * has not been fetched but say built from JSON or XML). When
- * {@link EbeanServer#save(Object)} is used Ebean determines whether to use an
- * insert or an update based on the state of the bean. Using this method will
- * force an update to occur.
- *
- *
- * It is expected that this method is most useful in stateless REST services
- * or web applications where you have the values you wish to update but no
- * existing bean.
- *
+ * Update a bean additionally specifying a transaction and the deleteMissingChildren setting.
*
* @param bean
* the bean to update
- * @param t
- * optionally you can specify the transaction to use (can be null).
+ * @param transaction
+ * the transaction to use (can be null).
* @param deleteMissingChildren
* specify false if you do not want 'missing children' of a OneToMany
* or ManyToMany to be automatically deleted.
*/
- public void update(Object bean, Transaction t, boolean deleteMissingChildren);
+ public void update(Object bean, Transaction transaction, boolean deleteMissingChildren) throws OptimisticLockException;
/**
- * Force the bean to be saved with an explicit insert.
- *
- * Typically you would use save() and let Ebean determine if the bean should
- * be inserted or updated. This can be useful when you are transferring data
- * between databases and want to explicitly insert a bean into a different
- * database that it came from.
- *
+ * Update a collection of beans. If there is no current transaction one is created and used to
+ * update all the beans in the collection.
+ */
+ public void update(Collection> beans) throws OptimisticLockException;
+
+ /**
+ * Update a collection of beans with an explicit transaction.
+ */
+ public void update(Collection> beans, Transaction transaction) throws OptimisticLockException;
+
+ /**
+ * Insert the bean.
*/
public void insert(Object bean);
/**
- * Force the bean to be saved with an explicit insert.
- *
- * Typically you would use save() and let Ebean determine if the bean should
- * be inserted or updated. This can be useful when you are transferring data
- * between databases and want to explicitly insert a bean into a different
- * database that it came from.
- *
+ * Insert the bean with a transaction.
*/
public void insert(Object bean, Transaction t);
+ /**
+ * Insert a collection of beans. If there is no current transaction one is created and used to
+ * insert all the beans in the collection.
+ */
+ public void insert(Collection> beans);
+
+ /**
+ * Insert a collection of beans with an explicit transaction.
+ */
+ public void insert(Collection> beans, Transaction t);
+
/**
* Delete the associations (from the intersection table) of a ManyToMany given
* the owner bean and the propertyName of the ManyToMany collection.
diff --git a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java
index a1bc4d89e..60833d743 100644
--- a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java
+++ b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java
@@ -511,7 +511,7 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
- * For forced update on a 'New' bean move set all the changedProperties to loaded properties.
+ * For forced update on a 'New' bean set all the loaded properties to changed.
*/
public void setNewBeanForUpdate() {
diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java
index 0ef43cdd0..50bee71ab 100644
--- a/src/main/java/com/avaje/ebean/config/ServerConfig.java
+++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java
@@ -66,7 +66,10 @@ import com.avaje.ebean.util.ClassUtil;
* @author rbygrave
*/
public class ServerConfig {
- /** The Constant DEFAULT_QUERY_BATCH_SIZE. Default: 100 */
+
+ /**
+ * The Constant DEFAULT_QUERY_BATCH_SIZE. Default: 100
+ */
private final static int DEFAULT_QUERY_BATCH_SIZE = 100;
/**
@@ -112,13 +115,19 @@ public class ServerConfig {
*/
private List searchJars = new ArrayList();
- /** The autofetch config. */
+ /**
+ * Config controlling the autofetch behaviour.
+ */
private AutofetchConfig autofetchConfig = new AutofetchConfig();
- /** The database platform name. */
+ /**
+ * The database platform name. Used to imply a DatabasePlatform to use.
+ */
private String databasePlatformName;
- /** The database platform. */
+ /**
+ * The database platform.
+ */
private DatabasePlatform databasePlatform;
/**
@@ -130,10 +139,14 @@ public class ServerConfig {
private int persistBatchSize = 20;
- /** The default batch size for lazy loading */
+ /**
+ * The default batch size for lazy loading
+ */
private int lazyLoadBatchSize = 1;
- /** The query batch size. */
+ /**
+ * The query batch size.
+ */
private int queryBatchSize = -1;
private boolean ddlGenerate;
@@ -152,26 +165,45 @@ public class ServerConfig {
*/
private PstmtDelegate pstmtDelegate;
- /** The data source. */
+ /**
+ * The data source (if programmatically provided).
+ */
private DataSource dataSource;
- /** The data source config. */
+ /**
+ * The data source config.
+ */
private DataSourceConfig dataSourceConfig = new DataSourceConfig();
- /** The data source jndi name. */
+ /**
+ * The data source JNDI name if using a JNDI DataSource.
+ */
private String dataSourceJndiName;
- /** The database boolean true. */
+ /**
+ * The database boolean true value (typically either 1, T, or Y).
+ */
private String databaseBooleanTrue;
- /** The database boolean false. */
+ /**
+ * The database boolean false value (typically either 0, F or N).
+ */
private String databaseBooleanFalse;
- /** The naming convention. */
+ /**
+ * The naming convention.
+ */
private NamingConvention namingConvention;
- /** The update changes only. */
+ /**
+ * Behaviour of update to include on the change properties.
+ */
private boolean updateChangesOnly = true;
+
+ /**
+ * Default behaviour for updates when cascade save on a O2M or M2M to delete any missing children.
+ */
+ private boolean updatesDeleteMissingChildren = true;
private List persistControllers = new ArrayList();
private List> persistListeners = new ArrayList>();
@@ -931,8 +963,23 @@ public class ServerConfig {
public void setUpdateChangesOnly(boolean updateChangesOnly) {
this.updateChangesOnly = updateChangesOnly;
}
-
-
+
+ /**
+ * Return true if updates by default delete missing children when cascading save to a OneToMany or
+ * ManyToMany. When not set this defaults to true.
+ */
+ public boolean isUpdatesDeleteMissingChildren() {
+ return updatesDeleteMissingChildren;
+ }
+
+ /**
+ * Set if updates by default delete missing children when cascading save to a OneToMany or
+ * ManyToMany. When not set this defaults to true.
+ */
+ public void setUpdatesDeleteMissingChildren(boolean updatesDeleteMissingChildren) {
+ this.updatesDeleteMissingChildren = updatesDeleteMissingChildren;
+ }
+
/**
* Return true if the ebeanServer should collection query statistics by ObjectGraphNode.
*/
@@ -1237,6 +1284,9 @@ public class ServerConfig {
collectQueryOrigins = p.getBoolean("collectQueryOrigins", true);
updateChangesOnly = p.getBoolean("updateChangesOnly", true);
+
+ boolean defaultDeleteMissingChildren = p.getBoolean("defaultDeleteMissingChildren", true);
+ updatesDeleteMissingChildren = p.getBoolean("updatesDeleteMissingChildren", defaultDeleteMissingChildren);
boolean batchMode = p.getBoolean("batch.mode", false);
persistBatching = p.getBoolean("persistBatching", batchMode);
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java
index b89fd5223..a69edb67e 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java
@@ -10,6 +10,7 @@ import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.BeanLoader;
import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.ObjectGraphNode;
+import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.core.PstmtBatch;
@@ -30,13 +31,15 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
public void shutdownManaged();
+ /**
+ * Return true if query origins should be collected.
+ */
public boolean isCollectQueryOrigins();
/**
- * Return true if DeleteMissingChildren defaults to true for stateless
- * updates.
+ * Return the server configuration.
*/
- public boolean isDefaultDeleteMissingChildren();
+ public ServerConfig getServerConfig();
/**
* Return the DatabasePlatform for this server.
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java
index 4ebc84e89..49a11d1ae 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/BeanRequest.java
@@ -21,18 +21,18 @@ public abstract class BeanRequest {
/**
* The server processing the request.
*/
- final SpiEbeanServer ebeanServer;
+ protected final SpiEbeanServer ebeanServer;
- final String serverName;
+ protected final String serverName;
/**
* The transaction this is part of.
*/
- SpiTransaction transaction;
+ protected SpiTransaction transaction;
- boolean createdTransaction;
+ protected boolean createdTransaction;
- boolean readOnly;
+ protected boolean readOnly;
public BeanRequest(SpiEbeanServer ebeanServer, SpiTransaction t) {
this.ebeanServer = ebeanServer;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
index 313d56664..3ba968eda 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
@@ -90,7 +90,6 @@ import com.avaje.ebeaninternal.server.deploy.InheritInfo;
import com.avaje.ebeaninternal.server.el.ElFilter;
import com.avaje.ebeaninternal.server.jmx.MAdminAutofetch;
import com.avaje.ebeaninternal.server.lib.ShutdownManager;
-import com.avaje.ebeaninternal.server.loadcontext.DLoadContext;
import com.avaje.ebeaninternal.server.query.CQuery;
import com.avaje.ebeaninternal.server.query.CQueryEngine;
import com.avaje.ebeaninternal.server.query.CallableQueryIds;
@@ -105,7 +104,6 @@ import com.avaje.ebeaninternal.server.query.SqlQueryFutureList;
import com.avaje.ebeaninternal.server.querydefn.DefaultOrmQuery;
import com.avaje.ebeaninternal.server.querydefn.DefaultOrmUpdate;
import com.avaje.ebeaninternal.server.querydefn.DefaultRelationalQuery;
-import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import com.avaje.ebeaninternal.server.text.csv.TCsvReader;
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
import com.avaje.ebeaninternal.server.transaction.RemoteTransactionEvent;
@@ -125,6 +123,8 @@ public final class DefaultServer implements SpiEbeanServer {
private static final String AVAJE_EBEAN = Ebean.class.getName().substring(0, 15);
+ private final ServerConfig serverConfig;
+
private final String serverName;
private final DatabasePlatform databasePlatform;
@@ -143,8 +143,6 @@ public final class DefaultServer implements SpiEbeanServer {
*/
private final boolean rollbackOnChecked;
- private final boolean defaultDeleteMissingChildren;
-
/**
* Handles the save, delete, updateSql CallableSql.
*/
@@ -228,8 +226,7 @@ public final class DefaultServer implements SpiEbeanServer {
*/
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
- ServerConfig serverConfig = config.getServerConfig();
-
+ this.serverConfig = config.getServerConfig();
this.objectGraphStats = new ConcurrentHashMap();
this.metaInfoManager = new DefaultMetaInfoManager(this);
this.serverCacheManager = cache;
@@ -251,9 +248,6 @@ public final class DefaultServer implements SpiEbeanServer {
this.collectQueryStatsByNode = serverConfig.isCollectQueryStatsByNode();
this.maxCallStack = GlobalProperties.getInt("ebean.maxCallStack", 5);
- this.defaultDeleteMissingChildren = "true".equalsIgnoreCase(config.getServerConfig()
- .getProperty("defaultDeleteMissingChildren", "true"));
-
this.rollbackOnChecked = GlobalProperties.getBoolean("ebean.transaction.rollbackOnChecked", true);
this.transactionManager = config.getTransactionManager();
this.transactionScopeManager = config.getTransactionScopeManager();
@@ -309,10 +303,6 @@ public final class DefaultServer implements SpiEbeanServer {
return collectQueryOrigins;
}
- public boolean isDefaultDeleteMissingChildren() {
- return defaultDeleteMissingChildren;
- }
-
public int getLazyLoadBatchSize() {
return lazyLoadBatchSize;
}
@@ -321,6 +311,10 @@ public final class DefaultServer implements SpiEbeanServer {
return pstmtBatch;
}
+ public ServerConfig getServerConfig() {
+ return serverConfig;
+ }
+
public DatabasePlatform getDatabasePlatform() {
return databasePlatform;
}
@@ -1571,63 +1565,109 @@ public final class DefaultServer implements SpiEbeanServer {
* Save the bean with an explicit transaction.
*/
public void save(Object bean, Transaction t) {
-
persister.save(checkEntityBean(bean), t);
}
/**
- * Force an update using the bean updating non-null properties.
+ * Update the bean using the default 'updatesDeleteMissingChildren' setting.
*/
public void update(Object bean) {
update(bean, null);
}
/**
- * Force an update using the bean explicitly stating which properties to
- * include in the update.
+ * Update the bean using the default 'updatesDeleteMissingChildren' setting.
*/
public void update(Object bean, Transaction t) {
- update(bean, t, defaultDeleteMissingChildren);
+ persister.update(checkEntityBean(bean), t);
}
/**
- * Force an update using the bean explicitly stating which properties to
- * include in the update.
+ * Update the bean specifying the deleteMissingChildren option.
*/
public void update(Object bean, Transaction t, boolean deleteMissingChildren) {
-
- persister.forceUpdate(checkEntityBean(bean), t, deleteMissingChildren);
+ persister.update(checkEntityBean(bean), t, deleteMissingChildren);
}
/**
- * Force the bean to be saved with an explicit insert.
- *
- * Typically you would use save() and let Ebean determine if the bean should
- * be inserted or updated. This can be useful when you are transferring data
- * between databases and want to explicitly insert a bean into a different
- * database that it came from.
- *
+ * Update all beans in the collection.
+ */
+ public void update(Collection> beans) {
+ update(beans, null);
+ }
+
+ /**
+ * Update all beans in the collection with an explicit transaction.
+ */
+ public void update(Collection> beans, Transaction t) {
+
+ if (beans == null || beans.isEmpty()) {
+ // Nothing to update?
+ return;
+ }
+
+ TransWrapper wrap = initTransIfRequired(t);
+ try {
+ SpiTransaction trans = wrap.transaction;
+ for (Object bean : beans) {
+ update(checkEntityBean(bean), trans);
+ }
+ wrap.commitIfCreated();
+
+ } catch (RuntimeException e) {
+ wrap.rollbackIfCreated();
+ throw e;
+ }
+ }
+
+ /**
+ * Insert the bean.
*/
public void insert(Object bean) {
insert(bean, null);
}
/**
- * Force the bean to be saved with an explicit insert.
- *
- * Typically you would use save() and let Ebean determine if the bean should
- * be inserted or updated. This can be useful when you are transferring data
- * between databases and want to explicitly insert a bean into a different
- * database that it came from.
- *
+ * Insert the bean with a transaction.
*/
public void insert(Object bean, Transaction t) {
- persister.forceInsert(checkEntityBean(bean), t);
+ persister.insert(checkEntityBean(bean), t);
+ }
+
+ /**
+ * Insert all beans in the collection.
+ */
+ public void insert(Collection> beans) {
+ insert(beans, null);
+ }
+
+ /**
+ * Insert all beans in the collection with a transaction.
+ */
+ public void insert(Collection> beans, Transaction t) {
+
+ if (beans == null || beans.isEmpty()) {
+ // Nothing to insert?
+ return;
+ }
+
+ TransWrapper wrap = initTransIfRequired(t);
+ try {
+ SpiTransaction trans = wrap.transaction;
+ for (Object bean : beans) {
+ persister.insert(checkEntityBean(bean), trans);
+ }
+ wrap.commitIfCreated();
+
+ } catch (RuntimeException e) {
+ wrap.rollbackIfCreated();
+ throw e;
+ }
}
private EntityBean checkEntityBean(Object bean) {
if (bean == null) {
- throw new NullPointerException(Message.msg("bean.isnull"));
+ throw new IllegalArgumentException(Message.msg("bean.isnull"));
}
if (bean instanceof EntityBean == false) {
throw new IllegalArgumentException("Was expecting an EntityBean but got a "+bean.getClass());
@@ -1735,6 +1775,14 @@ public final class DefaultServer implements SpiEbeanServer {
return save(c.iterator(), null);
}
+ /**
+ * Perform an update or insert on each bean in the collection. Returns the
+ * number of beans that where saved.
+ */
+ public int save(Collection> c, Transaction t) {
+ return save(c.iterator(), t);
+ }
+
/**
* Save all beans in the iterator with an explicit transaction.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java
index 5ce5a750f..9f2d9aa34 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java
@@ -15,14 +15,14 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
DETERMINE, INSERT, UPDATE, DELETE, ORMUPDATE, UPDATESQL, CALLABLESQL
};
- boolean persistCascade;
+ protected boolean persistCascade;
/**
* One of INSERT, UPDATE, DELETE, UPDATESQL or CALLABLESQL.
*/
- Type type;
+ protected Type type;
- final PersistExecute persistExecute;
+ protected final PersistExecute persistExecute;
/**
* Used by CallableSqlRequest and UpdateSqlRequest.
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 08236988b..7253ded64 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
@@ -32,66 +32,64 @@ import com.avaje.ebeaninternal.server.transaction.BeanPersistIdMap;
/**
* PersistRequest for insert update or delete of a bean.
*/
-public class PersistRequestBean extends PersistRequest implements BeanPersistRequest {
+public final class PersistRequestBean extends PersistRequest implements BeanPersistRequest {
- protected final BeanManager beanManager;
+ private final BeanManager beanManager;
- protected final BeanDescriptor beanDescriptor;
+ private final BeanDescriptor beanDescriptor;
- protected final BeanPersistListener beanPersistListener;
+ private final BeanPersistListener beanPersistListener;
/**
* For per post insert update delete control.
*/
- protected final BeanPersistController controller;
+ private final BeanPersistController controller;
/**
* The bean being persisted.
*/
- protected final T bean;
+ private final T bean;
- protected final EntityBean entityBean;
+ private final EntityBean entityBean;
/**
* The associated intercept.
*/
- protected final EntityBeanIntercept intercept;
+ private final EntityBeanIntercept intercept;
/**
* The parent bean for unidirectional save.
*/
- protected final Object parentBean;
+ private final Object parentBean;
- protected final boolean dirty;
+ private final boolean dirty;
- protected ConcurrencyMode concurrencyMode;
+ private ConcurrencyMode concurrencyMode;
/**
* The unique id used for logging summary.
*/
- protected Object idValue;
+ private Object idValue;
/**
* Hash value used to handle cascade delete both ways in a relationship.
*/
- protected Integer beanHash;
- protected Integer beanIdentityHash;
+ private Integer beanHash;
- protected boolean notifyCache;
+ private boolean notifyCache;
- private boolean statelessUpdate;
private boolean deleteMissingChildren;
private final Set dirtyPropertyNames;
/**
* Flag used to detect when only many properties where updated via a cascade. Used to ensure
- * appropriate cache updates occur in that case.
+ * appropriate caches are updated in that case.
*/
private boolean updatedManysOnly;
/**
- * Many properties that were cascade saved (and hence might need cache update later).
+ * Many properties that were cascade saved (and hence might need caches updated later).
*/
private List> updatedManys;
@@ -108,21 +106,23 @@ public class PersistRequestBean extends PersistRequest implements BeanPersist
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.persistCascade = t.isPersistCascade();
}
if (this.type == Type.UPDATE && intercept.isNew() ) {
+ // 'stateless update' - set bean up for doing update
intercept.setNewBeanForUpdate();
}
+ // derive the set of property names now as we will pass them to a beanPersistListener later
this.dirtyPropertyNames = (beanPersistListener == null) ? null : intercept.getDirtyPropertyNames();
this.bean = bean;
this.parentBean = parentBean;
-
this.controller = beanDescriptor.getPersistController();
this.concurrencyMode = beanDescriptor.getConcurrencyMode(intercept);
- // this is ok to not use isNewOrDirty() as used for updates only
this.dirty = intercept.isDirty();
}
@@ -268,13 +268,6 @@ public class PersistRequestBean extends PersistRequest implements BeanPersist
return beanDescriptor;
}
- /**
- * Return true if this is a stateless update.
- */
- public boolean isStatelessUpdate() {
- return statelessUpdate;
- }
-
/**
* Return true if a stateless update should also delete any missing details
* beans.
@@ -284,15 +277,9 @@ public class PersistRequestBean extends PersistRequest implements BeanPersist
}
/**
- * Set to true if this is a stateless update.
- *
- * By Stateless it means that the bean was not previously fetched (and so
- * does not have it's previous state) so we are doing an update on a bean
- * that was probably created from JSON or XML.
- *
+ * Set if deleteMissingChildren occurs on cascade save to OneToMany or ManyToMany.
*/
- public void setStatelessUpdate(boolean statelessUpdate, boolean deleteMissingChildren) {
- this.statelessUpdate = statelessUpdate;
+ public void setDeleteMissingChildren(boolean deleteMissingChildren) {
this.deleteMissingChildren = deleteMissingChildren;
}
@@ -609,8 +596,8 @@ public class PersistRequestBean extends PersistRequest implements BeanPersist
}
/**
- * A reference bean was saved. Check if any of its many properties where
- * cascade saved and hence we need to update related many property caches.
+ * Check if any of its many properties where cascade saved and hence we need to update related
+ * many property caches.
*/
public void checkUpdatedManysOnly() {
if (!dirty && updatedManys != null) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java b/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java
index 177dd712e..f04a45255 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/Persister.java
@@ -14,15 +14,20 @@ import com.avaje.ebean.bean.EntityBean;
*/
public interface Persister {
+ /**
+ * Update the bean.
+ */
+ public void update(EntityBean entityBean, Transaction t);
+
/**
- * Force an Update using the given bean.
+ * Update the bean specifying deleteMissingChildren.
*/
- public void forceUpdate(EntityBean entityBean, Transaction t, boolean deleteMissingChildren);
+ public void update(EntityBean entityBean, Transaction t, boolean deleteMissingChildren);
/**
* Force an Insert using the given bean.
*/
- public void forceInsert(EntityBean entityBean, Transaction t);
+ public void insert(EntityBean entityBean, Transaction t);
/**
* Insert or update the bean depending on its state.
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 813dce2c5..d90d6fc4f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -207,8 +207,9 @@ public class BeanDescriptor implements MetaBeanInfo {
* Derived list of properties that are used for version concurrency checking.
*/
private final BeanProperty versionProperty;
+
private final int versionPropertyIndex;
-
+
/**
* Properties local to this type (not from a super type).
*/
@@ -436,7 +437,7 @@ public class BeanDescriptor implements MetaBeanInfo {
this.versionPropertyIndex = (versionProperty == null) ? -1 : ebi.findProperty(versionProperty.getName());
}
}
-
+
/**
* Create an entity bean that is used as a prototype/factory to create new instances.
*/
@@ -1818,6 +1819,12 @@ public class BeanDescriptor implements MetaBeanInfo {
}
public boolean isInsertMode(EntityBeanIntercept ebi) {
+
+ if (ebi.isLoaded()) {
+ return false;
+ }
+
+ // determine based on Id property
if (idProperty.isEmbedded()) {
return !ebi.isLoaded();
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java
index b5d41b30d..3596113fb 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java
@@ -107,6 +107,13 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc {
}
}
}
+
+ /**
+ * Return the property value as an entity bean.
+ */
+ public EntityBean getValueAsEntityBean(EntityBean owner) {
+ return (EntityBean)getValue(owner);
+ }
public void setRelationshipProperty(BeanPropertyAssocMany> relationshipProperty){
this.relationshipProperty = relationshipProperty;
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 172cdd6b2..86d8b6981 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java
@@ -71,10 +71,12 @@ public final class DefaultPersister implements Persister {
private final BeanDescriptorManager beanDescriptorManager;
+ private final boolean updatesDeleteMissingChildren;
public DefaultPersister(SpiEbeanServer server, Binder binder, BeanDescriptorManager descMgr, PstmtBatch pstmtBatch) {
this.server = server;
+ this.updatesDeleteMissingChildren = server.getServerConfig().isUpdatesDeleteMissingChildren();
this.beanDescriptorManager = descMgr;
this.persistExecute = new DefaultPersistExecute(binder, pstmtBatch);
}
@@ -151,21 +153,34 @@ public final class DefaultPersister implements Persister {
}
/**
- * Force an Update using the given bean.
+ * Update the bean.
*/
- public void forceUpdate(EntityBean entityBean, Transaction t, boolean deleteMissingChildren) {
+ public void update(EntityBean entityBean, Transaction t) {
+ update(entityBean, t, updatesDeleteMissingChildren);
+ }
+
+ /**
+ * Update the bean specifying deleteMissingChildren.
+ */
+ public void update(EntityBean entityBean, Transaction t, boolean deleteMissingChildren) {
PersistRequestBean> req = createRequest(entityBean, t, null, PersistRequest.Type.UPDATE);
- if (req.isReference()) {
- // skip update as only got the Id property set
- return;
- }
- req.setStatelessUpdate(true, deleteMissingChildren);
+ req.setDeleteMissingChildren(deleteMissingChildren);
try {
req.initTransIfRequired();
- update(req);
+
+ if (req.isReference()) {
+ // its a reference so see if there are manys to save...
+ if (req.isPersistCascade()) {
+ saveAssocMany(false, req);
+ }
+ req.checkUpdatedManysOnly();
+
+ } else {
+ update(req);
+ }
+
req.commitTransIfRequired();
- // finished a 'normal' update
return;
} catch (RuntimeException ex) {
@@ -174,14 +189,22 @@ public final class DefaultPersister implements Persister {
}
}
+ /**
+ * Insert or update the bean.
+ */
public void save(EntityBean bean, Transaction t) {
- saveRecurse(bean, t, null);
+ if (bean._ebean_getIntercept().isLoaded()) {
+ // deleteMissingChildren is false when using 'save' on 'loaded' beans
+ update(bean, t, false);
+ } else {
+ insert(bean, t);
+ }
}
/**
- * Explicitly specify to insert this bean.
+ * Insert this bean.
*/
- public void forceInsert(EntityBean bean, Transaction t) {
+ public void insert(EntityBean bean, Transaction t) {
PersistRequestBean> req = createRequest(bean, t, null, PersistRequest.Type.INSERT);
try {
@@ -195,43 +218,17 @@ public final class DefaultPersister implements Persister {
}
}
- private void saveRecurse(Object bean, Transaction t, Object parentBean) {
- if (bean == null) {
- throw new NullPointerException(Message.msg("bean.isnull"));
- }
-
- if (bean instanceof EntityBean == false) {
- throw new IllegalArgumentException("This bean is of type ["+bean.getClass()+"] is not enhanced?");
- }
-
- PersistRequestBean> req = createRequest(bean, t, parentBean, PersistRequest.Type.DETERMINE);
- try {
- req.initTransIfRequired();
- saveEnhanced(req);
- req.commitTransIfRequired();
-
- } catch (RuntimeException ex) {
- req.rollbackTransIfRequired();
- throw ex;
- }
- }
-
- /**
- * Insert or update the bean depending on PersistControl and the bean state.
- */
- private void saveEnhanced(PersistRequestBean> request) {
-
- EntityBeanIntercept intercept = request.getEntityBeanIntercept();
+ private void saveRecurse(EntityBean bean, Transaction t, Object parentBean) {
+ // determine insert or update taking into account stateless updates
+ PersistRequestBean> request = createRequest(bean, t, parentBean, PersistRequest.Type.DETERMINE);
+
if (request.isReference()) {
// its a reference...
if (request.isPersistCascade()) {
// save any associated List held beans
- intercept.setLoaded();
saveAssocMany(false, request);
- intercept.setReference(-1);
- }
-
+ }
request.checkUpdatedManysOnly();
} else {
@@ -273,7 +270,7 @@ public final class DefaultPersister implements Persister {
}
/**
- * Update the bean. Return NOT_SAVED if the bean values have not changed.
+ * Update the bean.
*/
private void update(PersistRequestBean> request) {
@@ -555,7 +552,7 @@ public final class DefaultPersister implements Persister {
// check for partial beans
if (request.isLoadedProperty(prop)) {
- Object detailBean = prop.getValue(parentBean);
+ EntityBean detailBean = prop.getValueAsEntityBean(parentBean);
if (detailBean != null) {
if (prop.isSaveRecurseSkippable(detailBean)) {
// skip saving this bean
@@ -591,7 +588,6 @@ public final class DefaultPersister implements Persister {
private final EntityBean parentBean;
private final SpiTransaction transaction;
private final boolean cascade;
- private final boolean statelessUpdate;
private final boolean deleteMissingChildren;
private SaveManyPropRequest(boolean insertedParent, BeanPropertyAssocMany> many, EntityBean parentBean, PersistRequestBean> request) {
@@ -600,7 +596,6 @@ public final class DefaultPersister implements Persister {
this.cascade = many.getCascadeInfo().isSave();
this.parentBean = parentBean;
this.transaction = request.getTransaction();
- this.statelessUpdate = request.isStatelessUpdate();
this.deleteMissingChildren = request.isDeleteMissingChildren();
}
@@ -610,7 +605,6 @@ public final class DefaultPersister implements Persister {
this.parentBean = parentBean;
this.transaction = t;
this.cascade = true;
- this.statelessUpdate = false;
this.deleteMissingChildren = false;
}
@@ -626,10 +620,6 @@ public final class DefaultPersister implements Persister {
return ModifyListenMode.REMOVALS.equals(many.getModifyListenMode());
}
- private boolean isStatelessUpdate() {
- return statelessUpdate;
- }
-
private boolean isDeleteMissingChildren() {
return deleteMissingChildren;
}
@@ -672,12 +662,15 @@ public final class DefaultPersister implements Persister {
saveAssocManyIntersection(saveMany, saveMany.isDeleteMissingChildren());
}
} else {
- if (saveMany.isCascade()) {
+ if (saveMany.isModifyListenMode()) {
+ // delete any removed beans via private owned. Needs to occur before
+ // a 'deleteMissingChildren' statement occurs
+ removeAssocManyPrivateOwned(saveMany);
+ }
+ if (saveMany.isCascade()) {
+ // potentially deletes 'missing children' for 'stateless update'
saveAssocManyDetails(saveMany, saveMany.isDeleteMissingChildren());
}
- if (saveMany.isModifyListenMode()) {
- removeAssocManyPrivateOwned(saveMany);
- }
}
}
@@ -789,42 +782,32 @@ public final class DefaultPersister implements Persister {
}
}
- if (skipSavingThisBean) {
- // unmodified bean that does not recurse its save
- // so we can skip the save for this bean.
- // Reset skipSavingThisBean for the next detailBean
- skipSavingThisBean = false;
+ if (skipSavingThisBean) {
+ // unmodified bean that does not recurse its save
+ // so we can skip the save for this bean.
+ // Reset skipSavingThisBean for the next detailBean
+ skipSavingThisBean = false;
- } else if (!saveMany.isStatelessUpdate()) {
- // normal save recurse
- saveRecurse(detailBean, t, parentBean);
-
- } else {
- if (targetDescriptor.isStatelessUpdate(detail)) {
- // update based on the value of Version/Id properties
- // cascade update in stateless mode
- forceUpdate(detail, t, deleteMissingChildren);
- } else {
- // cascade insert
- forceInsert(detail, t);
- }
- }
- if (detailIds != null) {
- // remember the Id (other details not in the collection) will be removed
- Object id = targetDescriptor.getId(detail);
- if (!DmlUtil.isNullOrZero(id)) {
- detailIds.add(id);
- }
- }
- }
+ } else {
+ // normal save recurse
+ saveRecurse(detail, t, parentBean);
+ }
+ if (detailIds != null) {
+ // remember the Id (other details not in the collection) will be removed
+ Object id = targetDescriptor.getId(detail);
+ if (!DmlUtil.isNullOrZero(id)) {
+ detailIds.add(id);
+ }
+ }
+ }
}
if (detailIds != null) {
+ // deleteMissingChildren is true so deleting children that were not just processed
deleteManyDetails(t, prop.getBeanDescriptor(), parentBean, prop, detailIds);
}
t.depth(-1);
-
}
public int deleteManyToManyAssociations(EntityBean ownerBean, String propertyName, Transaction t) {
@@ -859,7 +842,7 @@ public final class DefaultPersister implements Persister {
} else if (prop instanceof BeanPropertyAssocOne>) {
BeanPropertyAssocOne> oneProp = (BeanPropertyAssocOne>) prop;
- Object assocBean = oneProp.getValue(parentBean);
+ EntityBean assocBean = oneProp.getValueAsEntityBean(parentBean);
int depth = oneProp.isOneToOneExported() ? 1 : -1;
int revertDepth = -1 * depth;
@@ -1094,7 +1077,7 @@ public final class DefaultPersister implements Persister {
// check for partial objects
if (request.isLoadedProperty(prop)) {
- Object detailBean = prop.getValue(request.getEntityBean());
+ EntityBean detailBean = prop.getValueAsEntityBean(request.getEntityBean());
if (detailBean != null) {
if (prop.isReference(detailBean)) {
// skip saving a reference
diff --git a/src/test/java/com/avaje/tests/compositekeys/TestCKeyLazyLoad.java b/src/test/java/com/avaje/tests/compositekeys/TestCKeyLazyLoad.java
index e9de7632b..5d6a42be0 100644
--- a/src/test/java/com/avaje/tests/compositekeys/TestCKeyLazyLoad.java
+++ b/src/test/java/com/avaje/tests/compositekeys/TestCKeyLazyLoad.java
@@ -32,7 +32,7 @@ public class TestCKeyLazyLoad extends BaseTestCase {
p.add(new CKeyDetail("somethine one"));
p.add(new CKeyDetail("somethine two"));
- Ebean.insert(p);
+ Ebean.save(p);
CKeyAssoc assoc2 = new CKeyAssoc();
assoc2.setAssocOne("assocTwo");
@@ -46,7 +46,7 @@ public class TestCKeyLazyLoad extends BaseTestCase {
p2.add(new CKeyDetail("somethine one"));
p2.add(new CKeyDetail("somethine two"));
- Ebean.insert(p2);
+ Ebean.save(p2);
CKeyParentId searchId = new CKeyParentId(1, "one");
diff --git a/src/test/java/com/avaje/tests/compositekeys/TestOnCascadeDeleteChildrenWithCompositeKeys.java b/src/test/java/com/avaje/tests/compositekeys/TestOnCascadeDeleteChildrenWithCompositeKeys.java
index a989eb717..480c28151 100644
--- a/src/test/java/com/avaje/tests/compositekeys/TestOnCascadeDeleteChildrenWithCompositeKeys.java
+++ b/src/test/java/com/avaje/tests/compositekeys/TestOnCascadeDeleteChildrenWithCompositeKeys.java
@@ -35,8 +35,8 @@ public class TestOnCascadeDeleteChildrenWithCompositeKeys extends BaseTestCase {
Ebean.delete(Ebean.find(User.class).findList());
// insert 2 User records
- Ebean.insert(new User(1L));
- Ebean.insert(new User(2L));
+ Ebean.save(new User(1L));
+ Ebean.save(new User(2L));
}
/**
diff --git a/src/test/java/com/avaje/tests/inheritance/TestSkippable.java b/src/test/java/com/avaje/tests/inheritance/TestSkippable.java
index eb6f6e23e..36ad402dc 100644
--- a/src/test/java/com/avaje/tests/inheritance/TestSkippable.java
+++ b/src/test/java/com/avaje/tests/inheritance/TestSkippable.java
@@ -3,6 +3,9 @@ package com.avaje.tests.inheritance;
import junit.framework.Assert;
import junit.framework.TestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.basic.AttributeHolder;
import com.avaje.tests.model.basic.ListAttribute;
@@ -10,7 +13,7 @@ import com.avaje.tests.model.basic.ListAttributeValue;
public class TestSkippable extends TestCase {
-
+ private static final Logger logger = LoggerFactory.getLogger(TestSkippable.class);
/**
* Test query.
@@ -18,8 +21,8 @@ public class TestSkippable extends TestCase {
* it was considered safe to skip as it didn't take into account any derived classes
* into account with e.g. collections and Cascade options
*/
- public void testQuery()
- {
+ public void testQuery() {
+
// Setup the data first
final ListAttributeValue value1 = new ListAttributeValue();
final ListAttributeValue value2 = new ListAttributeValue();
@@ -30,20 +33,21 @@ public class TestSkippable extends TestCase {
final ListAttribute listAttribute = new ListAttribute();
listAttribute.add(value1);
Ebean.save(listAttribute);
-
+ logger.info(" -- seeded data");
+
final ListAttribute listAttributeDB = Ebean.find(ListAttribute.class, listAttribute.getId());
Assert.assertNotNull(listAttributeDB);
final ListAttributeValue value1_DB = listAttributeDB.getValues().iterator().next();
-
-
Assert.assertTrue(value1.getId().equals(value1_DB.getId()));
+ logger.info(" -- asserted data in db");
final AttributeHolder holder = new AttributeHolder();
holder.add(listAttributeDB);
Ebean.save(holder);
+ logger.info(" -- saved holder");
// Now change the M2M listAttribute.values and save the holder
// The save should cascade as follows
@@ -53,11 +57,11 @@ public class TestSkippable extends TestCase {
// Save the holder - should cascade down to the listAtribute and save the values
Ebean.save(holder);
+ logger.info(" -- M2M detected delete of value1 and add of value2 ?");
final ListAttribute listAttributeDB_2 = Ebean.find(ListAttribute.class, listAttributeDB.getId());
Assert.assertNotNull(listAttributeDB_2);
-
final ListAttributeValue value2_DB_2 = listAttributeDB_2.getValues().iterator().next();
diff --git a/src/test/java/com/avaje/tests/insert/TestInsertCollection.java b/src/test/java/com/avaje/tests/insert/TestInsertCollection.java
new file mode 100644
index 000000000..583f027d8
--- /dev/null
+++ b/src/test/java/com/avaje/tests/insert/TestInsertCollection.java
@@ -0,0 +1,69 @@
+package com.avaje.tests.insert;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.tests.model.basic.Customer;
+
+public class TestInsertCollection extends BaseTestCase {
+
+ @Test
+ public void test() {
+
+ Customer cust1 = new Customer();
+ cust1.setName("jim");
+
+ Customer cust2 = new Customer();
+ cust2.setName("bob");
+
+ List customers = new ArrayList();
+ customers.add(cust1);
+ customers.add(cust2);
+
+ Ebean.insert(customers);
+
+ Assert.assertNotNull(cust1.getId());
+ Assert.assertNotNull(cust2.getId());
+
+ Customer cust1Check = Ebean.find(Customer.class, cust1.getId());
+ Assert.assertEquals(cust1.getName(), cust1Check.getName());
+ Customer cust2Check = Ebean.find(Customer.class, cust2.getId());
+ Assert.assertEquals(cust2.getName(), cust2Check.getName());
+
+ cust1.setName("jim-changed");
+ cust2.setName("bob-changed");
+
+ Ebean.update(customers);
+
+ Customer cust1Check2 = Ebean.find(Customer.class, cust1.getId());
+ Assert.assertEquals("jim-changed", cust1Check2.getName());
+ Customer cust2Check2 = Ebean.find(Customer.class, cust2.getId());
+ Assert.assertEquals("bob-changed", cust2Check2.getName());
+
+
+ cust1Check2.setName("jim-updated");
+ Customer cust3 = new Customer();
+ cust3.setName("mac");
+
+ List saveList = new ArrayList();
+ saveList.add(cust1Check2);
+ saveList.add(cust3);
+
+ Ebean.save(saveList);
+
+
+ Customer cust1Check3 = Ebean.find(Customer.class, cust1.getId());
+ Assert.assertEquals("jim-updated", cust1Check3.getName());
+ Customer cust3Check = Ebean.find(Customer.class, cust3.getId());
+ Assert.assertEquals("mac", cust3Check.getName());
+
+ }
+
+
+
+}
diff --git a/src/test/java/com/avaje/tests/model/basic/ResetBasicData.java b/src/test/java/com/avaje/tests/model/basic/ResetBasicData.java
index 4eb1eb360..8762b7e61 100644
--- a/src/test/java/com/avaje/tests/model/basic/ResetBasicData.java
+++ b/src/test/java/com/avaje/tests/model/basic/ResetBasicData.java
@@ -80,12 +80,12 @@ public class ResetBasicData {
Country c = new Country();
c.setCode("NZ");
c.setName("New Zealand");
- server.insert(c);
+ server.save(c);
Country au = new Country();
au.setCode("AU");
au.setName("Australia");
- server.insert(au);
+ server.save(au);
}
});
}
@@ -99,25 +99,25 @@ public class ResetBasicData {
p.setId(1);
p.setName("Chair");
p.setSku("C001");
- server.insert(p);
+ server.save(p);
p = new Product();
p.setId(2);
p.setName("Desk");
p.setSku("DSK1");
- server.insert(p);
+ server.save(p);
p = new Product();
p.setId(3);
p.setName("Computer");
p.setSku("C002");
- server.insert(p);
+ server.save(p);
p = new Product();
p.setId(4);
p.setName("Printer");
p.setSku("C003");
- server.insert(p);
+ server.save(p);
}
});
}
diff --git a/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java b/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java
index 3cabfa29b..5c0fb9bd9 100644
--- a/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java
+++ b/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java
@@ -2,6 +2,9 @@ package com.avaje.tests.update;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
import org.junit.Assert;
import org.junit.Before;
@@ -251,4 +254,120 @@ public class TestStatelessUpdate extends BaseTestCase {
// maybe check if update instead of insert has been executed,
// currently "Unique index or primary key violation" PersistenceException is throwing
}
+
+ @Test
+ public void testStatelessRecursiveUpdateWithChangesInDetailOnly() {
+ // arrange
+ Contact contact1 = new Contact();
+ contact1.setLastName("contact1");
+
+ Contact contact2 = new Contact();
+ contact2.setLastName("contact2");
+
+ Customer customer = new Customer();
+ customer.setName("something");
+ customer.getContacts().add(contact1);
+ customer.getContacts().add(contact2);
+
+ server.save(customer);
+
+
+
+ // act
+ Contact updateContact1 = new Contact();
+ updateContact1.setId(contact1.getId());
+ updateContact1.setLastName("contact1-changed");
+
+
+ Contact updateContact3 = new Contact();
+ //updateContact3.setId(contact3.getId());
+ updateContact3.setLastName("contact3-added");
+
+ Customer updateCustomer = new Customer();
+ updateCustomer.setId(customer.getId());
+ updateCustomer.getContacts().add(updateContact1);
+ updateCustomer.getContacts().add(updateContact3);
+
+ // not adding contact2 so it will get deleted
+ //updateCustomer.getContacts().add(updateContact2);
+
+ server.update(updateCustomer);
+
+
+ // assert
+ Customer assCustomer = server.find(Customer.class, customer.getId());
+ List assContacts = assCustomer.getContacts();
+ Assert.assertEquals(2, assContacts.size());
+ Set ids = new LinkedHashSet();
+ Set names = new LinkedHashSet();
+ for (Contact contact : assContacts) {
+ ids.add(contact.getId());
+ names.add(contact.getLastName());
+ }
+ Assert.assertTrue(ids.contains(contact1.getId()));
+ Assert.assertTrue(ids.contains(updateContact3.getId()));
+ Assert.assertFalse(ids.contains(contact2.getId()));
+
+ Assert.assertTrue(names.contains(updateContact1.getLastName()));
+ Assert.assertTrue(names.contains(updateContact3.getLastName()));
+ }
+
+
+ @Test
+ public void testStatelessRecursiveUpdateWithChangesInDetailOnlyAnd() {
+ // arrange
+ Contact contact1 = new Contact();
+ contact1.setLastName("contact1");
+
+ Contact contact2 = new Contact();
+ contact2.setLastName("contact2");
+
+ Customer customer = new Customer();
+ customer.setName("something");
+ customer.getContacts().add(contact1);
+ customer.getContacts().add(contact2);
+
+ server.save(customer);
+
+
+ // act
+ Contact updateContact1 = new Contact();
+ updateContact1.setId(contact1.getId());
+ updateContact1.setLastName("contact1-changed");
+
+
+ Contact updateContact3 = new Contact();
+ updateContact3.setLastName("contact3-added");
+
+ Customer updateCustomer = new Customer();
+ updateCustomer.setId(customer.getId());
+ updateCustomer.getContacts().add(updateContact1);
+ updateCustomer.getContacts().add(updateContact3);
+
+ // not adding contact2 but it won't be deleted in this case
+ boolean deleteMissingChildren = false;
+ server.update(updateCustomer, null, deleteMissingChildren);
+
+
+ // assert
+ Customer assCustomer = server.find(Customer.class, customer.getId());
+ List assContacts = assCustomer.getContacts();
+
+ // contact 2 was not deleted this time
+ Assert.assertEquals(3, assContacts.size());
+
+ Set ids = new LinkedHashSet();
+ Set names = new LinkedHashSet();
+ for (Contact contact : assContacts) {
+ ids.add(contact.getId());
+ names.add(contact.getLastName());
+ }
+ Assert.assertTrue(ids.contains(contact1.getId()));
+ Assert.assertTrue(ids.contains(updateContact3.getId()));
+ Assert.assertTrue(ids.contains(contact2.getId()));
+
+ Assert.assertTrue(names.contains(updateContact1.getLastName()));
+ Assert.assertTrue(names.contains(contact2.getLastName()));
+ Assert.assertTrue(names.contains(updateContact3.getLastName()));
+ }
}