mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#220 - Refactor Persist batch, add effectively add PersistBatch.INSERT (to ALL and NONE) and allow batching per request (save(), insert(), update(), delete())
This commit is contained in:
@@ -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
|
||||
* </p>
|
||||
*
|
||||
* <pre class="code">
|
||||
* String data = "This is a simple test of the batch processing"
|
||||
* + " mode and the transaction execute batch method";
|
||||
* <pre>{@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();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*/
|
||||
public void setBatchMode(boolean useBatch);
|
||||
|
||||
/**
|
||||
* The JDBC batch mode to use for this transaction.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
* This only takes effect when batch mode on the transaction has not already meant that
|
||||
* JDBC batch mode is being used.
|
||||
* </p>
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @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 {
|
||||
* <li>the batch size is reached</li>
|
||||
* <li>A query is executed on the same transaction</li>
|
||||
* <li>UpdateSql or CallableSql are mixed with bean save and delete</li>
|
||||
* <li>Transaction commit occurs</li>
|
||||
* </ul>
|
||||
*/
|
||||
public void flushBatch() throws PersistenceException, OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Deprecated in favour of {@link #flushBatch()}.
|
||||
* <p>
|
||||
* Exactly the same as flushBatch. Deprecated as a name change.
|
||||
* </p>
|
||||
*
|
||||
* @deprecated Please use flushBatch
|
||||
*/
|
||||
public void batchFlush() throws PersistenceException, OptimisticLockException;
|
||||
|
||||
/**
|
||||
* Return the underlying Connection object.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.avaje.ebean.config;
|
||||
|
||||
/**
|
||||
* Defines the mode for JDBC batch processing.
|
||||
* <p>
|
||||
* Used both at a per transaction basis and per request basis.
|
||||
* </p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*/
|
||||
public PersistBatch getPersistBatch() {
|
||||
return persistBatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JDBC batch mode to use at the transaction level.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*/
|
||||
public void setPersistBatch(PersistBatch persistBatch) {
|
||||
this.persistBatch = persistBatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JDBC batch mode to use per save(), delete(), insert() or update() request.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* This only takes effect when the persistBatch mode at the transaction level does not take effect.
|
||||
* </p>
|
||||
*/
|
||||
public PersistBatch getPersistBatchOnCascade() {
|
||||
return persistBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the JDBC batch mode to use per save(), delete(), insert() or update() request.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* This only takes effect when the persistBatch mode at the transaction level does not take effect.
|
||||
* </p>
|
||||
*/
|
||||
public void setPersistBatchOnCascade(PersistBatch persistBatchOnCascade) {
|
||||
this.persistBatchOnCascade = persistBatchOnCascade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated, please migrate to using setPersistBatch().
|
||||
* <p>
|
||||
* Set to true if you what to use JDBC batching for persisting and deleting
|
||||
* beans.
|
||||
* </p>
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isPersistBatching() {
|
||||
return persistBatching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if you what to use JDBC batching for persisting and deleting
|
||||
* beans.
|
||||
* <p>
|
||||
* 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)}
|
||||
* </p>
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
* You can also set the batch size on the transaction.
|
||||
* </p>
|
||||
* @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.
|
||||
* <p>
|
||||
@@ -476,7 +526,7 @@ public class ServerConfig {
|
||||
* invoked by default.
|
||||
* </p>
|
||||
* <p>
|
||||
* 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).
|
||||
* </p>
|
||||
* <p>
|
||||
* 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);
|
||||
|
||||
@@ -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
|
||||
* <code>Ebean.beginTransaction()</code>.
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 <T> SpiOrmQueryRequest<T> createQueryRequest(BeanDescriptor<T> desc, SpiQuery<T> 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 <T> BeanManager<T> getBeanManager(Class<T> beanClass) {
|
||||
return beanDescriptorManager.getBeanManager(beanClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the BeanDescriptors.
|
||||
*/
|
||||
@@ -1875,13 +1850,6 @@ public final class DefaultServer implements SpiEbeanServer {
|
||||
return beanDescriptorManager.getBeanDescriptorList();
|
||||
}
|
||||
|
||||
public List<MetaBeanInfo> getMetaBeanInfoList() {
|
||||
|
||||
List<MetaBeanInfo> list = new ArrayList<MetaBeanInfo>();
|
||||
list.addAll(getBeanDescriptors());
|
||||
return list;
|
||||
}
|
||||
|
||||
public void register(BeanPersistController c) {
|
||||
List<BeanDescriptor<?>> 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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -97,6 +97,21 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
private Set<String> 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<T> mgr, SpiTransaction t,
|
||||
PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse) {
|
||||
|
||||
@@ -127,6 +142,54 @@ public final class PersistRequestBean<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<String, BatchedBeanHolder> beanHoldMap = new HashMap<String, BatchedBeanHolder>();
|
||||
|
||||
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 {
|
||||
* </p>
|
||||
*/
|
||||
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<PersistRequest> 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<PersistRequest> 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()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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).
|
||||
* </p>
|
||||
*/
|
||||
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<String, BatchedBeanHolder> beanHoldMap = new HashMap<String, BatchedBeanHolder>();
|
||||
|
||||
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<PersistRequest> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
@@ -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<PersistRequest> inserts;
|
||||
/**
|
||||
* The list of bean insert requests.
|
||||
*/
|
||||
private ArrayList<PersistRequest> inserts;
|
||||
|
||||
/**
|
||||
* The list of bean update requests.
|
||||
*/
|
||||
private ArrayList<PersistRequest> updates;
|
||||
/**
|
||||
* The list of bean update requests.
|
||||
*/
|
||||
private ArrayList<PersistRequest> updates;
|
||||
|
||||
/**
|
||||
* The list of bean delete requests.
|
||||
*/
|
||||
private ArrayList<PersistRequest> deletes;
|
||||
/**
|
||||
* The list of bean delete requests.
|
||||
*/
|
||||
private ArrayList<PersistRequest> deletes;
|
||||
|
||||
private HashSet<Integer> beanHashCodes = new HashSet<Integer>();
|
||||
|
||||
/**
|
||||
* 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<Integer> beanHashCodes = new HashSet<Integer>();
|
||||
|
||||
/**
|
||||
* Return the depth.
|
||||
*/
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all the persist requests in this entry.
|
||||
* <p>
|
||||
* This will Batch all the similar requests into one or more BatchStatements
|
||||
* and then execute them.
|
||||
* </p>
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
* This will Batch all the similar requests into one or more BatchStatements
|
||||
* and then execute them.
|
||||
* </p>
|
||||
*/
|
||||
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<PersistRequest> 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<PersistRequest>();
|
||||
}
|
||||
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<PersistRequest>();
|
||||
}
|
||||
return updates;
|
||||
/**
|
||||
* Add the request to the appropriate persist list.
|
||||
*/
|
||||
public int append(PersistRequestBean<?> request) {
|
||||
|
||||
case DELETE:
|
||||
if (deletes == null) {
|
||||
deletes = new ArrayList<PersistRequest>();
|
||||
}
|
||||
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<PersistRequest>();
|
||||
}
|
||||
inserts.add(request);
|
||||
return inserts.size();
|
||||
|
||||
case UPDATE:
|
||||
if (updates == null) {
|
||||
updates = new ArrayList<PersistRequest>();
|
||||
}
|
||||
updates.add(request);
|
||||
return updates.size();
|
||||
|
||||
case DELETE:
|
||||
if (deletes == null) {
|
||||
deletes = new ArrayList<PersistRequest>();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<Object,Object> persistingBeans;
|
||||
|
||||
protected IdentityHashMap<Object, Object> persistingBeans;
|
||||
|
||||
protected HashSet<Integer> deletingBeansHash;
|
||||
|
||||
protected HashMap<String,String> m2mIntersectionSave;
|
||||
|
||||
|
||||
protected HashMap<String, String> m2mIntersectionSave;
|
||||
|
||||
protected HashMap<Integer, List<DerivedRelationshipData>> derivedRelMap;
|
||||
|
||||
|
||||
protected Map<String, Object> userObjects;
|
||||
|
||||
protected List<TransactionCallback> 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<DerivedRelationshipData> 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, List<DerivedRelationshipData>>();
|
||||
}
|
||||
Integer key = Integer.valueOf(System.identityHashCode(derivedRelationship.getAssocBean()));
|
||||
Integer key = new Integer(System.identityHashCode(derivedRelationship.getAssocBean()));
|
||||
|
||||
List<DerivedRelationshipData> 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.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void registerDeleteBean(Integer persistingBean) {
|
||||
if (deletingBeansHash == null) {
|
||||
deletingBeansHash = new HashSet<Integer>();
|
||||
@@ -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<Object,Object>();
|
||||
persistingBeans = new IdentityHashMap<Object, Object>();
|
||||
}
|
||||
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 {
|
||||
* <p>
|
||||
* The depth is used to help the ordering of batched statements.
|
||||
* </p>
|
||||
*
|
||||
* @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.
|
||||
* </p>
|
||||
*/
|
||||
@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.
|
||||
* </p>
|
||||
*/
|
||||
@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.
|
||||
* </p>
|
||||
*/
|
||||
@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.
|
||||
* <p>
|
||||
@@ -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<String,Object>();
|
||||
userObjects = new HashMap<String, Object>();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TransactionEventListener> 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.
|
||||
* <p>
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
+132
@@ -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<String> loggedSql = LoggedSqlCollector.stop();
|
||||
assertTrue(loggedSql.size() > 2);
|
||||
|
||||
|
||||
testUpdates();
|
||||
|
||||
}
|
||||
|
||||
private void testUpdates() {
|
||||
|
||||
EbeanServer server = Ebean.getServer(null);
|
||||
|
||||
List<UTMaster> 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<UTDetail> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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<UTMaster> masters = new ArrayList<UTMaster>();
|
||||
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<UTMaster> masters = new ArrayList<UTMaster>();
|
||||
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<UTDetail> details = new ArrayList<UTDetail>();
|
||||
|
||||
int count = 2 + random.nextInt(20);
|
||||
int count = 2 + random.nextInt(size);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
||||
|
||||
+66
@@ -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<EdChild> children = new ArrayList<EdChild>();
|
||||
children.add(child);
|
||||
parent.setChildren(children);
|
||||
|
||||
Ebean.save(parent);
|
||||
|
||||
// nothing flushed yet
|
||||
assertEquals(0, LoggedSqlCollector.start().size());
|
||||
|
||||
txn.flushBatch();
|
||||
|
||||
List<String> 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<String> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<EdChild> children = new ArrayList<EdChild>();
|
||||
children.add(child);
|
||||
parent.setChildren(children);
|
||||
|
||||
Ebean.save(parent);
|
||||
|
||||
// nothing flushed yet
|
||||
List<String> 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<String> loggedSql2 = LoggedSqlCollector.start();
|
||||
assertEquals(2, loggedSql2.size());
|
||||
|
||||
} finally {
|
||||
Ebean.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+70
@@ -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<EdChild> children = new ArrayList<EdChild>();
|
||||
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<EdChild>();
|
||||
children.add(child);
|
||||
extendedParent.setChildren(children);
|
||||
|
||||
// nothing flushed yet
|
||||
List<String> loggedSql0 = LoggedSqlCollector.start();
|
||||
assertEquals(0, loggedSql0.size());
|
||||
|
||||
// causes a flush as EdExtendedParent is different from EdParent
|
||||
Ebean.save(extendedParent);
|
||||
|
||||
// insert statements for EdParent
|
||||
List<String> loggedSql1 = LoggedSqlCollector.start();
|
||||
assertEquals(2, loggedSql1.size());
|
||||
|
||||
Ebean.commitTransaction();
|
||||
|
||||
// insert statements for EdExtendedParent
|
||||
List<String> loggedSql2 = LoggedSqlCollector.start();
|
||||
assertEquals(2, loggedSql2.size());
|
||||
|
||||
} finally {
|
||||
Ebean.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<Order> orders = Ebean.find(Order.class)
|
||||
.select("id, status, orderDate")
|
||||
.where().gt("details.orderQty", 0)
|
||||
.findList();
|
||||
|
||||
assertTrue(!orders.isEmpty());
|
||||
|
||||
List<String> 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<Order> 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<Order> 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 "));
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user