com.avaje.ebeaninternal.server.persist.BatchControl Maven / Gradle / Ivy
/**
* Copyright (C) 2006 Robin Bygrave
*
* This file is part of Ebean.
*
* Ebean is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Ebean is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Ebean; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.avaje.ebeaninternal.server.persist;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.PersistenceException;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
/**
* Controls the batch ordering of persist requests.
*
* Persist requests include bean inserts updates deletes and UpdateSql and
* CallableSql requests.
*
*
* This object queues up the requests into appropriate entries according to the
* 'depth' and the 'type' of the requests. The depth relates to how saves and
* deletes cascade following the associations of a bean. For saving Associated
* One cascades reduce the depth (-1) and associated many's increase the depth.
* The initial depth of a request is 0.
*
*/
public final class BatchControl {
private static final Logger logger = Logger.getLogger(BatchControl.class.getName());
/**
* 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();
/**
* 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 size relates to the size of a list in a BatchQueueEntry and not the
* total number of request which could be more than that.
*/
private int batchSize;
/**
* If true try to get generated keys from inserts.
*/
private boolean getGeneratedKeys;
private boolean batchFlushOnMixed = true;
private final BatchedBeanControl beanControl;
/**
* 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);
}
/**
* Set this flag to false to allow batching of a mix of Beans and UpdateSql
* (or CallableSql). Normally if you mix the two this will result in an
* automatic flush.
*
* Note that UpdateSql and CallableSql will ALWAYS flush first. This is due
* to it already having been bound to a PreparedStatement where as the Beans
* go through a 2 step process when they are flushed (delayed binding).
*
*/
public void setBatchFlushOnMixed(boolean flushBatchOnMixed) {
this.batchFlushOnMixed = flushBatchOnMixed;
}
/**
* Return the batchSize.
*/
public int getBatchSize() {
return batchSize;
}
/**
* Set the size of batch execution.
*
* The user can set this via the Transaction.
*
*/
public void setBatchSize(int batchSize) {
if (batchSize > 1) {
this.batchSize = batchSize;
}
}
/**
* Set whether or not to use getGeneratedKeys for this batch execution.
*
* The user can set this via the transaction
*
*/
public void setGetGeneratedKeys(Boolean getGeneratedKeys) {
if (getGeneratedKeys != null) {
this.getGeneratedKeys = getGeneratedKeys;
}
}
/**
* Execute a Orm Update, SqlUpdate or CallableSql.
*
* These all go straight to jdbc and use addBatch(). Entity beans goto a
* queue and wait there so that the jdbc is executed in the correct order
* according to the depth.
*
*/
public int executeStatementOrBatch(PersistRequest request, boolean batch) {
if (!batch || (batchFlushOnMixed && !beanControl.isEmpty())) {
// flush when mixing beans and updateSql
flush();
}
if (!batch) {
// execute the request immediately without batching
return request.executeNow();
}
if (pstmtHolder.getMaxSize() >= batchSize) {
flush();
}
// for OrmUpdate, SqlUpdate, CallableSql there is no queue...
// so straight to jdbc prepared statement and use addBatch().
// aka executeNow() may use addBatch().
request.executeNow();
return -1;
}
/**
* Entity Bean insert, update or delete. This will either execute the
* request immediately or queue it for batch processing later. The queue is
* flushed according to the depth (object graph depth).
*/
public int executeOrQueue(PersistRequestBean> request, boolean batch) {
if (!batch || (batchFlushOnMixed && !pstmtHolder.isEmpty())) {
// flush when mixing beans and updateSql
flush();
}
if (!batch) {
return request.executeNow();
}
// get the list we will add this request to
ArrayList persistList = beanControl.getPersistList(request);
if (persistList == null){
// special case where the same bean instance has been added
// to the batch more than once
if (logger.isLoggable(Level.FINE)){
logger.fine("Bean instance already in this batch: "+request.getBean());
}
return -1;
}
if (persistList.size() >= batchSize) {
// flush everything that has been batched
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;
}
/**
* Return the actual batch of PreparedStatements.
*/
public BatchedPstmtHolder getPstmtHolder() {
return pstmtHolder;
}
/**
* Return true if the queue is empty.
*/
public boolean isEmpty() {
return (beanControl.isEmpty() && pstmtHolder.isEmpty());
}
/**
* Flush any batched PreparedStatements.
*/
protected void flushPstmtHolder() {
pstmtHolder.flush(getGeneratedKeys);
}
/**
* Execute all the requests contained in the list.
*/
protected void executeNow(ArrayList list) {
for (int i = 0; i < list.size(); i++) {
PersistRequest request = list.get(i);
request.executeNow();
}
}
/**
* execute all the requests currently queued or batched.
*/
public void flush() throws PersistenceException {
if (!pstmtHolder.isEmpty()) {
// Flush existing pstmts (updateSql or callableSql)
flushPstmtHolder();
}
if (beanControl.isEmpty()) {
// Nothing in queue to flush
return;
}
// convert entry map to array for sorting
BatchedBeanHolder[] bsArray = beanControl.getArray();
// sort the entries by depth
Arrays.sort(bsArray, depthComparator);
if (transaction.isLogSummary()) {
transaction.logInternal("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();
}
}
}