From c8d01828b3a043ec8cfd11104ce54edf8d9c8284 Mon Sep 17 00:00:00 2001
From: Rob Bygrave
Date: Mon, 13 Mar 2017 20:50:22 +1300
Subject: [PATCH] #991 ENH: Add forUpdateNoWait() and forUpdateSkipLocked()
options ... #528 ENH: Improve exception handling
---
pom.xml | 4 +-
.../java/io/ebean/AcquireLockException.java | 20 +++
.../java/io/ebean/DataIntegrityException.java | 16 ++
.../java/io/ebean/DuplicateKeyException.java | 14 ++
src/main/java/io/ebean/ExpressionList.java | 21 +++
src/main/java/io/ebean/Query.java | 46 ++++++
src/main/java/io/ebean/Transaction.java | 5 +-
.../config/dbplatform/DataErrorType.java | 22 +++
.../config/dbplatform/DatabasePlatform.java | 17 +-
.../config/dbplatform/SqlCodeTranslator.java | 50 ++++++
.../config/dbplatform/SqlErrorCodes.java | 47 ++++++
.../dbplatform/SqlExceptionTranslator.java | 15 ++
.../config/dbplatform/db2/DB2Platform.java | 8 +
.../config/dbplatform/h2/H2Platform.java | 12 +-
.../dbplatform/mysql/MySqlPlatform.java | 12 +-
.../dbplatform/oracle/OraclePlatform.java | 12 +-
.../dbplatform/postgres/PostgresPlatform.java | 20 ++-
.../sqlserver/SqlServerPlatform.java | 8 +
.../ebeaninternal/api/ScopedTransaction.java | 19 ++-
.../java/io/ebeaninternal/api/SpiQuery.java | 5 +
.../io/ebeaninternal/api/SpiTransaction.java | 7 +
.../server/core/OrmQueryEngine.java | 7 +
.../server/core/OrmQueryRequest.java | 5 +
.../server/core/PersistRequest.java | 34 ++--
.../server/core/PersistRequestBean.java | 28 ++--
.../expression/DefaultExpressionList.java | 15 ++
.../server/expression/JunctionExpression.java | 15 ++
.../server/persist/BatchControl.java | 66 ++++----
.../server/persist/BatchedBeanHolder.java | 2 +-
.../server/persist/BatchedPstmt.java | 2 +-
.../server/persist/BatchedPstmtHolder.java | 6 +-
.../server/persist/BatchedSqlException.java | 22 +++
.../server/persist/dml/DmlBeanPersister.java | 27 ++--
.../persist/dml/DmlBeanPersisterFactory.java | 5 +-
.../io/ebeaninternal/server/query/CQuery.java | 25 +--
.../server/query/CQueryEngine.java | 33 +++-
.../server/query/DefaultOrmQueryEngine.java | 8 +-
.../server/querydefn/DefaultOrmQuery.java | 44 ++++--
.../server/querydefn/OrmQueryPlanKey.java | 94 ++++++-----
.../transaction/ExternalJdbcTransaction.java | 3 +-
.../server/transaction/JdbcTransaction.java | 77 +++++----
.../transaction/TransactionManager.java | 13 ++
.../io/ebeaninternal/api/message.properties | 4 +-
.../server/querydefn/DefaultOrmQueryTest.java | 3 +-
.../server/querydefn/OrmQueryPlanKeyTest.java | 147 ++++++++++--------
src/test/java/org/tests/basic/TestQuery.java | 46 ------
.../org/tests/basic/TestQueryForUpdate.java | 79 ++++++++++
.../TestInsertDataIntegrityException.java | 28 ++++
.../tests/insert/TestInsertDuplicateKey.java | 103 ++++++++++++
49 files changed, 985 insertions(+), 336 deletions(-)
create mode 100644 src/main/java/io/ebean/AcquireLockException.java
create mode 100644 src/main/java/io/ebean/DataIntegrityException.java
create mode 100644 src/main/java/io/ebean/DuplicateKeyException.java
create mode 100644 src/main/java/io/ebean/config/dbplatform/DataErrorType.java
create mode 100644 src/main/java/io/ebean/config/dbplatform/SqlCodeTranslator.java
create mode 100644 src/main/java/io/ebean/config/dbplatform/SqlErrorCodes.java
create mode 100644 src/main/java/io/ebean/config/dbplatform/SqlExceptionTranslator.java
create mode 100644 src/main/java/io/ebeaninternal/server/persist/BatchedSqlException.java
delete mode 100644 src/test/java/org/tests/basic/TestQuery.java
create mode 100644 src/test/java/org/tests/basic/TestQueryForUpdate.java
create mode 100644 src/test/java/org/tests/insert/TestInsertDataIntegrityException.java
create mode 100644 src/test/java/org/tests/insert/TestInsertDuplicateKey.java
diff --git a/pom.xml b/pom.xml
index ca06ab4cb..3e54cb0be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -160,7 +160,7 @@
io.ebean
ebean-agent
- 10.1.6
+ 10.1.7
test
@@ -225,7 +225,7 @@
io.ebean
ebean-maven-plugin
- 10.1.6
+ 10.1.7
test
diff --git a/src/main/java/io/ebean/AcquireLockException.java b/src/main/java/io/ebean/AcquireLockException.java
new file mode 100644
index 000000000..7bc7aa1b3
--- /dev/null
+++ b/src/main/java/io/ebean/AcquireLockException.java
@@ -0,0 +1,20 @@
+package io.ebean;
+
+import javax.persistence.PessimisticLockException;
+
+/**
+ * Thrown when failing to acquire a pessimistic lock.
+ *
+ * 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).
+ *
+ */
+public class AcquireLockException extends PessimisticLockException {
+
+ /**
+ * Create with a message and cause.
+ */
+ public AcquireLockException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/io/ebean/DataIntegrityException.java b/src/main/java/io/ebean/DataIntegrityException.java
new file mode 100644
index 000000000..719b08d71
--- /dev/null
+++ b/src/main/java/io/ebean/DataIntegrityException.java
@@ -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);
+ }
+}
diff --git a/src/main/java/io/ebean/DuplicateKeyException.java b/src/main/java/io/ebean/DuplicateKeyException.java
new file mode 100644
index 000000000..18a896370
--- /dev/null
+++ b/src/main/java/io/ebean/DuplicateKeyException.java
@@ -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);
+ }
+}
diff --git a/src/main/java/io/ebean/ExpressionList.java b/src/main/java/io/ebean/ExpressionList.java
index 336b86f3b..4b2ddfec4 100644
--- a/src/main/java/io/ebean/ExpressionList.java
+++ b/src/main/java/io/ebean/ExpressionList.java
@@ -118,6 +118,27 @@ public interface ExpressionList {
*/
Query asDraft();
+ /**
+ * Execute using "for update" clause which results in the DB locking the record.
+ */
+ Query forUpdate();
+
+ /**
+ * Execute using "for update" clause with No Wait option.
+ *
+ * This is typically a Postgres and Oracle only option at this stage.
+ *
+ */
+ Query forUpdateNoWait();
+
+ /**
+ * Execute using "for update" clause with Skip Locked option.
+ *
+ * This is typically a Postgres and Oracle only option at this stage.
+ *
+ */
+ Query forUpdateSkipLocked();
+
/**
* Execute the query including soft deleted rows.
*/
diff --git a/src/main/java/io/ebean/Query.java b/src/main/java/io/ebean/Query.java
index 118b01262..2a1d5d3ea 100644
--- a/src/main/java/io/ebean/Query.java
+++ b/src/main/java/io/ebean/Query.java
@@ -194,6 +194,26 @@ import java.util.function.Predicate;
*/
public interface Query {
+ /**
+ * 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 {
*/
Query setForUpdate(boolean forUpdate);
+ /**
+ * Execute using "for update" clause which results in the DB locking the record.
+ */
+ Query forUpdate();
+
+ /**
+ * Execute using "for update" clause with "no wait" option.
+ *
+ * This is typically a Postgres and Oracle only option at this stage.
+ *
+ */
+ Query forUpdateNoWait();
+
+ /**
+ * Execute using "for update" clause with "skip locked" option.
+ *
+ * This is typically a Postgres and Oracle only option at this stage.
+ *
+ */
+ Query forUpdateSkipLocked();
+
/**
* Return true if this query has forUpdate set.
*/
boolean isForUpdate();
+ /**
+ * Return the "for update" mode to use.
+ */
+ ForUpdate getForUpdateMode();
+
/**
* Set root table alias.
*/
diff --git a/src/main/java/io/ebean/Transaction.java b/src/main/java/io/ebean/Transaction.java
index ffcb8df78..ef7e45c42 100644
--- a/src/main/java/io/ebean/Transaction.java
+++ b/src/main/java/io/ebean/Transaction.java
@@ -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 {
* Perform post-commit processing updating L2 cache, ElasticSearch etc
*
*/
- void commitAndContinue() throws RollbackException;
+ void commitAndContinue();
/**
* Commit the transaction.
@@ -85,7 +84,7 @@ public interface Transaction extends Closeable {
* Mark the transaction as "Inactive"
*
*/
- void commit() throws RollbackException;
+ void commit();
/**
* Rollback the transaction.
diff --git a/src/main/java/io/ebean/config/dbplatform/DataErrorType.java b/src/main/java/io/ebean/config/dbplatform/DataErrorType.java
new file mode 100644
index 000000000..dd42c9e8c
--- /dev/null
+++ b/src/main/java/io/ebean/config/dbplatform/DataErrorType.java
@@ -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
+}
diff --git a/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java b/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java
index 7872ad6c4..ce49742f6 100644
--- a/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java
+++ b/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java
@@ -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;
}
diff --git a/src/main/java/io/ebean/config/dbplatform/SqlCodeTranslator.java b/src/main/java/io/ebean/config/dbplatform/SqlCodeTranslator.java
new file mode 100644
index 000000000..8db905d1d
--- /dev/null
+++ b/src/main/java/io/ebean/config/dbplatform/SqlCodeTranslator.java
@@ -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 map;
+
+ /**
+ * Create given the map of SQLState codes to error types.
+ */
+ public SqlCodeTranslator(Map 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);
+ }
+}
diff --git a/src/main/java/io/ebean/config/dbplatform/SqlErrorCodes.java b/src/main/java/io/ebean/config/dbplatform/SqlErrorCodes.java
new file mode 100644
index 000000000..e52462ccf
--- /dev/null
+++ b/src/main/java/io/ebean/config/dbplatform/SqlErrorCodes.java
@@ -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 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);
+ }
+}
diff --git a/src/main/java/io/ebean/config/dbplatform/SqlExceptionTranslator.java b/src/main/java/io/ebean/config/dbplatform/SqlExceptionTranslator.java
new file mode 100644
index 000000000..061bccfb8
--- /dev/null
+++ b/src/main/java/io/ebean/config/dbplatform/SqlExceptionTranslator.java
@@ -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);
+}
diff --git a/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java b/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java
index 58b86c027..16ca47608 100644
--- a/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java
+++ b/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java
@@ -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"));
diff --git a/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java b/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java
index 6df027356..c8317ea75 100644
--- a/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java
+++ b/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java
@@ -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";
}
}
diff --git a/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java b/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java
index 829de7982..2bbf39409 100644
--- a/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java
+++ b/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java
@@ -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";
}
}
diff --git a/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java b/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java
index b1458f8e0..339757f79 100644
--- a/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java
+++ b/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java
@@ -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";
+ }
}
}
diff --git a/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java b/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java
index 1ec7ef667..750b4e14e 100644
--- a/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java
+++ b/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java
@@ -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";
+ }
}
}
diff --git a/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerPlatform.java b/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerPlatform.java
index a3ca1e1a7..609c06042 100644
--- a/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerPlatform.java
+++ b/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerPlatform.java
@@ -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 = "]";
diff --git a/src/main/java/io/ebeaninternal/api/ScopedTransaction.java b/src/main/java/io/ebeaninternal/api/ScopedTransaction.java
index e25ccd76f..7c98280d9 100644
--- a/src/main/java/io/ebeaninternal/api/ScopedTransaction.java
+++ b/src/main/java/io/ebeaninternal/api/ScopedTransaction.java
@@ -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;
}
diff --git a/src/main/java/io/ebeaninternal/api/SpiQuery.java b/src/main/java/io/ebeaninternal/api/SpiQuery.java
index 8f8f6fb27..6250c883a 100644
--- a/src/main/java/io/ebeaninternal/api/SpiQuery.java
+++ b/src/main/java/io/ebeaninternal/api/SpiQuery.java
@@ -158,6 +158,11 @@ public interface SpiQuery extends Query {
*/
String getNativeSql();
+ /**
+ * Return the ForUpdate mode.
+ */
+ ForUpdate getForUpdateMode();
+
/**
* Return the bean descriptor for this query.
*/
diff --git a/src/main/java/io/ebeaninternal/api/SpiTransaction.java b/src/main/java/io/ebeaninternal/api/SpiTransaction.java
index ff51ec278..83aba831b 100644
--- a/src/main/java/io/ebeaninternal/api/SpiTransaction.java
+++ b/src/main/java/io/ebeaninternal/api/SpiTransaction.java
@@ -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.
*/
diff --git a/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java b/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java
index 6589a6987..76c5875c8 100644
--- a/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java
+++ b/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java
@@ -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.
*/
int update(OrmQueryRequest request);
+
+ /**
+ * Translate the SQLException to a specific persistence exception type if possible.
+ */
+ PersistenceException translate(OrmQueryRequest request, String bindLog, String sql, SQLException e);
}
diff --git a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
index f1e2c361b..f31eaaadd 100644
--- a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
+++ b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
@@ -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 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.
*/
diff --git a/src/main/java/io/ebeaninternal/server/core/PersistRequest.java b/src/main/java/io/ebeaninternal/server/core/PersistRequest.java
index 0f828c52f..cddf3db1d 100644
--- a/src/main/java/io/ebeaninternal/server/core/PersistRequest.java
+++ b/src/main/java/io/ebeaninternal/server/core/PersistRequest.java
@@ -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() {
diff --git a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
index c4057c157..fee90514e 100644
--- a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
+++ b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
@@ -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 extends PersistRequest implements BeanP
}
}
- public BeanManager getBeanManager() {
- return beanManager;
- }
-
/**
* Return the BeanDescriptor for the associated bean.
*/
@@ -697,17 +694,20 @@ public final class PersistRequestBean 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());
}
}
diff --git a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java
index 6b27483ac..d16e1a2b2 100644
--- a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java
+++ b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java
@@ -419,6 +419,21 @@ public class DefaultExpressionList implements SpiExpressionList {
return query.filterMany(prop);
}
+ @Override
+ public Query forUpdate() {
+ return query.forUpdate();
+ }
+
+ @Override
+ public Query forUpdateNoWait() {
+ return query.forUpdateNoWait();
+ }
+
+ @Override
+ public Query forUpdateSkipLocked() {
+ return query.forUpdateSkipLocked();
+ }
+
@Override
public Query select(String fetchProperties) {
return query.select(fetchProperties);
diff --git a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java
index 256bb62ec..c44d6dbea 100644
--- a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java
+++ b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java
@@ -419,6 +419,21 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression
return exprList.findUnique();
}
+ @Override
+ public Query forUpdate() {
+ return exprList.forUpdate();
+ }
+
+ @Override
+ public Query forUpdateNoWait() {
+ return exprList.forUpdateNoWait();
+ }
+
+ @Override
+ public Query forUpdateSkipLocked() {
+ return exprList.forUpdateSkipLocked();
+ }
+
/**
* Path exists - for the given path in a JSON document.
*/
diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchControl.java b/src/main/java/io/ebeaninternal/server/persist/BatchControl.java
index 688753e10..56f60cfe3 100644
--- a/src/main/java/io/ebeaninternal/server/persist/BatchControl.java
+++ b/src/main/java/io/ebeaninternal/server/persist/BatchControl.java
@@ -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.
*
*/
- 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 list) {
+ protected void executeNow(ArrayList 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());
diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchedBeanHolder.java b/src/main/java/io/ebeaninternal/server/persist/BatchedBeanHolder.java
index b5fed8155..4e1528b73 100644
--- a/src/main/java/io/ebeaninternal/server/persist/BatchedBeanHolder.java
+++ b/src/main/java/io/ebeaninternal/server/persist/BatchedBeanHolder.java
@@ -79,7 +79,7 @@ public class BatchedBeanHolder {
* and then execute them.
*
*/
- 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
diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java
index f96cebcaa..deddb087f 100644
--- a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java
+++ b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java
@@ -121,7 +121,7 @@ public class BatchedPstmt {
list.get(index).setGeneratedKey(idValue);
index++;
}
- }
+ }
}
}
diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java
index fa68fd486..b47b41a36 100644
--- a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java
+++ b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java
@@ -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);
}
}
diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchedSqlException.java b/src/main/java/io/ebeaninternal/server/persist/BatchedSqlException.java
new file mode 100644
index 000000000..bd8a6726c
--- /dev/null
+++ b/src/main/java/io/ebeaninternal/server/persist/BatchedSqlException.java
@@ -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;
+ }
+}
diff --git a/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java b/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java
index 2b876df14..0d62615f2 100644
--- a/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java
+++ b/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java
@@ -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) {
diff --git a/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java b/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java
index ca3e8f8fa..a06ac1505 100644
--- a/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java
+++ b/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java
@@ -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);
}
}
diff --git a/src/main/java/io/ebeaninternal/server/query/CQuery.java b/src/main/java/io/ebeaninternal/server/query/CQuery.java
index d7010dfe4..bf23da884 100644
--- a/src/main/java/io/ebeaninternal/server/query/CQuery.java
+++ b/src/main/java/io/ebeaninternal/server/query/CQuery.java
@@ -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 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);
}
/**
diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java b/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java
index d6eb541ba..7b6e495af 100644
--- a/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java
+++ b/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java
@@ -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 asOfTableMapping, Map 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.
+ */
+ PersistenceException translate(OrmQueryRequest 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);
}
}
diff --git a/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java b/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java
index 806977ce2..b3db79734 100644
--- a/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java
+++ b/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java
@@ -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 PersistenceException translate(OrmQueryRequest 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.
*/
diff --git a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
index 61b0585e5..c4cb36b69 100644
--- a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
+++ b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
@@ -206,9 +206,9 @@ public class DefaultOrmQuery implements SpiQuery {
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 implements SpiQuery {
// 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 implements SpiQuery {
return autoTune;
}
- @Override
- public boolean isForUpdate() {
- return forUpdate;
- }
-
@Override
public void setDefaultRawSqlIfRequired() {
if (beanDescriptor.isRawSqlBased() && rawSql == null) {
@@ -811,11 +806,42 @@ public class DefaultOrmQuery implements SpiQuery {
@Override
public DefaultOrmQuery setForUpdate(boolean forUpdate) {
- this.forUpdate = forUpdate;
+ this.forUpdate = (forUpdate) ? ForUpdate.BASE : null;
this.excludeBeanCache = true;
return this;
}
+ @Override
+ public DefaultOrmQuery forUpdate() {
+ return setForUpdateWithMode(ForUpdate.BASE);
+ }
+
+ @Override
+ public DefaultOrmQuery forUpdateNoWait() {
+ return setForUpdateWithMode(ForUpdate.NOWAIT);
+ }
+
+ @Override
+ public DefaultOrmQuery forUpdateSkipLocked() {
+ return setForUpdateWithMode(ForUpdate.SKIPLOCKED);
+ }
+
+ private DefaultOrmQuery 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;
diff --git a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java
index eee0b6e4c..68b2b9aac 100644
--- a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java
+++ b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java
@@ -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;
}
}
diff --git a/src/main/java/io/ebeaninternal/server/transaction/ExternalJdbcTransaction.java b/src/main/java/io/ebeaninternal/server/transaction/ExternalJdbcTransaction.java
index 25cfb588f..47b635ce0 100644
--- a/src/main/java/io/ebeaninternal/server/transaction/ExternalJdbcTransaction.java
+++ b/src/main/java/io/ebeaninternal/server/transaction/ExternalJdbcTransaction.java
@@ -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 {
*
*/
@Override
- public void commit() throws RollbackException {
+ public void commit() {
throw new PersistenceException("This is an external transaction so must be committed externally");
}
diff --git a/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
index ab94c25ee..9d611ce54 100644
--- a/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
+++ b/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
@@ -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 {
*
*/
@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.
*/
diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java
index 3300b7a32..3c38374d2 100644
--- a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java
+++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java
@@ -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);
diff --git a/src/main/resources/io/ebeaninternal/api/message.properties b/src/main/resources/io/ebeaninternal/api/message.properties
index ac455a28f..0a54fe207 100644
--- a/src/main/resources/io/ebeaninternal/api/message.properties
+++ b/src/main/resources/io/ebeaninternal/api/message.properties
@@ -41,8 +41,8 @@ fetch.bind.error=error binding parameter [{0}][{1}]
fetch.bind.datatype=Datatype [{0}] not handled for parameter [{1}][{2}]
fetch.bind.datatype2=Datatype for [{0}] not handled.
-fetch.sqlerror=Query threw SQLException:{0} \r\nBind values:[{1}] \r\nQuery was:\r\n{2} \r\n\r\n
-fetch.error=Query threw SQLException:{0} Query was:\r\n{1}\r\n\r\n
+fetch.sqlerror=Query threw SQLException:{0} Bind values:[{1}] Query was:{2}
+fetch.error=Query threw SQLException:{0} Query was:{1}
fetch.limit.orderby=You must specify an OrderBy if limiting the ResultSet
fetch.many.depth=Many property [{0}] can not be included due to the wrong depth.
diff --git a/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java b/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java
index 451353372..427539c80 100644
--- a/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java
+++ b/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.querydefn;
+import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.tests.model.basic.Customer;
import org.tests.model.basic.Order;
@@ -8,7 +9,7 @@ import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
-public class DefaultOrmQueryTest {
+public class DefaultOrmQueryTest extends BaseTestCase {
@Test
public void when_forUpdate_then_excludeFromBeanCache() {
diff --git a/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java b/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java
index 69ae03464..b0aece47b 100644
--- a/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java
+++ b/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.querydefn;
import io.ebean.OrderBy;
+import io.ebean.Query;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionList;
import io.ebeaninternal.api.SpiQuery;
@@ -21,8 +22,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
@Test
public void equals_when_defaults() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertSame(key1, key2);
}
@@ -32,8 +33,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
TableJoin tableJoin = tableJoin("table", "id", "customer_id");
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -44,8 +45,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
TableJoin tableJoin1 = tableJoin("one", "id", "customer_id");
TableJoin tableJoin2 = tableJoin("two", "id", "customer_id");
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -56,8 +57,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
TableJoin tableJoin1 = tableJoin("one", "id", "customer_id");
TableJoin tableJoin2 = tableJoin("one", "id", "customer_id");
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertSame(key1, key2);
}
@@ -73,8 +74,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
@Test
public void equals_when_diffQueryType() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.LIST, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.LIST, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -82,8 +83,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
@Test
public void equals_when_firstRowsDifferent() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 10, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 10, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -91,8 +92,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
@Test
public void equals_when_maxRowsDifferent() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -100,8 +101,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
@Test
public void equals_when_firstRowsMaxRowsSame() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertSame(key1, key2);
}
@@ -109,8 +110,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
@Test
public void equals_when_diffDisableLazyLoading() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, true, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, true, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -119,8 +120,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
public void equals_when_diffOrderByNull() {
OrderBy o1 = new OrderBy<>("id");
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o1, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o1, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -130,107 +131,121 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
OrderBy o1 = new OrderBy<>("id, name");
OrderBy o2 = new OrderBy<>("id, name");
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o1, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o2, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o1, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o2, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertSame(key1, key2);
}
@Test
public void equals_when_diffDistinct() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@Test
public void equals_when_sameDistinct() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertSame(key1, key2);
}
@Test
public void equals_when_diffSqlDistinct() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@Test
public void equals_when_sameSqlDistinct() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertSame(key1, key2);
}
@Test
public void equals_when_diffMapKeyNull() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@Test
public void equals_when_diffMapKey() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "diff", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "diff", null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@Test
public void equals_when_sameMapKey() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertSame(key1, key2);
}
@Test
public void equals_when_diffIdNull() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@Test
public void equals_when_idBothGiven() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 23, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 23, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertSame(key1, key2);
}
@Test
public void equals_when_diffTemporalMode() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.DRAFT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.DRAFT, null, null, null, null);
assertDifferent(key1, key2);
}
@Test
public void equals_when_diffForUpdate() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, true, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, Query.ForUpdate.BASE, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ assertDifferent(key1, key2);
+ }
+
+ @Test
+ public void equals_when_diffForUpdate_NoWait() {
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, Query.ForUpdate.BASE, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, Query.ForUpdate.NOWAIT, null, null, null);
+ assertDifferent(key1, key2);
+ }
+
+ @Test
+ public void equals_when_diffForUpdate_SkipLocked() {
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, Query.ForUpdate.BASE, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, Query.ForUpdate.SKIPLOCKED, null, null, null);
assertDifferent(key1, key2);
}
@Test
public void equals_when_diffRootAliasNull() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, "rootAlias", null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@Test
public void equals_when_diffRootAlias() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "diff", null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, "rootAlias", null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, "diff", null, null);
assertDifferent(key1, key2);
}
@Test
public void equals_when_sameRootAlias() {
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, "rootAlias", null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, "rootAlias", null, null);
assertSame(key1, key2);
}
@@ -257,8 +272,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
SpiExpressionList list1 = list_id_eq_42();
SpiExpressionList list2 = list_id_eq_43();
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list2, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list2, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertSame(key1, key2);
}
@@ -269,8 +284,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
SpiExpressionList where1 = list_id_eq_42();
SpiExpressionList where2 = list_id_eq_42_and_name_eq_rob();
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, where1, null, SpiQuery.TemporalMode.DRAFT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, where2, null, SpiQuery.TemporalMode.DRAFT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, where1, null, SpiQuery.TemporalMode.DRAFT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, where2, null, SpiQuery.TemporalMode.DRAFT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -279,8 +294,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
SpiExpressionList list1 = list_id_eq_42();
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -289,8 +304,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
SpiExpressionList list1 = list_id_eq_42();
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -300,8 +315,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
SpiExpression having1 = list_id_eq_42().copyForPlanKey();
SpiExpression having2 = list_id_eq_42_and_name_eq_rob().copyForPlanKey();
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -311,8 +326,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
SpiExpression having1 = list_id_eq_42().copyForPlanKey();
SpiExpression having2 = list_id_eq_42().copyForPlanKey();
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertSame(key1, key2);
}
@@ -321,8 +336,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
SpiExpression having1 = list_id_eq_42().copyForPlanKey();
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
@@ -331,8 +346,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
SpiExpression having1 = list_id_eq_42().copyForPlanKey();
- OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
- OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null, null);
+ OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
+ OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, null, null, null, null);
assertDifferent(key1, key2);
}
diff --git a/src/test/java/org/tests/basic/TestQuery.java b/src/test/java/org/tests/basic/TestQuery.java
deleted file mode 100644
index 887ebe61e..000000000
--- a/src/test/java/org/tests/basic/TestQuery.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.tests.basic;
-
-import io.ebean.BaseTestCase;
-import io.ebean.Ebean;
-import io.ebean.Query;
-import org.tests.model.basic.Order;
-import org.tests.model.basic.ResetBasicData;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class TestQuery extends BaseTestCase {
-
- @Test
- public void testCountOrderBy() {
-
- ResetBasicData.reset();
-
- Query query = Ebean.find(Order.class).setAutoTune(false).order().asc("orderDate")
- .order().desc("id");
- // .orderBy("orderDate");
-
- int rc = query.findList().size();
- Assert.assertTrue(rc > 0);
- // String generatedSql = query.getGeneratedSql();
- // Assert.assertFalse(generatedSql.contains("order by"));
-
- }
-
- public void testForUpdate() {
- ResetBasicData.reset();
-
- Query query = Ebean.find(Order.class).setAutoTune(false).setForUpdate(false)
- .setMaxRows(1).order().asc("orderDate").order().desc("id");
-
- int rc = query.findList().size();
- Assert.assertTrue(rc > 0);
- Assert.assertTrue(!query.getGeneratedSql().toLowerCase().contains("for update"));
-
- query = Ebean.find(Order.class).setAutoTune(false).setForUpdate(true).setMaxRows(1).order()
- .asc("orderDate").order().desc("id");
-
- rc = query.findList().size();
- Assert.assertTrue(rc > 0);
- Assert.assertTrue(query.getGeneratedSql().toLowerCase().contains("for update"));
- }
-}
diff --git a/src/test/java/org/tests/basic/TestQueryForUpdate.java b/src/test/java/org/tests/basic/TestQueryForUpdate.java
new file mode 100644
index 000000000..c2902c8a6
--- /dev/null
+++ b/src/test/java/org/tests/basic/TestQueryForUpdate.java
@@ -0,0 +1,79 @@
+package org.tests.basic;
+
+import io.ebean.AcquireLockException;
+import io.ebean.BaseTestCase;
+import io.ebean.Ebean;
+import io.ebean.EbeanServer;
+import io.ebean.Query;
+import io.ebean.Transaction;
+import org.junit.Test;
+import org.tests.model.basic.Customer;
+import org.tests.model.basic.ResetBasicData;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class TestQueryForUpdate extends BaseTestCase {
+
+ @Test
+ public void testForUpdate() {
+
+ if (isH2() || isPostgres()) {
+ ResetBasicData.reset();
+
+ Query query = Ebean.find(Customer.class)
+ .forUpdate()
+ .setMaxRows(1)
+ .order().desc("id");
+
+ query.findList();
+ assertThat(sqlOf(query)).contains("for update");
+ }
+ }
+
+ @Test
+ public void testForUpdate_noWait() {
+
+ if (isPostgres() || isOracle()) {
+ ResetBasicData.reset();
+
+ EbeanServer server = Ebean.getDefaultServer();
+
+ Ebean.beginTransaction();
+ try {
+ Query query = Ebean.find(Customer.class)
+ .forUpdateNoWait()
+ .setMaxRows(1)
+ .order().desc("id");
+
+ List list = query.findList();
+ Customer first = list.get(0);
+
+ assertThat(sqlOf(query)).contains("for update nowait");
+
+ // create a 2nd transaction to test that the
+ // row is locked and we can't acquire it
+ Transaction txn2 = server.createTransaction();
+ try {
+ logger.info("... attempt another acquire using 2nd transaction");
+ Query query2 =
+ server.find(Customer.class)
+ .where().idEq(first.getId())
+ .forUpdateNoWait();
+
+ server.findUnique(query2, txn2);
+ assertTrue(false); // never get here
+ } catch (AcquireLockException e) {
+ logger.info("... got AcquireLockException " + e);
+ } finally {
+ txn2.end();
+ }
+
+ } finally {
+ Ebean.endTransaction();
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java b/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java
new file mode 100644
index 000000000..94518ddce
--- /dev/null
+++ b/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java
@@ -0,0 +1,28 @@
+package org.tests.insert;
+
+import io.ebean.BaseTestCase;
+import io.ebean.DataIntegrityException;
+import io.ebean.Ebean;
+import org.junit.Test;
+import org.tests.model.basic.Customer;
+import org.tests.model.basic.Order;
+import org.tests.model.basic.ResetBasicData;
+
+public class TestInsertDataIntegrityException extends BaseTestCase {
+
+
+ @Test(expected = DataIntegrityException.class)
+ public void insert_invalidForeignKey() {
+
+ ResetBasicData.reset();
+
+ // an invalid foreign key value
+ Customer invalidCustomer = Ebean.getReference(Customer.class, 900000);
+
+ Order order = new Order();
+ order.setStatus(Order.Status.NEW);
+ order.setCustomer(invalidCustomer);
+
+ Ebean.save(order);
+ }
+}
diff --git a/src/test/java/org/tests/insert/TestInsertDuplicateKey.java b/src/test/java/org/tests/insert/TestInsertDuplicateKey.java
new file mode 100644
index 000000000..83ff17ad0
--- /dev/null
+++ b/src/test/java/org/tests/insert/TestInsertDuplicateKey.java
@@ -0,0 +1,103 @@
+package org.tests.insert;
+
+import io.ebean.BaseTestCase;
+import io.ebean.DuplicateKeyException;
+import io.ebean.Ebean;
+import io.ebean.annotation.Transactional;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.tests.model.draftable.Document;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestInsertDuplicateKey extends BaseTestCase {
+
+ private static final Logger log = LoggerFactory.getLogger(TestInsertDuplicateKey.class);
+
+ @Test(expected = DuplicateKeyException.class)
+ public void insert_duplicateKey() {
+
+ Document doc1 = new Document();
+ doc1.setTitle("ThisIsAUniqueKey");
+ doc1.setBody("one");
+
+ doc1.save();
+
+ Document doc2 = new Document();
+ doc2.setTitle("ThisIsAUniqueKey");
+ doc2.setBody("clashes with doc1");
+
+ doc2.save();
+ }
+
+ @Transactional(batchSize = 100)
+ @Test(expected = DuplicateKeyException.class)
+ public void insertBatch_duplicateKey() {
+
+ Document doc1 = new Document();
+ doc1.setTitle("ThisIsASecondUniqueKey");
+ doc1.setBody("one");
+
+ doc1.save();
+
+ Document doc2 = new Document();
+ doc2.setTitle("ThisIsASecondUniqueKey");
+ doc2.setBody("clash when batch flushed");
+
+ doc2.save();
+ }
+
+ @Test
+ public void insertBatch_duplicateKey_catchAndContinue() {
+
+ insertTheBatch_duplicateKey_catchAndContinue();
+
+ List found = Ebean.getDefaultServer()
+ .find(Document.class)
+ .asDraft()
+ .where().startsWith("body", "insertTheBatch_duplicateKey_catchAndContinue")
+ .findList();
+
+ assertThat(found).hasSize(1);
+ assertThat(found.get(0).getTitle()).isEqualTo("ThisIsAThirdUniqueKey");
+ assertThat(found.get(0).getBody()).isEqualTo("insertTheBatch_duplicateKey_catchAndContinue-1");
+ }
+
+ @Transactional//(batchSize = 100)
+ private void insertTheBatch_duplicateKey_catchAndContinue() {
+
+ Document doc1 = new Document();
+ doc1.setTitle("ThisIsAThirdUniqueKey");
+ doc1.setBody("insertTheBatch_duplicateKey_catchAndContinue-1");
+ doc1.save();
+
+ try {
+ Document doc2 = new Document();
+ doc2.setTitle("ThisIsAThirdUniqueKey");
+ doc2.setBody("insertTheBatch_duplicateKey_catchAndContinue-2");
+ doc2.save();
+
+ // flush at this point, fails
+ Ebean.getDefaultServer().currentTransaction().flushBatch();
+ } catch (DuplicateKeyException e) {
+ log.info("duplicate failed but just continue" + e.getMessage());
+ try {
+ // typically we would use transaction.commitAndContinue()
+ // ... this is a rollback and continue type scenario
+ // ... more sensible to use a second transaction that do this
+ Ebean.getDefaultServer().currentTransaction().getConnection().rollback();
+ } catch (SQLException e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ Document doc0 = new Document();
+ doc0.setTitle("ThisIsAThirdUniqueKey");
+ doc0.setBody("insertTheBatch_duplicateKey_catchAndContinue-1");
+ doc0.save();
+ }
+}