- * String data = "This is a simple test of the batch processing"
- * + " mode and the transaction execute batch method";
+ *
{@code
+ *
+ * String data = "This is a simple test of the batch processing"
+ * + " mode and the transaction execute batch method";
*
- * String[] da = data.split(" ");
+ * String[] da = data.split(" ");
*
- * String sql = "{call sp_t3(?,?)}";
+ * String sql = "{call sp_t3(?,?)}";
*
* CallableSql cs = new CallableSql(sql);
* cs.registerOut(2, Types.INTEGER);
*
* // (optional) inform eBean this stored procedure
* // inserts into a table called sp_test
- * cs.addModification("sp_test", true, false, false);
+ * cs.addModification("sp_test", true, false, false);
*
- * Transaction t = Ebean.beginTransaction();
- * t.setBatchMode(true);
- * t.setBatchSize(3);
+ * Transaction txn = ebeanServer.beginTransaction();
+ * txn.setBatchMode(true);
+ * txn.setBatchSize(3);
* try {
- * for (int i = 0; i < da.length;) {
- *
+ * for (int i = 0; i < da.length;) {
* cs.setParameter(1, da[i]);
- * Ebean.execute(cs);
+ * ebeanServer.execute(cs);
* }
*
* // NB: commit implicitly flushes
- * Ebean.commitTransaction();
+ * txn.commit();
*
* } finally {
- * Ebean.endTransaction();
+ * txn.end();
* }
- *
+ *
+ * }
*
*/
public void setBatchMode(boolean useBatch);
+ /**
+ * The JDBC batch mode to use for this transaction.
+ *
+ * If this is NONE then JDBC batch can still be used for each request - save(), insert(), update() or delete()
+ * and this would be useful if the request cascades to detail beans.
+ *
+ *
+ * @param persistBatchMode the batch mode to use for this transaction
+ *
+ * @see com.avaje.ebean.config.ServerConfig#setPersistBatch(com.avaje.ebean.config.PersistBatch)
+ */
+ public void setBatch(PersistBatch persistBatchMode);
+
+ /**
+ * Return the batch mode at the transaction level.
+ */
+ public PersistBatch getBatch();
+
+ /**
+ * Set the JDBC batch mode to use for a save() or delete() request.
+ *
+ * This only takes effect when batch mode on the transaction has not already meant that
+ * JDBC batch mode is being used.
+ *
+ *
+ * This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
+ * and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
+ *
+ *
+ * @param batchOnCascadeMode the batch mode to use per save(), insert(), update() or delete()
+ *
+ * @see com.avaje.ebean.config.ServerConfig#setPersistBatchOnCascade(com.avaje.ebean.config.PersistBatch)
+ */
+ public void setBatchOnCascade(PersistBatch batchOnCascadeMode);
+
+ /**
+ * Return the batch mode at the request level (for each save(), insert(), update() or delete()).
+ */
+ public PersistBatch getBatchOnCascade();
+
/**
* Specify the number of statements before a batch is flushed automatically.
*/
@@ -228,20 +271,11 @@ public interface Transaction extends Closeable {
*
the batch size is reached
*
A query is executed on the same transaction
*
UpdateSql or CallableSql are mixed with bean save and delete
+ *
Transaction commit occurs
*
*/
public void flushBatch() throws PersistenceException, OptimisticLockException;
- /**
- * Deprecated in favour of {@link #flushBatch()}.
- *
- * Exactly the same as flushBatch. Deprecated as a name change.
- *
- *
- * @deprecated Please use flushBatch
- */
- public void batchFlush() throws PersistenceException, OptimisticLockException;
-
/**
* Return the underlying Connection object.
*
diff --git a/src/main/java/com/avaje/ebean/config/PersistBatch.java b/src/main/java/com/avaje/ebean/config/PersistBatch.java
new file mode 100644
index 000000000..6890d23a5
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/config/PersistBatch.java
@@ -0,0 +1,45 @@
+package com.avaje.ebean.config;
+
+/**
+ * Defines the mode for JDBC batch processing.
+ *
+ * Used both at a per transaction basis and per request basis.
+ *
+ *
+ * @see com.avaje.ebean.config.ServerConfig#setPersistBatch(PersistBatch)
+ * @see com.avaje.ebean.config.ServerConfig#setPersistBatchOnCascade(PersistBatch)
+ *
+ * @see com.avaje.ebean.Transaction#setBatch(PersistBatch)
+ * @see com.avaje.ebean.Transaction#setBatchOnCascade(PersistBatch)
+ */
+public enum PersistBatch {
+
+ /**
+ * Do not use JDBC Batch mode.
+ */
+ NONE(false),
+
+ /**
+ * Use JDBC Batch mode on Inserts.
+ */
+ INSERT(true),
+
+ /**
+ * Use JDBC Batch mode on Inserts, Updates and Deletes.
+ */
+ ALL(true);
+
+ boolean forInsert;
+
+ PersistBatch(boolean forInsert) {
+ this.forInsert = forInsert;
+ }
+
+ /**
+ * Return true if persist cascade should use JDBC batch for inserts.
+ */
+ public boolean forInsert() {
+ return forInsert;
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java
index 9a51b260d..0c5e226c5 100644
--- a/src/main/java/com/avaje/ebean/config/ServerConfig.java
+++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java
@@ -133,7 +133,15 @@ public class ServerConfig {
*/
private int databaseSequenceBatchSize = 20;
- private boolean persistBatching;
+ /**
+ * Use for transaction scoped batch mode.
+ */
+ private PersistBatch persistBatch = PersistBatch.NONE;
+
+ /**
+ * Use for per request batch mode.
+ */
+ private PersistBatch persistBatchOnCascade = PersistBatch.NONE;
private int persistBatchSize = 20;
@@ -397,36 +405,74 @@ public class ServerConfig {
}
/**
- * Returns true if by default JDBC batching is used for persisting or deleting
+ * Return the PersistBatch mode to use by default at the transaction level.
+ *
+ * When INSERT or ALL is used then save(), delete() etc do not execute immediately but instead go into
+ * a JDBC batch execute buffer that is flushed. The buffer is flushed if a query is executed, transaction ends
+ * or the batch size is meet.
+ *
+ */
+ public PersistBatch getPersistBatch() {
+ return persistBatch;
+ }
+
+ /**
+ * Set the JDBC batch mode to use at the transaction level.
+ *
+ * When INSERT or ALL is used then save(), delete() etc do not execute immediately but instead go into
+ * a JDBC batch execute buffer that is flushed. The buffer is flushed if a query is executed, transaction ends
+ * or the batch size is meet.
+ *
+ */
+ public void setPersistBatch(PersistBatch persistBatch) {
+ this.persistBatch = persistBatch;
+ }
+
+ /**
+ * Return the JDBC batch mode to use per save(), delete(), insert() or update() request.
+ *
+ * This makes sense when a save() or delete() etc cascades and executes multiple child statements. The best caase
+ * for this is when saving a master/parent bean this cascade inserts many detail/child beans.
+ *
+ *
+ * This only takes effect when the persistBatch mode at the transaction level does not take effect.
+ *
+ */
+ public PersistBatch getPersistBatchOnCascade() {
+ return persistBatchOnCascade;
+ }
+
+ /**
+ * Set the JDBC batch mode to use per save(), delete(), insert() or update() request.
+ *
+ * This makes sense when a save() or delete() etc cascades and executes multiple child statements. The best caase
+ * for this is when saving a master/parent bean this cascade inserts many detail/child beans.
+ *
+ *
+ * This only takes effect when the persistBatch mode at the transaction level does not take effect.
+ *
+ */
+ public void setPersistBatchOnCascade(PersistBatch persistBatchOnCascade) {
+ this.persistBatchOnCascade = persistBatchOnCascade;
+ }
+
+ /**
+ * Deprecated, please migrate to using setPersistBatch().
+ *
+ * Set to true if you what to use JDBC batching for persisting and deleting
* beans.
+ *
*
* With this Ebean will batch up persist requests and use the JDBC batch api.
* This is a performance optimisation designed to reduce the network chatter.
*
- */
- public boolean isPersistBatching() {
- return persistBatching;
- }
-
- /**
- * Set to true if you what to use JDBC batching for persisting and deleting
- * beans.
*
- * With this Ebean will batch up persist requests and use the JDBC batch api.
- * This is a performance optimisation designed to reduce the network chatter.
+ * When true this is equivalent to {@code setPersistBatch(PersistBatch.ALL)} or
+ * when false to {@code setPersistBatch(PersistBatch.NONE)}
*
*/
public void setPersistBatching(boolean persistBatching) {
- this.persistBatching = persistBatching;
- }
-
- /**
- * Use setPersistBatching() instead.
- *
- * @deprecated
- */
- public void setUsePersistBatching(boolean persistBatching) {
- this.persistBatching = persistBatching;
+ this.persistBatch = (persistBatching) ? PersistBatch.ALL : PersistBatch.NONE;
}
/**
@@ -438,11 +484,34 @@ public class ServerConfig {
/**
* Set the batch size used for JDBC batching. If unset this defaults to 20.
+ *
+ * You can also set the batch size on the transaction.
+ *
+ * @see com.avaje.ebean.Transaction#setBatchSize(int)
*/
public void setPersistBatchSize(int persistBatchSize) {
this.persistBatchSize = persistBatchSize;
}
+ /**
+ * Gets the query batch size. This defaults to 100.
+ *
+ * @return the query batch size
+ */
+ public int getQueryBatchSize() {
+ return queryBatchSize;
+ }
+
+ /**
+ * Sets the query batch size. This defaults to 100.
+ *
+ * @param queryBatchSize
+ * the new query batch size
+ */
+ public void setQueryBatchSize(int queryBatchSize) {
+ this.queryBatchSize = queryBatchSize;
+ }
+
/**
* Return the default batch size for lazy loading of beans and collections.
*/
@@ -450,25 +519,6 @@ public class ServerConfig {
return lazyLoadBatchSize;
}
- /**
- * Gets the query batch size.
- *
- * @return the query batch size
- */
- public int getQueryBatchSize() {
- return queryBatchSize;
- }
-
- /**
- * Sets the query batch size.
- *
- * @param queryBatchSize
- * the new query batch size
- */
- public void setQueryBatchSize(int queryBatchSize) {
- this.queryBatchSize = queryBatchSize;
- }
-
/**
* Set the default batch size for lazy loading.
*
@@ -476,7 +526,7 @@ public class ServerConfig {
* invoked by default.
*
*
- * The default value is for this is 1 (load 1 bean or collection).
+ * The default value is for this is 10 (load 10 beans or collections).
*
*
* You can explicitly control the lazy loading batch size for a given join on
@@ -1689,8 +1739,12 @@ public class ServerConfig {
boolean defaultDeleteMissingChildren = p.getBoolean("defaultDeleteMissingChildren", updatesDeleteMissingChildren);
updatesDeleteMissingChildren = p.getBoolean("updatesDeleteMissingChildren", defaultDeleteMissingChildren);
- boolean batchMode = p.getBoolean("batch.mode", persistBatching);
- persistBatching = p.getBoolean("persistBatching", batchMode);
+ if (p.get("batch.mode") != null || p.get("persistBatching") != null) {
+ throw new IllegalArgumentException("Property 'batch.mode' or 'persistBatching' is being set but no longer used. Please change to use 'persistBatchMode'");
+ }
+
+ persistBatch = p.getEnum(PersistBatch.class, "persistBatch", persistBatch);
+ persistBatchOnCascade = p.getEnum(PersistBatch.class, "persistBatchOnCascade", persistBatchOnCascade);
int batchSize = p.getInt("batch.size", persistBatchSize);
persistBatchSize = p.getInt("persistBatchSize", batchSize);
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java
index 601d1b4d9..9cc52563c 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java
@@ -5,6 +5,8 @@ import java.util.List;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.bean.PersistenceContext;
+import com.avaje.ebeaninternal.server.core.PersistRequest;
+import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.persist.BatchControl;
/**
@@ -119,6 +121,11 @@ public interface SpiTransaction extends Transaction {
*/
public int depth(int diff);
+ /**
+ * Return the current depth.
+ */
+ public int depth();
+
/**
* Return true if this transaction was created explicitly via
* Ebean.beginTransaction().
@@ -144,7 +151,7 @@ public interface SpiTransaction extends Transaction {
* Return true if this request should be batched. Conversely returns false
* if this request should be executed immediately.
*/
- public boolean isBatchThisRequest();
+ public boolean isBatchThisRequest(PersistRequest.Type type);
/**
* Return the queue used to batch up persist requests.
@@ -194,4 +201,25 @@ public interface SpiTransaction extends Transaction {
* Return true if the manyToMany intersection should be persisted for this particular relationship direction.
*/
public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName);
+
+ /**
+ * Return true if batch mode got escalated for this request (and associated cascades).
+ */
+ public boolean checkBatchEscalationOnCascade(PersistRequestBean> request);
+
+ /**
+ * If batch mode was turned on for the request then flush the batch.
+ */
+ public void flushBatchOnCascade();
+
+ /**
+ * Potentially escalate batch mode on saving or deleting a collection.
+ */
+ public void checkBatchEscalationOnCollection();
+
+ /**
+ * Flush batch if we escalated batch mode on saving or deleting a collection.
+ */
+ public void flushBatchOnCollection();
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java
index 734402cc7..4945590d9 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java
@@ -94,7 +94,7 @@ public class TransactionEvent implements Serializable {
*/
public void add(PersistRequestBean> request) {
- if (request.isNotify(this)) {
+ if (request.isNotify()) {
// either a BeanListener or Cache is interested
if (eventBeans == null) {
eventBeans = new TransactionEventBeans();
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 eeed0025e..b4acc3a4a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
@@ -9,7 +9,6 @@ import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanQueryAdapter;
-import com.avaje.ebean.meta.MetaBeanInfo;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
@@ -439,20 +438,6 @@ public final class DefaultServer implements SpiEbeanServer {
return serverCacheManager;
}
- /**
- * Return the Profile Listener.
- */
- public AutoFetchManager getProfileListener() {
- return autoFetchManager;
- }
-
- /**
- * Return the Relational query engine.
- */
- public RelationalQueryEngine getRelationalQueryEngine() {
- return relationalQueryEngine;
- }
-
public void refreshMany(Object parentBean, String propertyName, Transaction t) {
beanLoader.refreshMany(checkEntityBean(parentBean), propertyName, t);
@@ -1029,15 +1014,10 @@ public final class DefaultServer implements SpiEbeanServer {
public SpiOrmQueryRequest createQueryRequest(BeanDescriptor desc, SpiQuery query, Transaction t) {
- if (desc.isAutoFetchTunable() && !query.isSqlSelect()) {
- // its a tunable query
- if (autoFetchManager.tuneQuery(query)) {
- // was automatically tuned by Autofetch
- } else {
- // use deployment FetchType.LAZY/EAGER annotations
- // to define the 'default' select clause
- query.setDefaultSelectClause();
- }
+ if (desc.isAutoFetchTunable() && !query.isSqlSelect() && !autoFetchManager.tuneQuery(query)) {
+ // use deployment FetchType.LAZY/EAGER annotations
+ // to define the 'default' select clause
+ query.setDefaultSelectClause();
}
if (query.selectAllForLazyLoadProperty()) {
@@ -1048,12 +1028,9 @@ public final class DefaultServer implements SpiEbeanServer {
}
}
- if (true) {
- // if determine cost and no origin for Autofetch
- if (query.getParentNode() == null) {
- CallStack callStack = createCallStack();
- query.setOrigin(callStack);
- }
+ // if determine cost and no origin for Autofetch
+ if (query.getParentNode() == null) {
+ query.setOrigin(createCallStack());
}
// determine extra joins required to support where clause
@@ -1711,6 +1688,7 @@ public final class DefaultServer implements SpiEbeanServer {
TransWrapper wrap = initTransIfRequired(t);
try {
+ wrap.batchEscalateOnCollection();
SpiTransaction trans = wrap.transaction;
int saveCount = 0;
while (it.hasNext()) {
@@ -1720,7 +1698,7 @@ public final class DefaultServer implements SpiEbeanServer {
}
wrap.commitIfCreated();
-
+ wrap.flushBatchOnCollection();
return saveCount;
} catch (RuntimeException e) {
@@ -1804,6 +1782,7 @@ public final class DefaultServer implements SpiEbeanServer {
TransWrapper wrap = initTransIfRequired(t);
try {
+ wrap.batchEscalateOnCollection();
SpiTransaction trans = wrap.transaction;
int deleteCount = 0;
while (it.hasNext()) {
@@ -1813,7 +1792,7 @@ public final class DefaultServer implements SpiEbeanServer {
}
wrap.commitIfCreated();
-
+ wrap.flushBatchOnCollection();
return deleteCount;
} catch (RuntimeException e) {
@@ -1864,10 +1843,6 @@ public final class DefaultServer implements SpiEbeanServer {
return execute(update, null);
}
- public BeanManager getBeanManager(Class beanClass) {
- return beanDescriptorManager.getBeanManager(beanClass);
- }
-
/**
* Return all the BeanDescriptors.
*/
@@ -1875,13 +1850,6 @@ public final class DefaultServer implements SpiEbeanServer {
return beanDescriptorManager.getBeanDescriptorList();
}
- public List getMetaBeanInfoList() {
-
- List list = new ArrayList();
- list.addAll(getBeanDescriptors());
- return list;
- }
-
public void register(BeanPersistController c) {
List> list = beanDescriptorManager.getBeanDescriptorList();
for (int i = 0; i < list.size(); i++) {
@@ -2006,7 +1974,7 @@ public final class DefaultServer implements SpiEbeanServer {
// create the 'interesting' part of the stackTrace
StackTraceElement[] finalTrace = new StackTraceElement[stackLength];
- System.arraycopy(stackTrace, 0 + startIndex, finalTrace, 0, stackLength);
+ System.arraycopy(stackTrace, startIndex, finalTrace, 0, stackLength);
if (stackLength < 1) {
// this should not really happen
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 9f2d9aa34..e6c0bfad6 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequest.java
@@ -13,7 +13,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
public enum Type {
DETERMINE, INSERT, UPDATE, DELETE, ORMUPDATE, UPDATESQL, CALLABLESQL
- };
+ }
protected boolean persistCascade;
@@ -24,7 +24,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
protected final PersistExecute persistExecute;
- /**
+ /**
* Used by CallableSqlRequest and UpdateSqlRequest.
*/
public PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute) {
@@ -41,25 +41,33 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
* Execute the request right now.
*/
public abstract int executeNow();
-
- public PstmtBatch getPstmtBatch() {
- return ebeanServer.getPstmtBatch();
- }
-
- public boolean isLogSql() {
- return transaction.isLogSql();
- }
-
- public boolean isLogSummary() {
- return transaction.isLogSummary();
- }
-
+
+ public PstmtBatch getPstmtBatch() {
+ return ebeanServer.getPstmtBatch();
+ }
+
+ public boolean isLogSql() {
+ return transaction.isLogSql();
+ }
+
+ public boolean isLogSummary() {
+ return transaction.isLogSummary();
+ }
+
+
+ /**
+ * Return true if this persist request should use JDBC batch.
+ */
+ public boolean isBatchThisRequest() {
+ return transaction.isBatchThisRequest(type);
+ }
+
/**
- * Execute the Callable statement.
+ * Execute the statement.
*/
public int executeStatement() {
- boolean batch = transaction.isBatchThisRequest();
+ boolean batch = isBatchThisRequest();
int rows;
BatchControl control = transaction.getBatchControl();
@@ -69,15 +77,15 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
} else if (batch) {
// need to create the BatchControl
control = persistExecute.createBatchControl(transaction);
- rows = control.executeStatementOrBatch(this, batch);
+ rows = control.executeStatementOrBatch(this, true);
} else {
rows = executeNow();
}
return rows;
}
-
- public void initTransIfRequired() {
+
+ public void initTransIfRequired() {
createImplicitTransIfRequired(false);
persistCascade = transaction.isPersistCascade();
}
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 3e80e1837..e0ab928c5 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
@@ -97,6 +97,21 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
*/
private Set updatedProperties;
+ /**
+ * Flag set when request is added to JDBC batch.
+ */
+ private boolean batched;
+
+ /**
+ * Flag set when batchOnCascade to avoid using batch on the top bean.
+ */
+ private boolean skipBatchForTopLevel;
+
+ /**
+ * Flag set when batch mode is turned on for a persist cascade.
+ */
+ private boolean batchOnCascadeSet;
+
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse) {
@@ -127,6 +142,54 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
this.dirty = intercept.isDirty();
}
+ /**
+ * Init the transaction and also check for batch on cascade escalation.
+ */
+ public void initTransIfRequiredWithBatchCascade() {
+ createImplicitTransIfRequired(false);
+ if (transaction.checkBatchEscalationOnCascade(this)) {
+ // we escalated to use batch mode so flush when done
+ // but if createdTransaction then commit will flush it
+ batchOnCascadeSet = !createdTransaction;
+ }
+ persistCascade = transaction.isPersistCascade();
+ }
+
+ /**
+ * If using batch on cascade flush if required.
+ */
+ public void flushBatchOnCascade() {
+ if (batchOnCascadeSet) {
+ // we escalated to batch mode for request so flush
+ transaction.flushBatchOnCascade();
+ batchOnCascadeSet = false;
+ }
+ }
+
+ /**
+ * Return true is this request was added to the JDBC batch.
+ */
+ public boolean isBatched() {
+ return batched;
+ }
+
+ /**
+ * Set when request is added to the JDBC batch.
+ */
+ public void setBatched() {
+ batched = true;
+ }
+
+
+ public void setSkipBatchForTopLevel() {
+ skipBatchForTopLevel = true;
+ }
+
+ @Override
+ public boolean isBatchThisRequest() {
+ return !skipBatchForTopLevel && super.isBatchThisRequest();
+ }
+
/**
* Return true if this is an insert request.
*/
@@ -149,7 +212,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
return intercept.getDirtyValues();
}
- public boolean isNotify(TransactionEvent txnEvent) {
+ public boolean isNotify() {
this.notifyCache = beanDescriptor.isCacheNotify();
return notifyCache || isNotifyPersistListener();
}
@@ -234,7 +297,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
if (id != null) {
hc += id.hashCode();
}
- beanHash = Integer.valueOf(hc);
+ beanHash = new Integer(hc);
}
return beanHash;
}
@@ -397,7 +460,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
@Override
public int executeOrQueue() {
- boolean batch = transaction.isBatchThisRequest();
+ boolean batch = isBatchThisRequest();
BatchControl control = transaction.getBatchControl();
if (control != null) {
@@ -405,7 +468,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
}
if (batch) {
control = persistExecute.createBatchControl(transaction);
- return control.executeOrQueue(this, batch);
+ return control.executeOrQueue(this, true);
} else {
return executeNow();
@@ -438,10 +501,15 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
String m = Message.msg("persist.conc2", "" + rowCount);
throw new OptimisticLockException(m, null, bean);
}
+ if (type == Type.DELETE) {
+ postDelete();
+ }
}
- public void postDelete() {
-
+ /**
+ * Aggressive L1 and L2 cache cleanup for deletes.
+ */
+ private void postDelete() {
// Delete the bean from the PersistenceContent
transaction.getPersistenceContext().clear(beanDescriptor.getBeanType(), idValue);
// Delete from cache early even if transaction fails
@@ -457,10 +525,8 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
controllerPost();
}
- if (intercept != null) {
- // if bean persisted again then should result in an update
- intercept.setLoaded();
- }
+ // if bean persisted again then should result in an update
+ intercept.setLoaded();
addEvent();
@@ -528,9 +594,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
if (concurrencyMode.equals(ConcurrencyMode.VERSION)) {
// check the version property was loaded
BeanProperty prop = beanDescriptor.getVersionProperty();
- if (prop != null && intercept.isLoadedProperty(prop.getPropertyIndex())) {
- // OK to use version property
- } else {
+ if (prop == null || !intercept.isLoadedProperty(prop.getPropertyIndex())) {
concurrencyMode = ConcurrencyMode.NONE;
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/TransWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/core/TransWrapper.java
index 67969c976..3ad7b74bc 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/TransWrapper.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/TransWrapper.java
@@ -23,6 +23,16 @@ final class TransWrapper {
wasCreated = created;
}
+ void batchEscalateOnCollection() {
+ transaction.checkBatchEscalationOnCollection();
+ }
+
+ void flushBatchOnCollection() {
+ if (!wasCreated) {
+ transaction.flushBatchOnCollection();
+ }
+ }
+
void commitIfCreated() {
if (wasCreated){
transaction.commit();
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java
index 630600fa9..3bc6b1b3b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchControl.java
@@ -2,14 +2,14 @@ package com.avaje.ebeaninternal.server.persist;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
* Controls the batch ordering of persist requests.
@@ -27,24 +27,25 @@ import org.slf4j.LoggerFactory;
*/
public final class BatchControl {
- private static final Logger logger = LoggerFactory.getLogger(BatchControl.class);
-
/**
* Used to sort queue entries by depth.
*/
private static final BatchDepthComparator depthComparator = new BatchDepthComparator();
- /**
- * The associated transaction.
- */
- private final SpiTransaction transaction;
-
/**
* Controls batching of the PreparedStatements. This should be flushed after
* each 'depth'.
*/
private final BatchedPstmtHolder pstmtHolder = new BatchedPstmtHolder();
+ /**
+ * Map of the BatchedBeanHolder objects. They each have a depth and are later
+ * sorted by their depth to get the execution order.
+ */
+ private final HashMap beanHoldMap = new HashMap();
+
+ private final SpiTransaction transaction;
+
/**
* The size at which the batch queue will flush. This should be close to the
* number of statements that are batched into a single PreparedStatement. This
@@ -60,17 +61,13 @@ public final class BatchControl {
private boolean batchFlushOnMixed = true;
- private final BatchedBeanControl beanControl;
-
/**
- * Create for a given transaction, PersistExecute, default size and
- * getGeneratedKeys.
+ * Create for a given transaction, PersistExecute, default size and getGeneratedKeys.
*/
public BatchControl(SpiTransaction t, int batchSize, boolean getGenKeys) {
this.transaction = t;
this.batchSize = batchSize;
this.getGeneratedKeys = getGenKeys;
- this.beanControl = new BatchedBeanControl(t, this);
transaction.setBatchControl(this);
}
@@ -128,7 +125,7 @@ public final class BatchControl {
*
*/
public int executeStatementOrBatch(PersistRequest request, boolean batch) {
- if (!batch || (batchFlushOnMixed && !beanControl.isEmpty())) {
+ if (!batch || (batchFlushOnMixed && !isBeansEmpty())) {
// flush when mixing beans and updateSql
flush();
}
@@ -161,31 +158,25 @@ public final class BatchControl {
if (!batch) {
return request.executeNow();
}
-
- // get the list we will add this request to
- ArrayList persistList = beanControl.getPersistList(request);
- if (persistList == null) {
- // special case where the same bean instance has been added
- // to the batch more than once
- if (logger.isDebugEnabled()) {
- logger.debug("Bean instance already in this batch: " + request.getEntityBean());
- }
- return -1;
- }
-
- if (persistList.size() >= batchSize) {
- // flush everything that has been batched
+ if (addToBatch(request)) {
+ // flush as the top level has hit the batch size
flush();
-
- // we need to get the persistList again after the
- // flush as the flush clears out the bean holders
- persistList = beanControl.getPersistList(request);
}
-
- persistList.add(request);
return -1;
}
+ /**
+ * Add the request to the batch and return true if we should flush.
+ */
+ private boolean addToBatch(PersistRequestBean> request) {
+
+ BatchedBeanHolder beanHolder = getBeanHolder(request);
+ int bufferSize = beanHolder.append(request);
+
+ // return true if top level has hit batch size
+ return bufferSize == batchSize && beanHolder.getOrder() == 100;
+ }
+
/**
* Return the actual batch of PreparedStatements.
*/
@@ -197,7 +188,7 @@ public final class BatchControl {
* Return true if the queue is empty.
*/
public boolean isEmpty() {
- return (beanControl.isEmpty() && pstmtHolder.isEmpty());
+ return (isBeansEmpty() && pstmtHolder.isEmpty());
}
/**
@@ -212,27 +203,45 @@ public final class BatchControl {
*/
protected void executeNow(ArrayList list) {
for (int i = 0; i < list.size(); i++) {
+ if (i % batchSize == 0) {
+ // hit the batch size so flush
+ flushPstmtHolder();
+ }
list.get(i).executeNow();
}
+ flushPstmtHolder();
+ }
+
+ /**
+ * Flush without resetting the topOrder (maintains the depth info).
+ */
+ public void flush() throws PersistenceException {
+ flush(false);
+ }
+
+ /**
+ * Flush with a reset the topOrder (fully empty the batch).
+ */
+ public void flushReset() throws PersistenceException {
+ flush(true);
}
/**
* execute all the requests currently queued or batched.
*/
- public void flush() throws PersistenceException {
+ private void flush(boolean resetTop) throws PersistenceException {
if (!pstmtHolder.isEmpty()) {
// Flush existing pstmts (updateSql or callableSql)
flushPstmtHolder();
}
- if (beanControl.isEmpty()) {
+ if (isEmpty()) {
// Nothing in queue to flush
return;
}
// convert entry map to array for sorting
- BatchedBeanHolder[] bsArray = beanControl.getArray();
-
+ BatchedBeanHolder[] bsArray = getBeanHolderArray();
// sort the entries by depth
Arrays.sort(bsArray, depthComparator);
@@ -240,11 +249,55 @@ public final class BatchControl {
transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray));
}
for (int i = 0; i < bsArray.length; i++) {
- BatchedBeanHolder bs = bsArray[i];
- bs.executeNow();
- // flush all the batched Pstmts
- flushPstmtHolder();
+ bsArray[i].executeNow();
+ }
+
+ if (resetTop) {
+ beanHoldMap.clear();
}
}
+ /**
+ * Return an entry for the given type description. The type description is
+ * typically the bean class name (or table name for MapBeans).
+ */
+ private BatchedBeanHolder getBeanHolder(PersistRequestBean> request) {
+
+ BeanDescriptor> beanDescriptor = request.getBeanDescriptor();
+ BatchedBeanHolder batchBeanHolder = beanHoldMap.get(beanDescriptor.getFullName());
+ if (batchBeanHolder == null) {
+ int relativeDepth = transaction.depth();
+ if (relativeDepth == 0 && !beanHoldMap.isEmpty()) {
+ // flush and reset the batch as we are changing the type of our top level
+ // bean so just keep it simple and flush and reset the top
+ flushReset();
+ }
+
+ batchBeanHolder = new BatchedBeanHolder(this, beanDescriptor, 100 + relativeDepth);
+ beanHoldMap.put(beanDescriptor.getFullName(), batchBeanHolder);
+ }
+ return batchBeanHolder;
+ }
+
+ /**
+ * Return true if this holds no persist requests.
+ */
+ private boolean isBeansEmpty() {
+ if (beanHoldMap.isEmpty()) {
+ return true;
+ }
+ for (BatchedBeanHolder beanHolder : beanHoldMap.values()) {
+ if (!beanHolder.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Return the BatchedBeanHolder's ready for sorting and executing.
+ */
+ private BatchedBeanHolder[] getBeanHolderArray() {
+ return beanHoldMap.values().toArray(new BatchedBeanHolder[beanHoldMap.size()]);
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanControl.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanControl.java
deleted file mode 100644
index a4caf8875..000000000
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanControl.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.avaje.ebeaninternal.server.persist;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-
-import com.avaje.ebeaninternal.api.SpiTransaction;
-import com.avaje.ebeaninternal.server.core.PersistRequest;
-import com.avaje.ebeaninternal.server.core.PersistRequestBean;
-import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
-
-/**
- * Holds all the batched beans.
- *
- * The beans are held here which delays the binding to a PreparedStatement. This
- * 'delayed' binding is required as the beans need to be bound and executed in
- * the correct order (according to the depth).
- *
- */
-public class BatchedBeanControl {
-
- /**
- * Map of the BatchedBeanHolder objects. They each have a depth and are later
- * sorted by their depth to get the execution order.
- */
- private final HashMap beanHoldMap = new HashMap();
-
- private final SpiTransaction transaction;
-
- private final BatchControl batchControl;
-
- private int topOrder;
-
- public BatchedBeanControl(SpiTransaction t, BatchControl batchControl) {
- this.transaction = t;
- this.batchControl = batchControl;
- }
-
- public ArrayList getPersistList(PersistRequestBean> request) {
- return getBeanHolder(request).getList(request);
- }
-
- /**
- * Return an entry for the given type description. The type description is
- * typically the bean class name (or table name for MapBeans).
- */
- private BatchedBeanHolder getBeanHolder(PersistRequestBean> request) {
-
- BeanDescriptor> beanDescriptor = request.getBeanDescriptor();
- BatchedBeanHolder batchBeanHolder = beanHoldMap.get(beanDescriptor.getFullName());
- if (batchBeanHolder == null) {
- int relativeDepth = transaction.depth(0);
- if (relativeDepth == 0){
- topOrder++;
- }
- int stmtOrder = topOrder*100 + relativeDepth;
-
- batchBeanHolder = new BatchedBeanHolder(batchControl, beanDescriptor, stmtOrder);
- beanHoldMap.put(beanDescriptor.getFullName(), batchBeanHolder);
- }
- return batchBeanHolder;
- }
-
- /**
- * Return true if this holds no persist requests.
- */
- public boolean isEmpty() {
- return beanHoldMap.isEmpty();
- }
-
- /**
- * Return the BatchedBeanHolder's ready for sorting and executing.
- */
- public BatchedBeanHolder[] getArray() {
- BatchedBeanHolder[] bsArray = new BatchedBeanHolder[beanHoldMap.size()];
- beanHoldMap.values().toArray(bsArray);
- return bsArray;
- }
-
-}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java
index 0c0c3000a..f997b0731 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanHolder.java
@@ -8,10 +8,10 @@ import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
- * Holds lists of persist requests for beans of a given typeDescription.
+ * Holds lists of persist requests for beans of a given type.
*
* This is used to delay the actual binding of the bean to PreparedStatements.
- * The reason is that don't have all the bind values yet in the case of inserts
+ * The reason is that we don't have all the bind values yet in the case of inserts
* with getGeneratedKeys.
*
*
@@ -21,118 +21,139 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
*/
public class BatchedBeanHolder {
- /**
- * The owning queue.
- */
- private final BatchControl control;
+ /**
+ * The owning queue.
+ */
+ private final BatchControl control;
- private final String shortDesc;
+ private final String shortDesc;
- /**
- * The 'depth' which is used to determine the execution order.
- */
- private final int order;
+ /**
+ * The 'depth' which is used to determine the execution order.
+ */
+ private final int order;
- /**
- * The list of bean insert requests.
- */
- private ArrayList inserts;
+ /**
+ * The list of bean insert requests.
+ */
+ private ArrayList inserts;
- /**
- * The list of bean update requests.
- */
- private ArrayList updates;
+ /**
+ * The list of bean update requests.
+ */
+ private ArrayList updates;
- /**
- * The list of bean delete requests.
- */
- private ArrayList deletes;
+ /**
+ * The list of bean delete requests.
+ */
+ private ArrayList deletes;
- private HashSet beanHashCodes = new HashSet();
-
- /**
- * Create a new entry with a given type and depth.
- */
- public BatchedBeanHolder(BatchControl control, BeanDescriptor> beanDescriptor, int order) {
- this.control = control;
- this.shortDesc = beanDescriptor.getName() + ":" + order;
- this.order = order;
- }
+ private HashSet beanHashCodes = new HashSet();
- /**
- * Return the depth.
- */
- public int getOrder() {
- return order;
- }
-
- /**
- * Execute all the persist requests in this entry.
- *
- * This will Batch all the similar requests into one or more BatchStatements
- * and then execute them.
- *
- */
- public void executeNow() {
- // process the requests. Creates one or more PreparedStatements
- // with binding addBatch() for each request.
+ /**
+ * Create a new entry with a given type and depth.
+ */
+ public BatchedBeanHolder(BatchControl control, BeanDescriptor> beanDescriptor, int order) {
+ this.control = control;
+ this.shortDesc = beanDescriptor.getName() + ":" + order;
+ this.order = order;
+ }
- // Note updates and deletes can result in many PreparedStatements
- // if their where clauses differ via use of IS NOT NULL.
- if (inserts != null && !inserts.isEmpty()) {
- control.executeNow(inserts);
- inserts.clear();
- }
- if (updates != null && !updates.isEmpty()) {
- control.executeNow(updates);
- updates.clear();
- }
- if (deletes != null && !deletes.isEmpty()) {
- control.executeNow(deletes);
- deletes.clear();
- }
- beanHashCodes.clear();
- }
+ /**
+ * Return the depth.
+ */
+ public int getOrder() {
+ return order;
+ }
- public String toString() {
- return shortDesc;
- }
+ /**
+ * Execute all the persist requests in this entry.
+ *
+ * This will Batch all the similar requests into one or more BatchStatements
+ * and then execute them.
+ *
+ */
+ public void executeNow() {
+ // process the requests. Creates one or more PreparedStatements
+ // with binding addBatch() for each request.
+ // Note updates and deletes can result in many PreparedStatements
+ // if their where clauses differ via use of IS NOT NULL.
+ if (inserts != null && !inserts.isEmpty()) {
+ control.executeNow(inserts);
+ inserts.clear();
+ }
+ if (updates != null && !updates.isEmpty()) {
+ control.executeNow(updates);
+ updates.clear();
+ }
+ if (deletes != null && !deletes.isEmpty()) {
+ control.executeNow(deletes);
+ deletes.clear();
+ }
+ beanHashCodes.clear();
+ }
- /**
- * Return the list for the typeCode.
- */
- public ArrayList getList(PersistRequestBean> request) {
-
- Integer objHashCode = Integer.valueOf(System.identityHashCode(request.getEntityBean()));
-
- if (!beanHashCodes.add(objHashCode)) {
- // special case where the same bean instance has already been
- // added to the batch (doesn't really occur with non-batching
- // as the bean gets changed from dirty to loaded earlier)
- return null;
- }
-
- switch (request.getType()) {
- case INSERT:
- if (inserts == null) {
- inserts = new ArrayList();
- }
- return inserts;
+ public String toString() {
+ StringBuilder sb = new StringBuilder(shortDesc.length()+18);
+ sb.append(shortDesc);
+ if (inserts != null) {
+ sb.append(" i:").append(inserts.size());
+ }
+ if (updates != null) {
+ sb.append(" u:").append(updates.size());
+ }
+ if (deletes != null) {
+ sb.append(" d:").append(deletes.size());
+ }
+ return sb.toString();
+ }
- case UPDATE:
- if (updates == null) {
- updates = new ArrayList();
- }
- return updates;
+ /**
+ * Add the request to the appropriate persist list.
+ */
+ public int append(PersistRequestBean> request) {
- case DELETE:
- if (deletes == null) {
- deletes = new ArrayList();
- }
- return deletes;
+ Integer objHashCode = new Integer(System.identityHashCode(request.getEntityBean()));
+ if (!beanHashCodes.add(objHashCode)) {
+ // special case where the same bean instance has already been
+ // added to the batch (doesn't really occur with non-batching
+ // as the bean gets changed from dirty to loaded earlier)
+ return 0;
+ }
- default:
- throw new RuntimeException("Invalid type code " + request.getType());
- }
- }
+ request.setBatched();
+
+ switch (request.getType()) {
+ case INSERT:
+ if (inserts == null) {
+ inserts = new ArrayList();
+ }
+ inserts.add(request);
+ return inserts.size();
+
+ case UPDATE:
+ if (updates == null) {
+ updates = new ArrayList();
+ }
+ updates.add(request);
+ return updates.size();
+
+ case DELETE:
+ if (deletes == null) {
+ deletes = new ArrayList();
+ }
+ deletes.add(request);
+ return deletes.size();
+
+ default:
+ throw new RuntimeException("Invalid type code " + request.getType());
+ }
+ }
+
+ /**
+ * Return true if this is empty containing no batched beans.
+ */
+ public boolean isEmpty() {
+ return beanHashCodes.isEmpty();
+ }
}
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 0248797cf..7d1fef57d 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersister.java
@@ -168,20 +168,19 @@ public final class DefaultPersister implements Persister {
PersistRequestBean> req = createRequest(entityBean, t, null, PersistRequest.Type.UPDATE);
req.setDeleteMissingChildren(deleteMissingChildren);
try {
- req.initTransIfRequired();
-
+ req.initTransIfRequiredWithBatchCascade();
if (req.isReference()) {
// its a reference so see if there are manys to save...
if (req.isPersistCascade()) {
saveAssocMany(false, req, false);
}
req.checkUpdatedManysOnly();
-
} else {
update(req);
}
req.commitTransIfRequired();
+ req.flushBatchOnCascade();
} catch (RuntimeException ex) {
req.rollbackTransIfRequired();
@@ -208,9 +207,10 @@ public final class DefaultPersister implements Persister {
PersistRequestBean> req = createRequest(bean, t, null, PersistRequest.Type.INSERT);
try {
- req.initTransIfRequired();
+ req.initTransIfRequiredWithBatchCascade();
insert(req);
req.commitTransIfRequired();
+ req.flushBatchOnCascade();
} catch (RuntimeException ex) {
req.rollbackTransIfRequired();
@@ -323,9 +323,10 @@ public final class DefaultPersister implements Persister {
}
try {
- req.initTransIfRequired();
+ req.initTransIfRequiredWithBatchCascade();
delete(req);
req.commitTransIfRequired();
+ req.flushBatchOnCascade();
} catch (RuntimeException ex) {
req.rollbackTransIfRequired();
@@ -558,9 +559,7 @@ public final class DefaultPersister implements Persister {
if (request.isLoadedProperty(prop)) {
EntityBean detailBean = prop.getValueAsEntityBean(parentBean);
if (detailBean != null) {
- if (prop.isSaveRecurseSkippable(detailBean)) {
- // skip saving this bean
- } else {
+ if (!prop.isSaveRecurseSkippable(detailBean)) {
t.depth(+1);
prop.setParentBeanToChild(parentBean, detailBean);
saveRecurse(detailBean, t, parentBean, insertMode);
@@ -748,11 +747,11 @@ public final class DefaultPersister implements Persister {
// set it to the appropriate property on the
// detail bean before we save it
boolean isMap = ManyType.JAVA_MAP.equals(prop.getManyType());
- EntityBean parentBean = (EntityBean)saveMany.getParentBean();
+ EntityBean parentBean = saveMany.getParentBean();
Object mapKeyValue = null;
boolean saveSkippable = prop.isSaveRecurseSkippable();
- boolean skipSavingThisBean = false;
+ boolean skipSavingThisBean;
for (Object detailBean : collection) {
if (isMap) {
@@ -762,11 +761,7 @@ public final class DefaultPersister implements Persister {
detailBean = entry.getValue();
}
- if (detailBean instanceof EntityBean == false) {
- skipSavingThisBean = true;
- logger.debug("Skip non entity bean");
-
- } else {
+ if (detailBean instanceof EntityBean) {
EntityBean detail = (EntityBean)detailBean;
EntityBeanIntercept ebi = detail._ebean_getIntercept();
if (prop.isManyToMany()) {
@@ -787,14 +782,7 @@ 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;
-
- } else {
- // normal save recurse
+ if (!skipSavingThisBean) {
saveRecurse(detail, t, parentBean, insertMode);
}
if (detailIds != null) {
@@ -879,9 +867,6 @@ public final class DefaultPersister implements Persister {
}
SpiTransaction t = saveManyPropRequest.getTransaction();
- Collection> additions = null;
- Collection> deletions = null;
-
boolean vanillaCollection = !(value instanceof BeanCollection>);
if (vanillaCollection || deleteMissingChildren) {
@@ -890,6 +875,9 @@ public final class DefaultPersister implements Persister {
deleteAssocManyIntersection(saveManyPropRequest.getParentBean(), prop, t);
}
+ Collection> deletions = null;
+ Collection> additions;
+
if (saveManyPropRequest.isInsertedParent() || vanillaCollection || deleteMissingChildren) {
// treat everything in the list/set/map as an intersection addition
if (value instanceof Map, ?>) {
@@ -1098,30 +1086,24 @@ public final class DefaultPersister implements Persister {
// imported ones with save cascade
BeanPropertyAssocOne>[] ones = desc.propertiesOneImportedSave();
- for (int i = 0; i < ones.length; i++) {
- BeanPropertyAssocOne> prop = ones[i];
+ for (int i = 0; i < ones.length; i++) {
+ BeanPropertyAssocOne> prop = ones[i];
- // check for partial objects
- if (request.isLoadedProperty(prop)) {
- EntityBean detailBean = prop.getValueAsEntityBean(request.getEntityBean());
- if (detailBean != null) {
- if (prop.isReference(detailBean)) {
- // skip saving a reference
- } else if (request.isParent(detailBean)) {
- // skip saving the parent as already saved
- } else if (prop.isSaveRecurseSkippable(detailBean)) {
- // we can skip saving this bean
-
- } else {
- SpiTransaction t = request.getTransaction();
- t.depth(-1);
- saveRecurse(detailBean, t, null, insertMode);
- t.depth(+1);
- }
- }
- }
- }
- }
+ // check for partial objects
+ if (request.isLoadedProperty(prop)) {
+ EntityBean detailBean = prop.getValueAsEntityBean(request.getEntityBean());
+ if (detailBean != null
+ && !prop.isSaveRecurseSkippable(detailBean)
+ && !prop.isReference(detailBean)
+ && !request.isParent(detailBean)) {
+ SpiTransaction t = request.getTransaction();
+ t.depth(-1);
+ saveRecurse(detailBean, t, null, insertMode);
+ t.depth(+1);
+ }
+ }
+ }
+ }
/**
* Support for loading any Imported Associated One properties that are not
@@ -1156,10 +1138,7 @@ public final class DefaultPersister implements Persister {
for (int i = 0; i < ones.length; i++) {
BeanPropertyAssocOne> prop = ones[i];
- if (!request.isLoadedProperty(prop)) {
- // handled by DeleteUnloadedForeignKeys that was built
- // via getDeleteUnloadedForeignKeys();
- } else {
+ if (request.isLoadedProperty(prop)) {
Object detailBean = prop.getValue(request.getEntityBean());
if (detailBean != null) {
EntityBean detail = (EntityBean)detailBean;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java
index 600718fbd..93c472f92 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java
@@ -20,100 +20,92 @@ import org.slf4j.LoggerFactory;
*/
public class ExeCallableSql {
- private static final Logger logger = LoggerFactory.getLogger(ExeCallableSql.class);
-
- private final Binder binder;
-
- private final PstmtFactory pstmtFactory;
-
- public ExeCallableSql(Binder binder, PstmtBatch pstmtBatch) {
- this.binder = binder;
- // no batch support for CallableStatement in Oracle anyway
- this.pstmtFactory = new PstmtFactory(null);
- }
-
- /**
- * execute the CallableSql requests.
- */
- public int execute(PersistRequestCallableSql request) {
+ private static final Logger logger = LoggerFactory.getLogger(ExeCallableSql.class);
- SpiTransaction t = request.getTransaction();
-
- boolean batchThisRequest = t.isBatchThisRequest();
-
- CallableStatement cstmt = null;
+ private final Binder binder;
+
+ private final PstmtFactory pstmtFactory;
+
+ public ExeCallableSql(Binder binder, PstmtBatch pstmtBatch) {
+ this.binder = binder;
+ // no batch support for CallableStatement in Oracle anyway
+ this.pstmtFactory = new PstmtFactory(null);
+ }
+
+ /**
+ * execute the CallableSql requests.
+ */
+ public int execute(PersistRequestCallableSql request) {
+
+ boolean batchThisRequest = request.isBatchThisRequest();
+
+ CallableStatement cstmt = null;
+ try {
+ cstmt = bindStmt(request, batchThisRequest);
+ if (batchThisRequest) {
+ cstmt.addBatch();
+ // return -1 to indicate batch mode
+ return -1;
+ } else {
+ // handles executeOverride() and also
+ // reading of registered OUT parameters
+ int rowCount = request.executeUpdate();
+ request.postExecute();
+ return rowCount;
+ }
+
+ } catch (SQLException ex) {
+ throw new PersistenceException(ex);
+
+ } finally {
+ if (!batchThisRequest && cstmt != null) {
try {
-
- cstmt = bindStmt(request, batchThisRequest);
-
- if (batchThisRequest){
- cstmt.addBatch();
- // return -1 to indicate batch mode
- return -1;
-
- } else {
- // handles executeOverride() and also
- // reading of registered OUT parameters
- int rowCount = request.executeUpdate();
- request.postExecute();
- return rowCount;
-
- }
-
- } catch (SQLException ex) {
- throw new PersistenceException(ex);
-
- } finally {
- if (!batchThisRequest && cstmt != null) {
- try {
- cstmt.close();
- } catch (SQLException e) {
- logger.error(null, e);
- }
- }
+ cstmt.close();
+ } catch (SQLException e) {
+ logger.error(null, e);
}
+ }
}
-
-
- private CallableStatement bindStmt(PersistRequestCallableSql request, boolean batchThisRequest) throws SQLException {
-
- SpiCallableSql callableSql = request.getCallableSql();
- SpiTransaction t = request.getTransaction();
-
- String sql = callableSql.getSql();
-
- BindParams bindParams = callableSql.getBindParams();
-
- // process named parameters if required
- sql = BindParamsParser.parse(bindParams, sql);
-
- boolean logSql = request.isLogSql();
-
- CallableStatement cstmt;
- if (batchThisRequest){
- cstmt = pstmtFactory.getCstmt(t, logSql, sql, request);
-
- } else {
- if (logSql){
- t.logSql(sql);
- }
- cstmt = pstmtFactory.getCstmt(t, sql);
- }
-
- if (callableSql.getTimeout() > 0){
- cstmt.setQueryTimeout(callableSql.getTimeout());
- }
-
- String bindLog = null;
- if (!bindParams.isEmpty()){
- bindLog = binder.bind(bindParams, new DataBind(cstmt));
- }
-
- request.setBindLog(bindLog);
-
- // required to read OUT params later
- request.setBound(bindParams, cstmt);
-
- return cstmt;
+ }
+
+
+ private CallableStatement bindStmt(PersistRequestCallableSql request, boolean batchThisRequest) throws SQLException {
+
+ SpiCallableSql callableSql = request.getCallableSql();
+ SpiTransaction t = request.getTransaction();
+
+ String sql = callableSql.getSql();
+
+ BindParams bindParams = callableSql.getBindParams();
+
+ // process named parameters if required
+ sql = BindParamsParser.parse(bindParams, sql);
+
+ boolean logSql = request.isLogSql();
+
+ CallableStatement cstmt;
+ if (batchThisRequest) {
+ cstmt = pstmtFactory.getCstmt(t, logSql, sql, request);
+ } else {
+ if (logSql) {
+ t.logSql(sql);
+ }
+ cstmt = pstmtFactory.getCstmt(t, sql);
}
+
+ if (callableSql.getTimeout() > 0) {
+ cstmt.setQueryTimeout(callableSql.getTimeout());
+ }
+
+ String bindLog = null;
+ if (!bindParams.isEmpty()) {
+ bindLog = binder.bind(bindParams, new DataBind(cstmt));
+ }
+
+ request.setBindLog(bindLog);
+
+ // required to read OUT params later
+ request.setBound(bindParams, cstmt);
+ return cstmt;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java
index d3fba3418..b935f6900 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java
@@ -21,121 +21,109 @@ import org.slf4j.LoggerFactory;
*/
public class ExeOrmUpdate {
- private static final Logger logger = LoggerFactory.getLogger(ExeOrmUpdate.class);
-
- private final Binder binder;
-
- private final PstmtFactory pstmtFactory;
-
- /**
- * Create with a given binder.
- */
- public ExeOrmUpdate(Binder binder, PstmtBatch pstmtBatch) {
- this.pstmtFactory = new PstmtFactory(pstmtBatch);
- this.binder = binder;
- }
-
- /**
- * Execute the orm update request.
- */
- public int execute(PersistRequestOrmUpdate request) {
+ private static final Logger logger = LoggerFactory.getLogger(ExeOrmUpdate.class);
- SpiTransaction t = request.getTransaction();
-
- boolean batchThisRequest = t.isBatchThisRequest();
-
- PreparedStatement pstmt = null;
+ private final Binder binder;
+
+ private final PstmtFactory pstmtFactory;
+
+ /**
+ * Create with a given binder.
+ */
+ public ExeOrmUpdate(Binder binder, PstmtBatch pstmtBatch) {
+ this.pstmtFactory = new PstmtFactory(pstmtBatch);
+ this.binder = binder;
+ }
+
+ /**
+ * Execute the orm update request.
+ */
+ public int execute(PersistRequestOrmUpdate request) {
+
+ boolean batchThisRequest = request.isBatchThisRequest();
+
+ PreparedStatement pstmt = null;
+ try {
+ pstmt = bindStmt(request, batchThisRequest);
+ if (batchThisRequest) {
+ PstmtBatch pstmtBatch = request.getPstmtBatch();
+ if (pstmtBatch != null) {
+ pstmtBatch.addBatch(pstmt);
+ } else {
+ pstmt.addBatch();
+ }
+ // return -1 to indicate batch mode
+ return -1;
+ } else {
+ SpiUpdate> ormUpdate = request.getOrmUpdate();
+ if (ormUpdate.getTimeout() > 0) {
+ pstmt.setQueryTimeout(ormUpdate.getTimeout());
+ }
+ int rowCount = pstmt.executeUpdate();
+ request.checkRowCount(rowCount);
+ request.postExecute();
+ return rowCount;
+ }
+
+ } catch (SQLException ex) {
+ throw new PersistenceException("Error executing: " + request.getOrmUpdate().getGeneratedSql(), ex);
+
+ } finally {
+ if (!batchThisRequest && pstmt != null) {
try {
-
- pstmt = bindStmt(request, batchThisRequest);
-
- if (batchThisRequest){
- PstmtBatch pstmtBatch = request.getPstmtBatch();
- if (pstmtBatch != null){
- pstmtBatch.addBatch(pstmt);
- } else {
- pstmt.addBatch();
- }
- // return -1 to indicate batch mode
- return -1;
-
- } else {
- SpiUpdate> ormUpdate = request.getOrmUpdate();
- if (ormUpdate.getTimeout() > 0){
- pstmt.setQueryTimeout(ormUpdate.getTimeout());
- }
-
- int rowCount = pstmt.executeUpdate();
- request.checkRowCount(rowCount);
- request.postExecute();
- return rowCount;
-
- }
-
- } catch (SQLException ex) {
- SpiUpdate> ormUpdate = request.getOrmUpdate();
- String msg = "Error executing: "+ormUpdate.getGeneratedSql();
- throw new PersistenceException(msg, ex);
-
- } finally {
- if (!batchThisRequest && pstmt != null) {
- try {
- pstmt.close();
- } catch (SQLException e) {
- logger.error(null, e);
- }
- }
+ pstmt.close();
+ } catch (SQLException e) {
+ logger.error(null, e);
}
+ }
}
-
- /**
- * Convert bean and property names to db table and columns.
- */
- private String translate(PersistRequestOrmUpdate request, String sql) {
-
- BeanDescriptor> descriptor = request.getBeanDescriptor();
- return descriptor.convertOrmUpdateToSql(sql);
- }
-
- private PreparedStatement bindStmt(PersistRequestOrmUpdate request, boolean batchThisRequest) throws SQLException {
-
- SpiUpdate> ormUpdate = request.getOrmUpdate();
- SpiTransaction t = request.getTransaction();
-
- String sql = ormUpdate.getUpdateStatement();
-
- // convert bean and property names to table and
- // column names if required
- sql = translate(request, sql);
-
- BindParams bindParams = ormUpdate.getBindParams();
-
- // process named parameters if required
- sql = BindParamsParser.parse(bindParams, sql);
-
- ormUpdate.setGeneratedSql(sql);
-
- boolean logSql = request.isLogSql();
-
- PreparedStatement pstmt;
- if (batchThisRequest){
- pstmt = pstmtFactory.getPstmt(t, logSql, sql, request);
-
- } else {
- if (logSql){
- t.logSql(sql);
- }
- pstmt = pstmtFactory.getPstmt(t, sql);
- }
-
- String bindLog = null;
- if (!bindParams.isEmpty()){
- bindLog = binder.bind(bindParams, new DataBind(pstmt));
- }
-
- request.setBindLog(bindLog);
-
- return pstmt;
+ }
+
+ /**
+ * Convert bean and property names to db table and columns.
+ */
+ private String translate(PersistRequestOrmUpdate request, String sql) {
+
+ BeanDescriptor> descriptor = request.getBeanDescriptor();
+ return descriptor.convertOrmUpdateToSql(sql);
+ }
+
+ private PreparedStatement bindStmt(PersistRequestOrmUpdate request, boolean batchThisRequest) throws SQLException {
+
+ SpiUpdate> ormUpdate = request.getOrmUpdate();
+ SpiTransaction t = request.getTransaction();
+
+ String sql = ormUpdate.getUpdateStatement();
+
+ // convert bean and property names to table and
+ // column names if required
+ sql = translate(request, sql);
+
+ BindParams bindParams = ormUpdate.getBindParams();
+
+ // process named parameters if required
+ sql = BindParamsParser.parse(bindParams, sql);
+
+ ormUpdate.setGeneratedSql(sql);
+
+ boolean logSql = request.isLogSql();
+
+ PreparedStatement pstmt;
+ if (batchThisRequest) {
+ pstmt = pstmtFactory.getPstmt(t, logSql, sql, request);
+ } else {
+ if (logSql) {
+ t.logSql(sql);
+ }
+ pstmt = pstmtFactory.getPstmt(t, sql);
}
+ String bindLog = null;
+ if (!bindParams.isEmpty()) {
+ bindLog = binder.bind(bindParams, new DataBind(pstmt));
+ }
+
+ request.setBindLog(bindLog);
+ return pstmt;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java
index c0b839f5e..680833d15 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java
@@ -1,10 +1,5 @@
package com.avaje.ebeaninternal.server.persist;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-
-import javax.persistence.PersistenceException;
-
import com.avaje.ebeaninternal.api.BindParams;
import com.avaje.ebeaninternal.api.SpiSqlUpdate;
import com.avaje.ebeaninternal.api.SpiTransaction;
@@ -16,185 +11,180 @@ import com.avaje.ebeaninternal.server.util.BindParamsParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.persistence.PersistenceException;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
/**
* Executes the UpdateSql requests.
*/
public class ExeUpdateSql {
- private static final Logger logger = LoggerFactory.getLogger(ExeUpdateSql.class);
-
- private final Binder binder;
-
- private final PstmtFactory pstmtFactory;
-
- private final PstmtBatch pstmtBatch;
-
- //TODO: get defaultBatchSize
- private int defaultBatchSize = 20;
-
- /**
- * Create with a given binder.
- */
- public ExeUpdateSql(Binder binder, PstmtBatch pstmtBatch) {
- this.binder = binder;
- this.pstmtBatch = pstmtBatch;
- this.pstmtFactory = new PstmtFactory(pstmtBatch);
- }
-
- /**
- * Execute the UpdateSql request.
- */
- public int execute(PersistRequestUpdateSql request) {
+ private static final Logger logger = LoggerFactory.getLogger(ExeUpdateSql.class);
- SpiTransaction t = request.getTransaction();
-
- boolean batchThisRequest = t.isBatchThisRequest();
-
- PreparedStatement pstmt = null;
+ private final Binder binder;
+
+ private final PstmtFactory pstmtFactory;
+
+ private final PstmtBatch pstmtBatch;
+
+ private int defaultBatchSize = 20;
+
+ /**
+ * Create with a given binder.
+ */
+ public ExeUpdateSql(Binder binder, PstmtBatch pstmtBatch) {
+ this.binder = binder;
+ this.pstmtBatch = pstmtBatch;
+ this.pstmtFactory = new PstmtFactory(pstmtBatch);
+ }
+
+ /**
+ * Execute the UpdateSql request.
+ */
+ public int execute(PersistRequestUpdateSql request) {
+
+ boolean batchThisRequest = request.isBatchThisRequest();
+
+ PreparedStatement pstmt = null;
+ try {
+
+ pstmt = bindStmt(request, batchThisRequest);
+
+ if (batchThisRequest) {
+ if (pstmtBatch != null) {
+ pstmtBatch.addBatch(pstmt);
+ } else {
+ pstmt.addBatch();
+ }
+ // return -1 to indicate batch mode
+ return -1;
+ } else {
+ int rowCount = pstmt.executeUpdate();
+ request.checkRowCount(rowCount);
+ request.postExecute();
+ return rowCount;
+ }
+ } catch (SQLException ex) {
+ throw new PersistenceException(ex);
+
+ } finally {
+ if (!batchThisRequest && pstmt != null) {
try {
-
- pstmt = bindStmt(request, batchThisRequest);
-
- if (batchThisRequest){
- if (pstmtBatch != null){
- pstmtBatch.addBatch(pstmt);
- } else {
- pstmt.addBatch();
- }
- // return -1 to indicate batch mode
- return -1;
-
- } else {
- int rowCount = pstmt.executeUpdate();
- request.checkRowCount(rowCount);
- request.postExecute();
- return rowCount;
-
- }
-
- } catch (SQLException ex) {
- throw new PersistenceException(ex);
-
- } finally {
- if (!batchThisRequest && pstmt != null) {
- try {
- pstmt.close();
- } catch (SQLException e) {
- logger.error(null, e);
- }
- }
+ pstmt.close();
+ } catch (SQLException e) {
+ logger.error(null, e);
}
+ }
}
-
-
- private PreparedStatement bindStmt(PersistRequestUpdateSql request, boolean batchThisRequest) throws SQLException {
-
- SpiSqlUpdate updateSql = request.getUpdateSql();
- SpiTransaction t = request.getTransaction();
-
- String sql = updateSql.getSql();
-
- BindParams bindParams = updateSql.getBindParams();
-
- // process named parameters if required
- sql = BindParamsParser.parse(bindParams, sql);
- updateSql.setGeneratedSql(sql);
-
- boolean logSql = request.isLogSql();
-
- PreparedStatement pstmt;
- if (batchThisRequest){
- pstmt = pstmtFactory.getPstmt(t, logSql, sql, request);
- if (pstmtBatch != null){
- // oracle specific JDBC setting batch size ahead of time
- int batchSize = t.getBatchSize();
- if (batchSize < 1){
- batchSize = defaultBatchSize;
- }
- pstmtBatch.setBatchSize(pstmt, batchSize);
- }
-
- } else {
- if (logSql){
- t.logSql(sql);
- }
- pstmt = pstmtFactory.getPstmt(t, sql);
- }
-
- if (updateSql.getTimeout() > 0){
- pstmt.setQueryTimeout(updateSql.getTimeout());
- }
-
- String bindLog = null;
- if (!bindParams.isEmpty()){
- bindLog = binder.bind(bindParams, new DataBind(pstmt));
+ }
+
+ private PreparedStatement bindStmt(PersistRequestUpdateSql request, boolean batchThisRequest) throws SQLException {
+
+ SpiSqlUpdate updateSql = request.getUpdateSql();
+ SpiTransaction t = request.getTransaction();
+
+ String sql = updateSql.getSql();
+
+ BindParams bindParams = updateSql.getBindParams();
+
+ // process named parameters if required
+ sql = BindParamsParser.parse(bindParams, sql);
+ updateSql.setGeneratedSql(sql);
+
+ boolean logSql = request.isLogSql();
+
+ PreparedStatement pstmt;
+ if (batchThisRequest) {
+ pstmt = pstmtFactory.getPstmt(t, logSql, sql, request);
+ if (pstmtBatch != null) {
+ // oracle specific JDBC setting batch size ahead of time
+ int batchSize = t.getBatchSize();
+ if (batchSize < 1) {
+ batchSize = defaultBatchSize;
}
-
- request.setBindLog(bindLog);
-
- // derive the statement type (for TransactionEvent)
- parseUpdate(sql, request);
-
- return pstmt;
+ pstmtBatch.setBatchSize(pstmt, batchSize);
+ }
+ } else {
+ if (logSql) {
+ t.logSql(sql);
+ }
+ pstmt = pstmtFactory.getPstmt(t, sql);
}
-
- private void determineType(String word1, String word2, String word3, PersistRequestUpdateSql request) {
- if (word1.equalsIgnoreCase("UPDATE")) {
- request.setType(SqlType.SQL_UPDATE, word2, "UpdateSql");
-
- } else if (word1.equalsIgnoreCase("DELETE")) {
- request.setType(SqlType.SQL_DELETE, word3, "DeleteSql");
-
- } else if (word1.equalsIgnoreCase("INSERT")) {
- request.setType(SqlType.SQL_INSERT, word3, "InsertSql");
-
- } else {
- request.setType(SqlType.SQL_UNKNOWN, null, "UnknownSql");
-
- }
+ if (updateSql.getTimeout() > 0) {
+ pstmt.setQueryTimeout(updateSql.getTimeout());
}
- private void parseUpdate(String sql, PersistRequestUpdateSql request) {
-
- int start = ltrim(sql);
-
- int[] pos = new int[3];
- int spaceCount = 0;
-
- int len = sql.length();
- for (int i = start; i < len; i++) {
- char c = sql.charAt(i);
- if (Character.isWhitespace(c)) {
- pos[spaceCount] = i;
- spaceCount++;
- if (spaceCount > 2){
- break;
- }
- }
- }
-
- String firstWord = sql.substring(0, pos[0]);
- String secWord = sql.substring(pos[0]+1, pos[1]);
- String thirdWord;
- if (pos[2] == 0){
- // there is nothing after the table name
- thirdWord = sql.substring(pos[1]+1);
- } else {
- thirdWord = sql.substring(pos[1]+1, pos[2]);
- }
-
- determineType(firstWord, secWord, thirdWord, request);
+ String bindLog = null;
+ if (!bindParams.isEmpty()) {
+ bindLog = binder.bind(bindParams, new DataBind(pstmt));
}
-
- private int ltrim(String s) {
- int len = s.length();
- int i = 0;
- for (i = 0; i < len; i++) {
- if (!Character.isWhitespace(s.charAt(i))) {
- return i;
- }
- }
- return 0;
+
+ request.setBindLog(bindLog);
+
+ // derive the statement type (for TransactionEvent)
+ parseUpdate(sql, request);
+ return pstmt;
+ }
+
+
+ private void determineType(String word1, String word2, String word3, PersistRequestUpdateSql request) {
+
+ if (word1.equalsIgnoreCase("UPDATE")) {
+ request.setType(SqlType.SQL_UPDATE, word2, "UpdateSql");
+
+ } else if (word1.equalsIgnoreCase("DELETE")) {
+ request.setType(SqlType.SQL_DELETE, word3, "DeleteSql");
+
+ } else if (word1.equalsIgnoreCase("INSERT")) {
+ request.setType(SqlType.SQL_INSERT, word3, "InsertSql");
+
+ } else {
+ request.setType(SqlType.SQL_UNKNOWN, null, "UnknownSql");
}
+ }
+
+ private void parseUpdate(String sql, PersistRequestUpdateSql request) {
+
+ int start = leadingTrim(sql);
+
+ int[] pos = new int[3];
+ int spaceCount = 0;
+
+ int len = sql.length();
+ for (int i = start; i < len; i++) {
+ char c = sql.charAt(i);
+ if (Character.isWhitespace(c)) {
+ pos[spaceCount] = i;
+ spaceCount++;
+ if (spaceCount > 2) {
+ break;
+ }
+ }
+ }
+
+ String firstWord = sql.substring(0, pos[0]);
+ String secWord = sql.substring(pos[0] + 1, pos[1]);
+ String thirdWord;
+ if (pos[2] == 0) {
+ // there is nothing after the table name
+ thirdWord = sql.substring(pos[1] + 1);
+ } else {
+ thirdWord = sql.substring(pos[1] + 1, pos[2]);
+ }
+
+ determineType(firstWord, secWord, thirdWord, request);
+ }
+
+ private int leadingTrim(String s) {
+ int len = s.length();
+ int i;
+ for (i = 0; i < len; i++) {
+ if (!Character.isWhitespace(s.charAt(i))) {
+ return i;
+ }
+ }
+ return 0;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java
index a98c4ffdb..09d676d4f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DeleteHandler.java
@@ -28,20 +28,16 @@ public class DeleteHandler extends DmlHandler {
public void bind() throws SQLException {
sql = meta.getSql(persistRequest);
-
SpiTransaction t = persistRequest.getTransaction();
- boolean isBatch = t.isBatchThisRequest();
PreparedStatement pstmt;
- if (isBatch) {
+ if (persistRequest.isBatched()) {
pstmt = getPstmt(t, sql, persistRequest, false);
} else {
pstmt = getPstmt(t, sql, false);
}
dataBind = new DataBind(pstmt);
-
meta.bind(persistRequest, this);
-
logSql(sql);
}
@@ -51,9 +47,6 @@ public class DeleteHandler extends DmlHandler {
public void execute() throws SQLException, OptimisticLockException {
int rowCount = dataBind.executeUpdate();
checkRowCount(rowCount);
-
- // Deletes the bean from the PersistenceContext
- persistRequest.postDelete();
}
public void registerDerivedRelationship(DerivedRelationshipData assocBean) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java
index fd30a83f9..3e148e129 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlBeanPersister.java
@@ -69,35 +69,28 @@ public final class DmlBeanPersister implements BeanPersister {
/**
* execute request taking batching into account.
*/
- private void execute(PersistRequest request, PersistHandler handler) {
-
- SpiTransaction trans = request.getTransaction();
- boolean batchThisRequest = trans.isBatchThisRequest();
+ private void execute(PersistRequestBean> request, PersistHandler handler) {
+ boolean batched = request.isBatched();
try {
-
handler.bind();
-
- if (batchThisRequest) {
+ if (batched) {
handler.addBatch();
-
} else {
- // immediate insert
handler.execute();
}
} catch (SQLException e) {
- // log the error to the transaction log
- String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r","\n"}, "\\n ");
- String msg = "ERROR executing DML bindLog["+handler.getBindLog()+"] error["+errMsg+"]";
- if (request.getTransaction().isLogSummary()) {
- request.getTransaction().logSummary(msg);
- }
-
+ // log the error to the transaction log
+ String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r","\n"}, "\\n ");
+ String msg = "ERROR executing DML bindLog["+handler.getBindLog()+"] error["+errMsg+"]";
+ if (request.getTransaction().isLogSummary()) {
+ request.getTransaction().logSummary(msg);
+ }
throw new PersistenceException(msg, e);
} finally {
- if (!batchThisRequest && handler != null) {
+ if (!batched && handler != null) {
try {
handler.close();
} catch (SQLException e) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java
index 17694d525..5aeccace0 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/InsertHandler.java
@@ -88,13 +88,12 @@ public class InsertHandler extends DmlHandler {
}
SpiTransaction t = persistRequest.getTransaction();
- boolean isBatch = t.isBatchThisRequest();
// get the appropriate sql
sql = meta.getSql(withId);
PreparedStatement pstmt;
- if (isBatch) {
+ if (persistRequest.isBatched()) {
pstmt = getPstmt(t, sql, persistRequest, useGeneratedKeys);
} else {
pstmt = getPstmt(t, sql, useGeneratedKeys);
@@ -137,9 +136,7 @@ public class InsertHandler extends DmlHandler {
}
checkRowCount(rc);
- //setAdditionalProperties();
executeDerivedRelationships();
-
persistRequest.postInsert();
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java
index 247340a5f..948180c02 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateHandler.java
@@ -40,10 +40,9 @@ public class UpdateHandler extends DmlHandler {
sql = updatePlan.getSql();
SpiTransaction t = persistRequest.getTransaction();
- boolean isBatch = t.isBatchThisRequest();
PreparedStatement pstmt;
- if (isBatch) {
+ if (persistRequest.isBatched()) {
pstmt = getPstmt(t, sql, persistRequest, false);
} else {
pstmt = getPstmt(t, sql, false);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java
index a06ce88f3..39812210a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java
@@ -1,29 +1,25 @@
package com.avaje.ebeaninternal.server.transaction;
-import java.io.IOException;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.persistence.PersistenceException;
-import javax.persistence.RollbackException;
-
import com.avaje.ebean.TransactionCallback;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.avaje.ebean.bean.PersistenceContext;
+import com.avaje.ebean.config.PersistBatch;
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.TransactionEvent;
+import com.avaje.ebeaninternal.server.core.PersistRequest;
+import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.lib.util.Str;
import com.avaje.ebeaninternal.server.persist.BatchControl;
import com.avaje.ebeaninternal.server.transaction.TransactionManager.OnQueryOnly;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.persistence.PersistenceException;
+import javax.persistence.RollbackException;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.*;
/**
* JDBC Connection based transaction.
@@ -95,10 +91,11 @@ public class JdbcTransaction implements SpiTransaction {
protected boolean localReadOnly;
- /**
- * Set to true if using batch processing.
- */
- protected boolean batchMode;
+ protected PersistBatch oldBatchMode;
+
+ protected PersistBatch batchMode;
+
+ protected PersistBatch batchOnCascadeMode;
protected int batchSize = -1;
@@ -109,7 +106,7 @@ public class JdbcTransaction implements SpiTransaction {
protected Boolean batchFlushOnMixed;
protected String logPrefix;
-
+
/**
* The depth used by batch processing to help the ordering of statements.
*/
@@ -120,18 +117,20 @@ public class JdbcTransaction implements SpiTransaction {
*/
protected final boolean autoCommit;
- protected IdentityHashMap
*/
+ @Override
public void registerDeleteBean(Integer persistingBean) {
if (deletingBeansHash == null) {
deletingBeansHash = new HashSet();
@@ -269,6 +272,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Unregister the persisted bean.
*/
+ @Override
public void unregisterDeleteBean(Integer persistedBean) {
if (deletingBeansHash != null) {
deletingBeansHash.remove(persistedBean);
@@ -278,6 +282,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return true if this is a bean that has already been saved/deleted.
*/
+ @Override
public boolean isRegisteredDeleteBean(Integer persistingBean) {
return deletingBeansHash != null && deletingBeansHash.contains(persistingBean);
}
@@ -285,19 +290,21 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Unregister the persisted bean.
*/
+ @Override
public void unregisterBean(Object bean) {
persistingBeans.remove(bean);
}
-
+
/**
* Return true if this is a bean that has already been saved. This will
* register the bean if it is not already.
*/
+ @Override
public boolean isRegisteredBean(Object bean) {
if (persistingBeans == null) {
- persistingBeans = new IdentityHashMap();
+ persistingBeans = new IdentityHashMap();
}
- return (persistingBeans.put(bean,PLACEHOLDER) != null);
+ return (persistingBeans.put(bean, PLACEHOLDER) != null);
}
/**
@@ -317,8 +324,8 @@ public class JdbcTransaction implements SpiTransaction {
// first time into this intersection table so allow
m2mIntersectionSave.put(intersectionTable, beanName);
return true;
- }
-
+ }
+
// only allow if save coming from the same bean type
// to stop saves coming from both directions of m2m
return existingBean.equals(beanName);
@@ -336,16 +343,25 @@ public class JdbcTransaction implements SpiTransaction {
*
* The depth is used to help the ordering of batched statements.
*
- *
- * @param diff
- * the amount to add or subtract from the depth.
+ *
+ * @param diff the amount to add or subtract from the depth.
* @return the current depth plus the diff
*/
+ @Override
public int depth(int diff) {
depth += diff;
return depth;
}
+ /**
+ * Return the current depth.
+ */
+ @Override
+ public int depth() {
+ return depth;
+ }
+
+ @Override
public boolean isReadOnly() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -357,6 +373,7 @@ public class JdbcTransaction implements SpiTransaction {
}
}
+ @Override
public void setReadOnly(boolean readOnly) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -369,13 +386,41 @@ public class JdbcTransaction implements SpiTransaction {
}
}
+ @Override
public void setBatchMode(boolean batchMode) {
+ if (!isActive()) {
+ throw new IllegalStateException(illegalStateMessage);
+ }
+ this.batchMode = (batchMode) ? PersistBatch.ALL : PersistBatch.NONE;
+ }
+
+ @Override
+ public void setBatch(PersistBatch batchMode) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
this.batchMode = batchMode;
}
+ @Override
+ public PersistBatch getBatch() {
+ return batchMode;
+ }
+
+ @Override
+ public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
+ if (!isActive()) {
+ throw new IllegalStateException(illegalStateMessage);
+ }
+ this.batchOnCascadeMode = batchOnCascadeMode;
+ }
+
+ @Override
+ public PersistBatch getBatchOnCascade() {
+ return batchOnCascadeMode;
+ }
+
+ @Override
public void setBatchGetGeneratedKeys(boolean getGeneratedKeys) {
this.batchGetGeneratedKeys = getGeneratedKeys;
if (batchControl != null) {
@@ -383,6 +428,7 @@ public class JdbcTransaction implements SpiTransaction {
}
}
+ @Override
public void setBatchFlushOnMixed(boolean batchFlushOnMixed) {
this.batchFlushOnMixed = batchFlushOnMixed;
if (batchControl != null) {
@@ -396,10 +442,12 @@ public class JdbcTransaction implements SpiTransaction {
* Returning 0 implies to use the system wide default batch size.
*
*/
+ @Override
public int getBatchSize() {
return batchSize;
}
+ @Override
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
if (batchControl != null) {
@@ -407,10 +455,12 @@ public class JdbcTransaction implements SpiTransaction {
}
}
+ @Override
public boolean isBatchFlushOnQuery() {
return batchFlushOnQuery;
}
+ @Override
public void setBatchFlushOnQuery(boolean batchFlushOnQuery) {
this.batchFlushOnQuery = batchFlushOnQuery;
}
@@ -419,15 +469,113 @@ public class JdbcTransaction implements SpiTransaction {
* Return true if this request should be batched. Returning false means that
* this request should be executed immediately.
*/
- public boolean isBatchThisRequest() {
- if (!explicit && depth <= 0) {
- // implicit transaction ... no gain
- // by batching where depth <= 0
+ @Override
+ public boolean isBatchThisRequest(PersistRequest.Type type) {
+ if (!batchOnCascadeSet && !explicit && depth <= 0) {
+ // implicit transaction, no gain by batching where depth <= 0
return false;
}
- return batchMode;
+ switch (batchMode) {
+ case ALL:
+ return true;
+ case INSERT:
+ return type == PersistRequest.Type.INSERT;
+ default:
+ return false;
+ }
}
+ /**
+ * Return true if JDBC batch should be used on cascade persist.
+ */
+ private boolean isBatchOnCascade(PersistRequest.Type type) {
+
+ switch (batchOnCascadeMode) {
+ case ALL:
+ return true;
+ case INSERT:
+ return type == PersistRequest.Type.INSERT;
+ default:
+ return false;
+ }
+ }
+
+ public void checkBatchEscalationOnCollection() {
+ if (batchMode == PersistBatch.NONE && batchOnCascadeMode != PersistBatch.NONE) {
+ batchMode = batchOnCascadeMode;
+ batchOnCascadeSet = true;
+ }
+ }
+
+ public void flushBatchOnCollection() {
+ if (batchOnCascadeSet) {
+ if (batchControl != null) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("... flushBatchOnCollection");
+ }
+ batchControl.flushReset();
+ }
+ // restore the previous batch mode of NONE
+ batchMode = PersistBatch.NONE;
+ }
+ }
+
+ /**
+ * Flush after completing persist cascade.
+ */
+ @Override
+ public void flushBatchOnCascade() {
+ if (batchControl != null) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("... flushBatchOnCascade");
+ }
+ batchControl.flushReset();
+ }
+ // restore the previous batch mode
+ batchMode = oldBatchMode;
+ }
+
+ private boolean isAlreadyBatching(PersistRequest.Type type) {
+ switch (batchMode) {
+ case ALL:
+ return true;
+ case INSERT:
+ return type == PersistRequest.Type.INSERT;
+ default:
+ return false;
+ }
+ }
+
+ public boolean checkBatchEscalationOnCascade(PersistRequestBean> request) {
+
+ if (isAlreadyBatching(request.getType())) {
+ // already batching (at top level)
+ return false;
+ }
+
+ if (isBatchOnCascade(request.getType())) {
+ // escalate up to batch mode for this request (and cascade)
+ oldBatchMode = batchMode;
+ batchMode = PersistBatch.ALL;
+ if (batchControl != null) {
+ // flush with reset so that this request goes into it's own batch buffer
+ batchControl.flushReset();
+ }
+ // skip using jdbc batch for the top level bean (no gain there)
+ request.setSkipBatchForTopLevel();
+ return true;
+ }
+
+ if (batchControl != null && !batchControl.isEmpty()) {
+ if (logger.isTraceEnabled()) {
+ logger.trace("... flush from batchOnCascade ");
+ }
+ batchControl.flushReset();
+ }
+ return false;
+ }
+
+ @Override
public BatchControl getBatchControl() {
return batchControl;
}
@@ -436,6 +584,7 @@ public class JdbcTransaction implements SpiTransaction {
* Set the BatchControl to the transaction. This is done once per transaction
* on the first persist request.
*/
+ @Override
public void setBatchControl(BatchControl batchControl) {
queryOnly = false;
this.batchControl = batchControl;
@@ -458,6 +607,7 @@ public class JdbcTransaction implements SpiTransaction {
* executing.
*
*/
+ @Override
public void flushBatch() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -467,13 +617,10 @@ public class JdbcTransaction implements SpiTransaction {
}
}
- public void batchFlush() {
- flushBatch();
- }
-
/**
* Return the persistence context associated with this transaction.
*/
+ @Override
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
@@ -486,6 +633,7 @@ public class JdbcTransaction implements SpiTransaction {
* then set it back later to a second transaction.
*
*/
+ @Override
public void setPersistenceContext(PersistenceContext context) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -496,6 +644,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return the underlying TransactionEvent.
*/
+ @Override
public TransactionEvent getEvent() {
queryOnly = false;
if (event == null) {
@@ -507,29 +656,35 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return true if this was an explicitly created transaction.
*/
+ @Override
public boolean isExplicit() {
return explicit;
}
+ @Override
public boolean isLogSql() {
return TransactionManager.SQL_LOGGER.isDebugEnabled();
}
+ @Override
public boolean isLogSummary() {
return TransactionManager.SUM_LOGGER.isDebugEnabled();
}
-
+
+ @Override
public void logSql(String msg) {
TransactionManager.SQL_LOGGER.trace(Str.add(logPrefix, msg));
}
-
+
+ @Override
public void logSummary(String msg) {
TransactionManager.SUM_LOGGER.debug(Str.add(logPrefix, msg));
}
-
+
/**
* Return the transaction id.
*/
+ @Override
public String getId() {
return id;
}
@@ -537,6 +692,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return the underlying connection for internal use.
*/
+ @Override
public Connection getInternalConnection() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -547,6 +703,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return the underlying connection for public use.
*/
+ @Override
public Connection getConnection() {
queryOnly = false;
return getInternalConnection();
@@ -598,7 +755,7 @@ public class JdbcTransaction implements SpiTransaction {
manager.notifyOfQueryOnly(true, this, null);
}
}
-
+
/**
* Rollback, Commit or Close for query only transaction.
*
@@ -609,17 +766,17 @@ public class JdbcTransaction implements SpiTransaction {
protected void connectionEndForQueryOnly() {
try {
switch (onQueryOnly) {
- case ROLLBACK:
- performRollback();
- break;
- case COMMIT:
- performCommit();
- break;
- case CLOSE_ON_READCOMMITTED:
- // valid at READ COMMITTED Isolation
- break;
- default:
- performRollback();
+ case ROLLBACK:
+ performRollback();
+ break;
+ case COMMIT:
+ performCommit();
+ break;
+ case CLOSE_ON_READCOMMITTED:
+ // valid at READ COMMITTED Isolation
+ break;
+ default:
+ performRollback();
}
} catch (SQLException e) {
logger.error("Error when ending a query only transaction via " + onQueryOnly, e);
@@ -643,6 +800,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* End the transaction on a query only request.
*/
+ @Override
public void endQueryOnly() {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -652,13 +810,14 @@ public class JdbcTransaction implements SpiTransaction {
} finally {
// these will not throw an exception
deactivate();
- notifyQueryOnly();
+ notifyQueryOnly();
}
}
-
+
/**
* Commit the transaction.
*/
+ @Override
public void commit() throws RollbackException {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -680,12 +839,12 @@ public class JdbcTransaction implements SpiTransaction {
} catch (Exception e) {
throw new RollbackException(e);
-
+
} finally {
// these will not throw an exception
firePostCommit();
deactivate();
- notifyCommit();
+ notifyCommit();
}
}
@@ -705,6 +864,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Rollback the transaction.
*/
+ @Override
public void rollback() throws PersistenceException {
rollback(null);
}
@@ -713,6 +873,7 @@ public class JdbcTransaction implements SpiTransaction {
* Rollback the transaction. If there is a throwable it is logged as the cause
* in the transaction log.
*/
+ @Override
public void rollback(Throwable cause) throws PersistenceException {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
@@ -723,7 +884,7 @@ public class JdbcTransaction implements SpiTransaction {
} catch (Exception ex) {
throw new PersistenceException(ex);
-
+
} finally {
// these will not throw an exception
firePostRollback();
@@ -735,6 +896,7 @@ public class JdbcTransaction implements SpiTransaction {
/**
* If the transaction is active then perform rollback.
*/
+ @Override
public void end() throws PersistenceException {
if (isActive()) {
rollback();
@@ -744,29 +906,35 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Return true if the transaction is active.
*/
+ @Override
public boolean isActive() {
return active;
}
+ @Override
public boolean isPersistCascade() {
return persistCascade;
}
+ @Override
public void setPersistCascade(boolean persistCascade) {
this.persistCascade = persistCascade;
}
+ @Override
public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes) {
getEvent().add(tableName, inserts, updates, deletes);
}
+ @Override
public void putUserObject(String name, Object value) {
if (userObjects == null) {
- userObjects = new HashMap();
+ userObjects = new HashMap();
}
userObjects.put(name, value);
}
+ @Override
public Object getUserObject(String name) {
if (userObjects == null) {
return null;
@@ -777,11 +945,12 @@ public class JdbcTransaction implements SpiTransaction {
/**
* Alias for end(), which enables this class to be used in try-with-resources.
*/
+ @Override
public void close() throws IOException {
try {
- end();
+ end();
} catch (PersistenceException ex) {
- throw new IOException(ex);
+ throw new IOException(ex);
}
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
index e83802c5d..43e111b7e 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.transaction;
import com.avaje.ebean.BackgroundExecutor;
+import com.avaje.ebean.config.PersistBatch;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.event.TransactionEventListener;
import com.avaje.ebeaninternal.api.SpiTransaction;
@@ -37,8 +38,8 @@ public class TransactionManager {
public static final Logger SUM_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.SUM");
public static final Logger TXN_LOGGER = LoggerFactory.getLogger("org.avaje.ebean.TXN");
-
- /**
+
+ /**
* The behavior desired when ending a query only transaction.
*/
public enum OnQueryOnly {
@@ -79,32 +80,33 @@ public class TransactionManager {
*/
protected final OnQueryOnly onQueryOnly;
- /**
- * The default batchMode for transactions.
- */
- protected final boolean defaultBatchMode;
-
protected final BackgroundExecutor backgroundExecutor;
protected final ClusterManager clusterManager;
protected final String serverName;
-
+
+ protected final PersistBatch persistBatch;
+
+ protected final PersistBatch persistBatchOnCascade;
+
/**
* Id's for transaction logging.
*/
- protected AtomicLong transactionCounter = new AtomicLong(1000);
+ protected final AtomicLong transactionCounter = new AtomicLong(1000);
protected final BulkEventListenerMap bulkEventListenerMap;
- protected TransactionEventListener[] transactionEventListeners;
+ protected final TransactionEventListener[] transactionEventListeners;
/**
* Create the TransactionManager
*/
public TransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, ServerConfig config,
BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
-
+
+ this.persistBatch = config.getPersistBatch();
+ this.persistBatchOnCascade = config.getPersistBatchOnCascade();
this.beanDescriptorManager = descMgr;
this.clusterManager = clusterManager;
this.serverName = config.getName();
@@ -115,7 +117,6 @@ public class TransactionManager {
List transactionEventListeners = bootupClasses.getTransactionEventListeners();
this.transactionEventListeners = transactionEventListeners.toArray(new TransactionEventListener[transactionEventListeners.size()]);
- this.defaultBatchMode = config.isPersistBatching();
this.prefix = "";
this.externalTransPrefix = "e";
@@ -145,8 +146,16 @@ public class TransactionManager {
public BulkEventListenerMap getBulkEventListenerMap() {
return bulkEventListenerMap;
}
-
- /**
+
+ public PersistBatch getPersistBatch() {
+ return persistBatch;
+ }
+
+ public PersistBatch getPersistBatchOnCascade() {
+ return persistBatchOnCascade;
+ }
+
+ /**
* Return the behaviour to use when a query only transaction is committed.
*
* There is a potential optimisation available when read committed is the default
@@ -232,12 +241,9 @@ public class TransactionManager {
ExternalJdbcTransaction t = new ExternalJdbcTransaction(id, true, c, this);
- // set the default batch mode. This can be on for
- // jdbc drivers that support getGeneratedKeys
- if (defaultBatchMode){
- t.setBatchMode(true);
- }
-
+ // set the default batch mode
+ t.setBatch(persistBatch);
+ t.setBatchOnCascade(persistBatchOnCascade);
return t;
}
@@ -251,12 +257,6 @@ public class TransactionManager {
long id = transactionCounter.incrementAndGet();
SpiTransaction t = createTransaction(explicit, c, id);
-
- // set the default batch mode. This can be on for
- // jdbc drivers that support getGeneratedKeys
- if (defaultBatchMode){
- t.setBatchMode(true);
- }
if (isolationLevel > -1) {
c.setTransactionIsolation(isolationLevel);
}
@@ -286,16 +286,8 @@ public class TransactionManager {
c = dataSource.getConnection();
long id = transactionCounter.incrementAndGet();
- SpiTransaction t = createTransaction(false, c, id);
-
- // set the default batch mode. Can be true for
- // jdbc drivers that support getGeneratedKeys
- if (defaultBatchMode){
- t.setBatchMode(true);
- }
-
- return t;
-
+ return createTransaction(false, c, id);
+
} catch (PersistenceException ex) {
// close the connection and re-throw the exception
try {
@@ -454,8 +446,6 @@ public class TransactionManager {
beanPersist.notifyCacheAndListener();
}
}
-
}
-
}
diff --git a/src/test/java/com/avaje/ebean/config/ServerConfigTest.java b/src/test/java/com/avaje/ebean/config/ServerConfigTest.java
new file mode 100644
index 000000000..d31577022
--- /dev/null
+++ b/src/test/java/com/avaje/ebean/config/ServerConfigTest.java
@@ -0,0 +1,50 @@
+package com.avaje.ebean.config;
+
+import org.junit.Test;
+
+import java.util.Properties;
+
+import static org.junit.Assert.*;
+
+public class ServerConfigTest {
+
+
+ @Test
+ public void testLoadFromEbeanProperties() {
+
+ ServerConfig serverConfig = new ServerConfig();
+ serverConfig.loadFromProperties();
+
+ assertEquals(PersistBatch.NONE, serverConfig.getPersistBatch());
+ assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
+ }
+
+ @Test
+ public void testLoadWithProperties() {
+
+ ServerConfig serverConfig = new ServerConfig();
+ serverConfig.setPersistBatch(PersistBatch.NONE);
+ serverConfig.setPersistBatchOnCascade(PersistBatch.NONE);
+
+ Properties props = new Properties();
+ props.setProperty("persistBatch", "INSERT");
+ props.setProperty("persistBatchOnCascade", "INSERT");
+
+ serverConfig.loadFromProperties(props);
+
+ assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatch());
+ assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatchOnCascade());
+
+ serverConfig.setPersistBatch(PersistBatch.NONE);
+ serverConfig.setPersistBatchOnCascade(PersistBatch.NONE);
+
+ Properties props1 = new Properties();
+ props1.setProperty("ebean.persistBatch", "ALL");
+ props1.setProperty("ebean.persistBatchOnCascade", "ALL");
+
+ serverConfig.loadFromProperties(props1);
+
+ assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch());
+ assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/transaction/TestBatchPersistCascade.java b/src/test/java/com/avaje/ebeaninternal/server/transaction/TestBatchPersistCascade.java
new file mode 100644
index 000000000..68cf9d6ee
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/transaction/TestBatchPersistCascade.java
@@ -0,0 +1,132 @@
+package com.avaje.ebeaninternal.server.transaction;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.EbeanServer;
+import com.avaje.ebean.Transaction;
+import com.avaje.ebean.config.PersistBatch;
+import com.avaje.tests.model.basic.UTDetail;
+import com.avaje.tests.model.basic.UTMaster;
+import org.avaje.ebeantest.LoggedSqlCollector;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+import static org.junit.Assert.assertTrue;
+
+public class TestBatchPersistCascade extends BaseTestCase {
+
+ Logger logger = LoggerFactory.getLogger(TestBatchPersistCascade.class);
+
+ @Test
+ public void test() {
+
+
+ EbeanServer ebeanServer = Ebean.getServer(null);
+
+ LoggedSqlCollector.start();
+
+ Transaction txn = ebeanServer.beginTransaction();
+ try {
+ txn.setBatch(PersistBatch.INSERT);
+ logger.info("start ------------");
+
+ for (int i = 0; i < 3; i++) {
+ UTMaster master = createMaster(i);
+ logger.info("save ------------ {}", i);
+ ebeanServer.save(master);
+ //txn.flushBatch();
+ }
+
+ logger.info("commit ------------");
+ txn.commit();
+
+ } finally {
+ txn.end();
+ }
+
+ List loggedSql = LoggedSqlCollector.stop();
+ assertTrue(loggedSql.size() > 2);
+
+
+ testUpdates();
+
+ }
+
+ private void testUpdates() {
+
+ EbeanServer server = Ebean.getServer(null);
+
+ List list = server.find(UTMaster.class).fetch("details").findList();
+
+ Transaction txn = server.beginTransaction();
+ try {
+ txn.setBatch(PersistBatch.INSERT);
+ txn.setBatchOnCascade(PersistBatch.ALL);
+
+ for (int i = 0; i < 3; i++) {
+ UTMaster master = createMaster(i+500);
+ logger.info("save ------------ {}", i);
+ server.save(master);
+ }
+
+ logger.info("starting updates ------------ ");
+
+ UTMaster lastMaster = null;
+ for (UTMaster utMaster : list) {
+ utMaster.setName(utMaster.getName()+" + mod");
+ List details = utMaster.getDetails();
+ for (UTDetail detail : details) {
+ detail.setQty(detail.getQty()+7);
+ detail.setName(detail.getName()+" + foo");
+ }
+
+ server.save(utMaster);
+ lastMaster = utMaster;
+ }
+
+ logger.info("starting some inserts ------------ ");
+
+ for (int i = 0; i < 3; i++) {
+ UTMaster master = createMaster(i+1000);
+ logger.info("save ------------ {}", i);
+ server.save(master);
+ if (i == 1) {
+ logger.info("save lastMaster ------------ ");
+ lastMaster.setName("mod");
+ server.save(lastMaster);
+ }
+ }
+
+ logger.info("commit ------------ ");
+
+ server.commitTransaction();
+ } finally {
+ server.endTransaction();
+ }
+
+ }
+
+ private UTDetail createUTDetail(String master, int count) {
+ UTDetail detail = new UTDetail();
+ detail.setName(master+"-"+count);
+ detail.setAmount(50d);
+ detail.setQty(count);
+ return detail;
+ }
+
+ private UTMaster createMaster(int count) {
+
+ String name = "master"+count;
+
+ UTMaster m0 = new UTMaster();
+ m0.setName(name);
+ for (int i =0; i< 5; i++) {
+ m0.addDetail(createUTDetail(name, i));
+ }
+ return m0;
+ }
+
+}
diff --git a/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalDateTimeTest.java b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalDateTimeTest.java
index 86d3cbd83..33ab414cb 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalDateTimeTest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeLocalDateTimeTest.java
@@ -23,7 +23,7 @@ public class ScalarTypeLocalDateTimeTest {
long now = System.currentTimeMillis();
long toMillis = type.convertToMillis(LocalDateTime.now());
- assertTrue(toMillis - now < 10);
+ assertTrue(toMillis - now < 30);
}
@Test
diff --git a/src/test/java/com/avaje/tests/batchinsert/TestBatchInsertSimple.java b/src/test/java/com/avaje/tests/batchinsert/TestBatchInsertSimple.java
index bfbbf5f54..7bad50f4d 100644
--- a/src/test/java/com/avaje/tests/batchinsert/TestBatchInsertSimple.java
+++ b/src/test/java/com/avaje/tests/batchinsert/TestBatchInsertSimple.java
@@ -1,53 +1,115 @@
package com.avaje.tests.batchinsert;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
-import org.junit.Test;
-
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.Transaction;
+import com.avaje.ebean.config.PersistBatch;
import com.avaje.tests.model.basic.UTDetail;
import com.avaje.tests.model.basic.UTMaster;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
public class TestBatchInsertSimple extends BaseTestCase {
Random random = new Random();
@Test
- public void testSimpleJdbcBatching() {
+ public void testJdbcBatchPerRequestWithMasterAndDetails() {
- int numOfMasters = 10;// 2 + random.nextInt(8);
+ int numOfMasters = 4;// 2 + random.nextInt(8);
+
+ Transaction transaction = Ebean.beginTransaction();
+ try {
+ transaction.setBatch(PersistBatch.NONE);
+ transaction.setBatchOnCascade(PersistBatch.INSERT);
+ transaction.setBatchSize(30);
+
+ for (int i = 0; i < numOfMasters; i++) {
+ UTMaster master = createMasterAndDetails(i, 20);
+ Ebean.save(master);
+ }
+
+ transaction.commit();
+
+ } finally {
+ transaction.end();
+ }
+ }
+
+
+ @Test
+ public void testJdbcBatchPerRequestWithMasterOnly() {
+
+ int numOfMasters = 4;
+
+ Transaction transaction = Ebean.beginTransaction();
+ try {
+ transaction.setBatch(PersistBatch.NONE);
+ transaction.setBatchOnCascade(PersistBatch.INSERT);
+ transaction.setBatchSize(30);
+
+ for (int i = 0; i < numOfMasters; i++) {
+ UTMaster master = createMaster(i);
+ Ebean.save(master);
+ }
+
+ transaction.commit();
+
+ } finally {
+ transaction.end();
+ }
+ }
+
+ @Test
+ public void testJdbcBatchOnCollection() {
+
+ int numOfMasters = 3;
List masters = new ArrayList();
for (int i = 0; i < numOfMasters; i++) {
- masters.add(createMasterAndDetails(i));
+ masters.add(createMasterAndDetails(i, 7));
}
Transaction transaction = Ebean.beginTransaction();
try {
- transaction.setBatchMode(true);
- transaction.setBatchSize(4);
- // transaction.setLogLevel(LogLevel.SUMMARY);
- // transaction.setBatchGetGeneratedKeys(false);
+ transaction.setBatch(PersistBatch.NONE);
+ transaction.setBatchOnCascade(PersistBatch.ALL);
+ transaction.setBatchSize(20);
+ // escalate based on batchOnCascade value
Ebean.save(masters);
transaction.commit();
} finally {
- Ebean.endTransaction();
+ transaction.end();
}
}
- private UTMaster createMasterAndDetails(int masterPos) {
+ @Test
+ public void testJdbcBatchOnCollectionNoTransaction() {
+
+ int numOfMasters = 3;
+
+ List masters = new ArrayList();
+ for (int i = 0; i < numOfMasters; i++) {
+ masters.add(createMasterAndDetails(i, 5));
+ }
+
+ // escalate based on batchOnCascade value
+ Ebean.save(masters);
+
+ }
+
+ private UTMaster createMasterAndDetails(int masterPos, int size) {
UTMaster master = createMaster(masterPos);
List details = new ArrayList();
- int count = 2 + random.nextInt(20);
+ int count = 2 + random.nextInt(size);
for (int i = 0; i < count; i++) {
diff --git a/src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchThenFlushThenUpdate.java b/src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchThenFlushThenUpdate.java
new file mode 100644
index 000000000..b0214727b
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchThenFlushThenUpdate.java
@@ -0,0 +1,66 @@
+package com.avaje.tests.model.basic.xtra;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.Transaction;
+import com.avaje.ebean.config.PersistBatch;
+import org.avaje.ebeantest.LoggedSqlCollector;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TestInsertBatchThenFlushThenUpdate extends BaseTestCase {
+
+ @Test
+ public void test() {
+
+ LoggedSqlCollector.start();
+ Transaction txn = Ebean.beginTransaction();
+ try {
+ txn.setBatch(PersistBatch.ALL);
+
+ EdParent parent = new EdParent();
+ parent.setName("MyComputer");
+
+ EdChild child = new EdChild();
+ child.setName("Harddisk 123");
+ child.setParent(parent);
+ ArrayList children = new ArrayList();
+ children.add(child);
+ parent.setChildren(children);
+
+ Ebean.save(parent);
+
+ // nothing flushed yet
+ assertEquals(0, LoggedSqlCollector.start().size());
+
+ txn.flushBatch();
+
+ List loggedSql1 = LoggedSqlCollector.start();
+ assertEquals(loggedSql1.toString(), 2, loggedSql1.size());
+
+ parent.setName("MyDesk");
+ Ebean.save(parent);
+
+ // nothing flushed yet
+ assertEquals(0, LoggedSqlCollector.start().size());
+
+ Ebean.commitTransaction();
+
+ // insert statements for EdExtendedParent
+ List loggedSql2 = LoggedSqlCollector.start();
+ assertEquals(2, loggedSql2.size());
+ assertTrue(loggedSql2.get(0).contains(" update td_parent "));
+ assertTrue(loggedSql2.get(1).contains(" update td_child "));
+
+ } finally {
+ Ebean.endTransaction();
+ }
+ }
+
+
+}
diff --git a/src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchThenUpdate.java b/src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchThenUpdate.java
new file mode 100644
index 000000000..200484a17
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchThenUpdate.java
@@ -0,0 +1,59 @@
+package com.avaje.tests.model.basic.xtra;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.Transaction;
+import com.avaje.ebean.config.PersistBatch;
+import org.avaje.ebeantest.LoggedSqlCollector;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestInsertBatchThenUpdate extends BaseTestCase {
+
+ @Test
+ public void test() {
+
+ LoggedSqlCollector.start();
+ Transaction txn = Ebean.beginTransaction();
+ try {
+ txn.setBatch(PersistBatch.ALL);
+
+ EdParent parent = new EdParent();
+ parent.setName("MyComputer");
+
+ EdChild child = new EdChild();
+ child.setName("Harddisk 123");
+ child.setParent(parent);
+ ArrayList children = new ArrayList();
+ children.add(child);
+ parent.setChildren(children);
+
+ Ebean.save(parent);
+
+ // nothing flushed yet
+ List loggedSql0 = LoggedSqlCollector.start();
+ assertEquals(0, loggedSql0.size());
+
+ parent.setName("MyDesk");
+ Ebean.save(parent);
+
+ // nothing flushed yet
+ assertEquals(0, LoggedSqlCollector.start().size());
+
+ Ebean.commitTransaction();
+
+ // insert statements for EdExtendedParent
+ List loggedSql2 = LoggedSqlCollector.start();
+ assertEquals(2, loggedSql2.size());
+
+ } finally {
+ Ebean.endTransaction();
+ }
+ }
+
+
+}
diff --git a/src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchWithDifferentRootTypes.java b/src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchWithDifferentRootTypes.java
new file mode 100644
index 000000000..44e7c2159
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchWithDifferentRootTypes.java
@@ -0,0 +1,70 @@
+package com.avaje.tests.model.basic.xtra;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.Transaction;
+import com.avaje.ebean.config.PersistBatch;
+import org.avaje.ebeantest.LoggedSqlCollector;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class TestInsertBatchWithDifferentRootTypes extends BaseTestCase {
+
+ @Test
+ public void testDifferRootTypes() {
+
+ LoggedSqlCollector.start();
+ Transaction txn = Ebean.beginTransaction();
+ try {
+ txn.setBatch(PersistBatch.ALL);
+
+ EdParent parent = new EdParent();
+ parent.setName("MyComputer");
+
+ EdChild child = new EdChild();
+ child.setName("Harddisk 123");
+ child.setParent(parent);
+ ArrayList children = new ArrayList();
+ children.add(child);
+ parent.setChildren(children);
+
+ Ebean.save(parent);
+
+ EdExtendedParent extendedParent = new EdExtendedParent();
+ extendedParent.setName("My second computer");
+ extendedParent.setExtendedName("Multimedia");
+
+ child = new EdChild();
+ child.setName("DVBS Card");
+ children = new ArrayList();
+ children.add(child);
+ extendedParent.setChildren(children);
+
+ // nothing flushed yet
+ List loggedSql0 = LoggedSqlCollector.start();
+ assertEquals(0, loggedSql0.size());
+
+ // causes a flush as EdExtendedParent is different from EdParent
+ Ebean.save(extendedParent);
+
+ // insert statements for EdParent
+ List loggedSql1 = LoggedSqlCollector.start();
+ assertEquals(2, loggedSql1.size());
+
+ Ebean.commitTransaction();
+
+ // insert statements for EdExtendedParent
+ List loggedSql2 = LoggedSqlCollector.start();
+ assertEquals(2, loggedSql2.size());
+
+ } finally {
+ Ebean.endTransaction();
+ }
+ }
+
+
+}
diff --git a/src/test/java/com/avaje/tests/query/joins/TestQueryJoinManyNonRoot.java b/src/test/java/com/avaje/tests/query/joins/TestQueryJoinManyNonRoot.java
index c73660483..459251354 100644
--- a/src/test/java/com/avaje/tests/query/joins/TestQueryJoinManyNonRoot.java
+++ b/src/test/java/com/avaje/tests/query/joins/TestQueryJoinManyNonRoot.java
@@ -2,18 +2,39 @@ package com.avaje.tests.query.joins;
import java.util.List;
+import com.avaje.ebean.*;
+import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Assert;
import org.junit.Test;
-import com.avaje.ebean.BaseTestCase;
-import com.avaje.ebean.Ebean;
-import com.avaje.ebean.FetchConfig;
-import com.avaje.ebean.Query;
import com.avaje.tests.model.basic.Order;
import com.avaje.tests.model.basic.ResetBasicData;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
public class TestQueryJoinManyNonRoot extends BaseTestCase {
+ @Test
+ public void test_manyPredicate() {
+
+ ResetBasicData.reset();
+
+ LoggedSqlCollector.start();
+
+ List orders = Ebean.find(Order.class)
+ .select("id, status, orderDate")
+ .where().gt("details.orderQty", 0)
+ .findList();
+
+ assertTrue(!orders.isEmpty());
+
+ List loggedSql = LoggedSqlCollector.stop();
+ assertEquals(1, loggedSql.size());
+ assertTrue(loggedSql.get(0).contains("select distinct "));
+ assertTrue(loggedSql.get(0).contains(" from o_order t0 join o_order_detail u1 on u1.order_id = t0.id "));
+ }
+
@Test
public void test_manyNonRoot() {
@@ -27,9 +48,9 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase {
List list = q.findList();
String sql = q.getGeneratedSql();
- Assert.assertTrue(list.size() > 0);
- Assert.assertTrue(sql.contains("join o_customer t1 on t1.id "));
- Assert.assertTrue(sql.contains("left outer join contact t2 on"));
+ assertTrue(list.size() > 0);
+ assertTrue(sql.contains("join o_customer t1 on t1.id "));
+ assertTrue(sql.contains("left outer join contact t2 on"));
// select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.name c4, t0.cretime c5, t0.updtime c6,
// t1.id c7, t1.status c8, t1.name c9, t1.smallnote c10, t1.anniversary c11, t1.cretime c12, t1.updtime c13, t1.billing_address_id c14, t1.shipping_address_id c15,
@@ -56,10 +77,10 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase {
List list = q.findList();
String sql = q.getGeneratedSql();
- Assert.assertTrue(list.size() > 0);
- Assert.assertTrue(sql.contains("join o_customer t1 on t1.id "));
- Assert.assertTrue(sql.contains("left outer join o_order_detail "));
- Assert.assertTrue(sql.contains("left outer join o_product "));
+ assertTrue(list.size() > 0);
+ assertTrue(sql.contains("join o_customer t1 on t1.id "));
+ assertTrue(sql.contains("left outer join o_order_detail "));
+ assertTrue(sql.contains("left outer join o_product "));
Assert.assertFalse(sql.contains("left outer join contact"));
@@ -84,9 +105,9 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase {
order.getCustomer().getContacts().size();
}
- Assert.assertTrue(list.size() > 0);
- Assert.assertTrue(sql.contains("join o_customer t1 on t1.id "));
- Assert.assertTrue(sql.contains("left outer join contact "));
+ assertTrue(list.size() > 0);
+ assertTrue(sql.contains("join o_customer t1 on t1.id "));
+ assertTrue(sql.contains("left outer join contact "));
Assert.assertFalse(sql.contains("left outer join o_order_detail "));
Assert.assertFalse(sql.contains("left outer join o_product "));
diff --git a/src/test/resources/ebean.properties b/src/test/resources/ebean.properties
index 9849f9cdd..699ba9e67 100644
--- a/src/test/resources/ebean.properties
+++ b/src/test/resources/ebean.properties
@@ -27,6 +27,8 @@ ebean.autofetch.traceUsageCollection=false
ebean.ddl.generate=true
ebean.ddl.run=true
+ebean.persistBatch=NONE
+ebean.persistBatchOnCascade=ALL
ebean.debug.sql=true
#ebean.debug.lazyload=false