#991 ENH: Add forUpdateNoWait() and forUpdateSkipLocked() options ... #528 ENH: Improve exception handling

This commit is contained in:
Rob Bygrave
2017-03-13 20:50:22 +13:00
parent a3dbd4538f
commit c8d01828b3
49 changed files with 985 additions and 336 deletions
@@ -0,0 +1,20 @@
package io.ebean;
import javax.persistence.PessimisticLockException;
/**
* Thrown when failing to acquire a pessimistic lock.
* <p>
* Typically when "select for update nowait" or "select for update" is being used and
* the lock can not be obtained (as it is held by another transaction).
* </p>
*/
public class AcquireLockException extends PessimisticLockException {
/**
* Create with a message and cause.
*/
public AcquireLockException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,16 @@
package io.ebean;
import javax.persistence.PersistenceException;
/**
* Thrown when a foreign key constraint is enforced.
*/
public class DataIntegrityException extends PersistenceException {
/**
* Create with a message and cause.
*/
public DataIntegrityException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,14 @@
package io.ebean;
/**
* Thrown when a duplicate is attempted on a unique constraint.
*/
public class DuplicateKeyException extends DataIntegrityException {
/**
* Create with a message and cause.
*/
public DuplicateKeyException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -118,6 +118,27 @@ public interface ExpressionList<T> {
*/
Query<T> asDraft();
/**
* Execute using "for update" clause which results in the DB locking the record.
*/
Query<T> forUpdate();
/**
* Execute using "for update" clause with No Wait option.
* <p>
* This is typically a Postgres and Oracle only option at this stage.
* </p>
*/
Query<T> forUpdateNoWait();
/**
* Execute using "for update" clause with Skip Locked option.
* <p>
* This is typically a Postgres and Oracle only option at this stage.
* </p>
*/
Query<T> forUpdateSkipLocked();
/**
* Execute the query including soft deleted rows.
*/
+46
View File
@@ -194,6 +194,26 @@ import java.util.function.Predicate;
*/
public interface Query<T> {
/**
* For update mode.
*/
enum ForUpdate {
/**
* Standard For update clause.
*/
BASE,
/**
* For update with No Wait option.
*/
NOWAIT,
/**
* For update with Skip Locked option.
*/
SKIPLOCKED
}
/**
* Return the RawSql that was set to use for this query.
*/
@@ -1355,11 +1375,37 @@ public interface Query<T> {
*/
Query<T> setForUpdate(boolean forUpdate);
/**
* Execute using "for update" clause which results in the DB locking the record.
*/
Query<T> forUpdate();
/**
* Execute using "for update" clause with "no wait" option.
* <p>
* This is typically a Postgres and Oracle only option at this stage.
* </p>
*/
Query<T> forUpdateNoWait();
/**
* Execute using "for update" clause with "skip locked" option.
* <p>
* This is typically a Postgres and Oracle only option at this stage.
* </p>
*/
Query<T> forUpdateSkipLocked();
/**
* Return true if this query has forUpdate set.
*/
boolean isForUpdate();
/**
* Return the "for update" mode to use.
*/
ForUpdate getForUpdateMode();
/**
* Set root table alias.
*/
+2 -3
View File
@@ -5,7 +5,6 @@ import io.ebean.config.DocStoreConfig;
import io.ebean.config.ServerConfig;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import java.io.Closeable;
import java.sql.Connection;
@@ -67,7 +66,7 @@ public interface Transaction extends Closeable {
* <li>Perform post-commit processing updating L2 cache, ElasticSearch etc</li>
* </ul>
*/
void commitAndContinue() throws RollbackException;
void commitAndContinue();
/**
* Commit the transaction.
@@ -85,7 +84,7 @@ public interface Transaction extends Closeable {
* <li>Mark the transaction as "Inactive"</li>
* </ul>
*/
void commit() throws RollbackException;
void commit();
/**
* Rollback the transaction.
@@ -0,0 +1,22 @@
package io.ebean.config.dbplatform;
/**
* Specific persistence error types we wish to map.
*/
public enum DataErrorType {
/**
* Error trying to acquire lock (e.g. failure executing select for update nowait)
*/
AcquireLock,
/**
* Error with a duplicate primary or unique key.
*/
DuplicateKey,
/**
* Data integrity error like an invalid foreign key.
*/
DataIntegrity
}
@@ -12,6 +12,7 @@ import io.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
@@ -172,12 +173,21 @@ public class DatabasePlatform {
protected boolean supportsNativeIlike;
protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator();
/**
* Instantiates a new database platform.
*/
public DatabasePlatform() {
}
/**
* Translate the SQLException into a specific persistence exception if possible.
*/
public PersistenceException translate(String message, SQLException e) {
return exceptionTranslator.translate(message, e);
}
/**
* Configure UUID Storage etc based on ServerConfig settings.
*/
@@ -554,17 +564,16 @@ public class DatabasePlatform {
}
public String completeSql(String sql, Query<?> query) {
if (Boolean.TRUE.equals(query.isForUpdate())) {
sql = withForUpdate(sql);
if (query.isForUpdate()) {
sql = withForUpdate(sql, query.getForUpdateMode());
}
return sql;
}
protected String withForUpdate(String sql) {
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
// silently assume the database does not support the "for update" clause.
logger.info("it seems your database does not support the 'for update' clause");
return sql;
}
@@ -0,0 +1,50 @@
package io.ebean.config.dbplatform;
import io.ebean.AcquireLockException;
import io.ebean.DataIntegrityException;
import io.ebean.DuplicateKeyException;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Map;
/**
* Translate SQLException based on SQLState codes.
*/
public class SqlCodeTranslator implements SqlExceptionTranslator {
private final Map<String,DataErrorType> map;
/**
* Create given the map of SQLState codes to error types.
*/
public SqlCodeTranslator(Map<String,DataErrorType> map) {
this.map = map;
}
/**
* Create "No-op" implementation.
*/
public SqlCodeTranslator() {
this.map = Collections.emptyMap();
}
@Override
public PersistenceException translate(String message, SQLException e) {
DataErrorType errorType = map.get(e.getSQLState());
if (errorType != null) {
switch (errorType) {
case AcquireLock:
return new AcquireLockException(message, e);
case DuplicateKey:
return new DuplicateKeyException(message, e);
case DataIntegrity:
return new DataIntegrityException(message, e);
}
}
// return a generic exception
return new PersistenceException(message, e);
}
}
@@ -0,0 +1,47 @@
package io.ebean.config.dbplatform;
import java.util.HashMap;
import java.util.Map;
/**
* Used to build a SQLCodeTranslator given DB platform specific codes.
*/
public class SqlErrorCodes {
private Map<String,DataErrorType> map = new HashMap<>();
/**
* Map the codes to AcquireLockException.
*/
public SqlErrorCodes addAcquireLock(String... codes) {
return add(DataErrorType.AcquireLock, codes);
}
/**
* Map the codes to DataIntegrityException.
*/
public SqlErrorCodes addDataIntegrity(String... codes) {
return add(DataErrorType.DataIntegrity, codes);
}
/**
* Map the codes to DuplicateKeyException.
*/
public SqlErrorCodes addDuplicateKey(String... codes) {
return add(DataErrorType.DuplicateKey, codes);
}
private SqlErrorCodes add(DataErrorType type, String... codes) {
for (String code : codes) {
map.put(code, type);
}
return this;
}
/**
* Build and return the SQLCodeTranslator with the mapped codes.
*/
public SqlCodeTranslator build() {
return new SqlCodeTranslator(map);
}
}
@@ -0,0 +1,15 @@
package io.ebean.config.dbplatform;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
/**
* Used to translate SQLExceptions to specific persistence exceptions.
*/
public interface SqlExceptionTranslator {
/**
* Translate the given exception.
*/
PersistenceException translate(String message, SQLException e);
}
@@ -6,6 +6,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.platform.DB2Ddl;
import javax.sql.DataSource;
@@ -27,6 +28,13 @@ public class DB2Platform extends DatabasePlatform {
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.exceptionTranslator =
new SqlErrorCodes()
//.addAcquireLock("")
.addDuplicateKey("-803")
.addDataIntegrity("-407","-530","-531","-532","-543","-544","-545","-603","-667")
.build();
booleanDbType = Types.BOOLEAN;
dbTypeMap.put(DbType.REAL, new DbPlatformType("real"));
dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint"));
@@ -2,11 +2,13 @@ package io.ebean.config.dbplatform.h2;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.platform.H2Ddl;
import javax.sql.DataSource;
@@ -26,6 +28,13 @@ public class H2Platform extends DatabasePlatform {
this.dbDefaultValue.setNow("now()");
this.columnAliasPrefix = null;
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("50200")
.addDuplicateKey("23001","23505")
.addDataIntegrity("22001","22003","22012","22018","22025","23000","23002","23003","23502","23503","23506","23507","23513")
.build();
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
@@ -50,7 +59,8 @@ public class H2Platform extends DatabasePlatform {
}
@Override
protected String withForUpdate(String sql) {
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
// NOWAIT and SKIP LOCKED currently not supported with H2
return sql + " for update";
}
}
@@ -2,11 +2,13 @@ package io.ebean.config.dbplatform.mysql;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.platform.MySqlDdl;
import javax.sql.DataSource;
@@ -40,6 +42,13 @@ public class MySqlPlatform extends DatabasePlatform {
this.dbIdentity.setSupportsIdentity(true);
this.dbIdentity.setSupportsSequence(false);
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("1205")
.addDuplicateKey("1062")
.addDataIntegrity("630","839","840","893","1169","1215","1216","1217","1364","1451","1452","1557")
.build();
this.openQuote = "`";
this.closeQuote = "`";
@@ -66,7 +75,8 @@ public class MySqlPlatform extends DatabasePlatform {
}
@Override
protected String withForUpdate(String sql) {
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
// NOWAIT and SKIP LOCKED currently not supported with MySQL
return sql + " for update";
}
}
@@ -2,6 +2,7 @@ package io.ebean.config.dbplatform.oracle;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.config.dbplatform.BasicSqlAnsiLimiter;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
@@ -69,7 +70,14 @@ public class OraclePlatform extends DatabasePlatform {
}
@Override
protected String withForUpdate(String sql) {
return sql + " for update";
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
switch (forUpdateMode) {
case SKIPLOCKED:
return sql + " for update skip locked";
case NOWAIT:
return sql + " for update nowait";
default:
return sql + " for update";
}
}
}
@@ -2,12 +2,14 @@ package io.ebean.config.dbplatform.postgres;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.DdlHandler;
import io.ebean.dbmigration.ddlgeneration.platform.PostgresDdl;
@@ -42,7 +44,12 @@ public class PostgresPlatform extends DatabasePlatform {
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
//this.columnAliasPrefix = "as c";
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("55P03")
.addDuplicateKey("23505")
.addDataIntegrity("23000","23502","23503","23514")
.build();
this.openQuote = "\"";
this.closeQuote = "\"";
@@ -102,7 +109,14 @@ public class PostgresPlatform extends DatabasePlatform {
}
@Override
protected String withForUpdate(String sql) {
return sql + " for update";
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
switch (forUpdateMode) {
case SKIPLOCKED:
return sql + " for update skip locked";
case NOWAIT:
return sql + " for update nowait";
default:
return sql + " for update";
}
}
}
@@ -6,6 +6,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.platform.SqlServerDdl;
import java.sql.Types;
@@ -31,6 +32,13 @@ public class SqlServerPlatform extends DatabasePlatform {
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsIdentity(true);
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("1222")
.addDuplicateKey("2601","2627")
.addDataIntegrity("544","8114","8115")
.build();
this.openQuote = "[";
this.closeQuote = "]";
@@ -1,9 +1,9 @@
package io.ebeaninternal.api;
import io.ebean.PersistBatch;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
import io.ebean.PersistBatch;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
@@ -13,20 +13,20 @@ import io.ebeaninternal.server.persist.BatchControl;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Wrapper of a ScopeTrans request and it's underlying transaction.
*/
public class ScopedTransaction implements SpiTransaction {
final ScopeTrans scopeTrans;
private final ScopeTrans scopeTrans;
final SpiTransaction transaction;
private final SpiTransaction transaction;
boolean committed;
private boolean committed;
public ScopedTransaction(ScopeTrans scopeTrans) {
this.scopeTrans = scopeTrans;
@@ -34,12 +34,17 @@ public class ScopedTransaction implements SpiTransaction {
}
@Override
public void commitAndContinue() throws RollbackException {
public PersistenceException translate(String message, SQLException cause) {
return transaction.translate(message, cause);
}
@Override
public void commitAndContinue() {
transaction.commitAndContinue();
}
@Override
public void commit() throws RollbackException {
public void commit() {
scopeTrans.commitTransaction();
committed = true;
}
@@ -158,6 +158,11 @@ public interface SpiQuery<T> extends Query<T> {
*/
String getNativeSql();
/**
* Return the ForUpdate mode.
*/
ForUpdate getForUpdateMode();
/**
* Return the bean descriptor for this query.
*/
@@ -11,7 +11,9 @@ import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Extends Transaction with additional API required on server.
@@ -242,6 +244,11 @@ public interface SpiTransaction extends Transaction {
*/
void flushBatchOnRollback();
/**
* Translate the SQLException.
*/
PersistenceException translate(String message, SQLException cause);
/**
* Mark the transaction explicitly as not being query only.
*/
@@ -4,6 +4,8 @@ import io.ebean.QueryIterator;
import io.ebean.Version;
import io.ebean.bean.BeanCollection;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.List;
/**
@@ -55,4 +57,9 @@ public interface OrmQueryEngine {
* Execute the query as a update statement.
*/
<T> int update(OrmQueryRequest<T> request);
/**
* Translate the SQLException to a specific persistence exception type if possible.
*/
<T> PersistenceException translate(OrmQueryRequest<T> request, String bindLog, String sql, SQLException e);
}
@@ -29,6 +29,7 @@ import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -76,6 +77,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
this.readOnly = query.isReadOnly();
}
public PersistenceException translate(String bindLog, String sql, SQLException e) {
return queryEngine.translate(this, bindLog, sql, e);
}
/**
* Mark the transaction as not being query only.
*/
@@ -4,6 +4,7 @@ import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.BatchPostExecute;
import io.ebeaninternal.server.persist.BatchedSqlException;
import io.ebeaninternal.server.persist.PersistExecute;
/**
@@ -15,14 +16,14 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
INSERT, UPDATE, DELETE, SOFT_DELETE, DELETE_PERMANENT, UPDATESQL, CALLABLESQL
}
protected boolean persistCascade;
boolean persistCascade;
/**
* One of INSERT, UPDATE, DELETE, UPDATESQL or CALLABLESQL.
*/
protected Type type;
protected final PersistExecute persistExecute;
final PersistExecute persistExecute;
/**
* Used by CallableSqlRequest and UpdateSqlRequest.
@@ -63,24 +64,27 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
/**
* Execute the statement.
*/
public int executeStatement() {
int executeStatement() {
boolean batch = isBatchThisRequest();
int rows;
BatchControl control = transaction.getBatchControl();
if (control != null) {
rows = control.executeStatementOrBatch(this, batch);
try {
int rows;
BatchControl control = transaction.getBatchControl();
if (control != null) {
rows = control.executeStatementOrBatch(this, batch);
} else if (batch) {
// need to create the BatchControl
control = persistExecute.createBatchControl(transaction);
rows = control.executeStatementOrBatch(this, true);
} else {
rows = executeNow();
} else if (batch) {
// need to create the BatchControl
control = persistExecute.createBatchControl(transaction);
rows = control.executeStatementOrBatch(this, true);
} else {
rows = executeNow();
}
return rows;
} catch (BatchedSqlException e) {
throw transaction.translate(e.getMessage(), e.getCause());
}
return rows;
}
public void initTransIfRequired() {
@@ -20,6 +20,7 @@ import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.BatchedSqlException;
import io.ebeaninternal.server.persist.PersistExecute;
import io.ebeaninternal.server.transaction.BeanPersistIdMap;
import io.ebeanservice.docstore.api.DocStoreUpdate;
@@ -499,10 +500,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
public BeanManager<T> getBeanManager() {
return beanManager;
}
/**
* Return the BeanDescriptor for the associated bean.
*/
@@ -697,17 +694,20 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public int executeOrQueue() {
boolean batch = isBatchThisRequest();
try {
BatchControl control = transaction.getBatchControl();
if (control != null) {
return control.executeOrQueue(this, batch);
}
if (batch) {
control = persistExecute.createBatchControl(transaction);
return control.executeOrQueue(this, true);
BatchControl control = transaction.getBatchControl();
if (control != null) {
return control.executeOrQueue(this, batch);
}
if (batch) {
control = persistExecute.createBatchControl(transaction);
return control.executeOrQueue(this, true);
} else {
return executeNow();
} else {
return executeNow();
}
} catch (BatchedSqlException e) {
throw transaction.translate(e.getMessage(), e.getCause());
}
}
@@ -419,6 +419,21 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.filterMany(prop);
}
@Override
public Query<T> forUpdate() {
return query.forUpdate();
}
@Override
public Query<T> forUpdateNoWait() {
return query.forUpdateNoWait();
}
@Override
public Query<T> forUpdateSkipLocked() {
return query.forUpdateSkipLocked();
}
@Override
public Query<T> select(String fetchProperties) {
return query.select(fetchProperties);
@@ -419,6 +419,21 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.findUnique();
}
@Override
public Query<T> forUpdate() {
return exprList.forUpdate();
}
@Override
public Query<T> forUpdateNoWait() {
return exprList.forUpdateNoWait();
}
@Override
public Query<T> forUpdateSkipLocked() {
return exprList.forUpdateSkipLocked();
}
/**
* Path exists - for the given path in a JSON document.
*/
@@ -5,7 +5,6 @@ import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import javax.persistence.PersistenceException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -123,7 +122,7 @@ public final class BatchControl {
* to the depth.
* </p>
*/
public int executeStatementOrBatch(PersistRequest request, boolean batch) {
public int executeStatementOrBatch(PersistRequest request, boolean batch) throws BatchedSqlException {
if (!batch || (batchFlushOnMixed && !isBeansEmpty())) {
// flush when mixing beans and updateSql
flush();
@@ -148,7 +147,7 @@ public final class BatchControl {
* immediately or queue it for batch processing later. The queue is flushedIntercept
* according to the depth (object graph depth).
*/
public int executeOrQueue(PersistRequestBean<?> request, boolean batch) {
public int executeOrQueue(PersistRequestBean<?> request, boolean batch) throws BatchedSqlException {
if (!batch || (batchFlushOnMixed && !pstmtHolder.isEmpty())) {
// flush when mixing beans and updateSql
@@ -167,7 +166,7 @@ public final class BatchControl {
/**
* Add the request to the batch and return true if we should flush.
*/
private boolean addToBatch(PersistRequestBean<?> request) {
private boolean addToBatch(PersistRequestBean<?> request) throws BatchedSqlException {
BatchedBeanHolder beanHolder = getBeanHolder(request);
int bufferSize = beanHolder.append(request);
@@ -193,14 +192,14 @@ public final class BatchControl {
/**
* Flush any batched PreparedStatements.
*/
protected void flushPstmtHolder() {
protected void flushPstmtHolder() throws BatchedSqlException {
pstmtHolder.flush(getGeneratedKeys);
}
/**
* Execute all the requests contained in the list.
*/
protected void executeNow(ArrayList<PersistRequest> list) {
protected void executeNow(ArrayList<PersistRequest> list) throws BatchedSqlException {
for (int i = 0; i < list.size(); i++) {
if (i % batchSize == 0) {
// hit the batch size so flush
@@ -214,14 +213,14 @@ public final class BatchControl {
/**
* Flush without resetting the topOrder (maintains the depth info).
*/
public void flush() throws PersistenceException {
public void flush() throws BatchedSqlException {
flush(false);
}
/**
* Flush with a reset the topOrder (fully empty the batch).
*/
public void flushReset() throws PersistenceException {
public void flushReset() throws BatchedSqlException {
flush(true);
}
@@ -236,31 +235,38 @@ public final class BatchControl {
/**
* execute all the requests currently queued or batched.
*/
private void flush(boolean resetTop) throws PersistenceException {
private void flush(boolean resetTop) throws BatchedSqlException {
if (!pstmtHolder.isEmpty()) {
// Flush existing pstmts (updateSql or callableSql)
flushPstmtHolder();
}
if (isEmpty()) {
// Nothing in queue to flush
return;
}
try {
if (!pstmtHolder.isEmpty()) {
// Flush existing pstmts (updateSql or callableSql)
flushPstmtHolder();
}
if (isEmpty()) {
// Nothing in queue to flush
return;
}
// convert entry map to array for sorting
BatchedBeanHolder[] bsArray = getBeanHolderArray();
// sort the entries by depth
Arrays.sort(bsArray, depthComparator);
// convert entry map to array for sorting
BatchedBeanHolder[] bsArray = getBeanHolderArray();
// sort the entries by depth
Arrays.sort(bsArray, depthComparator);
if (transaction.isLogSummary()) {
transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray));
}
for (BatchedBeanHolder aBsArray : bsArray) {
aBsArray.executeNow();
}
if (transaction.isLogSummary()) {
transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray));
}
for (BatchedBeanHolder aBsArray : bsArray) {
aBsArray.executeNow();
}
if (resetTop) {
beanHoldMap.clear();
if (resetTop) {
beanHoldMap.clear();
}
} catch (BatchedSqlException e) {
// clear the batch on error in case we want to
// catch, rollback and continue processing
clear();
throw e;
}
}
@@ -268,7 +274,7 @@ public final class BatchControl {
* 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) {
private BatchedBeanHolder getBeanHolder(PersistRequestBean<?> request) throws BatchedSqlException {
BeanDescriptor<?> beanDescriptor = request.getBeanDescriptor();
BatchedBeanHolder batchBeanHolder = beanHoldMap.get(beanDescriptor.getFullName());
@@ -79,7 +79,7 @@ public class BatchedBeanHolder {
* and then execute them.
* </p>
*/
public void executeNow() {
public void executeNow() throws BatchedSqlException {
// process the requests. Creates one or more PreparedStatements
// with binding addBatch() for each request.
// Note updates and deletes can result in many PreparedStatements
@@ -121,7 +121,7 @@ public class BatchedPstmt {
list.get(index).setGeneratedKey(idValue);
index++;
}
}
}
}
}
@@ -79,7 +79,7 @@ public class BatchedPstmtHolder {
*
* @param getGeneratedKeys if true try to get generated keys for inserts
*/
public void flush(boolean getGeneratedKeys) throws PersistenceException {
public void flush(boolean getGeneratedKeys) throws BatchedSqlException {
SQLException firstError = null;
String errorSql = null;
@@ -96,7 +96,7 @@ public class BatchedPstmtHolder {
} catch (SQLException ex) {
SQLException next = ex.getNextException();
while (next != null) {
logger.error("Next Exception during batch execution", next);
logger.trace("Next Exception during batch execution", next);
next = next.getNextException();
}
@@ -122,7 +122,7 @@ public class BatchedPstmtHolder {
if (firstError != null) {
String msg = "Error when batch flush on sql: " + errorSql;
throw new PersistenceException(msg, firstError);
throw new BatchedSqlException(msg, firstError);
}
}
@@ -0,0 +1,22 @@
package io.ebeaninternal.server.persist;
import java.sql.SQLException;
/**
* Holds the first SQLException found when executing a JDBC batch.
*/
public class BatchedSqlException extends Exception {
private SQLException cause;
BatchedSqlException(String message, SQLException cause) {
super(message, cause);
this.cause = cause;
}
@Override
public SQLException getCause() {
return cause;
}
}
@@ -1,10 +1,10 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.util.StringHelper;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BeanPersister;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
/**
@@ -18,15 +18,16 @@ import java.sql.SQLException;
*/
public final class DmlBeanPersister implements BeanPersister {
private final DatabasePlatform dbPlatform;
private final UpdateMeta updateMeta;
private final InsertMeta insertMeta;
private final DeleteMeta deleteMeta;
public DmlBeanPersister(UpdateMeta updateMeta, InsertMeta insertMeta, DeleteMeta deleteMeta) {
public DmlBeanPersister(DatabasePlatform dbPlatform, UpdateMeta updateMeta, InsertMeta insertMeta, DeleteMeta deleteMeta) {
this.dbPlatform = dbPlatform;
this.updateMeta = updateMeta;
this.insertMeta = insertMeta;
this.deleteMeta = deleteMeta;
@@ -37,9 +38,7 @@ public final class DmlBeanPersister implements BeanPersister {
*/
@Override
public int delete(PersistRequestBean<?> request) {
DeleteHandler delete = new DeleteHandler(request, deleteMeta);
return execute(request, delete);
return execute(request, new DeleteHandler(request, deleteMeta));
}
/**
@@ -47,9 +46,7 @@ public final class DmlBeanPersister implements BeanPersister {
*/
@Override
public void insert(PersistRequestBean<?> request) {
InsertHandler insert = new InsertHandler(request, insertMeta);
execute(request, insert);
execute(request, new InsertHandler(request, insertMeta));
}
/**
@@ -57,9 +54,7 @@ public final class DmlBeanPersister implements BeanPersister {
*/
@Override
public void update(PersistRequestBean<?> request) {
UpdateHandler update = new UpdateHandler(request, updateMeta);
execute(request, update);
execute(request, new UpdateHandler(request, updateMeta));
}
/**
@@ -80,12 +75,12 @@ public final class DmlBeanPersister implements BeanPersister {
} 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 + "]";
String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r", "\n"}, " ");
String msg = "Error[" + errMsg + "]";
if (request.getTransaction().isLogSummary()) {
request.getTransaction().logSummary(msg);
}
throw new PersistenceException(msg, e);
throw dbPlatform.translate(msg, e);
} finally {
if (!batched) {
@@ -10,9 +10,12 @@ import io.ebeaninternal.server.persist.BeanPersisterFactory;
*/
public class DmlBeanPersisterFactory implements BeanPersisterFactory {
private final DatabasePlatform dbPlatform;
private final MetaFactory metaFactory;
public DmlBeanPersisterFactory(DatabasePlatform dbPlatform) {
this.dbPlatform = dbPlatform;
this.metaFactory = new MetaFactory(dbPlatform);
}
@@ -29,7 +32,7 @@ public class DmlBeanPersisterFactory implements BeanPersisterFactory {
UpdateMeta updMeta = metaFactory.createUpdate(desc);
DeleteMeta delMeta = metaFactory.createDelete(desc);
InsertMeta insMeta = metaFactory.createInsert(desc);
return new DmlBeanPersister(updMeta, insMeta, delMeta);
return new DmlBeanPersister(dbPlatform, updMeta, insMeta, delMeta);
}
}
@@ -10,12 +10,10 @@ import io.ebean.bean.NodeUsageListener;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.util.StringHelper;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Mode;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.Message;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanCollectionHelp;
@@ -665,28 +663,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
* Create a PersistenceException including interesting information like the bindLog and sql used.
*/
PersistenceException createPersistenceException(SQLException e) {
return createPersistenceException(e, getTransaction(), bindLog, sql);
}
/**
* Create a PersistenceException including interesting information like the bindLog and sql used.
*/
static PersistenceException createPersistenceException(SQLException e, SpiTransaction t, String bindLog, String sql) {
if (t.isLogSummary()) {
// log the error to the transaction log
String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r", "\n"}, "\\n ");
String msg = "ERROR executing query: bindLog[" + bindLog + "] error[" + errMsg + "]";
t.logSummary(msg);
}
// ensure 'rollback' is logged if queryOnly transaction
t.getConnection();
// build a decent error message for the exception
String m = Message.msg("fetch.sqlerror", e.getMessage(), bindLog, sql);
return new PersistenceException(m, e);
return request.translate(bindLog, sql, e);
}
/**
@@ -8,8 +8,11 @@ import io.ebean.bean.EntityBean;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.util.StringHelper;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.DiffHelp;
import io.ebeaninternal.server.core.Message;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.lib.util.Str;
@@ -18,6 +21,7 @@ import io.ebeaninternal.server.transaction.TransactionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -45,7 +49,10 @@ public class CQueryEngine {
private final CQueryHistorySupport historySupport;
private final DatabasePlatform dbPlatform;
public CQueryEngine(ServerConfig serverConfig, DatabasePlatform dbPlatform, Binder binder, Map<String, String> asOfTableMapping, Map<String, String> draftTableMap) {
this.dbPlatform = dbPlatform;
this.defaultFetchSizeFindEach = serverConfig.getJdbcFetchSizeFindEach();
this.defaultFetchSizeFindList = serverConfig.getJdbcFetchSizeFindList();
this.forwardOnlyHintOnFindIterate = dbPlatform.isForwardOnlyHintOnFindIterate();
@@ -83,7 +90,7 @@ public class CQueryEngine {
return rows;
} catch (SQLException e) {
throw CQuery.createPersistenceException(e, request.getTransaction(), query.getBindLog(), query.getGeneratedSql());
throw translate(request, query.getBindLog(), query.getGeneratedSql(), e);
}
}
@@ -109,10 +116,30 @@ public class CQueryEngine {
return list;
} catch (SQLException e) {
throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql());
throw translate(request, rcQuery.getBindLog(), rcQuery.getGeneratedSql(), e);
}
}
/**
* Translate the SQLException into a PersistenceException.
*/
<T> PersistenceException translate(OrmQueryRequest<T> request, String bindLog, String sql, SQLException e) {
SpiTransaction t = request.getTransaction();
if (t.isLogSummary()) {
// log the error to the transaction log
String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r", "\n"}, "\\n ");
String msg = "ERROR executing query, bindLog[" + bindLog + "] error[" + errMsg + "]";
t.logSummary(msg);
}
// ensure 'rollback' is logged if queryOnly transaction
t.getConnection();
// build a decent error message for the exception
String m = Message.msg("fetch.sqlerror", e.getMessage(), bindLog, sql);
return dbPlatform.translate(m, e);
}
/**
* Build and execute the find Id's query.
*/
@@ -155,7 +182,7 @@ public class CQueryEngine {
return count;
} catch (SQLException e) {
throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql());
throw translate(request, rcQuery.getBindLog(), rcQuery.getGeneratedSql(), e);
}
}
@@ -11,6 +11,8 @@ import io.ebeaninternal.server.core.OrmQueryEngine;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
@@ -28,10 +30,14 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
* Create the Finder.
*/
public DefaultOrmQueryEngine(CQueryEngine queryEngine) {
this.queryEngine = queryEngine;
}
@Override
public <T> PersistenceException translate(OrmQueryRequest<T> request, String bindLog, String sql, SQLException e) {
return queryEngine.translate(request, bindLog, sql, e);
}
/**
* Flushes the jdbc batch by default unless explicitly turned off on the transaction.
*/
@@ -206,9 +206,9 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private Boolean autoTune;
/**
* Allow to fetch a record "for update" which should lock it on read
* For update mode.
*/
private boolean forUpdate;
private ForUpdate forUpdate;
private boolean singleAttribute;
@@ -579,7 +579,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
// includes joins and we use - delete ... where id in (...)
maxRows = 0;
firstRow = 0;
forUpdate = false;
forUpdate = null;
rootTableAlias = "${RTA}"; // alias we remove later
setSelectId();
}
@@ -791,11 +791,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return autoTune;
}
@Override
public boolean isForUpdate() {
return forUpdate;
}
@Override
public void setDefaultRawSqlIfRequired() {
if (beanDescriptor.isRawSqlBased() && rawSql == null) {
@@ -811,11 +806,42 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
@Override
public DefaultOrmQuery<T> setForUpdate(boolean forUpdate) {
this.forUpdate = forUpdate;
this.forUpdate = (forUpdate) ? ForUpdate.BASE : null;
this.excludeBeanCache = true;
return this;
}
@Override
public DefaultOrmQuery<T> forUpdate() {
return setForUpdateWithMode(ForUpdate.BASE);
}
@Override
public DefaultOrmQuery<T> forUpdateNoWait() {
return setForUpdateWithMode(ForUpdate.NOWAIT);
}
@Override
public DefaultOrmQuery<T> forUpdateSkipLocked() {
return setForUpdateWithMode(ForUpdate.SKIPLOCKED);
}
private DefaultOrmQuery<T> setForUpdateWithMode(ForUpdate mode) {
this.forUpdate = mode;
this.excludeBeanCache = true;
return this;
}
@Override
public boolean isForUpdate() {
return forUpdate != null;
}
@Override
public ForUpdate getForUpdateMode() {
return forUpdate;
}
@Override
public ProfilingListener getProfilingListener() {
return profilingListener;
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.querydefn;
import io.ebean.OrderBy;
import io.ebean.Query;
import io.ebean.RawSql;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.CQueryPlanKey;
@@ -14,61 +15,69 @@ import io.ebeaninternal.server.deploy.TableJoin;
*/
class OrmQueryPlanKey implements CQueryPlanKey {
private final String m2mIncludeTable;
private final String orderByAsSting;
private final SpiExpression where;
private final SpiExpression having;
private final RawSql.Key rawSqlKey;
private final boolean hasIdValue;
private final SpiQuery.Type type;
private final int maxRows;
private final int firstRow;
private final boolean disableLazyLoading;
private final boolean distinct;
private final boolean sqlDistinct;
private final String mapKey;
private final SpiQuery.TemporalMode temporalMode;
private final boolean forUpdate;
private final String rootTableAlias;
private final OrmUpdateProperties updateProperties;
private final int planHash;
private final int bindCount;
private final String options;
OrmQueryPlanKey(TableJoin m2mIncludeTable, SpiQuery.Type type, OrmQueryDetail detail, int maxRows, int firstRow, boolean disableLazyLoading, OrderBy<?> orderBy, boolean distinct, boolean sqlDistinct, String mapKey, Object id, BindParams bindParams, SpiExpression whereExpressions, SpiExpression havingExpressions, SpiQuery.TemporalMode temporalMode, boolean forUpdate, String rootTableAlias, RawSql rawSql, OrmUpdateProperties updateProperties) {
OrmQueryPlanKey(TableJoin m2mIncludeTable, SpiQuery.Type type, OrmQueryDetail detail, int maxRows, int firstRow, boolean disableLazyLoading,
OrderBy<?> orderBy, boolean distinct, boolean sqlDistinct, String mapKey, Object id, BindParams bindParams,
SpiExpression whereExpressions, SpiExpression havingExpressions, SpiQuery.TemporalMode temporalMode,
Query.ForUpdate forUpdate, String rootTableAlias, RawSql rawSql, OrmUpdateProperties updateProperties) {
this.m2mIncludeTable = m2mIncludeTable == null ? null : m2mIncludeTable.getTable();
this.type = type;
StringBuilder sb = new StringBuilder(300);
if (type != null) {
sb.append("t:").append(type.ordinal());
}
if (temporalMode != SpiQuery.TemporalMode.CURRENT) {
sb.append(",temp:").append(temporalMode.ordinal());
}
if (forUpdate != null) {
sb.append(",forUpd:").append(forUpdate.ordinal());
}
if (id != null) {
sb.append(",id:");
}
if (distinct) {
sb.append(",dist:");
}
if (sqlDistinct) {
sb.append(",sqlD:");
}
if (disableLazyLoading) {
sb.append(",disLazy:");
}
if (rootTableAlias != null) {
sb.append(",root:").append(rootTableAlias);
}
if (orderBy != null) {
sb.append(",orderBy:").append(orderBy.toStringFormat());
}
if (m2mIncludeTable != null) {
sb.append(",m2m:").append(m2mIncludeTable.getTable());
}
if (mapKey != null) {
sb.append(",mapKey:").append(mapKey);
}
this.options = sb.toString();
this.maxRows = maxRows;
this.firstRow = firstRow;
this.disableLazyLoading = disableLazyLoading;
this.orderByAsSting = (orderBy == null) ? null : orderBy.toStringFormat();
this.distinct = distinct;
this.sqlDistinct = sqlDistinct;
this.mapKey = mapKey;
this.hasIdValue = (id != null);
this.where = (whereExpressions == null) ? null : whereExpressions.copyForPlanKey();
this.having = (havingExpressions == null) ? null : havingExpressions.copyForPlanKey();
this.temporalMode = temporalMode;
this.forUpdate = forUpdate;
this.rootTableAlias = rootTableAlias;
this.updateProperties = updateProperties;
this.rawSqlKey = (rawSql == null) ? null : rawSql.getKey();
// exclude bind values and things unrelated to the sql being generated
HashQueryPlanBuilder builder = new HashQueryPlanBuilder();
builder.add((type == null ? 0 : type.ordinal() + 1));
builder.add(distinct).add(sqlDistinct);
builder.add(options.hashCode());
builder.add(firstRow).add(maxRows);
builder.add(orderBy).add(forUpdate);
builder.add(mapKey);
builder.add(disableLazyLoading);
builder.add(hasIdValue);
builder.add(temporalMode);
builder.add(rawSqlKey == null ? 0 : rawSqlKey.hashCode());
builder.add(this.m2mIncludeTable);
builder.add(rootTableAlias);
if (detail != null) {
detail.queryPlanHash(builder);
@@ -111,23 +120,10 @@ class OrmQueryPlanKey implements CQueryPlanKey {
if (bindCount != that.bindCount) return false;
if (maxRows != that.maxRows) return false;
if (firstRow != that.firstRow) return false;
if (disableLazyLoading != that.disableLazyLoading) return false;
if (distinct != that.distinct) return false;
if (sqlDistinct != that.sqlDistinct) return false;
if (forUpdate != that.forUpdate) return false;
if (hasIdValue != that.hasIdValue) return false;
if (type != that.type) return false;
if (temporalMode != that.temporalMode) return false;
if (m2mIncludeTable != null ? !m2mIncludeTable.equals(that.m2mIncludeTable) : that.m2mIncludeTable != null)
return false;
if (orderByAsSting != null ? !orderByAsSting.equals(that.orderByAsSting) : that.orderByAsSting != null)
return false;
if (!options.equals(that.options)) return false;
if (where != null ? !where.isSameByPlan(that.where) : that.where != null) return false;
if (having != null ? !having.isSameByPlan(that.having) : that.having != null) return false;
if (updateProperties != null ? !updateProperties.isSameByPlan(that.updateProperties) : that.updateProperties != null)
return false;
if (rawSqlKey != null ? !rawSqlKey.equals(that.rawSqlKey) : that.rawSqlKey != null) return false;
if (mapKey != null ? !mapKey.equals(that.mapKey) : that.mapKey != null) return false;
return rootTableAlias != null ? rootTableAlias.equals(that.rootTableAlias) : that.rootTableAlias == null;
if (updateProperties != null ? !updateProperties.isSameByPlan(that.updateProperties) : that.updateProperties != null) return false;
return rawSqlKey != null ? rawSqlKey.equals(that.rawSqlKey) : that.rawSqlKey == null;
}
}
@@ -1,7 +1,6 @@
package io.ebeaninternal.server.transaction;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import java.sql.Connection;
/**
@@ -43,7 +42,7 @@ public class ExternalJdbcTransaction extends JdbcTransaction {
* </p>
*/
@Override
public void commit() throws RollbackException {
public void commit() {
throw new PersistenceException("This is an external transaction so must be committed externally");
}
@@ -15,6 +15,7 @@ import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.BatchedSqlException;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -620,28 +621,45 @@ public class JdbcTransaction implements SpiTransaction {
@Override
public void flushBatchOnCollection() {
if (batchOnCascadeSet) {
if (batchControl != null) {
if (logger.isTraceEnabled()) {
logger.trace("... flushBatchOnCollection");
}
batchControl.flushReset();
}
batchFlushReset();
// restore the previous batch mode of NONE
batchMode = PersistBatch.NONE;
}
}
private void batchFlush() {
if (batchControl != null) {
try {
batchControl.flush();
} catch (BatchedSqlException e) {
throw translate(e.getMessage(), e.getCause());
}
}
}
private void batchFlushReset() {
if (batchControl != null) {
try {
batchControl.flushReset();
} catch (BatchedSqlException e) {
throw translate(e.getMessage(), e.getCause());
}
}
}
public PersistenceException translate(String message, SQLException cause) {
if (manager != null) {
return manager.translate(message, cause);
}
return new PersistenceException(message, cause);
}
/**
* Flush after completing persist cascade.
*/
@Override
public void flushBatchOnCascade() {
if (batchControl != null) {
if (logger.isTraceEnabled()) {
logger.trace("... flushBatchOnCascade");
}
batchControl.flushReset();
}
batchFlushReset();
// restore the previous batch mode
batchMode = oldBatchMode;
}
@@ -670,21 +688,13 @@ public class JdbcTransaction implements SpiTransaction {
// 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();
}
batchFlushReset();
// 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();
}
batchFlushReset();
return false;
}
@@ -732,9 +742,7 @@ public class JdbcTransaction implements SpiTransaction {
* Flush the JDBC batch and execute derived relationship statements if necessary.
*/
private void internalBatchFlush() {
if (batchControl != null) {
batchControl.flush();
}
batchFlush();
if (deferredList != null) {
for (PersistDeferredRelationship deferred : deferredList) {
deferred.execute(this);
@@ -935,7 +943,7 @@ public class JdbcTransaction implements SpiTransaction {
* </p>
*/
@Override
public void commitAndContinue() throws RollbackException {
public void commitAndContinue() {
if (rollbackOnly) {
return;
}
@@ -951,7 +959,7 @@ public class JdbcTransaction implements SpiTransaction {
} catch (Exception e) {
doRollback(e);
throw new RollbackException(e);
throw wrapIfNeeded(e);
}
}
@@ -959,7 +967,7 @@ public class JdbcTransaction implements SpiTransaction {
* Commit the transaction.
*/
@Override
public void commit() throws RollbackException {
public void commit() {
if (rollbackOnly) {
rollback();
return;
@@ -976,13 +984,24 @@ public class JdbcTransaction implements SpiTransaction {
} catch (Exception e) {
doRollback(e);
throw new RollbackException(e);
throw wrapIfNeeded(e);
} finally {
deactivate();
}
}
/**
* Try to keep specific exceptions and otherwise wrap as RollbackException.
*/
private RuntimeException wrapIfNeeded(Exception e) {
if (e instanceof PersistenceException) {
// keep more specific exception if we have it
return (PersistenceException)e;
}
return new RollbackException(e);
}
/**
* Notify the transaction manager.
*/
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.transaction;
import io.ebean.BackgroundExecutor;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.PersistBatch;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
@@ -19,8 +20,10 @@ import io.ebeanservice.docstore.api.DocStoreUpdates;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
@@ -100,11 +103,14 @@ public class TransactionManager {
private final TransactionFactory transactionFactory;
private final DatabasePlatform databasePlatform;
/**
* Create the TransactionManager
*/
public TransactionManager(TransactionManagerOptions options) {
this.databasePlatform = options.config.getDatabasePlatform();
this.skipCacheAfterWrite = options.config.isSkipCacheAfterWrite();
this.localL2Caching = options.localL2Caching;
this.persistBatch = options.config.getPersistBatch();
@@ -132,6 +138,13 @@ public class TransactionManager {
}
}
/**
* Translate the SQLException into a specific exception if possible based on the DB platform.
*/
public PersistenceException translate(String message, SQLException cause) {
return databasePlatform.translate(message, cause);
}
public void shutdown(boolean shutdownDataSource, boolean deregisterDriver) {
if (shutdownDataSource) {
dataSourceSupplier.shutdown(deregisterDriver);