From 6bb38f2674dfa27beb782ce4f378ea4623d36255 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Wed, 10 Dec 2014 03:29:44 +1300 Subject: [PATCH] #220 - Refactor Persist batch, add effectively add PersistBatch.INSERT (to ALL and NONE) and allow batching per request (save(), insert(), update(), delete()) --- .../java/com/avaje/ebean/Transaction.java | 84 +++-- .../com/avaje/ebean/config/PersistBatch.java | 45 +++ .../com/avaje/ebean/config/ServerConfig.java | 142 +++++--- .../ebeaninternal/api/SpiTransaction.java | 30 +- .../ebeaninternal/api/TransactionEvent.java | 2 +- .../server/core/DefaultServer.java | 56 +-- .../server/core/PersistRequest.java | 48 +-- .../server/core/PersistRequestBean.java | 90 ++++- .../server/core/TransWrapper.java | 10 + .../server/persist/BatchControl.java | 141 +++++--- .../server/persist/BatchedBeanControl.java | 79 ----- .../server/persist/BatchedBeanHolder.java | 227 ++++++------ .../server/persist/DefaultPersister.java | 85 ++--- .../server/persist/ExeCallableSql.java | 174 +++++---- .../server/persist/ExeOrmUpdate.java | 210 ++++++----- .../server/persist/ExeUpdateSql.java | 330 +++++++++--------- .../server/persist/dml/DeleteHandler.java | 9 +- .../server/persist/dml/DmlBeanPersister.java | 27 +- .../server/persist/dml/InsertHandler.java | 5 +- .../server/persist/dml/UpdateHandler.java | 3 +- .../server/transaction/JdbcTransaction.java | 317 +++++++++++++---- .../transaction/TransactionManager.java | 66 ++-- .../avaje/ebean/config/ServerConfigTest.java | 50 +++ .../transaction/TestBatchPersistCascade.java | 132 +++++++ .../type/ScalarTypeLocalDateTimeTest.java | 2 +- .../batchinsert/TestBatchInsertSimple.java | 94 ++++- .../TestInsertBatchThenFlushThenUpdate.java | 66 ++++ .../basic/xtra/TestInsertBatchThenUpdate.java | 59 ++++ ...TestInsertBatchWithDifferentRootTypes.java | 70 ++++ .../query/joins/TestQueryJoinManyNonRoot.java | 49 ++- src/test/resources/ebean.properties | 2 + 31 files changed, 1731 insertions(+), 973 deletions(-) create mode 100644 src/main/java/com/avaje/ebean/config/PersistBatch.java delete mode 100644 src/main/java/com/avaje/ebeaninternal/server/persist/BatchedBeanControl.java create mode 100644 src/test/java/com/avaje/ebean/config/ServerConfigTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/transaction/TestBatchPersistCascade.java create mode 100644 src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchThenFlushThenUpdate.java create mode 100644 src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchThenUpdate.java create mode 100644 src/test/java/com/avaje/tests/model/basic/xtra/TestInsertBatchWithDifferentRootTypes.java diff --git a/src/main/java/com/avaje/ebean/Transaction.java b/src/main/java/com/avaje/ebean/Transaction.java index 1621352df..250283c36 100644 --- a/src/main/java/com/avaje/ebean/Transaction.java +++ b/src/main/java/com/avaje/ebean/Transaction.java @@ -1,5 +1,7 @@ package com.avaje.ebean; +import com.avaje.ebean.config.PersistBatch; + import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; import javax.persistence.RollbackException; @@ -128,42 +130,83 @@ public interface Transaction extends Closeable { * Example: batch processing executing every 3 rows *

* - *
-   * 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 persistingBeans; - + protected IdentityHashMap persistingBeans; + protected HashSet deletingBeansHash; - - protected HashMap m2mIntersectionSave; - + + protected HashMap m2mIntersectionSave; + protected HashMap> derivedRelMap; - + protected Map userObjects; protected List callbackList; + protected boolean batchOnCascadeSet; + /** * Create a new JdbcTransaction. */ @@ -143,6 +142,8 @@ public class JdbcTransaction implements SpiTransaction { this.explicit = explicit; this.manager = manager; this.connection = connection; + this.batchMode = manager == null ? PersistBatch.NONE : manager.getPersistBatch(); + this.batchOnCascadeMode = manager == null ? PersistBatch.NONE : manager.getPersistBatchOnCascade(); this.onQueryOnly = manager == null ? OnQueryOnly.ROLLBACK : manager.getOnQueryOnly(); this.persistenceContext = new DefaultPersistenceContext(); this.autoCommit = connection.getAutoCommit(); @@ -156,7 +157,7 @@ public class JdbcTransaction implements SpiTransaction { } private static String deriveLogPrefix(String id) { - + StringBuilder sb = new StringBuilder(); sb.append("txn["); if (id != null) { @@ -165,11 +166,12 @@ public class JdbcTransaction implements SpiTransaction { sb.append("] "); return sb.toString(); } - + + @Override public String getLogPrefix() { return logPrefix; } - + public String toString() { return logPrefix; } @@ -230,20 +232,20 @@ public class JdbcTransaction implements SpiTransaction { } } - + @Override public List getDerivedRelationship(Object bean) { if (derivedRelMap == null) { return null; } - Integer key = Integer.valueOf(System.identityHashCode(bean)); - return derivedRelMap.get(key); + return derivedRelMap.get(System.identityHashCode(bean)); } + @Override public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) { if (derivedRelMap == null) { derivedRelMap = new HashMap>(); } - Integer key = Integer.valueOf(System.identityHashCode(derivedRelationship.getAssocBean())); + Integer key = new Integer(System.identityHashCode(derivedRelationship.getAssocBean())); List list = derivedRelMap.get(key); if (list == null) { @@ -259,6 +261,7 @@ public class JdbcTransaction implements SpiTransaction { * This is to handle bi-directional relationships where both sides Cascade. *

    */ + @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