Fixes for #94 - Additional API - added update(Collection beans); and insert(Collection beans); and #93 - ebean.properties defaultDeleteMissingChildren moved to updatesDeleteMissingChildren

This commit is contained in:
Rob Bygrave
2014-04-26 23:43:08 +12:00
parent e1ec0bcdfe
commit 1a13fc9742
19 changed files with 627 additions and 344 deletions
+43 -32
View File
@@ -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().
* <p>
* 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.
* <b>Stateless updates:</b> 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'.
* </p>
* <p>
* 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.
* <b>Optimistic Locking: </b> Note that if the version property is not set when update() is
* called then no optimistic locking is performed (internally ConcurrencyMode.NONE is used).
* </p>
* <p>
* 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)}.
* <b>{@link ServerConfig#setUpdatesDeleteMissingChildren(boolean)}: </b> 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.
* </p>
* <p>
* <b>{@link ServerConfig#setUpdateChangesOnly(boolean)}: </b> The updateChangesOnly setting
* controls if only the changed properties are included in the update or if all the loaded
* properties are included instead.
* </p>
*
* <pre class="code">
*
* Customer c = new Customer();
* c.setId(7);
* c.setName(&quot;ModifiedNameNoOCC&quot;);
*
* // 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(&quot;ModifiedNameNoOCC&quot;);
* ebeanServer.update(customer);
*
* </pre>
*
* @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);
}
/**
+92 -102
View File
@@ -85,9 +85,12 @@ import com.avaje.ebean.text.json.JsonContext;
public interface EbeanServer {
/**
* Shutdown the EbeanServer.
* Shutdown the EbeanServer programmatically.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
@@ -410,12 +413,25 @@ public interface EbeanServer {
public <T> T find(Class<T> beanType, Object uid);
/**
* Get a reference Object (see {@link Ebean#getReference(Class, Object)}.
* Get a reference bean (see {@link Ebean#getReference(Class, Object)}.
* <p>
* This will not perform a query against the database.
* </p>
* <pre class="code">
* 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();
* </pre>
*
* @param beanType
* the type of entity bean
* @param id
* the id value
*/
public <T> T getReference(Class<T> beanType, Object uid);
@@ -428,21 +444,21 @@ public interface EbeanServer {
/**
* Return the Id values of the query as a List.
*/
public <T> List<Object> findIds(Query<T> query, Transaction t);
public <T> List<Object> findIds(Query<T> 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 <T> QueryIterator<T> findIterate(Query<T> query, Transaction t);
public <T> QueryIterator<T> findIterate(Query<T> 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 <T> void findVisit(Query<T> query, QueryResultVisitor<T> visitor, Transaction t);
public <T> void findVisit(Query<T> query, QueryResultVisitor<T> 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 <T> FutureRowCount<T> findFutureRowCount(Query<T> query, Transaction t);
public <T> FutureRowCount<T> findFutureRowCount(Query<T> 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 <T> FutureIds<T> findFutureIds(Query<T> query, Transaction t);
public <T> FutureIds<T> findFutureIds(Query<T> 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 <T> FutureList<T> findFutureList(Query<T> query, Transaction t);
public <T> FutureList<T> findFutureList(Query<T> 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 <T> PagingList<T> findPagingList(Query<T> query, Transaction t, int pageSize);
public <T> PagingList<T> findPagingList(Query<T> 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().
* <p>
* 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.
* <b>Stateless updates:</b> 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'.
* </p>
* <p>
* 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.
* <b>Optimistic Locking: </b> Note that if the version property is not set when update() is
* called then no optimistic locking is performed (internally ConcurrencyMode.NONE is used).
* </p>
* <p>
* 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)}.
* <b>{@link ServerConfig#setUpdatesDeleteMissingChildren(boolean)}: </b> 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.
* </p>
* <p>
* <b>{@link ServerConfig#setUpdateChangesOnly(boolean)}: </b> The updateChangesOnly setting
* controls if only the changed properties are included in the update or if all the loaded
* properties are included instead.
* </p>
*
* <pre class="code">
*
* Customer c = new Customer();
* c.setId(7);
* c.setName(&quot;ModifiedNameNoOCC&quot;);
*
* // 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(&quot;ModifiedNameNoOCC&quot;);
* ebeanServer.update(customer);
*
* </pre>
*
* @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.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* 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)}.
* </p>
* 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.
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* <p>
* 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.
* </p>
* 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.
* <p>
* 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.
* </p>
* 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.
* <p>
* 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.
* </p>
* 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.
@@ -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() {
@@ -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<String> searchJars = new ArrayList<String>();
/** 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<BeanPersistController> persistControllers = new ArrayList<BeanPersistController>();
private List<BeanPersistListener<?>> persistListeners = new ArrayList<BeanPersistListener<?>>();
@@ -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);
@@ -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.
@@ -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;
@@ -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<ObjectGraphNode, CObjectGraphNodeStatistics>();
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.
* <p>
* 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.
* </p>
* 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.
* <p>
* 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.
* </p>
* 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.
*/
@@ -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.
@@ -32,66 +32,64 @@ import com.avaje.ebeaninternal.server.transaction.BeanPersistIdMap;
/**
* PersistRequest for insert update or delete of a bean.
*/
public class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T> {
public final class PersistRequestBean<T> extends PersistRequest implements BeanPersistRequest<T> {
protected final BeanManager<T> beanManager;
private final BeanManager<T> beanManager;
protected final BeanDescriptor<T> beanDescriptor;
private final BeanDescriptor<T> beanDescriptor;
protected final BeanPersistListener<T> beanPersistListener;
private final BeanPersistListener<T> 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<String> 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<BeanPropertyAssocMany<?>> updatedManys;
@@ -108,21 +106,23 @@ public class PersistRequestBean<T> 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<T> 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<T> extends PersistRequest implements BeanPersist
}
/**
* Set to true if this is a stateless update.
* <p>
* 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.
* </p>
* 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<T> 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) {
@@ -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.
@@ -207,8 +207,9 @@ public class BeanDescriptor<T> 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<T> 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<T> implements MetaBeanInfo {
}
public boolean isInsertMode(EntityBeanIntercept ebi) {
if (ebi.isLoaded()) {
return false;
}
// determine based on Id property
if (idProperty.isEmbedded()) {
return !ebi.isLoaded();
}
@@ -107,6 +107,13 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
}
}
/**
* 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;
@@ -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