diff --git a/src/main/java/com/avaje/ebean/Ebean.java b/src/main/java/com/avaje/ebean/Ebean.java index e24f760bf..68fe10ae2 100644 --- a/src/main/java/com/avaje/ebean/Ebean.java +++ b/src/main/java/com/avaje/ebean/Ebean.java @@ -5,20 +5,20 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.avaje.ebean.annotation.CacheStrategy; import com.avaje.ebean.cache.ServerCacheManager; import com.avaje.ebean.config.GlobalProperties; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.text.csv.CsvReader; import com.avaje.ebean.text.json.JsonContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * This Ebean object is effectively a singleton that holds a map of registered @@ -462,48 +462,59 @@ public final class Ebean { } /** - * Force an update using the bean updating the non-null properties. + * Insert a collection of beans. + */ + public static void insert(Collection beans) { + serverMgr.getPrimaryServer().insert(beans); + } + + /** + * 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 Ebean#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
-   * Ebean.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 static void update(Object bean) { + public static void update(Object bean) throws OptimisticLockException { serverMgr.getPrimaryServer().update(bean); } + /** + * Update the beans in the collection. + */ + public static void update(Collection beans) throws OptimisticLockException { + serverMgr.getPrimaryServer().update(beans); + } + /** * Save all the beans from an Iterator. */ @@ -514,8 +525,8 @@ public final class Ebean { /** * Save all the beans from a Collection. */ - public static int save(Collection c) throws OptimisticLockException { - return save(c.iterator()); + public static int save(Collection beans) throws OptimisticLockException { + return serverMgr.getPrimaryServer().save(beans); } /** diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java index b2fb352f4..10deb53c9 100644 --- a/src/main/java/com/avaje/ebean/EbeanServer.java +++ b/src/main/java/com/avaje/ebean/EbeanServer.java @@ -85,9 +85,12 @@ import com.avaje.ebean.text.json.JsonContext; public interface EbeanServer { /** - * Shutdown the EbeanServer. + * Shutdown the EbeanServer programmatically. *

- * If the under underlying DataSource is the EbeanORM implementation then you + * This method is not normally required. Ebean registers a shutdown hook and shuts down cleanly. + *

+ *

+ * If the under underlying DataSource is the Ebean implementation then you * also have the option of shutting down the DataSource and deregistering the * JDBC driver. *

@@ -410,12 +413,25 @@ public interface EbeanServer { public T find(Class beanType, Object uid); /** - * Get a reference Object (see {@link Ebean#getReference(Class, Object)}. + * Get a reference bean (see {@link Ebean#getReference(Class, Object)}. *

* This will not perform a query against the database. *

+ *
+   * Product product = Ebean.getReference(Product.class, 1);
    * 
-   * @see Ebean#getReference(Class, Object)
+   * // You can get the id without causing a fetch/lazy load
+   * Integer productId = product.getId();
+   * 
+   * // If you try to get any other property a fetch/lazy loading will occur
+   * // This will cause a query to execute...
+   * String name = product.getName();
+   * 
+ * + * @param beanType + * the type of entity bean + * @param id + * the id value */ public T getReference(Class beanType, Object uid); @@ -428,21 +444,21 @@ public interface EbeanServer { /** * Return the Id values of the query as a List. */ - public List findIds(Query query, Transaction t); + public List findIds(Query query, Transaction transaction); /** * Return a QueryIterator for the query. This is similar to findVisit in that * not all the result beans need to be held in memory at the same time and as * such is go for processing large queries. */ - public QueryIterator findIterate(Query query, Transaction t); + public QueryIterator findIterate(Query query, Transaction transaction); /** * Execute the query visiting the results. This is similar to findIterate in * that not all the result beans need to be held in memory at the same time * and as such is go for processing large queries. */ - public void findVisit(Query query, QueryResultVisitor visitor, Transaction t); + public void findVisit(Query query, QueryResultVisitor visitor, Transaction transaction); /** * Execute a query returning a list of beans. @@ -477,7 +493,7 @@ public interface EbeanServer { * the transaction (can be null). * @return a Future object for the row count query */ - public FutureRowCount findFutureRowCount(Query query, Transaction t); + public FutureRowCount findFutureRowCount(Query query, Transaction transaction); /** * Execute find Id's query in a background thread. @@ -493,7 +509,7 @@ public interface EbeanServer { * the transaction (can be null). * @return a Future object for the list of Id's */ - public FutureIds findFutureIds(Query query, Transaction t); + public FutureIds findFutureIds(Query query, Transaction transaction); /** * Execute find list query in a background thread. @@ -509,7 +525,7 @@ public interface EbeanServer { * the transaction (can be null). * @return a Future object for the list result of the query */ - public FutureList findFutureList(Query query, Transaction t); + public FutureList findFutureList(Query query, Transaction transaction); /** * Execute find list SQL query in a background thread. @@ -525,12 +541,12 @@ public interface EbeanServer { * the transaction (can be null). * @return a Future object for the list result of the query */ - public SqlFutureList findFutureList(SqlQuery query, Transaction t); + public SqlFutureList findFutureList(SqlQuery query, Transaction transaction); /** * Find using a PagingList with explicit transaction and pageSize. */ - public PagingList findPagingList(Query query, Transaction t, int pageSize); + public PagingList findPagingList(Query 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())); + } }