diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml
index ce3ba3688..380438cc2 100644
--- a/ebean-api/pom.xml
+++ b/ebean-api/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
ebean api
@@ -50,7 +50,7 @@
io.ebean
ebean-annotation
- 6.15
+ 7.0
diff --git a/ebean-api/src/main/java/io/ebean/DatabaseFactory.java b/ebean-api/src/main/java/io/ebean/DatabaseFactory.java
index 6ad7b1d89..4aace866a 100644
--- a/ebean-api/src/main/java/io/ebean/DatabaseFactory.java
+++ b/ebean-api/src/main/java/io/ebean/DatabaseFactory.java
@@ -78,7 +78,7 @@ public class DatabaseFactory {
Database server = createInternal(config);
if (config.isRegister()) {
if (config.isDefaultServer()) {
- if (defaultServerName != null) {
+ if (defaultServerName != null && !defaultServerName.equals(config.getName())) {
throw new IllegalStateException("Registering [" + config.getName() + "] as the default server but [" + defaultServerName + "] is already registered as the default");
}
defaultServerName = config.getName();
diff --git a/ebean-api/src/main/java/io/ebean/ExpressionList.java b/ebean-api/src/main/java/io/ebean/ExpressionList.java
index 72f1c06f9..e91846e08 100644
--- a/ebean-api/src/main/java/io/ebean/ExpressionList.java
+++ b/ebean-api/src/main/java/io/ebean/ExpressionList.java
@@ -1671,7 +1671,7 @@ public interface ExpressionList {
ExpressionList endAnd();
/**
- * End a AND junction - synonym for endJunction().
+ * End a OR junction - synonym for endJunction().
*/
ExpressionList endOr();
diff --git a/ebean-api/src/main/java/io/ebean/FetchConfig.java b/ebean-api/src/main/java/io/ebean/FetchConfig.java
index 64f4c3daa..e2d4fbe35 100644
--- a/ebean-api/src/main/java/io/ebean/FetchConfig.java
+++ b/ebean-api/src/main/java/io/ebean/FetchConfig.java
@@ -46,7 +46,7 @@ public class FetchConfig implements Serializable {
/**
* Deprecated - migrate to one of the static factory methods like {@link FetchConfig#ofQuery()}
- *
+ *
* Construct using default JOIN mode.
*/
@Deprecated
@@ -89,7 +89,7 @@ public class FetchConfig implements Serializable {
* Return FetchConfig to lazily load the relationship.
*/
public static FetchConfig ofLazy() {
- return new FetchConfig(LAZY_MODE, 10);
+ return new FetchConfig(LAZY_MODE, 0);
}
/**
@@ -110,8 +110,8 @@ public class FetchConfig implements Serializable {
* We want to migrate away from mutating FetchConfig to a fully immutable FetchConfig.
*/
private FetchConfig mutate(int mode, int batchSize) {
- if (batchSize < 1) {
- throw new IllegalArgumentException("batch size "+batchSize+" must be > 0");
+ if (batchSize < 0) {
+ throw new IllegalArgumentException("batch size " + batchSize + " must be > 0");
}
this.mode = mode;
this.batchSize = batchSize;
@@ -124,7 +124,7 @@ public class FetchConfig implements Serializable {
*/
@Deprecated
public FetchConfig lazy() {
- return mutate(LAZY_MODE, 10);
+ return mutate(LAZY_MODE, 0);
}
/**
@@ -137,7 +137,7 @@ public class FetchConfig implements Serializable {
/**
* Deprecated - migrate to FetchConfig.ofQuery().
- *
+ *
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
*
@@ -150,17 +150,15 @@ public class FetchConfig implements Serializable {
/**
* Deprecated - migrate to FetchConfig.ofQuery(batchSize).
- *
+ *
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
*
* The queryBatchSize is the number of parent id's that this separate query
* will load per batch.
- *
*
* This will load all beans on this path eagerly unless a {@link #lazy(int)}
* is also used.
- *
*
* @param batchSize the batch size used to load beans on this path
*/
@@ -171,13 +169,12 @@ public class FetchConfig implements Serializable {
/**
* Deprecated - migrate to FetchConfig.ofQuery(batchSize).
- *
+ *
* Eagerly fetch the first batch of beans on this path.
* This is similar to {@link #query(int)} but only fetches the first batch.
*
* If there are more parent beans than the batch size then they will not be
* loaded eagerly but instead use lazy loading.
- *
*
* @param batchSize the number of parent beans this path is populated for
*/
@@ -188,7 +185,7 @@ public class FetchConfig implements Serializable {
/**
* Deprecated - migrate to FetchConfig.ofCache().
- *
+ *
* Eagerly fetch the beans fetching the beans from the L2 bean cache
* and using the DB for beans not in the cache.
*/
diff --git a/ebean-api/src/main/java/io/ebean/Transaction.java b/ebean-api/src/main/java/io/ebean/Transaction.java
index d4918e84e..0856a6e4e 100644
--- a/ebean-api/src/main/java/io/ebean/Transaction.java
+++ b/ebean-api/src/main/java/io/ebean/Transaction.java
@@ -59,6 +59,14 @@ public interface Transaction extends AutoCloseable {
*/
void register(TransactionCallback callback);
+ /**
+ * EXPERIMENTAL - turn on automatic persistence of dirty beans and batchMode true.
+ *
+ * With this turned on beans that are dirty in the persistence context
+ * are automatically persisted on flush() and commit().
+ */
+ void setAutoPersistUpdates(boolean autoPersistUpdates);
+
/**
* Set a label on the transaction.
*
@@ -339,15 +347,12 @@ public interface Transaction extends AutoCloseable {
* The batch is automatically flushed when it hits the batch size and also when we
* execute queries or when we mix UpdateSql and CallableSql with save and delete of
* beans.
- *
*
* We use {@link #flush()} to explicitly flush the batch and we can use
* {@link #setFlushOnQuery(boolean)} and {@link #setFlushOnMixed(boolean)}
* to control the automatic flushing behaviour.
- *
*
* Example: batch processing of CallableSql executing every 10 rows
- *
*
* {@code
*
@@ -392,14 +397,11 @@ public interface Transaction extends AutoCloseable {
*
* This only takes effect when batch mode on the transaction has not already meant that
* JDBC batch mode is being used.
- *
*
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
- *
*
* This is effectively already turned on for all platforms apart from older Sql Server.
- *
*
* @param batchMode the batch mode to use per save(), insert(), update() or delete()
* @see io.ebean.config.DatabaseConfig#setPersistBatchOnCascade(PersistBatch)
@@ -422,15 +424,19 @@ public interface Transaction extends AutoCloseable {
int getBatchSize();
/**
- * Specify if you want batched inserts to use getGeneratedKeys.
+ * Specify if we want batched inserts to use getGeneratedKeys.
*
* By default batched inserts will try to use getGeneratedKeys if it is
* supported by the underlying jdbc driver and database.
- *
*
- * You may want to turn getGeneratedKeys off when you are inserting a large
- * number of objects and you don't care about getting back the ids.
- *
+ * We want to turn off getGeneratedKeys when we are inserting a large
+ * number of objects and we don't care about getting back the ids. In this
+ * way we avoid the extra cost of getting back the generated id values
+ * from the database.
+ *
+ * Note that when we do turn off getGeneratedKeys then we have the limitation
+ * that after a bean has been inserted we are unable to then mutate the bean
+ * and update it in the same transaction as we have not obtained it's id value.
*/
void setGetGeneratedKeys(boolean getGeneratedKeys);
@@ -449,13 +455,11 @@ public interface Transaction extends AutoCloseable {
*
* If you want to execute both WITHOUT having the batch automatically flush
* you need to call this with batchFlushOnMixed = false.
- *
*
* Note that UpdateSql and CallableSql are ALWAYS executed first (before the
* beans are executed). This is because the UpdateSql and CallableSql have
* already been bound to their PreparedStatements. The beans on the other hand
* have a 2 step process (delayed binding).
- *
*/
void setFlushOnMixed(boolean batchFlushOnMixed);
@@ -473,7 +477,6 @@ public interface Transaction extends AutoCloseable {
*
* Calling this method with batchFlushOnQuery = false means that you can
* execute a query and the batch will not be automatically flushed.
- *
*/
void setFlushOnQuery(boolean batchFlushOnQuery);
@@ -490,7 +493,6 @@ public interface Transaction extends AutoCloseable {
* should be flushed prior to executing a query.
*
* The default is for this to be true.
- *
*/
boolean isFlushOnQuery();
@@ -507,7 +509,6 @@ public interface Transaction extends AutoCloseable {
* flush the batch if you like.
*
* Flushing occurs automatically when:
- *
*
* the batch size is reached
* A query is executed on the same transaction
@@ -533,11 +534,9 @@ public interface Transaction extends AutoCloseable {
* commit() rollback() and end() methods on the Transaction should still be
* used. Calling these methods on the Connection would be a big no no unless
* you know what you are doing.
- *
*
* Examples of when a developer may wish to use the connection directly are:
* Savepoints, advanced CLOB BLOB use and advanced stored procedure calls.
- *
*/
Connection getConnection();
@@ -545,17 +544,14 @@ public interface Transaction extends AutoCloseable {
* Add table modification information to the TransactionEvent.
*
* Use this in conjunction with getConnection() and raw JDBC.
- *
*
* This effectively informs Ebean of the data that has been changed by the
* transaction and this information is normally automatically handled by Ebean
* when you save entity beans or use UpdateSql etc.
- *
*
* If you use raw JDBC then you can use this method to inform Ebean for the
* tables that have been modified. Ebean uses this information to keep its
* caches in synch and maintain text indexes.
- *
*/
void addModification(String tableName, boolean inserts, boolean updates, boolean deletes);
diff --git a/ebean-api/src/main/java/io/ebean/TxScope.java b/ebean-api/src/main/java/io/ebean/TxScope.java
index ee9fdca7b..59d9f6971 100644
--- a/ebean-api/src/main/java/io/ebean/TxScope.java
+++ b/ebean-api/src/main/java/io/ebean/TxScope.java
@@ -2,6 +2,7 @@ package io.ebean;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxIsolation;
+import io.ebean.annotation.TxOption;
import io.ebean.annotation.TxType;
import java.util.ArrayList;
@@ -33,6 +34,8 @@ public final class TxScope {
private TxIsolation isolation;
+ private TxOption autoPersistUpdates;
+
private PersistBatch batch;
private PersistBatch batchOnCascade;
@@ -123,6 +126,13 @@ public final class TxScope {
+ "] serverName[" + serverName + "] rollbackFor[" + rollbackFor + "] noRollbackFor[" + noRollbackFor + "]";
}
+ /**
+ * Return the AutoPersistUpdates mode as a nullable Boolean.
+ */
+ public Boolean getAutoPersistUpdates() {
+ return autoPersistUpdates == null ? null : autoPersistUpdates.asBoolean();
+ }
+
/**
* Return true if PersistBatch has been set.
*/
@@ -176,6 +186,14 @@ public final class TxScope {
return this;
}
+ /**
+ * Set the autoPersistUpdates mode.
+ */
+ public TxScope setAutoPersistUpdates(TxOption autoPersistUpdates) {
+ this.autoPersistUpdates = autoPersistUpdates;
+ return this;
+ }
+
/**
* Return the transaction profile id.
*/
diff --git a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java
index a8a42e51b..8c861af1f 100644
--- a/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java
+++ b/ebean-api/src/main/java/io/ebean/bean/PersistenceContext.java
@@ -1,5 +1,7 @@
package io.ebean.bean;
+import java.util.List;
+
/**
* Holds entity beans by there type and id.
*
@@ -77,6 +79,11 @@ public interface PersistenceContext {
*/
boolean resetLimit();
+ /**
+ * Return the list of dirty beans held by this persistence context.
+ */
+ List dirtyBeans();
+
/**
* Wrapper on a bean to also indicate if a bean has been deleted.
*
diff --git a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
index 8e65f9bb3..be2ac6c1f 100644
--- a/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
+++ b/ebean-api/src/main/java/io/ebean/config/DatabaseConfig.java
@@ -232,6 +232,12 @@ public class DatabaseConfig {
*/
private String historyTableSuffix = "_history";
+ /**
+ * When true explicit transactions beans that have been made dirty will be
+ * automatically persisted via update on flush.
+ */
+ private boolean autoPersistUpdates;
+
/**
* Use for transaction scoped batch mode.
*/
@@ -498,14 +504,23 @@ public class DatabaseConfig {
private boolean notifyL2CacheInForeground;
/**
- * Set to true to support query plan capture.
+ * Set to true to enable bind capture required for query plan capture.
*/
- private boolean collectQueryPlans;
+ private boolean queryPlanEnable;
/**
* The default threshold in micros for collecting query plans.
*/
- private long collectQueryPlanThresholdMicros = Long.MAX_VALUE;
+ private long queryPlanThresholdMicros = Long.MAX_VALUE;
+
+ /**
+ * Set to true to enable automatic periodic query plan capture.
+ */
+ private boolean queryPlanCapture;
+ private long queryPlanCapturePeriodSecs = 60 * 10; // 10 minutes
+ private long queryPlanCaptureMaxTimeMillis = 10_000; // 10 seconds
+ private int queryPlanCaptureMaxCount = 10;
+ private QueryPlanListener queryPlanListener;
/**
* The time in millis used to determine when a query is alerted for being slow.
@@ -896,6 +911,20 @@ public class DatabaseConfig {
this.tenantCatalogProvider = tenantCatalogProvider;
}
+ /**
+ * Return true if dirty beans are automatically persisted.
+ */
+ public boolean isAutoPersistUpdates() {
+ return autoPersistUpdates;
+ }
+
+ /**
+ * Set to true if dirty beans are automatically persisted.
+ */
+ public void setAutoPersistUpdates(boolean autoPersistUpdates) {
+ this.autoPersistUpdates = autoPersistUpdates;
+ }
+
/**
* Return the PersistBatch mode to use by default at the transaction level.
*
@@ -1045,7 +1074,6 @@ public class DatabaseConfig {
* This is a performance optimisation to reduce the number times Ebean
* requests a sequence to be used as an Id for a bean (aka reduce network
* chatter).
-
*/
public void setDatabaseSequenceBatchSize(int databaseSequenceBatchSize) {
platformConfig.setDatabaseSequenceBatchSize(databaseSequenceBatchSize);
@@ -2693,7 +2721,10 @@ public class DatabaseConfig {
}
/**
- * Load settings from ebean.properties.
+ * Load settings from application.properties, application.yaml and other sources.
+ *
+ * Uses avaje-config to load configuration properties. Goto https://avaje.io/config
+ * for detail on how and where properties are loaded from.
*/
public void loadFromProperties() {
this.properties = Config.asProperties();
@@ -2796,14 +2827,19 @@ public class DatabaseConfig {
loadDocStoreSettings(p);
defaultServer = p.getBoolean("defaultServer", defaultServer);
+ autoPersistUpdates = p.getBoolean("autoPersistUpdates", autoPersistUpdates);
loadModuleInfo = p.getBoolean("loadModuleInfo", loadModuleInfo);
maxCallStack = p.getInt("maxCallStack", maxCallStack);
dumpMetricsOnShutdown = p.getBoolean("dumpMetricsOnShutdown", dumpMetricsOnShutdown);
dumpMetricsOptions = p.get("dumpMetricsOptions", dumpMetricsOptions);
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
- collectQueryPlans = p.getBoolean("collectQueryPlans", collectQueryPlans);
- collectQueryPlanThresholdMicros = p.getLong("collectQueryPlanThresholdMicros", collectQueryPlanThresholdMicros);
+ queryPlanEnable = p.getBoolean("queryPlan.enable", queryPlanEnable);
+ queryPlanThresholdMicros = p.getLong("queryPlan.thresholdMicros", queryPlanThresholdMicros);
+ queryPlanCapture = p.getBoolean("queryPlan.capture", queryPlanCapture);
+ queryPlanCapturePeriodSecs = p.getLong("queryPlan.capturePeriodSecs", queryPlanCapturePeriodSecs);
+ queryPlanCaptureMaxTimeMillis = p.getLong("queryPlan.captureMaxTimeMillis", queryPlanCaptureMaxTimeMillis);
+ queryPlanCaptureMaxCount = p.getInt("queryPlan.captureMaxCount", queryPlanCaptureMaxCount);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
localOnlyL2Cache = p.getBoolean("localOnlyL2Cache", localOnlyL2Cache);
@@ -3093,14 +3129,17 @@ public class DatabaseConfig {
}
/**
- * Return the query plan time to live.
+ * Return the time to live for ebean's internal query plan.
*/
public int getQueryPlanTTLSeconds() {
return queryPlanTTLSeconds;
}
/**
- * Set the query plan time to live.
+ * Set the time to live for ebean's internal query plan.
+ *
+ * This is the plan that knows how to execute the query, read the result
+ * and collects execution metrics. By default this is set to 5 mins.
*/
public void setQueryPlanTTLSeconds(int queryPlanTTLSeconds) {
this.queryPlanTTLSeconds = queryPlanTTLSeconds;
@@ -3173,29 +3212,110 @@ public class DatabaseConfig {
/**
* Return true if query plan capture is enabled.
*/
- public boolean isCollectQueryPlans() {
- return collectQueryPlans;
+ public boolean isQueryPlanEnable() {
+ return queryPlanEnable;
}
/**
* Set to true to enable query plan capture.
*/
- public void setCollectQueryPlans(boolean collectQueryPlans) {
- this.collectQueryPlans = collectQueryPlans;
+ public void setQueryPlanEnable(boolean queryPlanEnable) {
+ this.queryPlanEnable = queryPlanEnable;
}
/**
* Return the query plan collection threshold in microseconds.
*/
- public long getCollectQueryPlanThresholdMicros() {
- return collectQueryPlanThresholdMicros;
+ public long getQueryPlanThresholdMicros() {
+ return queryPlanThresholdMicros;
}
/**
* Set the query plan collection threshold in microseconds.
+ *
+ * Queries executing slower than this will have bind values captured such that later
+ * the query plan can be captured and reported.
*/
- public void setCollectQueryPlanThresholdMicros(long collectQueryPlanThresholdMicros) {
- this.collectQueryPlanThresholdMicros = collectQueryPlanThresholdMicros;
+ public void setQueryPlanThresholdMicros(long queryPlanThresholdMicros) {
+ this.queryPlanThresholdMicros = queryPlanThresholdMicros;
+ }
+
+ /**
+ * Return true if periodic capture of query plans is enabled.
+ */
+ public boolean isQueryPlanCapture() {
+ return queryPlanCapture;
+ }
+
+ /**
+ * Set to true to turn on periodic capture of query plans.
+ */
+ public void setQueryPlanCapture(boolean queryPlanCapture) {
+ this.queryPlanCapture = queryPlanCapture;
+ }
+
+ /**
+ * Return the frequency to capture query plans.
+ */
+ public long getQueryPlanCapturePeriodSecs() {
+ return queryPlanCapturePeriodSecs;
+ }
+
+ /**
+ * Set the frequency in seconds to capture query plans.
+ */
+ public void setQueryPlanCapturePeriodSecs(long queryPlanCapturePeriodSecs) {
+ this.queryPlanCapturePeriodSecs = queryPlanCapturePeriodSecs;
+ }
+
+ /**
+ * Return the time after which a capture query plans request will
+ * stop capturing more query plans.
+ *
+ * Effectively this controls the amount of load/time we want to
+ * allow for query plan capture.
+ */
+ public long getQueryPlanCaptureMaxTimeMillis() {
+ return queryPlanCaptureMaxTimeMillis;
+ }
+
+ /**
+ * Set the time after which a capture query plans request will
+ * stop capturing more query plans.
+ *
+ * Effectively this controls the amount of load/time we want to
+ * allow for query plan capture.
+ */
+ public void setQueryPlanCaptureMaxTimeMillis(long queryPlanCaptureMaxTimeMillis) {
+ this.queryPlanCaptureMaxTimeMillis = queryPlanCaptureMaxTimeMillis;
+ }
+
+ /**
+ * Return the max number of query plans captured per request.
+ */
+ public int getQueryPlanCaptureMaxCount() {
+ return queryPlanCaptureMaxCount;
+ }
+
+ /**
+ * Set the max number of query plans captured per request.
+ */
+ public void setQueryPlanCaptureMaxCount(int queryPlanCaptureMaxCount) {
+ this.queryPlanCaptureMaxCount = queryPlanCaptureMaxCount;
+ }
+
+ /**
+ * Return the listener used to process captured query plans.
+ */
+ public QueryPlanListener getQueryPlanListener() {
+ return queryPlanListener;
+ }
+
+ /**
+ * Set the listener used to process captured query plans.
+ */
+ public void setQueryPlanListener(QueryPlanListener queryPlanListener) {
+ this.queryPlanListener = queryPlanListener;
}
/**
@@ -3249,7 +3369,7 @@ public class DatabaseConfig {
this.loadModuleInfo = loadModuleInfo;
}
- public enum UuidVersion {
+ public enum UuidVersion {
VERSION4,
VERSION1,
VERSION1RND
diff --git a/ebean-api/src/main/java/io/ebean/config/QueryPlanCapture.java b/ebean-api/src/main/java/io/ebean/config/QueryPlanCapture.java
new file mode 100644
index 000000000..6f34097fe
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/config/QueryPlanCapture.java
@@ -0,0 +1,34 @@
+package io.ebean.config;
+
+import io.ebean.Database;
+import io.ebean.meta.MetaQueryPlan;
+
+import java.util.List;
+
+/**
+ * The captured query plans.
+ */
+public class QueryPlanCapture {
+
+ private final Database database;
+ private final List plans;
+
+ public QueryPlanCapture(Database database, List plans) {
+ this.database = database;
+ this.plans = plans;
+ }
+
+ /**
+ * Return the database the plans were captured for.
+ */
+ public Database getDatabase() {
+ return database;
+ }
+
+ /**
+ * Return the captured query plans.
+ */
+ public List getPlans() {
+ return plans;
+ }
+}
diff --git a/ebean-api/src/main/java/io/ebean/config/QueryPlanListener.java b/ebean-api/src/main/java/io/ebean/config/QueryPlanListener.java
new file mode 100644
index 000000000..8368186cf
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/config/QueryPlanListener.java
@@ -0,0 +1,13 @@
+package io.ebean.config;
+
+/**
+ * EXPERIMENTAL: Listener for captured query plans.
+ */
+@FunctionalInterface
+public interface QueryPlanListener {
+
+ /**
+ * Process the captured query plans.
+ */
+ void process(QueryPlanCapture capture);
+}
diff --git a/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java b/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java
index 3510b30cc..c4a69b1aa 100644
--- a/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java
+++ b/ebean-api/src/main/java/io/ebean/meta/QueryPlanRequest.java
@@ -41,6 +41,9 @@ public class QueryPlanRequest {
/**
* Set the maximum number of plans to capture.
+ *
+ * Use this to limit how much query plan capturing is done as query
+ * plan capture is actual database load.
*/
public void setMaxCount(int maxCount) {
this.maxCount = maxCount;
@@ -58,7 +61,9 @@ public class QueryPlanRequest {
/**
* Set the maximum amount of time we want to use to capture plans.
*
- * Query plan collection will stop once this time is exceeded.
+ * Query plan collection will stop once this time is exceeded. We use
+ * this to ensure the query plan capture does not use excessive amount
+ * of time - put too much load on the database.
*/
public void setMaxTimeMillis(long maxTimeMillis) {
this.maxTimeMillis = maxTimeMillis;
diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml
index 5c081a9df..590680c8a 100644
--- a/ebean-autotune/pom.xml
+++ b/ebean-autotune/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
@@ -26,7 +26,7 @@
io.ebean
ebean-core
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
provided
diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml
index e96c248d7..aaa2b5961 100644
--- a/ebean-bom/pom.xml
+++ b/ebean-bom/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
ebean bom
@@ -18,8 +18,8 @@
12.4.0
4.1
7.0
- 12.6.6
- 12.6.6
+ 12.8.2
+ 12.8.2
@@ -81,88 +81,88 @@
io.ebean
ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-api
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-core
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-core-type
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-ddl-generator
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-externalmapping-api
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-externalmapping-xml
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-autotune
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-querybean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
querybean-generator
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
provided
io.ebean
kotlin-querybean-generator
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
provided
io.ebean
ebean-test
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
io.ebean
ebean-postgis
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-redis
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
diff --git a/ebean-core-type/pom.xml b/ebean-core-type/pom.xml
index 386d6d511..04497471c 100644
--- a/ebean-core-type/pom.xml
+++ b/ebean-core-type/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
ebean-core-type
@@ -16,7 +16,7 @@
io.ebean
ebean-api
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml
index 9d7f1e236..dbde9f167 100644
--- a/ebean-core/pom.xml
+++ b/ebean-core/pom.xml
@@ -3,7 +3,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
ebean-core
@@ -72,7 +72,7 @@
io.ebean
ebean-ddl-generator
- 12.6.1
+ 12.8.4a
test
@@ -87,19 +87,19 @@
io.ebean
ebean-api
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-core-type
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-externalmapping-api
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
@@ -302,7 +302,7 @@
io.ebean
ebean-maven-plugin
- 12.6.6
+ 12.8.2
test
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/CacheIdLookup.java b/ebean-core/src/main/java/io/ebeaninternal/api/CacheIdLookup.java
index 0ae791747..cf29cf6d5 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/CacheIdLookup.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/CacheIdLookup.java
@@ -1,52 +1,25 @@
package io.ebeaninternal.api;
-import io.ebeaninternal.server.expression.IdInExpression;
-
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
/**
- * Used for bean cache lookup with where ids in expression.
+ * Process Cache lookup by Id(s).
*/
-public class CacheIdLookup {
-
- private final IdInExpression idInExpression;
-
- private int remaining;
-
- public CacheIdLookup(IdInExpression idInExpression) {
- this.idInExpression = idInExpression;
- }
+public interface CacheIdLookup {
/**
- * Return the Id values for the in expression.
+ * Return the Id values to lookup against the L2 cache.
*/
- public Collection> idValues() {
- return idInExpression.idValues();
- }
+ Collection> idValues();
/**
- * Process the hits returning the beans fetched from cache and
- * adjusting the in expression (to not fetch the hits).
+ * Remove the hits returning the beans fetched from L2 cache.
*/
- public List removeHits(BeanCacheResult cacheResult) {
+ List removeHits(BeanCacheResult cacheResult);
- Set hitIds = new HashSet<>();
- List beans = new ArrayList<>(hitIds.size());
-
- for (BeanCacheResult.Entry hit : cacheResult.hits()) {
- hitIds.add(hit.getKey());
- beans.add(hit.getBean());
- }
-
- this.remaining = idInExpression.removeIds(hitIds);
- return beans;
- }
-
- public boolean allHits() {
- return remaining == 0;
- }
+ /**
+ * Return true if all beans where found in L2 cache.
+ */
+ boolean allHits();
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/CacheIdLookupMany.java b/ebean-core/src/main/java/io/ebeaninternal/api/CacheIdLookupMany.java
new file mode 100644
index 000000000..bfe9d5b84
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/CacheIdLookupMany.java
@@ -0,0 +1,55 @@
+package io.ebeaninternal.api;
+
+import io.ebeaninternal.server.expression.IdInExpression;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Used for bean cache lookup with where ids in expression.
+ */
+public class CacheIdLookupMany implements CacheIdLookup {
+
+ private final IdInExpression idInExpression;
+
+ private int remaining;
+
+ public CacheIdLookupMany(IdInExpression idInExpression) {
+ this.idInExpression = idInExpression;
+ }
+
+ /**
+ * Return the Id values for the in expression.
+ */
+ @Override
+ public Collection> idValues() {
+ return idInExpression.idValues();
+ }
+
+ /**
+ * Process the hits returning the beans fetched from cache and
+ * adjusting the in expression (to not fetch the hits).
+ */
+ @Override
+ public List removeHits(BeanCacheResult cacheResult) {
+
+ Set hitIds = new HashSet<>();
+ List beans = new ArrayList<>(hitIds.size());
+
+ for (BeanCacheResult.Entry hit : cacheResult.hits()) {
+ hitIds.add(hit.getKey());
+ beans.add(hit.getBean());
+ }
+
+ this.remaining = idInExpression.removeIds(hitIds);
+ return beans;
+ }
+
+ @Override
+ public boolean allHits() {
+ return remaining == 0;
+ }
+}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/CacheIdLookupSingle.java b/ebean-core/src/main/java/io/ebeaninternal/api/CacheIdLookupSingle.java
new file mode 100644
index 000000000..47ee89c4b
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/CacheIdLookupSingle.java
@@ -0,0 +1,38 @@
+package io.ebeaninternal.api;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Used for bean cache lookup with a single id value.
+ */
+public class CacheIdLookupSingle implements CacheIdLookup {
+
+ private final Object idValue;
+ private boolean found;
+
+ public CacheIdLookupSingle(Object idValue) {
+ this.idValue = idValue;
+ }
+
+ @Override
+ public Collection> idValues() {
+ return Collections.singleton(idValue);
+ }
+
+ @Override
+ public List removeHits(BeanCacheResult cacheResult) {
+ final List> hits = cacheResult.hits();
+ if (hits.size() == 1) {
+ found = true;
+ return Collections.singletonList(hits.get(0).getBean());
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean allHits() {
+ return found;
+ }
+}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java b/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java
index 4ba56604f..51f02826c 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/LoadContext.java
@@ -16,7 +16,7 @@ public interface LoadContext {
/**
* Return the minimum batch size when using QueryIterator with query joins.
*/
- int getSecondaryQueriesMinBatchSize(int defaultQueryBatch);
+ int getSecondaryQueriesMinBatchSize();
/**
* Execute any secondary (+query) queries if there are any defined.
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java b/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java
index e00ad57f1..813121f92 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/NoopQueryPlanManager.java
@@ -8,6 +8,11 @@ import java.util.List;
class NoopQueryPlanManager implements QueryPlanManager {
+ @Override
+ public void setDefaultThreshold(long thresholdMicros) {
+ // do nothing
+ }
+
@Override
public SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan) {
return SpiQueryBindCapture.NOOP;
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java b/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java
index f9750c47a..da7ce2b43 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/QueryPlanManager.java
@@ -12,6 +12,11 @@ public interface QueryPlanManager {
QueryPlanManager NOOP = new NoopQueryPlanManager();
+ /**
+ * Update the global default threshold used when new query plans are created.
+ */
+ void setDefaultThreshold(long thresholdMicros);
+
/**
* Create the bind capture for the given query plan.
*/
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/ScopeTrans.java b/ebean-core/src/main/java/io/ebeaninternal/api/ScopeTrans.java
index 5fbb03f25..631b6dfb8 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/ScopeTrans.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/ScopeTrans.java
@@ -73,6 +73,10 @@ public class ScopeTrans {
restoreBatchGeneratedKeys = transaction.getBatchGetGeneratedKeys();
restoreBatchFlushOnQuery = transaction.isFlushOnQuery();
}
+ Boolean autoPersistUpdates = txScope.getAutoPersistUpdates();
+ if (autoPersistUpdates != null) {
+ transaction.setAutoPersistUpdates(autoPersistUpdates);
+ }
if (txScope.isBatchSet()) {
transaction.setBatchMode(txScope.isBatchMode());
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQueryFetch.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQueryFetch.java
index 396827978..716dbc076 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQueryFetch.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQueryFetch.java
@@ -1,6 +1,7 @@
package io.ebeaninternal.api;
import io.ebean.FetchConfig;
+import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import java.util.Set;
@@ -19,4 +20,8 @@ public interface SpiQueryFetch {
*/
void fetchProperties(String name, Set properties, FetchConfig config);
+ /**
+ * Add a nested fetch graph.
+ */
+ void addNested(String name, OrmQueryDetail nestedDetail, FetchConfig config);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java
index 74faeccb0..8c59fa7dc 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java
@@ -147,6 +147,11 @@ public interface SpiTransaction extends Transaction {
*/
int depth();
+ /**
+ * Return true if dirty beans are automatically persisted.
+ */
+ boolean isAutoPersistUpdates();
+
/**
* Return true if this transaction was created explicitly via
* Ebean.beginTransaction().
diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java
index fe5351ddc..5fd2a3c6e 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java
@@ -43,6 +43,16 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.getLabel();
}
+ @Override
+ public void setAutoPersistUpdates(boolean autoPersistUpdates) {
+ transaction.setAutoPersistUpdates(autoPersistUpdates);
+ }
+
+ @Override
+ public boolean isAutoPersistUpdates() {
+ return transaction.isAutoPersistUpdates();
+ }
+
@Override
public void commitAndContinue() {
transaction.commitAndContinue();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java
new file mode 100644
index 000000000..051d331ba
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultQueryPlanListener.java
@@ -0,0 +1,25 @@
+package io.ebeaninternal.server.core;
+
+import io.ebean.config.QueryPlanCapture;
+import io.ebean.config.QueryPlanListener;
+import io.ebean.meta.MetaQueryPlan;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class DefaultQueryPlanListener implements QueryPlanListener {
+
+ static final QueryPlanListener INSTANT = new DefaultQueryPlanListener();
+
+ private static final Logger log = LoggerFactory.getLogger("io.ebean.QUERYPLAN");
+
+ @Override
+ public void process(QueryPlanCapture capture) {
+ // better to log this in JSON form?
+ String dbName = capture.getDatabase().getName();
+ for (MetaQueryPlan plan : capture.getPlans()) {
+ log.info("queryPlan db:{} label:{} queryTimeMicros:{} loc:{} sql:{} bind:{} plan:{}",
+ dbName, plan.getLabel(), plan.getQueryTimeMicros(), plan.getProfileLocation(),
+ plan.getSql(), plan.getBind(), plan.getPlan());
+ }
+ }
+}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
index 34a094301..6595d9816 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java
@@ -45,12 +45,7 @@ import io.ebean.bean.PersistenceContext.WithOption;
import io.ebean.bean.SingleBeanLoader;
import io.ebean.cache.ServerCacheManager;
import io.ebean.common.CopyOnFirstWriteList;
-import io.ebean.config.CurrentTenantProvider;
-import io.ebean.config.DatabaseConfig;
-import io.ebean.config.EncryptKeyManager;
-import io.ebean.config.SlowQueryEvent;
-import io.ebean.config.SlowQueryListener;
-import io.ebean.config.TenantMode;
+import io.ebean.config.*;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.event.BeanPersistController;
import io.ebean.event.ShutdownManager;
@@ -125,6 +120,7 @@ import io.ebeanservice.docstore.api.DocStoreIntegration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.annotation.Nonnull;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
@@ -137,7 +133,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -145,6 +140,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -196,7 +192,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final SpiLogManager logManager;
private final PersistenceContextScope defaultPersistenceContextScope;
private final int lazyLoadBatchSize;
- private final int queryBatchSize;
private final boolean updateAllPropertiesInBatch;
private final long slowQueryMicros;
private final SlowQueryListener slowQueryListener;
@@ -217,7 +212,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.extraMetrics = config.getExtraMetrics();
this.serverName = this.config.getName();
this.lazyLoadBatchSize = this.config.getLazyLoadBatchSize();
- this.queryBatchSize = this.config.getQueryBatchSize();
this.cqueryEngine = config.getCQueryEngine();
this.expressionFactory = config.getExpressionFactory();
this.encryptKeyManager = this.config.getEncryptKeyManager();
@@ -242,7 +236,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.clockService = config.getClockService();
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
- this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
+ this.transactionManager = config.createTransactionManager(this, docStoreComponents.updateProcessor());
this.documentStore = docStoreComponents.documentStore();
this.queryPlanManager = config.initQueryPlanManager(transactionManager);
this.metaInfoManager = new DefaultMetaInfoManager(this);
@@ -305,10 +299,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return lazyLoadBatchSize;
}
- public int getQueryBatchSize() {
- return queryBatchSize;
- }
-
@Override
public Object currentTenantId() {
return currentTenantProvider == null ? null : currentTenantProvider.currentId();
@@ -410,6 +400,31 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
migrationRunner.loadProperties(config.getProperties());
migrationRunner.run(config.getDataSource());
}
+ startQueryPlanCapture();
+ }
+
+ private void startQueryPlanCapture() {
+ if (config.isQueryPlanCapture()) {
+ long secs = config.getQueryPlanCapturePeriodSecs();
+ if (secs > 10) {
+ logger.info("capture query plan enabled, every {}secs", secs);
+ backgroundExecutor.scheduleWithFixedDelay(this::collectQueryPlans, secs, secs, TimeUnit.SECONDS);
+ }
+ }
+ }
+
+ private void collectQueryPlans() {
+ QueryPlanRequest request = new QueryPlanRequest();
+ request.setMaxCount(config.getQueryPlanCaptureMaxCount());
+ request.setMaxTimeMillis(config.getQueryPlanCaptureMaxTimeMillis());
+
+ // obtains query explain plans ...
+ List plans = metaInfoManager.queryPlanCollectNow(request);
+ QueryPlanListener listener = config.getQueryPlanListener();
+ if (listener == null) {
+ listener = DefaultQueryPlanListener.INSTANT;
+ }
+ listener.process(new QueryPlanCapture(this, plans));
}
@Override
@@ -1150,6 +1165,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
+ @Nonnull
@Override
public Optional findOneOrEmpty(Query query, Transaction transaction) {
return Optional.ofNullable(findOne(query, transaction));
@@ -1180,6 +1196,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
+ @Nonnull
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Set findSet(Query query, Transaction t) {
@@ -1196,6 +1213,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
+ @Nonnull
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Map findMap(Query query, Transaction t) {
@@ -1217,6 +1235,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
+ @Nonnull
@Override
@SuppressWarnings("unchecked")
public List findSingleAttributeList(Query query, Transaction t) {
@@ -1227,7 +1246,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
try {
request.initTransIfRequired();
- return (List) request.findSingleAttributeList();
+ return request.findSingleAttributeList();
} finally {
request.endTransIfRequired();
}
@@ -1277,6 +1296,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
+ @Nonnull
@Override
public List findIds(Query query, Transaction t) {
return findIdsWithCopy(((SpiQuery) query).copy(), t);
@@ -1337,26 +1357,29 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
+ @Nonnull
@Override
public FutureRowCount findFutureCount(Query q, Transaction t) {
SpiQuery copy = ((SpiQuery) q).copy();
copy.setFutureFetch(true);
Transaction newTxn = createTransaction();
- QueryFutureRowCount queryFuture = new QueryFutureRowCount<>(new CallableQueryCount(this, copy, newTxn));
+ QueryFutureRowCount queryFuture = new QueryFutureRowCount<>(new CallableQueryCount<>(this, copy, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
+ @Nonnull
@Override
public FutureIds findFutureIds(Query query, Transaction t) {
SpiQuery copy = ((SpiQuery) query).copy();
copy.setFutureFetch(true);
Transaction newTxn = createTransaction();
- QueryFutureIds queryFuture = new QueryFutureIds<>(new CallableQueryIds(this, copy, newTxn));
+ QueryFutureIds queryFuture = new QueryFutureIds<>(new CallableQueryIds<>(this, copy, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
+ @Nonnull
@Override
public FutureList findFutureList(Query query, Transaction t) {
SpiQuery spiQuery = (SpiQuery) query;
@@ -1369,11 +1392,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
// Create a new transaction solely to execute the findList() at some future time
Transaction newTxn = createTransaction();
- QueryFutureList queryFuture = new QueryFutureList<>(new CallableQueryList(this, spiQuery, newTxn));
+ QueryFutureList queryFuture = new QueryFutureList<>(new CallableQueryList<>(this, spiQuery, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
+ @Nonnull
@Override
public PagedList findPagedList(Query query, Transaction transaction) {
SpiQuery spiQuery = (SpiQuery) query;
@@ -1387,6 +1411,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new LimitOffsetPagedList<>(this, spiQuery);
}
+ @Nonnull
@Override
public QueryIterator findIterate(Query query, Transaction t) {
SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, t);
@@ -1399,11 +1424,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
+ @Nonnull
@Override
public Stream findLargeStream(Query query, Transaction transaction) {
return findStream(query, transaction);
}
+ @Nonnull
@Override
public Stream findStream(Query query, Transaction transaction) {
SpiOrmQueryRequest request = createQueryRequest(Type.ITERATE, query, transaction);
@@ -1470,6 +1497,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
// no try finally - findEachWhile guarantee's cleanup of the transaction if required
}
+ @Nonnull
@Override
public List> findVersions(Query query, Transaction transaction) {
SpiOrmQueryRequest request = createQueryRequest(Type.LIST, query, transaction);
@@ -1481,6 +1509,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
+ @Nonnull
@Override
public List findList(Query query, Transaction t) {
return findList(query, t, false);
@@ -1540,6 +1569,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
+ @Nonnull
@Override
public List findList(SqlQuery query, Transaction t) {
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, t);
@@ -2241,6 +2271,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return checkUniqueness(bean, null);
}
+ @Nonnull
@Override
public Set checkUniqueness(Object bean, Transaction transaction) {
EntityBean entityBean = checkEntityBean(bean);
@@ -2252,14 +2283,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
Object id = idProperty.getVal(entityBean);
if (entityBean._ebean_getIntercept().isNew() && id != null) {
- // Primary Key is changeable only on new models - so skip check if we are not
- // new.
+ // Primary Key is changeable only on new models - so skip check if we are not new
Query> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
query.setId(id);
if (findCount(query, transaction) > 0) {
- Set ret = new HashSet<>();
- ret.add(idProperty);
- return ret;
+ return Collections.singleton(idProperty);
}
}
for (BeanProperty[] props : beanDesc.getUniqueProps()) {
@@ -2322,6 +2350,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
List queryPlanInit(QueryPlanInit initRequest) {
+ if (initRequest.isAll()) {
+ queryPlanManager.setDefaultThreshold(initRequest.getThresholdMicros());
+ }
return beanDescriptorManager.queryPlanInit(initRequest);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java
index 120e98a27..2372ed7c3 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DtoQueryRequest.java
@@ -23,6 +23,9 @@ import java.util.function.Predicate;
*/
public final class DtoQueryRequest extends AbstractSqlQueryRequest {
+ private static final String ENC_PREFIX = EncryptAlias.PREFIX;
+ private static final String ENC_PREFIX_UPPER = EncryptAlias.PREFIX.toUpperCase();
+
private final SpiDtoQuery query;
private final DtoQueryEngine queryEngine;
@@ -133,9 +136,9 @@ public final class DtoQueryRequest extends AbstractSqlQueryRequest {
}
static String parseColumn(String columnLabel) {
- if (columnLabel.startsWith("_e_") || columnLabel.startsWith("_E_")) {
+ if (columnLabel.startsWith(ENC_PREFIX) || columnLabel.startsWith(ENC_PREFIX_UPPER)) {
// encrypted column alias in the form _e__
- final int pos = columnLabel.indexOf("_", 3);
+ final int pos = columnLabel.indexOf("_", 4);
if (pos > -1) {
return columnLabel.substring(pos + 1);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/EncryptAlias.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/EncryptAlias.java
new file mode 100644
index 000000000..e73a2a9e9
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/EncryptAlias.java
@@ -0,0 +1,8 @@
+package io.ebeaninternal.server.core;
+
+/**
+ * Used to create column alias for encrypted columns.
+ */
+public interface EncryptAlias {
+ String PREFIX = "zx__";
+}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
index 56ea7589a..dca8e3059 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/InternalConfiguration.java
@@ -423,13 +423,13 @@ public class InternalConfiguration {
/**
* Create the TransactionManager taking into account autoCommit mode.
*/
- TransactionManager createTransactionManager(DocStoreUpdateProcessor indexUpdateProcessor) {
+ TransactionManager createTransactionManager(SpiServer server, DocStoreUpdateProcessor indexUpdateProcessor) {
TransactionScopeManager scopeManager = createTransactionScopeManager();
boolean notifyL2CacheInForeground = cacheManager.isLocalL2Caching() || config.isNotifyL2CacheInForeground();
TransactionManagerOptions options =
- new TransactionManagerOptions(notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
+ new TransactionManagerOptions(server, notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
tableModState, cacheNotify, clockService);
@@ -617,10 +617,10 @@ public class InternalConfiguration {
}
public QueryPlanManager initQueryPlanManager(TransactionManager transactionManager) {
- if (!config.isCollectQueryPlans()) {
+ if (!config.isQueryPlanEnable()) {
return QueryPlanManager.NOOP;
}
- long threshold = config.getCollectQueryPlanThresholdMicros();
+ long threshold = config.getQueryPlanThresholdMicros();
return new CQueryPlanManager(transactionManager, threshold, queryPlanLogger(databasePlatform.getPlatform()), extraMetrics);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
index 9b0e1235e..3ec5deac7 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java
@@ -173,8 +173,8 @@ public final class OrmQueryRequest extends BeanRequest implements SpiOrmQuery
* iteration is fine.
*
*/
- public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) {
- return loadContext.getSecondaryQueriesMinBatchSize(defaultQueryBatch);
+ public int getSecondaryQueriesMinBatchSize() {
+ return loadContext.getSecondaryQueriesMinBatchSize();
}
/**
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
index eb48fc2ef..ce6a39878 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java
@@ -887,6 +887,14 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
}
}
+ /**
+ * Remove deleted beans from the persistence context early.
+ */
+ public void removeFromPersistenceContext() {
+ idValue = beanDescriptor.getId(entityBean);
+ beanDescriptor.contextDeleted(transaction.getPersistenceContext(), idValue);
+ }
+
/**
* Aggressive L1 and L2 cache cleanup for deletes.
*/
@@ -1030,6 +1038,17 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
if (!publish) {
beanDescriptor.setDraft(entityBean);
}
+ if (transaction.isAutoPersistUpdates() && idValue != null) {
+ // with getGeneratedKeys off we will not have a idValue
+ beanDescriptor.contextPut(transaction.getPersistenceContext(), idValue, entityBean);
+ }
+ }
+
+ /**
+ * Return if persist can be skipped on the reference only bean.
+ */
+ public boolean isSkipReference() {
+ return intercept.isReference() || (Flags.isRecurse(flags) && beanDescriptor.referenceIdPropertyOnly(intercept));
}
public boolean isReference() {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
index a2d902f13..a6841d449 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -2473,7 +2473,7 @@ public class BeanDescriptor implements BeanType, STreeType {
* Return the property path given the db table and column.
*/
public String findBeanPath(String schemaName, String tableName, String columnName) {
- if (matchBaseTable(schemaName, tableName)) {
+ if (matchBaseTable(tableName)) {
return columnPath.get(columnName);
}
BeanPropertyAssoc> assocProperty = tablePath.get(tableName);
@@ -2489,10 +2489,10 @@ public class BeanDescriptor implements BeanType, STreeType {
return null;
}
- private boolean matchBaseTable(String schemaName, String tableName) {
+ boolean matchBaseTable(String tableName) {
return tableName.isEmpty()
|| baseTable.equalsIgnoreCase(tableName)
- || baseTable.equalsIgnoreCase(schemaName + "." + tableName);
+ || baseTable.endsWith("." + tableName);
}
/**
@@ -3134,7 +3134,7 @@ public class BeanDescriptor implements BeanType, STreeType {
return ebi.isReference() || referenceIdPropertyOnly(ebi);
}
- boolean referenceIdPropertyOnly(EntityBeanIntercept ebi) {
+ public boolean referenceIdPropertyOnly(EntityBeanIntercept ebi) {
return idOnlyReference && ebi.hasIdOnly(idPropertyIndex);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java
index 0cb5deeaa..dca6b2b09 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java
@@ -93,6 +93,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptorManager.class);
private static final BeanDescComparator beanDescComparator = new BeanDescComparator();
+ public static final String JAVA_LANG_RECORD = "java.lang.Record";
private final ReadAnnotations readAnnotations;
private final TransientProperties transientProperties;
@@ -1418,14 +1419,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
private void checkInheritedClasses(Class> beanClass) {
Class> superclass = beanClass.getSuperclass();
- if (Object.class.equals(superclass)) {
+ if (Object.class.equals(superclass) || Model.class.equals(superclass) || JAVA_LANG_RECORD.equals(superclass.getName())) {
// we got to the top of the inheritance
return;
}
- if (Model.class.equals(superclass)) {
- // top of the inheritance. Not enhancing Model at this stage
- return;
- }
if (!EntityBean.class.isAssignableFrom(superclass)) {
if (isMappedSuperWithNoProperties(superclass)) {
// ok to stop and treat just the same as Object.class
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java
index d3b1f429b..8793942b6 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java
@@ -18,6 +18,7 @@ import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
+import io.ebeaninternal.server.core.EncryptAlias;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedWhenCreated;
@@ -55,8 +56,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
-import static io.ebean.util.StringHelper.replace;
-
/**
* Description of a property of a bean. Includes its deployment information such
* as database column mapping information.
@@ -65,6 +64,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class);
+ private static final String ENC_PREFIX = " " + EncryptAlias.PREFIX;
+
/**
* Flag to mark this is the id property.
*/
@@ -353,13 +354,13 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
}
private String tableAliasIntern(BeanDescriptor> descriptor, String s, boolean dbEncrypted, String dbColumn) {
- if (descriptor != null) {
- s = replace(s, "${ta}.", "${}");
- s = replace(s, "${ta}", "${}");
+ if (s != null && descriptor != null) {
+ s = s.replace("${ta}.", "${}");
+ s = s.replace("${ta}", "${}");
if (dbEncrypted) {
s = dbEncryptFunction.getDecryptSql(s);
String namedParam = ":encryptkey_" + descriptor.getBaseTable() + "___" + dbColumn;
- s = replace(s, "?", namedParam);
+ s = s.replace("?", namedParam);
}
}
return InternString.intern(s);
@@ -531,7 +532,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
* Return the SQL for the column including decryption function and column alias.
*/
private String getDecryptSqlWithColumnAlias(String tableAlias) {
- return dbEncryptFunction.getDecryptSql(tableAlias + "." + this.getDbColumn()) + " _e_" + tableAlias + "_" + this.getDbColumn();
+ return dbEncryptFunction.getDecryptSql(tableAlias + "." + this.getDbColumn()) + ENC_PREFIX + tableAlias + "_" + this.getDbColumn();
}
@Override
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java
index 398713a18..06a2a2145 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManySqlHelp.java
@@ -81,18 +81,18 @@ class BeanPropertyAssocManySqlHelp {
@Override
public void visitOneImported(BeanPropertyAssocOne> p) {
-
+ // do nothing
}
@Override
- public void visitScalar(BeanProperty p) {
+ public void visitScalar(BeanProperty p, boolean allowNonNull) {
sb.append(",").append(p.getDbColumn());
colCount++;
}
@Override
public void visitEnd() {
-
+ // do nothing
}
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/BaseTablePropertyVisitor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/BaseTablePropertyVisitor.java
index cc23a5c1f..e6741d12a 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/BaseTablePropertyVisitor.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/BaseTablePropertyVisitor.java
@@ -44,9 +44,11 @@ public abstract class BaseTablePropertyVisitor implements BeanPropertyVisitor {
public abstract void visitOneImported(BeanPropertyAssocOne> p);
/**
- * Override this method for normal scalar property.
+ * Override this method for normal scalar property. With allowNonNull
+ * used with properties of Embedded beans that can only be Nonnull when
+ * the Embedded bean is Nonnull.
*/
@Override
- public abstract void visitScalar(BeanProperty p);
+ public abstract void visitScalar(BeanProperty p, boolean allowNonNull);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/BeanPropertyVisitor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/BeanPropertyVisitor.java
index 1cadc2b1b..dd0dd7c6e 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/BeanPropertyVisitor.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/BeanPropertyVisitor.java
@@ -40,8 +40,8 @@ public interface BeanPropertyVisitor {
void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne> embedded);
/**
- * Visit a scalar property.
+ * Visit a scalar property specify allowing non-null (e.g. embedded bean scalar properties).
*/
- void visitScalar(BeanProperty p);
+ void visitScalar(BeanProperty p, boolean allowNonNull);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/VisitProperties.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/VisitProperties.java
index deab1104c..5363b0add 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/VisitProperties.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/visitor/VisitProperties.java
@@ -72,7 +72,7 @@ public class VisitProperties {
} else {
// simple scalar type
- pv.visitScalar(p);
+ pv.visitScalar(p, true);
}
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBaseContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBaseContext.java
index 1be4717e9..24233cf87 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBaseContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBaseContext.java
@@ -33,7 +33,7 @@ abstract class DLoadBaseContext {
final boolean queryFetch;
- DLoadBaseContext(DLoadContext parent, BeanDescriptor> desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) {
+ DLoadBaseContext(DLoadContext parent, BeanDescriptor> desc, String path, OrmQueryProperties queryProps) {
this.parent = parent;
this.serverName = parent.getEbeanServer().getName();
this.desc = desc;
@@ -42,11 +42,7 @@ abstract class DLoadBaseContext {
this.hitCache = parent.isBeanCacheGet() && desc.isBeanCaching();
this.objectGraphNode = parent.getObjectGraphNode(path);
this.queryFetch = queryProps != null && queryProps.isQueryFetch();
- this.batchSize = initBatchSize(defaultBatchSize, queryProps);
- }
-
- private int initBatchSize(int batchSize, OrmQueryProperties queryProps) {
- return queryProps == null ? batchSize : queryProps.getBatchSize();
+ this.batchSize = parent.batchSize(queryProps);
}
/**
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java
index d24a24532..25fffe5b7 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadBeanContext.java
@@ -31,8 +31,8 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
private LoadBuffer currentBuffer;
- DLoadBeanContext(DLoadContext parent, BeanDescriptor> desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) {
- super(parent, desc, path, defaultBatchSize, queryProps);
+ DLoadBeanContext(DLoadContext parent, BeanDescriptor> desc, String path, OrmQueryProperties queryProps) {
+ super(parent, desc, path, queryProps);
// bufferList only required when using query joins (queryFetch)
this.bufferList = (!queryFetch) ? null : new ArrayList<>();
this.currentBuffer = createBuffer(batchSize);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java
index b5aa6f873..060633073 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadContext.java
@@ -90,7 +90,7 @@ public class DLoadContext implements LoadContext {
this.planLabel = null;
this.profileLocation = null;
this.profilingListener = null;
- this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
+ this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, null);
}
private ObjectGraphOrigin initOrigin() {
@@ -128,7 +128,7 @@ public class DLoadContext implements LoadContext {
}
// initialise rootBeanContext after origin and relativePath have been set
- this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
+ this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, null);
registerSecondaryQueries(secondaryQueries);
}
@@ -182,7 +182,7 @@ public class DLoadContext implements LoadContext {
* Return the minimum batch size when using QueryIterator with query joins.
*/
@Override
- public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) {
+ public int getSecondaryQueriesMinBatchSize() {
if (secQuery == null) {
return -1;
}
@@ -190,7 +190,7 @@ public class DLoadContext implements LoadContext {
for (OrmQueryProperties aSecQuery : secQuery) {
int batchSize = aSecQuery.getBatchSize();
if (batchSize == 0) {
- batchSize = defaultQueryBatch;
+ batchSize = 100;
}
maxBatch = Math.max(maxBatch, batchSize);
}
@@ -287,51 +287,55 @@ public class DLoadContext implements LoadContext {
getManyContext(path, many).register(bc);
}
+ int batchSize(OrmQueryProperties props) {
+ if (props == null) {
+ return defaultBatchSize;
+ }
+ int batchSize = props.getBatchSize();
+ return batchSize == 0 ? defaultBatchSize : batchSize;
+ }
+
DLoadBeanContext getBeanContext(String path) {
if (path == null) {
return rootBeanContext;
}
- return beanMap.computeIfAbsent(path, p -> createBeanContext(p, defaultBatchSize, null));
+ return beanMap.computeIfAbsent(path, p -> createBeanContext(p, null));
}
DLoadBeanContext getBeanContextWithInherit(String path, BeanPropertyAssocOne> property) {
String key = path + ":" + property.getTargetDescriptor().getName();
- return beanMap.computeIfAbsent(key, p -> createBeanContext(property, path, defaultBatchSize, null));
+ return beanMap.computeIfAbsent(key, p -> createBeanContext(property, path, null));
}
private void registerSecondaryNode(boolean many, OrmQueryProperties props) {
- int batchSize = props.getBatchSize();
- if (batchSize == 0) {
- batchSize = defaultBatchSize;
- }
String path = props.getPath();
if (many) {
- manyMap.put(path, createManyContext(path, batchSize, props));
+ manyMap.put(path, createManyContext(path, props));
} else {
- beanMap.put(path, createBeanContext(path, batchSize, props));
+ beanMap.put(path, createBeanContext(path, props));
}
}
DLoadManyContext getManyContext(String path, BeanPropertyAssocMany> many) {
- return manyMap.computeIfAbsent(path, p -> createManyContext(p, many, defaultBatchSize));
+ return manyMap.computeIfAbsent(path, p -> createManyContext(p, many));
}
- private DLoadManyContext createManyContext(String path, BeanPropertyAssocMany> many, int batchSize) {
- return new DLoadManyContext(this, many, path, batchSize, null);
+ private DLoadManyContext createManyContext(String path, BeanPropertyAssocMany> many) {
+ return new DLoadManyContext(this, many, path, null);
}
- private DLoadManyContext createManyContext(String path, int batchSize, OrmQueryProperties queryProps) {
+ private DLoadManyContext createManyContext(String path, OrmQueryProperties queryProps) {
BeanPropertyAssocMany> p = (BeanPropertyAssocMany>) getBeanProperty(rootDescriptor, path);
- return new DLoadManyContext(this, p, path, batchSize, queryProps);
+ return new DLoadManyContext(this, p, path, queryProps);
}
- private DLoadBeanContext createBeanContext(String path, int batchSize, OrmQueryProperties queryProps) {
+ private DLoadBeanContext createBeanContext(String path, OrmQueryProperties queryProps) {
BeanPropertyAssoc> p = (BeanPropertyAssoc>) getBeanProperty(rootDescriptor, path);
- return new DLoadBeanContext(this, p.getTargetDescriptor(), path, batchSize, queryProps);
+ return new DLoadBeanContext(this, p.getTargetDescriptor(), path, queryProps);
}
- private DLoadBeanContext createBeanContext(BeanPropertyAssoc> property, String path, int batchSize, OrmQueryProperties queryProps) {
- return new DLoadBeanContext(this, property.getTargetDescriptor(), path, batchSize, queryProps);
+ private DLoadBeanContext createBeanContext(BeanPropertyAssoc> property, String path, OrmQueryProperties queryProps) {
+ return new DLoadBeanContext(this, property.getTargetDescriptor(), path, queryProps);
}
private BeanProperty getBeanProperty(BeanDescriptor> desc, String path) {
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java
index 7a51127d8..5035f0802 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/loadcontext/DLoadManyContext.java
@@ -31,11 +31,8 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
private LoadBuffer currentBuffer;
- DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany> property,
- String path, int defaultBatchSize, OrmQueryProperties queryProps) {
-
- super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps);
-
+ DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany> property, String path, OrmQueryProperties queryProps) {
+ super(parent, property.getBeanDescriptor(), path, queryProps);
this.property = property;
this.docStoreMapped = property.isTargetDocStoreMapped();
// bufferList only required when using query joins (queryFetch)
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchControl.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchControl.java
index 10ba7f205..f276bb331 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchControl.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchControl.java
@@ -81,7 +81,7 @@ public final class BatchControl {
*/
private int bufferMax;
- private Queue[] queues = new Queue[3];
+ private final Queue[] queues = new Queue[3];
/**
* Create for a given transaction, PersistExecute, default size and getGeneratedKeys.
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
index a7b21f0c8..c63b7ed9a 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java
@@ -447,7 +447,7 @@ public final class DefaultPersister implements Persister {
public void insert(EntityBean bean, Transaction t) {
PersistRequestBean> req = createRequest(bean, t, PersistRequest.Type.INSERT);
- if (req.isReference()) {
+ if (req.isSkipReference()) {
// skip insert on reference bean
return;
}
@@ -881,6 +881,7 @@ public final class DefaultPersister implements Persister {
}
int count = request.executeOrQueue();
+ request.removeFromPersistenceContext();
if (request.isPersistCascade()) {
deleteAssocOne(request);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBindCapture.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBindCapture.java
index 713169640..e3a1a5e54 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBindCapture.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBindCapture.java
@@ -9,7 +9,7 @@ import java.util.concurrent.locks.ReentrantLock;
class CQueryBindCapture implements SpiQueryBindCapture {
- private static final double multiplier = 1.3d;
+ private static final double multiplier = 1.5d;
private final ReentrantLock lock = new ReentrantLock();
private final CQueryPlanManager manager;
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java
index 945b343ca..6e4804ca6 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java
@@ -37,8 +37,6 @@ public class CQueryEngine {
private static final Logger logger = LoggerFactory.getLogger(CQueryEngine.class);
- private static final int defaultSecondaryQueryBatchSize = 100;
-
private static final String T0 = "t0";
private final int defaultFetchSizeFindList;
@@ -214,7 +212,7 @@ public class CQueryEngine {
logSql(cquery);
}
// first check batch sizes set on query joins
- int iterateBufferSize = request.getSecondaryQueriesMinBatchSize(defaultSecondaryQueryBatchSize);
+ int iterateBufferSize = request.getSecondaryQueriesMinBatchSize();
if (iterateBufferSize < 1) {
// not set on query joins so check if batch size set on query itself
int queryBatch = request.getQuery().getLazyLoadBatchSize();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java
index c2e6aa358..4ff62f44d 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanManager.java
@@ -30,14 +30,14 @@ public class CQueryPlanManager implements QueryPlanManager {
private final TransactionManager transactionManager;
- private final long defaultThreshold;
-
private final QueryPlanLogger planLogger;
private final TimedMetric timeCollection;
private final TimedMetric timeBindCapture;
+ private long defaultThreshold;
+
public CQueryPlanManager(TransactionManager transactionManager, long defaultThreshold, QueryPlanLogger planLogger, ExtraMetrics extraMetrics) {
this.transactionManager = transactionManager;
this.defaultThreshold = defaultThreshold;
@@ -46,6 +46,11 @@ public class CQueryPlanManager implements QueryPlanManager {
this.timeBindCapture = extraMetrics.getBindCapture();
}
+ @Override
+ public void setDefaultThreshold(long thresholdMicros) {
+ this.defaultThreshold = thresholdMicros;
+ }
+
@Override
public SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan) {
return new CQueryBindCapture(this, queryPlan, defaultThreshold);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java
index 5b2ed841a..9f7cb9b7e 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/DefaultFetchGroupQuery.java
@@ -644,4 +644,9 @@ class DefaultFetchGroupQuery implements SpiFetchGroupQuery, SpiQueryFetch
public void fetchProperties(String property, Set columns, FetchConfig config) {
detail.fetchProperties(property, columns, config);
}
+
+ @Override
+ public void addNested(String name, OrmQueryDetail nestedDetail, FetchConfig config) {
+ detail.addNested(name, nestedDetail, config);
+ }
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
index 074f364bc..867e16dfa 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java
@@ -35,6 +35,8 @@ import io.ebean.plugin.BeanType;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.CacheIdLookup;
+import io.ebeaninternal.api.CacheIdLookupMany;
+import io.ebeaninternal.api.CacheIdLookupSingle;
import io.ebeaninternal.api.HashQuery;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.NaturalKeyQueryData;
@@ -740,10 +742,14 @@ public class DefaultOrmQuery implements SpiQuery {
return null;
}
List underlyingList = whereExpressions.getUnderlyingList();
- if (underlyingList.size() == 1) {
+ if (underlyingList.isEmpty()) {
+ if (id != null) {
+ return new CacheIdLookupSingle<>(id);
+ }
+ } else if (underlyingList.size() == 1) {
SpiExpression singleExpression = underlyingList.get(0);
if (singleExpression instanceof IdInExpression) {
- return new CacheIdLookup<>((IdInExpression) singleExpression);
+ return new CacheIdLookupMany<>((IdInExpression) singleExpression);
}
}
return null;
@@ -1412,6 +1418,11 @@ public class DefaultOrmQuery implements SpiQuery {
detail.fetchProperties(path, other);
}
+ @Override
+ public void addNested(String name, OrmQueryDetail nestedDetail, FetchConfig config) {
+ detail.addNested(name, nestedDetail, config);
+ }
+
@Override
public Query select(String columns) {
detail.select(columns);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java
index d1a3aeafc..466bb3eb9 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryProperties.java
@@ -120,8 +120,8 @@ public class OrmQueryProperties implements Serializable {
this.parentPath = SplitName.parent(path);
this.allProperties = other.allProperties;
this.included = other.included;
- this.cache = other.cache;
this.fetchConfig = fetchConfig;
+ this.cache = fetchConfig.isCache();
}
/**
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java
index df92246fa..27eea2e4c 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/DefaultPersistenceContext.java
@@ -1,11 +1,9 @@
package io.ebeaninternal.server.transaction;
+import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -200,6 +198,20 @@ public final class DefaultPersistenceContext implements PersistenceContext {
}
}
+ @Override
+ public List dirtyBeans() {
+ lock.lock();
+ try {
+ List list = new ArrayList<>();
+ for (ClassContext classContext : typeCache.values()) {
+ classContext.dirtyBeans(list);
+ }
+ return list;
+ } finally {
+ lock.unlock();
+ }
+ }
+
@Override
public String toString() {
lock.lock();
@@ -318,6 +330,17 @@ public final class DefaultPersistenceContext implements PersistenceContext {
deleteSet.add(id);
map.remove(id);
}
+
+ /**
+ * Add the dirty beans to the list.
+ */
+ void dirtyBeans(List list) {
+ for (Object value : map.values()) {
+ if (((EntityBean) value)._ebean_getIntercept().isDirty()) {
+ list.add(value);
+ }
+ }
+ }
}
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
index 79bf85335..5f2b5d195 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java
@@ -95,6 +95,16 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
return startNanos;
}
+ @Override
+ public void setAutoPersistUpdates(boolean autoPersistUpdates) {
+ // do nothing
+ }
+
+ @Override
+ public boolean isAutoPersistUpdates() {
+ return false;
+ }
+
@Override
public void setLabel(String label) {
// do nothing
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
index 8c1897ff5..7d3b3ddf1 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java
@@ -187,6 +187,8 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
private final long startNanos;
+ private boolean autoPersistUpdates;
+
/**
* Create a new JdbcTransaction.
*/
@@ -209,11 +211,12 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.batchOnCascadeMode = false;
this.onQueryOnly = OnQueryOnly.ROLLBACK;
} else {
+ this.autoPersistUpdates = explicit && manager.isAutoPersistUpdates();
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.skipCacheAfterWrite = manager.isSkipCacheAfterWrite();
- this.batchMode = manager.getPersistBatch();
- this.batchOnCascadeMode = manager.getPersistBatchOnCascade();
+ this.batchMode = manager.isPersistBatch();
+ this.batchOnCascadeMode = manager.isPersistBatchOnCascade();
this.onQueryOnly = manager.getOnQueryOnly();
}
@@ -294,6 +297,17 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
return sb.toString();
}
+ @Override
+ public void setAutoPersistUpdates(boolean autoPersistUpdates) {
+ this.autoPersistUpdates = autoPersistUpdates;
+ this.batchMode = true;
+ }
+
+ @Override
+ public boolean isAutoPersistUpdates() {
+ return autoPersistUpdates;
+ }
+
@Override
public boolean isSkipCacheExplicit() {
return (skipCache != null && !skipCache);
@@ -774,6 +788,10 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
* Flush the JDBC batch and execute derived relationship statements if necessary.
*/
private void internalBatchFlush() {
+ if (autoPersistUpdates) {
+ // Experimental - flush dirty beans held by the persistence context
+ manager.flushTransparent(persistenceContext, this);
+ }
batchFlush();
if (deferredList != null) {
for (PersistDeferredRelationship deferred : deferredList) {
@@ -1042,7 +1060,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
throw new IllegalStateException(illegalStateMessage);
}
try {
- if (queryOnly) {
+ if (queryOnly && !autoPersistUpdates) {
connectionEndForQueryOnly();
} else {
flushCommitAndNotify();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
index 2ef3f2058..0d3c82cd6 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java
@@ -25,6 +25,16 @@ class NoTransaction implements SpiTransaction {
static final NoTransaction INSTANCE = new NoTransaction();
+ @Override
+ public void setAutoPersistUpdates(boolean autoPersistUpdates) {
+ // do nothing
+ }
+
+ @Override
+ public boolean isAutoPersistUpdates() {
+ return false;
+ }
+
@Override
public void setLabel(String label) {
// do nothing
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java
index e42ce2fe3..b71868e5d 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java
@@ -5,6 +5,7 @@ import io.ebean.ProfileLocation;
import io.ebean.TxScope;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxType;
+import io.ebean.bean.PersistenceContext;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.config.CurrentTenantProvider;
@@ -17,6 +18,7 @@ import io.ebean.meta.MetricVisitor;
import io.ebean.metric.MetricFactory;
import io.ebean.metric.TimedMetric;
import io.ebean.metric.TimedMetricMap;
+import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiLogManager;
@@ -59,6 +61,8 @@ public class TransactionManager implements SpiTransactionManager {
private static final Logger clusterLogger = LoggerFactory.getLogger("io.ebean.Cluster");
+ private final SpiServer server;
+
private final BeanDescriptorManager beanDescriptorManager;
/**
@@ -100,6 +104,8 @@ public class TransactionManager implements SpiTransactionManager {
*/
final DocStoreUpdateProcessor docStoreUpdateProcessor;
+ private final boolean autoPersistUpdates;
+
private final boolean persistBatch;
private final boolean persistBatchOnCascade;
@@ -153,7 +159,7 @@ public class TransactionManager implements SpiTransactionManager {
* Create the TransactionManager
*/
public TransactionManager(TransactionManagerOptions options) {
-
+ this.server = options.server;
this.logManager = options.logManager;
this.txnLogger = logManager.txn();
this.txnDebug = txnLogger.isDebug();
@@ -161,6 +167,7 @@ public class TransactionManager implements SpiTransactionManager {
this.supportsSavepointId = databasePlatform.isSupportsSavepointId();
this.skipCacheAfterWrite = options.config.isSkipCacheAfterWrite();
this.notifyL2CacheInForeground = options.notifyL2CacheInForeground;
+ this.autoPersistUpdates = options.config.isAutoPersistUpdates();
this.persistBatch = PersistBatch.ALL == options.config.getPersistBatch();
this.persistBatchOnCascade = PersistBatch.ALL == options.config.appliedPersistBatchOnCascade();
this.rollbackOnChecked = options.config.isTransactionRollbackOnChecked();
@@ -279,11 +286,15 @@ public class TransactionManager implements SpiTransactionManager {
return bulkEventListenerMap;
}
- boolean getPersistBatch() {
+ boolean isAutoPersistUpdates() {
+ return autoPersistUpdates;
+ }
+
+ boolean isPersistBatch() {
return persistBatch;
}
- public boolean getPersistBatchOnCascade() {
+ boolean isPersistBatchOnCascade() {
return persistBatchOnCascade;
}
@@ -786,4 +797,14 @@ public class TransactionManager implements SpiTransactionManager {
public boolean isLogSummary() {
return logManager.sum().isDebug();
}
+
+ /**
+ * Experimental - find dirty beans in the persistence context and persist them.
+ */
+ public void flushTransparent(PersistenceContext persistenceContext, SpiTransaction transaction) {
+ List dirtyBeans = persistenceContext.dirtyBeans();
+ if (!dirtyBeans.isEmpty()) {
+ server.updateAll(dirtyBeans, transaction);
+ }
+ }
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java
index 9a1b0139b..5d0c87794 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/TransactionManagerOptions.java
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.transaction;
import io.ebean.BackgroundExecutor;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.config.DatabaseConfig;
+import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.server.cluster.ClusterManager;
@@ -15,6 +16,7 @@ import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
*/
public class TransactionManagerOptions {
+ final SpiServer server;
final boolean notifyL2CacheInForeground;
final DatabaseConfig config;
final ClusterManager clusterManager;
@@ -31,11 +33,11 @@ public class TransactionManagerOptions {
final ClockService clockService;
- public TransactionManagerOptions(boolean notifyL2CacheInForeground, DatabaseConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
+ public TransactionManagerOptions(SpiServer server, boolean notifyL2CacheInForeground, DatabaseConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler,
SpiLogManager logManager, TableModState tableModState, ServerCacheNotify cacheNotify, ClockService clockService) {
-
+ this.server = server;
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
this.config = config;
this.scopeManager = scopeManager;
diff --git a/ebean-core/src/test/java/io/ebean/FetchConfigTest.java b/ebean-core/src/test/java/io/ebean/FetchConfigTest.java
index abd09d692..f85808f6c 100644
--- a/ebean-core/src/test/java/io/ebean/FetchConfigTest.java
+++ b/ebean-core/src/test/java/io/ebean/FetchConfigTest.java
@@ -1,6 +1,5 @@
package io.ebean;
-import io.ebean.FetchConfig;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -10,7 +9,7 @@ public class FetchConfigTest {
@Test
public void testLazy() {
FetchConfig config = new FetchConfig().lazy();
- assertThat(config.getBatchSize()).isEqualTo(10);
+ assertThat(config.getBatchSize()).isEqualTo(0);
}
@Test
diff --git a/ebean-core/src/test/java/io/ebean/config/ServerConfigTest.java b/ebean-core/src/test/java/io/ebean/config/ServerConfigTest.java
index 8fda93985..4e92798d6 100644
--- a/ebean-core/src/test/java/io/ebean/config/ServerConfigTest.java
+++ b/ebean-core/src/test/java/io/ebean/config/ServerConfigTest.java
@@ -72,10 +72,16 @@ public class ServerConfigTest {
props.setProperty("enabledL2Regions", "r0,users,orgs");
props.setProperty("caseSensitiveCollation", "false");
props.setProperty("loadModuleInfo", "true");
- props.setProperty("collectQueryPlanThresholdMicros", "10000");
props.setProperty("forUpdateNoKey", "true");
props.setProperty("defaultServer", "false");
+ props.setProperty("queryPlan.enable", "true");
+ props.setProperty("queryPlan.thresholdMicros", "10000");
+ props.setProperty("queryPlan.capture", "true");
+ props.setProperty("queryPlan.capturePeriodSecs", "42");
+ props.setProperty("queryPlan.captureMaxTimeMillis", "560");
+ props.setProperty("queryPlan.captureMaxCount", "7");
+
serverConfig.loadFromProperties(props);
assertFalse(serverConfig.isDefaultServer());
@@ -97,7 +103,6 @@ public class ServerConfigTest {
assertEquals(PlatformConfig.DbUuid.BINARY, serverConfig.getPlatformConfig().getDbUuid());
assertEquals(JsonConfig.DateTime.MILLIS, serverConfig.getJsonDateTime());
assertEquals(JsonConfig.Date.MILLIS, serverConfig.getJsonDate());
- assertEquals(10000, serverConfig.getCollectQueryPlanThresholdMicros());
assertEquals("r0,users,orgs", serverConfig.getEnabledL2Regions());
@@ -106,6 +111,13 @@ public class ServerConfigTest {
assertEquals(4, serverConfig.getBackgroundExecutorSchedulePoolSize());
assertEquals(98, serverConfig.getBackgroundExecutorShutdownSecs());
+ assertTrue(serverConfig.isQueryPlanEnable());
+ assertEquals(10000, serverConfig.getQueryPlanThresholdMicros());
+ assertTrue(serverConfig.isQueryPlanCapture());
+ assertEquals(42, serverConfig.getQueryPlanCapturePeriodSecs());
+ assertEquals(560, serverConfig.getQueryPlanCaptureMaxTimeMillis());
+ assertEquals(7, serverConfig.getQueryPlanCaptureMaxCount());
+
assertThat(serverConfig.getMappingLocations()).containsExactly("classpath:/foo","bar");
serverConfig.setPersistBatch(PersistBatch.NONE);
@@ -137,6 +149,7 @@ public class ServerConfigTest {
ServerConfig serverConfig = new ServerConfig();
assertTrue(serverConfig.isIdGeneratorAutomatic());
assertTrue(serverConfig.isDefaultServer());
+ assertFalse(serverConfig.isAutoPersistUpdates());
serverConfig.setIdGeneratorAutomatic(false);
assertFalse(serverConfig.isIdGeneratorAutomatic());
@@ -144,10 +157,18 @@ public class ServerConfigTest {
assertEquals(JsonConfig.Date.ISO8601, serverConfig.getJsonDate());
assertTrue(serverConfig.getPlatformConfig().isCaseSensitiveCollation());
assertTrue(serverConfig.isAutoLoadModuleInfo());
- assertEquals(Long.MAX_VALUE, serverConfig.getCollectQueryPlanThresholdMicros());
+
+ assertFalse(serverConfig.isQueryPlanEnable());
+ assertEquals(Long.MAX_VALUE, serverConfig.getQueryPlanThresholdMicros());
+ assertFalse(serverConfig.isQueryPlanCapture());
+ assertEquals(600, serverConfig.getQueryPlanCapturePeriodSecs());
+ assertEquals(10000L, serverConfig.getQueryPlanCaptureMaxTimeMillis());
+ assertEquals(10, serverConfig.getQueryPlanCaptureMaxCount());
serverConfig.setLoadModuleInfo(false);
assertFalse(serverConfig.isAutoLoadModuleInfo());
+ serverConfig.setAutoPersistUpdates(true);
+ assertTrue(serverConfig.isAutoPersistUpdates());
}
@Test
diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/core/DtoQueryRequestTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/core/DtoQueryRequestTest.java
index f8bdd66bd..4b85f18f1 100644
--- a/ebean-core/src/test/java/io/ebeaninternal/server/core/DtoQueryRequestTest.java
+++ b/ebean-core/src/test/java/io/ebeaninternal/server/core/DtoQueryRequestTest.java
@@ -11,10 +11,10 @@ public class DtoQueryRequestTest {
public void testParse() {
assertEquals("foo", DtoQueryRequest.parseColumn("foo"));
- assertEquals("bar", DtoQueryRequest.parseColumn("_e_t0_bar"));
- assertEquals("BAR", DtoQueryRequest.parseColumn("_E_T0_BAR"));
- assertEquals("baz", DtoQueryRequest.parseColumn("_e_t42_baz"));
- assertEquals("BAZ", DtoQueryRequest.parseColumn("_E_T42_BAZ"));
+ assertEquals("bar", DtoQueryRequest.parseColumn("zx__t0_bar"));
+ assertEquals("BAR", DtoQueryRequest.parseColumn("ZX__T0_BAR"));
+ assertEquals("baz", DtoQueryRequest.parseColumn("zx__t42_baz"));
+ assertEquals("BAZ", DtoQueryRequest.parseColumn("ZX__T42_BAZ"));
assertEquals("e_t42_nope", DtoQueryRequest.parseColumn("e_t42_nope"));
assertEquals("_f_t42_nope", DtoQueryRequest.parseColumn("_f_t42_nope"));
diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanDescriptorTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanDescriptorTest.java
index d4cbcbfbf..ab8ae6a87 100644
--- a/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanDescriptorTest.java
+++ b/ebean-core/src/test/java/io/ebeaninternal/server/deploy/BeanDescriptorTest.java
@@ -4,21 +4,22 @@ import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.bean.EntityBean;
import io.ebean.plugin.Property;
+import io.ebeaninternal.server.core.CacheOptions;
+import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
+import io.ebeaninternal.server.deploy.meta.DeployIdentityMode;
+import io.ebeanservice.docstore.api.DocStoreBeanAdapter;
import org.junit.Test;
-import org.tests.model.basic.Animal;
-import org.tests.model.basic.AnimalShelter;
-import org.tests.model.basic.Cat;
-import org.tests.model.basic.Contact;
-import org.tests.model.basic.Country;
-import org.tests.model.basic.Customer;
-import org.tests.model.basic.Dog;
-import org.tests.model.basic.Order;
+import org.tests.model.basic.*;
import org.tests.model.bridge.BSite;
import org.tests.model.bridge.BUser;
import java.util.Collection;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class BeanDescriptorTest extends BaseTestCase {
@@ -88,6 +89,42 @@ public class BeanDescriptorTest extends BaseTestCase {
assertThat(props).extracting("name").contains("id", "status", "orderDate", "shipDate");
}
+ @Test
+ public void matchBaseTable() {
+ BeanDescriptor desc = getBeanDescriptor(Customer.class);
+ assertTrue(desc.matchBaseTable("o_customer"));
+ }
+
+ @Test
+ public void matchBaseTable_whenTableHasSchema_expect_matchRegardlessOfSchema() {
+
+ DeployBeanDescriptor deploy = mockDeployCustomer();
+
+ when(deploy.getBaseTable()).thenReturn("foo.o_customer");
+ BeanDescriptor> desc1 = new BeanDescriptor<>(mockOwner(), deploy);
+ assertTrue(desc1.matchBaseTable("o_customer"));
+
+ when(deploy.getBaseTable()).thenReturn("bar.o_customer");
+ BeanDescriptor> desc2 = new BeanDescriptor<>(mockOwner(), deploy);
+ assertTrue(desc2.matchBaseTable("o_customer"));
+ }
+
+ @SuppressWarnings("unchecked")
+ private DeployBeanDescriptor mockDeployCustomer() {
+ DeployBeanDescriptor deploy = mock(DeployBeanDescriptor.class);
+ when(deploy.getBeanType()).thenReturn(Customer.class);
+ when(deploy.getIdentityMode()).thenReturn(DeployIdentityMode.auto());
+ when(deploy.buildIdentityMode()).thenReturn(IdentityMode.NONE);
+ when(deploy.getCacheOptions()).thenReturn(CacheOptions.NO_CACHING);
+ return deploy;
+ }
+
+ private BeanDescriptorMap mockOwner() {
+ BeanDescriptorMap owner = mock(BeanDescriptorMap.class);
+ when(owner.createDocStoreBeanAdapter(any(), any())).thenReturn(mock(DocStoreBeanAdapter.class));
+ return owner;
+ }
+
@Test
public void merge_when_empty() {
diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/grammer/ParseFetchConfigTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/grammer/ParseFetchConfigTest.java
index a940cd26b..c4976f56d 100644
--- a/ebean-core/src/test/java/io/ebeaninternal/server/grammer/ParseFetchConfigTest.java
+++ b/ebean-core/src/test/java/io/ebeaninternal/server/grammer/ParseFetchConfigTest.java
@@ -19,7 +19,7 @@ public class ParseFetchConfigTest {
@Test
public void parseLazy() {
FetchConfig lazy = ParseFetchConfig.parse("lazy");
- assertThat(lazy.getBatchSize()).isEqualTo(10);
+ assertThat(lazy.getBatchSize()).isEqualTo(0);
}
@Test
diff --git a/ebean-core/src/test/java/org/MainMemoryLeak.java b/ebean-core/src/test/java/org/MainMemoryLeak.java
index d3f085ef3..b659297b4 100644
--- a/ebean-core/src/test/java/org/MainMemoryLeak.java
+++ b/ebean-core/src/test/java/org/MainMemoryLeak.java
@@ -46,7 +46,12 @@ public class MainMemoryLeak {
config.addClass(ECachedBean.class);
config.loadFromProperties();
+ Database server1 = DatabaseFactory.create(config);
+ assert server1 != null;
+
+ // create again for #2193
Database server = DatabaseFactory.create(config);
+ assert server != null;
// create a string with 10k chars
char[] c = new char[10_000];
diff --git a/ebean-core/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java b/ebean-core/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java
index 58ddea0a2..144253edf 100644
--- a/ebean-core/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java
+++ b/ebean-core/src/test/java/org/tests/cache/personinfo/PersonCacheEmail.java
@@ -25,6 +25,10 @@ public class PersonCacheEmail {
this.email = email;
}
+ public PersonCacheEmail(String id) {
+ this.id = id;
+ }
+
public String getId() {
return id;
}
diff --git a/ebean-core/src/test/java/org/tests/cache/personinfo/PersonOther.java b/ebean-core/src/test/java/org/tests/cache/personinfo/PersonOther.java
new file mode 100644
index 000000000..b4b505125
--- /dev/null
+++ b/ebean-core/src/test/java/org/tests/cache/personinfo/PersonOther.java
@@ -0,0 +1,73 @@
+package org.tests.cache.personinfo;
+
+import io.ebean.annotation.WhenCreated;
+import io.ebean.annotation.WhenModified;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Version;
+import javax.validation.constraints.Size;
+import java.time.Instant;
+
+@Entity
+public class PersonOther {
+
+ @Id
+ @Size(max=128)
+ private String id;
+
+ private String email;
+
+ @WhenCreated
+ private Instant whenCreated;
+
+ @WhenModified
+ private Instant whenModified;
+
+ @Version
+ private long version;
+
+ public PersonOther(String id) {
+ this.id = id;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public Instant getWhenCreated() {
+ return whenCreated;
+ }
+
+ public void setWhenCreated(Instant whenCreated) {
+ this.whenCreated = whenCreated;
+ }
+
+ public Instant getWhenModified() {
+ return whenModified;
+ }
+
+ public void setWhenModified(Instant whenModified) {
+ this.whenModified = whenModified;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ public void setVersion(long version) {
+ this.version = version;
+ }
+}
diff --git a/ebean-core/src/test/java/org/tests/cache/personinfo/TestStringIdOnly.java b/ebean-core/src/test/java/org/tests/cache/personinfo/TestStringIdOnly.java
new file mode 100644
index 000000000..6379491ff
--- /dev/null
+++ b/ebean-core/src/test/java/org/tests/cache/personinfo/TestStringIdOnly.java
@@ -0,0 +1,30 @@
+package org.tests.cache.personinfo;
+
+import io.ebean.BaseTestCase;
+import io.ebean.DB;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestStringIdOnly extends BaseTestCase {
+
+ @Test
+ public void insert() {
+
+ PersonCacheEmail b0 = new PersonCacheEmail("IdOnly");
+ DB.save(b0);
+
+ PersonCacheEmail found = DB.find(PersonCacheEmail.class, b0.getId());
+ assertThat(found).isNotNull();
+ }
+
+ @Test
+ public void insert_whenIdOnly() {
+
+ PersonOther b0 = new PersonOther("IdOnly");
+ DB.save(b0);
+
+ PersonOther found = DB.find(PersonOther.class, b0.getId());
+ assertThat(found).isNotNull();
+ }
+}
diff --git a/ebean-core/src/test/java/org/tests/model/embedded/EEmbDatePeriod.java b/ebean-core/src/test/java/org/tests/model/embedded/EEmbDatePeriod.java
index 50cb225a3..2542a066b 100644
--- a/ebean-core/src/test/java/org/tests/model/embedded/EEmbDatePeriod.java
+++ b/ebean-core/src/test/java/org/tests/model/embedded/EEmbDatePeriod.java
@@ -1,12 +1,16 @@
package org.tests.model.embedded;
+import io.ebean.annotation.NotNull;
+
import javax.persistence.Embeddable;
import java.util.Date;
@Embeddable
public class EEmbDatePeriod {
+ @NotNull
Date date1;
+ @NotNull
Date date2;
public Date getDate1() {
diff --git a/ebean-core/src/test/java/org/tests/query/TestQueryFindEach.java b/ebean-core/src/test/java/org/tests/query/TestQueryFindEach.java
index 1a2c16bab..b37f67279 100644
--- a/ebean-core/src/test/java/org/tests/query/TestQueryFindEach.java
+++ b/ebean-core/src/test/java/org/tests/query/TestQueryFindEach.java
@@ -169,22 +169,40 @@ public class TestQueryFindEach extends BaseTestCase {
DB.find(OmBasicParent.class).delete();
insertData();
+ test_setLazyLoadBatchSize_withFetchLazy();
+
LoggedSqlCollector.start();
- try (final Transaction transaction = DB.beginTransaction()) {
- // DB.find(OmBasicParent.class).findList();
- DB.find(OmBasicChild.class)
- .setLazyLoadBatchSize(100)
- //.fetchQuery("parent","name")
- //.fetch("parent","name")
- .findEach(child -> {
- assertNotNull(child.getParent().getName());
- });
- }
+
+ DB.find(OmBasicChild.class)
+ .setLazyLoadBatchSize(100)
+ .findEach(child -> {
+ assertNotNull(child.getParent().getName());
+ });
final List sql = LoggedSqlCollector.stop();
assertThat(sql.size()).isLessThan(50);
}
+ private void test_setLazyLoadBatchSize_withFetchLazy() {
+
+ LoggedSqlCollector.start();
+
+ DB.find(OmBasicParent.class)
+ .setLazyLoadBatchSize(5)
+ .fetchLazy("children")
+ .setMaxRows(50)
+ .findEach(it -> {
+ it.getChildren().size();
+ });
+
+ final List sql = LoggedSqlCollector.stop();
+ assertThat(sql).hasSize(11);
+ assertThat(sql.get(0)).contains(" from om_basic_parent ");
+ for (int i = 1; i < 11; i++) {
+ assertThat(sql.get(i)).contains(" --bind(Array[5]");
+ }
+ }
+
@Transactional(batchSize = 40)
private void insertData() {
for (int i = 0; i < 150; i++) {
diff --git a/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java b/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java
index 48cc264c6..eeefe42c2 100644
--- a/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java
+++ b/ebean-core/src/test/java/org/tests/query/finder/TestCustomerFinder.java
@@ -31,7 +31,7 @@ public class TestCustomerFinder extends BaseTestCase {
runQueries();
StringBuilder buffer0 = new StringBuilder();
- server().getMetaInfoManager()
+ DB.getDefault().getMetaInfoManager()
.collectMetricsAsJson()
.withHeader(false)
.write(buffer0);
@@ -45,7 +45,7 @@ public class TestCustomerFinder extends BaseTestCase {
runQueries();
StringBuilder buffer1 = new StringBuilder();
- server().getMetaInfoManager()
+ DB.getDefault().getMetaInfoManager()
.collectMetricsAsJson()
.withHeader(false)
.write(buffer1);
@@ -55,7 +55,6 @@ public class TestCustomerFinder extends BaseTestCase {
assertThat(json1).contains("\"name\":\"txn.main\"");
assertThat(json1).contains("\"name\":\"orm.Customer.findList\"");
assertThat(json1).doesNotContain("\"sql\":\"select t0.id, t0.status, t0.name");
-
}
@Test
@@ -85,7 +84,6 @@ public class TestCustomerFinder extends BaseTestCase {
assertThat(customer.getName()).isEqualTo(customer1.getName());
assertThat(Customer.find.db().getName()).isEqualTo(DB.getDefault().getName());
-
}
@Test
@@ -175,20 +173,27 @@ public class TestCustomerFinder extends BaseTestCase {
ResetBasicData.reset();
+ // change default collect query plan threshold to 200 micros
+ QueryPlanInit init0 = new QueryPlanInit();
+ init0.setAll(true);
+ init0.setThresholdMicros(2);
+ final List plans = server().getMetaInfoManager().queryPlanInit(init0);
+ assertThat(plans.size()).isGreaterThan(1);
+
// the server has some plans
runQueries();
- // enable query plan bind capture on all plans threshold 100 micros
+ // change query plan threshold to 100 micros
QueryPlanInit init = new QueryPlanInit();
init.setAll(true);
- init.setThresholdMicros(100);
+ init.setThresholdMicros(1);
final List appliedToPlans = server().getMetaInfoManager().queryPlanInit(init);
assertThat(appliedToPlans.size()).isGreaterThan(4);
- // will collect bind captures
+ // run queries again
runQueries();
- ServerMetrics metrics = server().getMetaInfoManager().collectMetrics();
+ ServerMetrics metrics = DB.getDefault().getMetaInfoManager().collectMetrics();
List planStats = metrics.getQueryMetrics();
assertThat(planStats.size()).isGreaterThan(4);
@@ -203,12 +208,21 @@ public class TestCustomerFinder extends BaseTestCase {
// obtains db query plans ...
QueryPlanRequest request = new QueryPlanRequest();
- List plans = server().getMetaInfoManager().queryPlanCollectNow(request);
- assertThat(plans).isNotEmpty();
+ // collect max 1000 plans (use something more like 10)
+ request.setMaxCount(1_000);
+ // don't collect any more plans if used 10 secs
+ request.setMaxTimeMillis(10_000);
+ List plans0 = server().getMetaInfoManager().queryPlanCollectNow(request);
+ assertThat(plans0).isNotEmpty();
for (MetaQueryPlan plan : plans) {
+ logger.info("queryplan label:{}, queryTimeMicros:{} loc:{} sql:{} bind:{} plan:{}",
+ plan.getLabel(), plan.getQueryTimeMicros(), plan.getProfileLocation(),
+ plan.getSql(), plan.getBind(), plan.getPlan());
System.out.println(plan);
}
+
+ //DB.getBackgroundExecutor().scheduleWithFixedDelay(...)
}
@Test
diff --git a/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlPositionedParams.java b/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlPositionedParams.java
index f1c3ef4fd..75d678fd6 100644
--- a/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlPositionedParams.java
+++ b/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlPositionedParams.java
@@ -1,38 +1,42 @@
package org.tests.rawsql;
import io.ebean.BaseTestCase;
-import io.ebean.Ebean;
-import io.ebean.Query;
+import io.ebean.DB;
import io.ebean.RawSql;
import io.ebean.RawSqlBuilder;
+import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
-import org.junit.Test;
import java.util.List;
-import static org.junit.Assert.assertNotNull;
+import static org.assertj.core.api.Assertions.assertThat;
public class TestRawSqlPositionedParams extends BaseTestCase {
+ private static final RawSql RAWSQL_1 = RawSqlBuilder
+ .parse("select r.id, r.name from o_customer r where r.id >= ? and r.name like ?")
+ .create();
+
+ private static final RawSql RAW_SQL_2 = RawSqlBuilder
+ .unparsed("select r.id, r.name from o_customer r where r.id >= ? and r.name like ?")
+ .columnMapping("r.id", "id")
+ .columnMapping("r.name", "name")
+ .create();
+
@Test
public void test() {
ResetBasicData.reset();
- RawSql rawSql = RawSqlBuilder
- .parse("select r.id, r.name from o_customer r where r.id >= ? and r.name like ?")
- .create();
+ List list = DB.find(Customer.class)
+ .setRawSql(RAWSQL_1)
+ .setParameter(1)
+ .setParameter("R%")
+ .where().lt("id", 2001)
+ .findList();
- Query query = Ebean.find(Customer.class);
- query.setRawSql(rawSql);
- query.setParameter(1, 1);
- query.setParameter(2, "R%");
- query.where().lt("id", 2001);
-
- List list = query.findList();
-
- assertNotNull(list);
+ assertThat(list).isNotNull();
}
@Test
@@ -40,18 +44,12 @@ public class TestRawSqlPositionedParams extends BaseTestCase {
ResetBasicData.reset();
- RawSql rawSql = RawSqlBuilder
- .unparsed("select r.id, r.name from o_customer r where r.id >= ? and r.name like ?")
- .columnMapping("r.id", "id")
- .columnMapping("r.name", "name").create();
+ List list = DB.find(Customer.class)
+ .setRawSql(RAW_SQL_2)
+ .setParameter(1)
+ .setParameter("R%")
+ .findList();
- Query query = Ebean.find(Customer.class);
- query.setRawSql(rawSql);
- query.setParameter(1, 1);
- query.setParameter(2, "R%");
-
- List list = query.findList();
-
- assertNotNull(list);
+ assertThat(list).isNotNull();
}
}
diff --git a/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlUnparsedQuery.java b/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlUnparsedQuery.java
index 8d900b15f..1e576f0b7 100644
--- a/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlUnparsedQuery.java
+++ b/ebean-core/src/test/java/org/tests/rawsql/TestRawSqlUnparsedQuery.java
@@ -1,19 +1,25 @@
package org.tests.rawsql;
import io.ebean.BaseTestCase;
-import io.ebean.Ebean;
-import io.ebean.Query;
+import io.ebean.DB;
import io.ebean.RawSql;
import io.ebean.RawSqlBuilder;
+import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
-import org.junit.Assert;
-import org.junit.Test;
import java.util.List;
+import static org.assertj.core.api.Assertions.assertThat;
+
public class TestRawSqlUnparsedQuery extends BaseTestCase {
+ private static final RawSql rawSql = RawSqlBuilder
+ .unparsed("select r.id, r.name from o_customer r where r.id >= :a and r.name like :b")
+ .columnMapping("r.id", "id")
+ .columnMapping("r.name", "name")
+ .create();
+
@Test
public void testDoubleUnparsedQuery() {
@@ -25,17 +31,14 @@ public class TestRawSqlUnparsedQuery extends BaseTestCase {
}
private static void test() {
- RawSql rawSql = RawSqlBuilder
- .unparsed("select r.id, r.name from o_customer r where r.id >= :a and r.name like :b")
- .columnMapping("r.id", "id").columnMapping("r.name", "name").create();
- Query query = Ebean.find(Customer.class);
- query.setRawSql(rawSql);
- query.setParameter("a", 1);
- query.setParameter("b", "R%");
+ List list = DB.find(Customer.class)
+ .setRawSql(rawSql)
+ .setParameter("a", 1)
+ .setParameter("b", "R%")
+ .findList();
- List list = query.findList();
- Assert.assertNotNull(list);
+ assertThat(list).isNotNull();
}
}
diff --git a/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
new file mode 100644
index 000000000..75d767b3b
--- /dev/null
+++ b/ebean-core/src/test/java/org/tests/transparentpersist/TestTransparentPersist.java
@@ -0,0 +1,298 @@
+package org.tests.transparentpersist;
+
+import io.ebean.*;
+import io.ebean.annotation.*;
+import io.ebeaninternal.api.SpiTransaction;
+import io.ebeantest.LoggedSql;
+import org.junit.Test;
+import org.tests.model.basic.Customer;
+import org.tests.model.basic.EBasicVer;
+import org.tests.model.basic.Order;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestTransparentPersist extends BaseTestCase {
+
+ @Test
+ public void insertFlush_mutateFlush_expect_update() {
+
+ LoggedSql.start();
+
+ EBasicVer newBean;
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
+
+ newBean = new EBasicVer("insertMe");
+ DB.save(newBean);
+
+ // flush - new bean needs to get registered into persistence context
+ transaction.flush();
+
+ // make it dirty, we expect it to execute an update on flush()
+ newBean.setName("make it dirty - auto save");
+
+ // flush again, auto persist dirty bean in persistence context
+ transaction.commit();
+ }
+
+ List sql = LoggedSql.stop();
+
+ EBasicVer found = DB.find(EBasicVer.class, newBean.getId());
+ assertThat(found.getName()).isEqualTo("make it dirty - auto save");
+
+ assertThat(sql).hasSize(4);
+ assertThat(sql.get(0)).contains("insert into e_basicver");
+ assertThat(sql.get(1)).contains(" -- bind(");
+ assertThat(sql.get(2)).contains("update e_basicver set name=?, last_update=? where id=? and last_update=?");
+ assertThat(sql.get(3)).contains(" -- bind(");
+
+ DB.delete(found);
+ }
+
+ @Test
+ public void delete_expect_beanRemovedFromPersistenceContext() {
+
+ EBasicVer b0 = new EBasicVer("simpleDelete");
+ DB.save(b0);
+
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
+
+ EBasicVer found = DB.find(EBasicVer.class, b0.getId());
+ // make it dirty
+ found.setName("make it dirty");
+
+ // delete it, should remove it from the "live" part of persistence context
+ // with the expectation that no update is executed (no dirty in PC update)
+ DB.delete(found);
+ transaction.commit();
+ }
+
+ EBasicVer after = DB.find(EBasicVer.class, b0.getId());
+ assertThat(after).isNull();
+ }
+
+ @Test
+ public void simpleInsertUpdateDelete_experimental() {
+
+ EBasicVer b0 = new EBasicVer("simpleIUD_0");
+ b0.save();
+ EBasicVer b1 = new EBasicVer("simpleIUD_1");
+ b1.save();
+ EBasicVer b2 = new EBasicVer("simpleIUD_2");
+ b2.save();
+
+ EBasicVer newBean;
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
+
+ EBasicVer found = DB.find(EBasicVer.class, b0.getId());
+ found.setName("auto dirty");
+
+ // delete by id
+ DB.delete(EBasicVer.class, b1.getId());
+ // find and delete, note the delete is batched up to execute later
+ DB.delete(DB.find(EBasicVer.class, b2.getId()));
+
+ // insert is batched up to execute later
+ newBean = new EBasicVer("simpleIUD_New1");
+ DB.save(newBean);
+ // can still mutate newBean before flush (but not after flush yet as new bean isn't put into Persistence context)
+ newBean.setName("simpleIUD_New2");
+
+ transaction.commit();
+ }
+
+ EBasicVer after = DB.find(EBasicVer.class, b0.getId());
+ assertThat(after.getName()).isEqualTo("auto dirty");
+
+ EBasicVer wasInserted = DB.find(EBasicVer.class, newBean.getId());
+ assertThat(wasInserted.getName()).isEqualTo("simpleIUD_New2");
+
+ assertThat(DB.find(EBasicVer.class, b1.getId())).isNull();
+ assertThat(DB.find(EBasicVer.class, b2.getId())).isNull();
+
+ DB.delete(after);
+ DB.delete(wasInserted);
+ }
+
+ @Test
+ public void simpleUpdate_experimental() {
+
+ EBasicVer transPersist = new EBasicVer("simulate_simpleUpdate");
+ transPersist.save();
+
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
+
+ EBasicVer found = DB.find(EBasicVer.class, transPersist.getId());
+ found.setName("Persisted automatically as dirty");
+
+ transaction.commit();
+ }
+
+ EBasicVer after = DB.find(EBasicVer.class,transPersist.getId());
+ assertThat(after.getName()).isEqualTo("Persisted automatically as dirty");
+ DB.delete(after);
+ }
+
+ @Test
+ public void updateWithPersistCascadeInsert() {
+
+ // setup data
+ Customer c0 = new Customer();
+ c0.setName("firstCust");
+ Order order = new Order();
+ order.setStatus(Order.Status.NEW);
+ order.setCustomer(c0);
+ DB.save(order);
+
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
+
+ Order foundOrder = DB.find(Order.class, order.getId());
+ foundOrder.setStatus(Order.Status.APPROVED);
+ // cascade persist will insert this customer (even though it isn't in the persistence context)
+ Customer c1 = new Customer();
+ c1.setName("newCust CascadePersist");
+ foundOrder.setCustomer(c1);
+
+ transaction.commit();
+ }
+
+ Order checkOrder = DB.find(Order.class, order.getId());
+
+ assertThat(checkOrder.getStatus()).isEqualTo(Order.Status.APPROVED);
+ assertThat(checkOrder.getCustomer().getName()).isEqualTo("newCust CascadePersist");
+
+ DB.delete(checkOrder);
+ DB.delete(Customer.class, checkOrder.getCustomer().getId());
+ DB.delete(Customer.class, c0.getId());
+ }
+
+ @Test
+ public void updateReferenceOnlyWithPersistCascade_Insert_andUpdateForeignKey() {
+
+ // setup data
+ Customer c0 = new Customer();
+ c0.setName("firstCust");
+ Order order = new Order();
+ order.setStatus(Order.Status.NEW);
+ order.setCustomer(c0);
+ DB.save(order);
+
+ LoggedSql.start();
+
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setAutoPersistUpdates(true); // EXPERIMENTAL feature
+
+ Order foundOrder = DB.find(Order.class, order.getId());
+ // we ONLY mutate the foreign key
+ // cascade persist will insert this customer (even though it isn't in the persistence context)
+ Customer c1 = new Customer();
+ c1.setName("newCust CascadePersist");
+ foundOrder.setCustomer(c1);
+
+ transaction.commit();
+ }
+
+ List sql = LoggedSql.stop();
+
+ Order checkOrder = DB.find(Order.class, order.getId());
+
+ assertThat(checkOrder.getStatus()).isEqualTo(Order.Status.NEW);
+ assertThat(checkOrder.getCustomer().getName()).isEqualTo("newCust CascadePersist");
+
+ assertThat(sql).hasSize(5);
+ assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.order_date");
+ assertThat(sql.get(1)).contains("insert into o_customer");
+ assertThat(sql.get(2)).contains(" -- bind(");
+ assertThat(sql.get(3)).contains("update o_order set updtime=?, kcustomer_id=? where id=? and updtime=?");
+ assertThat(sql.get(4)).contains(" -- bind(");
+
+ DB.delete(checkOrder);
+ DB.delete(Customer.class, checkOrder.getCustomer().getId());
+ DB.delete(Customer.class, c0.getId());
+ }
+
+ @Test
+ public void txScope_setAutoPersistUpdates() {
+
+ EBasicVer b0 = new EBasicVer("txScopeUpdate");
+ b0.save();
+
+ DB.execute(TxScope.required().setAutoPersistUpdates(TxOption.ON), () -> {
+ EBasicVer found = DB.find(EBasicVer.class, b0.getId());
+ found.setName("mutable txScope bean");
+ });
+
+ EBasicVer after = DB.find(EBasicVer.class, b0.getId());
+ assertThat(after.getName()).isEqualTo("mutable txScope bean");
+
+ DB.delete(after);
+ }
+
+ @Test
+ public void txn_setAutoPersistUpdates() {
+
+ EBasicVer b0 = new EBasicVer("txn autoPersistUpdates ON");
+ b0.save();
+
+ performUpdate(b0);
+
+ EBasicVer after = DB.find(EBasicVer.class, b0.getId());
+ assertThat(after.getName()).isEqualTo("mutated with autoPersistUpdates ON");
+
+ DB.delete(after);
+ }
+
+ @Transactional(autoPersistUpdates = TxOption.ON)
+ private void performUpdate(EBasicVer b0) {
+ EBasicVer found = DB.find(EBasicVer.class, b0.getId());
+ found.setName("mutated with autoPersistUpdates ON");
+ }
+
+ @Test
+ public void simulate_transparentPersistence_forSimpleUpdate() {
+
+ EBasicVer transPersist = new EBasicVer("simulate_simpleUpdate");
+ transPersist.save();
+
+ try (Transaction transaction = DB.beginTransaction()) {
+ transaction.setBatchMode(true);
+ transaction.setBatchSize(10);
+
+ EBasicVer found = DB.find(EBasicVer.class, transPersist.getId());
+ found.setName("Changed");
+
+ // simulate transparent persistence
+ List dirtyBeans = simulateTransparentPersist(transaction);
+ assertThat(dirtyBeans).hasSize(1);
+ assertThat(dirtyBeans).contains(found);
+
+ // would occur as first part of flush
+ transaction.flush();
+ transaction.commit();
+ }
+
+ EBasicVer after = DB.find(EBasicVer.class,transPersist.getId());
+ assertThat(after.getName()).isEqualTo("Changed");
+ DB.delete(after);
+ }
+
+ private List simulateTransparentPersist(Transaction transaction) {
+ Database db = DB.getDefault();
+ List dirtyBeans = getDirtyBeansFromPersistenceContext(transaction);
+ for (Object dirtyBean : dirtyBeans) {
+ db.update(dirtyBean, transaction);
+ }
+ return dirtyBeans;
+ }
+
+ private List getDirtyBeansFromPersistenceContext(Transaction transaction) {
+ return ((SpiTransaction)transaction).getPersistenceContext().dirtyBeans();
+ }
+
+}
diff --git a/ebean-core/src/test/resources/ebean.properties b/ebean-core/src/test/resources/ebean.properties
index 953d2c720..aa1210739 100644
--- a/ebean-core/src/test/resources/ebean.properties
+++ b/ebean-core/src/test/resources/ebean.properties
@@ -27,8 +27,8 @@ datasource.default=h2
ebean.dumpMetricsOnShutdown=true
ebean.dumpMetricsOptions=sql,hash
-#ebean.collectQueryPlanThresholdMicros=1000
-ebean.collectQueryPlans=true
+#ebean.queryPlan.thresholdMicros=1000
+ebean.queryPlan.enable=true
ebean.autoReadOnlyDataSource=true
#datasource.h2-ro.url=jdbc:h2:mem:tests
diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml
index 1ef8c0725..f94924830 100644
--- a/ebean-ddl-generator/pom.xml
+++ b/ebean-ddl-generator/pom.xml
@@ -4,13 +4,12 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
ebean ddl generation
DDL and DB Migration generation
ebean-ddl-generator
-
@@ -29,14 +28,14 @@
io.ebean
ebean-core-type
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
provided
io.ebean
ebean-core
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
provided
@@ -77,7 +76,7 @@
io.ebean
ebean-maven-plugin
- 12.6.6
+ 12.8.2
test
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java
index 41e91274e..0e80c2676 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdl.java
@@ -150,7 +150,7 @@ public class BaseTableDdl implements TableDdl {
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
}
for (String ddlScript : before) {
- buffer.appendStatement(translate(ddlScript, tableName, columnName, this.defaultValue));
+ buffer.appendStatement(translate(ddlScript, tableName, columnName, defaultValue));
}
}
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/OracleDdl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/OracleDdl.java
index ea7d50d62..740870b09 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/OracleDdl.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/OracleDdl.java
@@ -15,6 +15,7 @@ public class OracleDdl extends PlatformDdl {
this.dropConstraintIfExists = "drop constraint";
this.dropIndexIfExists = "drop index ";
this.dropTableCascade = " cascade constraints purge";
+ this.addColumn = "add";
this.alterColumn = "modify";
this.columnSetNotnull = "not null";
this.columnSetNull = "null";
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java
index 648cfac83..59cf38e0d 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl.java
@@ -3,7 +3,6 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.annotation.ConstraintMode;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.DbConstraintNaming;
-import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbDefaultValue;
import io.ebean.config.dbplatform.DbIdentity;
@@ -113,6 +112,7 @@ public class PlatformDdl {
protected String uniqueIndex = "unique";
protected String indexConcurrent = "";
+ protected String createIndexIfNotExists = "";
/**
* Set false for MsSqlServer to allow multiple nulls for OneToOne mapping.
@@ -414,6 +414,9 @@ public class PlatformDdl {
if (create.isConcurrent()) {
buffer.append(indexConcurrent);
}
+ if (create.isNotExistsCheck()) {
+ buffer.append(createIndexIfNotExists);
+ }
buffer.append(maxConstraintName(create.getIndexName())).append(" on ").append(create.getTableName());
appendColumns(create.getColumns(), buffer);
return buffer.toString();
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java
index daeaf57fb..807bec456 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PostgresDdl.java
@@ -18,6 +18,7 @@ public class PostgresDdl extends PlatformDdl {
this.dropTableCascade = " cascade";
this.columnSetType = "type ";
this.alterTableIfExists = "if exists ";
+ this.createIndexIfNotExists = "if not exists ";
this.columnSetNull = "drop not null";
this.addForeignKeySkipCheck = " not valid";
this.indexConcurrent = "concurrently ";
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java
index c743c979c..fbb7b72ce 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/WriteCreateIndex.java
@@ -12,7 +12,11 @@ class WriteCreateIndex {
private final boolean unique;
private final boolean concurrent;
private final String definition;
+ private final boolean notExistsCheck;
+ /**
+ * Create index for foreign key.
+ */
WriteCreateIndex(String indexName, String tableName, String[] columns, boolean unique) {
this.indexName = indexName;
this.tableName = tableName;
@@ -20,8 +24,12 @@ class WriteCreateIndex {
this.unique = unique;
this.concurrent = false;
this.definition = null;
+ this.notExistsCheck = false;
}
+ /**
+ * Create non-foreign key index.
+ */
public WriteCreateIndex(CreateIndex index) {
this.indexName = index.getIndexName();
this.tableName = index.getTableName();
@@ -29,6 +37,7 @@ class WriteCreateIndex {
this.unique = Boolean.TRUE.equals(index.isUnique());
this.concurrent = Boolean.TRUE.equals(index.isConcurrent());
this.definition = index.getDefinition();
+ this.notExistsCheck = true;
}
public String getIndexName() {
@@ -58,4 +67,8 @@ class WriteCreateIndex {
public boolean useDefinition() {
return definition != null && !definition.isEmpty();
}
+
+ public boolean isNotExistsCheck() {
+ return notExistsCheck;
+ }
}
diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java
index 7b13cf581..3a7457a56 100644
--- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java
+++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/model/build/ModelBuildPropertyVisitor.java
@@ -175,7 +175,8 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
if (p instanceof BeanPropertyAssocOne) {
visitOneImported((BeanPropertyAssocOne>)p);
} else {
- visitScalar(p);
+ // only allow Nonnull if embedded is Nonnull
+ visitScalar(p, !embedded.isNullable());
}
if (embedded.isId()) {
// compound primary key
@@ -259,8 +260,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
}
@Override
- public void visitScalar(BeanProperty p) {
-
+ public void visitScalar(BeanProperty p, boolean allowNonNull) {
if (p.isSecondaryTable()) {
lastColumn = null;
return;
@@ -292,7 +292,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
}
} else {
col.setDefaultValue(p.getDbColumnDefault());
- if (!p.isNullable() || p.isDDLNotNull()) {
+ if (allowNonNull && (!p.isNullable() || p.isDDLNotNull())) {
col.setNotnull(true);
}
}
diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdlTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdlTest.java
index c0bbd4df6..c32d6ad80 100644
--- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdlTest.java
+++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/BaseTableDdlTest.java
@@ -61,7 +61,7 @@ public class BaseTableDdlTest {
ddlGen.alterTableAddColumn(write.apply(), "mytable", column, false, false);
String ddl = write.apply().getBuffer();
- assertThat(ddl).contains("alter table mytable add column col_name varchar2(20)");
+ assertThat(ddl).contains("alter table mytable add col_name varchar2(20)");
}
@Test
diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java
index 3a0a8d727..a696c4e62 100644
--- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java
+++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java
@@ -9,11 +9,15 @@ import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
+import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.ddlgeneration.PlatformDdlBuilder;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.AlterForeignKey;
+import io.ebeaninternal.dbmigration.migration.Column;
import org.junit.Test;
+import java.io.IOException;
+
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -238,6 +242,22 @@ public class PlatformDdl_AlterColumnTest {
assertTrue(exceptionCaught);
}
+ @Test
+ public void oracle_alterTableAddColumn() throws IOException {
+ DdlWrite write = new DdlWrite();
+ oraDdl.alterTableAddColumn(write.apply(), "my_table", simpleColumn(), false, "1");
+ assertThat(write.apply().getBuffer()).isEqualTo("alter table my_table add my_column int default 1 not null;\n");
+ }
+
+ private Column simpleColumn() {
+ Column column = new Column();
+ column.setName("my_column");
+ column.setType("int");
+ column.setNotnull(true);
+ column.setDefaultValue("1");
+ return column;
+ }
+
@Test
public void useIdentityType_h2() {
assertEquals(h2Ddl.useIdentityType(null), IdType.IDENTITY);
diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_CreateIndexTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_CreateIndexTest.java
new file mode 100644
index 000000000..d7d33feae
--- /dev/null
+++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_CreateIndexTest.java
@@ -0,0 +1,98 @@
+package io.ebeaninternal.dbmigration.ddlgeneration.platform;
+
+import io.ebean.DB;
+import io.ebean.config.DatabaseConfig;
+import io.ebean.config.dbplatform.h2.H2Platform;
+import io.ebean.config.dbplatform.hana.HanaPlatform;
+import io.ebean.config.dbplatform.mysql.MySqlPlatform;
+import io.ebean.config.dbplatform.oracle.OraclePlatform;
+import io.ebean.config.dbplatform.postgres.PostgresPlatform;
+import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
+import io.ebeaninternal.dbmigration.ddlgeneration.PlatformDdlBuilder;
+import io.ebeaninternal.dbmigration.migration.CreateIndex;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+
+public class PlatformDdl_CreateIndexTest {
+
+ private final PlatformDdl h2Ddl = PlatformDdlBuilder.create(new H2Platform());
+ private final PlatformDdl pgDdl = PlatformDdlBuilder.create(new PostgresPlatform());
+ private final PlatformDdl mysqlDdl = PlatformDdlBuilder.create(new MySqlPlatform());
+ private final PlatformDdl oraDdl = PlatformDdlBuilder.create(new OraclePlatform());
+ private final PlatformDdl sqlServerDdl = PlatformDdlBuilder.create(new SqlServer17Platform());
+ private final PlatformDdl hanaDdl = PlatformDdlBuilder.create(new HanaPlatform());
+
+ {
+ DatabaseConfig config = DB.getDefault().getPluginApi().getServerConfig();
+ h2Ddl.configure(config);
+ pgDdl.configure(config);
+ mysqlDdl.configure(config);
+ oraDdl.configure(config);
+ sqlServerDdl.configure(config);
+ hanaDdl.configure(config);
+ }
+
+ WriteCreateIndex writeCreateIndex() {
+
+ return writeCreateIndex(true, true);
+ }
+
+ WriteCreateIndex writeCreateIndex(boolean unique, boolean concurrent) {
+ CreateIndex createIndex = new CreateIndex();
+ createIndex.setIndexName("ix_mytab_acol");
+ createIndex.setTableName("mytab");
+ createIndex.setColumns("acol");
+ createIndex.setUnique(unique);
+ createIndex.setConcurrent(concurrent);
+ return new WriteCreateIndex(createIndex);
+ }
+
+ WriteCreateIndex fkeyCreateIndex(boolean unique) {
+ return new WriteCreateIndex("ix_mytab_acol", "mytab", new String[]{"acol"}, unique);
+ }
+
+
+ @Test
+ public void createUniqueIndex() {
+
+ WriteCreateIndex createIndex = writeCreateIndex();
+
+ String sql = h2Ddl.createIndex(createIndex);
+ assertEquals("create unique index ix_mytab_acol on mytab (acol)", sql);
+ sql = pgDdl.createIndex(createIndex);
+ assertEquals("create unique index concurrently if not exists ix_mytab_acol on mytab (acol)", sql);
+ sql = mysqlDdl.createIndex(createIndex);
+ assertEquals("create unique index ix_mytab_acol on mytab (acol)", sql);
+ sql = sqlServerDdl.createIndex(createIndex);
+ assertEquals("create unique index ix_mytab_acol on mytab (acol)", sql);
+ sql = oraDdl.createIndex(createIndex);
+ assertEquals("create unique index ix_mytab_acol on mytab (acol)", sql);
+ sql = hanaDdl.createIndex(createIndex);
+ assertThat(sql).isEqualTo("-- explicit index \"ix_mytab_acol\" for single column \"acol\" of table \"mytab\" is not necessary");
+ }
+
+ @Test
+ public void postgres_createIndex() {
+
+ String sql = pgDdl.createIndex(writeCreateIndex(true, true));
+ assertEquals("create unique index concurrently if not exists ix_mytab_acol on mytab (acol)", sql);
+ sql = pgDdl.createIndex(writeCreateIndex(false, false));
+ assertEquals("create index if not exists ix_mytab_acol on mytab (acol)", sql);
+ sql = pgDdl.createIndex(writeCreateIndex(true, false));
+ assertEquals("create unique index if not exists ix_mytab_acol on mytab (acol)", sql);
+ sql = pgDdl.createIndex(writeCreateIndex(false, true));
+ assertEquals("create index concurrently if not exists ix_mytab_acol on mytab (acol)", sql);
+ }
+
+ @Test
+ public void postgres_fkeyCreateIndex() {
+ String sql = pgDdl.createIndex(fkeyCreateIndex(true));
+ assertEquals("create unique index ix_mytab_acol on mytab (acol)", sql);
+
+ sql = pgDdl.createIndex(fkeyCreateIndex(false));
+ assertEquals("create index ix_mytab_acol on mytab (acol)", sql);
+ }
+
+}
diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.1.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.1.sql
index a7ebf50ef..7e6c98a90 100644
--- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.1.sql
+++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.1.sql
@@ -17,11 +17,11 @@ create table migtest_mtm_m_migtest_mtm_c (
constraint pk_migtest_mtm_m_migtest_mtm_c primary key (migtest_mtm_m_id,migtest_mtm_c_id)
);
-alter table migtest_ckey_detail add column one_key number(10);
-alter table migtest_ckey_detail add column two_key varchar2(127);
+alter table migtest_ckey_detail add one_key number(10);
+alter table migtest_ckey_detail add two_key varchar2(127);
alter table migtest_ckey_detail add constraint fk_migtest_ckey_detail_parent foreign key (one_key,two_key) references migtest_ckey_parent (one_key,two_key);
-alter table migtest_ckey_parent add column assoc_id number(10);
+alter table migtest_ckey_parent add assoc_id number(10);
alter table migtest_fk_cascade drop constraint fk_migtest_fk_cascade_one_id;
alter table migtest_fk_cascade add constraint fk_migtest_fk_cascade_one_id foreign key (one_id) references migtest_fk_cascade_one (id);
@@ -46,14 +46,14 @@ alter table migtest_e_basic modify status2 null;
insert into migtest_e_user (id) select distinct user_id from migtest_e_basic;
alter table migtest_e_basic add constraint fk_migtest_e_basic_user_id foreign key (user_id) references migtest_e_user (id);
alter table migtest_e_basic modify user_id null;
-alter table migtest_e_basic add column new_string_field varchar2(255) default 'foo''bar' not null;
-alter table migtest_e_basic add column new_boolean_field number(1) default 1 not null;
+alter table migtest_e_basic add new_string_field varchar2(255) default 'foo''bar' not null;
+alter table migtest_e_basic add new_boolean_field number(1) default 1 not null;
update migtest_e_basic set new_boolean_field = old_boolean;
-alter table migtest_e_basic add column new_boolean_field2 number(1) default 1 not null;
-alter table migtest_e_basic add column progress number(10) default 0 not null;
+alter table migtest_e_basic add new_boolean_field2 number(1) default 1 not null;
+alter table migtest_e_basic add progress number(10) default 0 not null;
alter table migtest_e_basic add constraint ck_migtest_e_basic_progress check ( progress in (0,1,2));
-alter table migtest_e_basic add column new_integer number(10) default 42 not null;
+alter table migtest_e_basic add new_integer number(10) default 42 not null;
alter table migtest_e_basic drop constraint uq_migtest_e_basic_indextest2;
alter table migtest_e_basic drop constraint uq_migtest_e_basic_indextest6;
@@ -70,12 +70,12 @@ comment on table migtest_e_history is 'We have history now';
update migtest_e_history2 set test_string = 'unknown' where test_string is null;
alter table migtest_e_history2 modify test_string default 'unknown';
alter table migtest_e_history2 modify test_string not null;
-alter table migtest_e_history2 add column test_string2 varchar2(255);
-alter table migtest_e_history2 add column test_string3 varchar2(255) default 'unknown' not null;
-alter table migtest_e_history2 add column new_column varchar2(20);
+alter table migtest_e_history2 add test_string2 varchar2(255);
+alter table migtest_e_history2 add test_string3 varchar2(255) default 'unknown' not null;
+alter table migtest_e_history2 add new_column varchar2(20);
alter table migtest_e_history4 modify test_number number(19);
-alter table migtest_e_history5 add column test_boolean number(1) default 0 not null;
+alter table migtest_e_history5 add test_boolean number(1) default 0 not null;
-- NOTE: table has @History - special migration may be necessary
@@ -83,9 +83,9 @@ update migtest_e_history6 set test_number1 = 42 where test_number1 is null;
alter table migtest_e_history6 modify test_number1 default 42;
alter table migtest_e_history6 modify test_number1 not null;
alter table migtest_e_history6 modify test_number2 null;
-alter table migtest_e_softdelete add column deleted number(1) default 0 not null;
+alter table migtest_e_softdelete add deleted number(1) default 0 not null;
-alter table migtest_oto_child add column master_id number(19);
+alter table migtest_oto_child add master_id number(19);
create index ix_migtest_e_basic_indextest3 on migtest_e_basic (indextest3);
create index ix_migtest_e_basic_indextest6 on migtest_e_basic (indextest6);
diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.3.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.3.sql
index 47a095e4e..ceafdbcff 100644
--- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.3.sql
+++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/oracle/1.3.sql
@@ -31,9 +31,9 @@ update migtest_e_basic set user_id = 23 where user_id is null;
alter table migtest_e_basic drop constraint fk_migtest_e_basic_user_id;
alter table migtest_e_basic modify user_id default 23;
alter table migtest_e_basic modify user_id not null;
-alter table migtest_e_basic add column old_boolean number(1) default 0 not null;
-alter table migtest_e_basic add column old_boolean2 number(1);
-alter table migtest_e_basic add column eref_id number(10);
+alter table migtest_e_basic add old_boolean number(1) default 0 not null;
+alter table migtest_e_basic add old_boolean2 number(1);
+alter table migtest_e_basic add eref_id number(10);
alter table migtest_e_basic drop constraint uq_mgtst__bsc_stts_ndxtst1;
alter table migtest_e_basic drop constraint uq_migtest_e_basic_name;
@@ -47,8 +47,8 @@ comment on column migtest_e_history.test_string is '';
comment on table migtest_e_history is '';
alter table migtest_e_history2 modify test_string drop default;
alter table migtest_e_history2 modify test_string null;
-alter table migtest_e_history2 add column obsolete_string1 varchar2(255);
-alter table migtest_e_history2 add column obsolete_string2 varchar2(255);
+alter table migtest_e_history2 add obsolete_string1 varchar2(255);
+alter table migtest_e_history2 add obsolete_string2 varchar2(255);
alter table migtest_e_history4 modify test_number number(10);
alter table migtest_e_history6 modify test_number1 drop default;
diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql
index 5229ed032..af08de6d3 100644
--- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql
+++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.0__initial.sql
@@ -161,11 +161,11 @@ create table migtest_oto_master (
constraint pk_migtest_oto_master primary key (id)
);
-create index ix_migtest_e_basic_indextest1 on migtest_e_basic (indextest1);
-create index ix_migtest_e_basic_indextest5 on migtest_e_basic (indextest5);
+create index if not exists ix_migtest_e_basic_indextest1 on migtest_e_basic (indextest1);
+create index if not exists ix_migtest_e_basic_indextest5 on migtest_e_basic (indextest5);
create index idxd_migtest_0 on migtest_oto_child using hash (upper(name)) where upper(name) = 'JIM';
-create index concurrently ix_migtest_oto_child_lowername_id on migtest_oto_child (lower(name),id);
-create index ix_migtest_oto_child_lowername on migtest_oto_child (lower(name));
+create index concurrently if not exists ix_migtest_oto_child_lowername_id on migtest_oto_child (lower(name),id);
+create index if not exists ix_migtest_oto_child_lowername on migtest_oto_child (lower(name));
create index ix_migtest_fk_cascade_one_id on migtest_fk_cascade (one_id);
alter table migtest_fk_cascade add constraint fk_migtest_fk_cascade_one_id foreign key (one_id) references migtest_fk_cascade_one (id) on delete cascade on update restrict;
diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql
index 6c97d734e..1c0ef5385 100644
--- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql
+++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.1.sql
@@ -100,9 +100,9 @@ alter table migtest_e_softdelete add column deleted boolean default false not nu
alter table migtest_oto_child add column master_id bigint;
-create index ix_migtest_e_basic_indextest3 on migtest_e_basic (indextest3);
-create index ix_migtest_e_basic_indextest6 on migtest_e_basic (indextest6);
-create index ix_migtest_oto_child_name on migtest_oto_child (name);
+create index if not exists ix_migtest_e_basic_indextest3 on migtest_e_basic (indextest3);
+create index if not exists ix_migtest_e_basic_indextest6 on migtest_e_basic (indextest6);
+create index if not exists ix_migtest_oto_child_name on migtest_oto_child (name);
drop index if exists ix_migtest_e_basic_indextest1;
drop index if exists ix_migtest_e_basic_indextest5;
drop index if exists idxd_migtest_0;
diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.3.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.3.sql
index a57b95ae2..dbcb50aac 100644
--- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.3.sql
+++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/postgres/1.3.sql
@@ -66,11 +66,11 @@ alter table migtest_e_history6 alter column test_number1 drop not null;
update migtest_e_history6 set test_number2 = 7 where test_number2 is null;
alter table migtest_e_history6 alter column test_number2 set default 7;
alter table migtest_e_history6 alter column test_number2 set not null;
-create index ix_migtest_e_basic_indextest1 on migtest_e_basic (indextest1);
-create index ix_migtest_e_basic_indextest5 on migtest_e_basic (indextest5);
-create index ix_m12_otoc71 on migtest_oto_child (name);
-create unique index uq_m12_otoc71 on migtest_oto_child (lower(name));
-create unique index ix_migtest_oto_master_lowername on migtest_oto_master (lower(name));
+create index if not exists ix_migtest_e_basic_indextest1 on migtest_e_basic (indextest1);
+create index if not exists ix_migtest_e_basic_indextest5 on migtest_e_basic (indextest5);
+create index if not exists ix_m12_otoc71 on migtest_oto_child (name);
+create unique index if not exists uq_m12_otoc71 on migtest_oto_child (lower(name));
+create unique index if not exists ix_migtest_oto_master_lowername on migtest_oto_master (lower(name));
drop index if exists ix_migtest_e_basic_indextest3;
drop index if exists ix_migtest_e_basic_indextest6;
create index ix_migtest_e_basic_eref_id on migtest_e_basic (eref_id);
diff --git a/ebean-externalmapping-api/pom.xml b/ebean-externalmapping-api/pom.xml
index 4efce3a65..079eebec9 100644
--- a/ebean-externalmapping-api/pom.xml
+++ b/ebean-externalmapping-api/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
ebean external mapping api
diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml
index cd885187e..bcf9f10ff 100644
--- a/ebean-externalmapping-xml/pom.xml
+++ b/ebean-externalmapping-xml/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
@@ -33,7 +33,7 @@
io.ebean
ebean-externalmapping-api
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
@@ -59,14 +59,14 @@
io.ebean
ebean-core
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
io.ebean
ebean-ddl-generator
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml
index 5b4bceec8..4e1b6063b 100644
--- a/ebean-postgis/pom.xml
+++ b/ebean-postgis/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
ebean postgis
@@ -23,7 +23,7 @@
io.ebean
ebean-core
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
provided
@@ -74,7 +74,7 @@
io.ebean
ebean-test
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml
index 51f490b3b..ff6dd8754 100644
--- a/ebean-querybean/pom.xml
+++ b/ebean-querybean/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
ebean querybean
@@ -17,7 +17,7 @@
io.ebean
ebean-core
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
provided
@@ -57,21 +57,21 @@
io.ebean
ebean-ddl-generator
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
io.ebean
querybean-generator
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
io.ebean
ebean-test
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PInteger.java b/ebean-querybean/src/main/java/io/ebean/typequery/PInteger.java
index 3aefa8f46..8657461be 100644
--- a/ebean-querybean/src/main/java/io/ebean/typequery/PInteger.java
+++ b/ebean-querybean/src/main/java/io/ebean/typequery/PInteger.java
@@ -24,4 +24,70 @@ public class PInteger extends PBaseNumber {
super(name, root, prefix);
}
+ /**
+ * Add bitwise AND expression of the given bit flags to compare with the match/mask.
+ *
+ *
{@code
+ *
+ * // Flags Bulk + Size = Size
+ * // ... meaning Bulk is not set and Size is set
+ *
+ * int selectedFlags = BwFlags.HAS_BULK + BwFlags.HAS_SIZE;
+ * int mask = BwFlags.HAS_SIZE; // Only Size flag set
+ *
+ * bitwiseAnd(selectedFlags, mask)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAnd(int flags, int mask) {
+ expr().bitwiseAnd(_name, flags, mask);
+ return _root;
+ }
+
+ /**
+ * Add expression for ALL of the given bit flags to be set.
+ * {@code
+ *
+ * bitwiseAll(BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAll(int flags) {
+ expr().bitwiseAll(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for ANY of the given bit flags to be set.
+ * {@code
+ *
+ * bitwiseAny(BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAny(int flags) {
+ expr().bitwiseAny(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for the given bit flags to be NOT set.
+ * {@code
+ *
+ * bitwiseNot(BwFlags.HAS_COLOUR)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseNot(int flags) {
+ expr().bitwiseNot(_name, flags);
+ return _root;
+ }
}
diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PLong.java b/ebean-querybean/src/main/java/io/ebean/typequery/PLong.java
index e5869682c..d8e4cf58b 100644
--- a/ebean-querybean/src/main/java/io/ebean/typequery/PLong.java
+++ b/ebean-querybean/src/main/java/io/ebean/typequery/PLong.java
@@ -5,7 +5,7 @@ package io.ebean.typequery;
*
* @param the root query bean type
*/
-public class PLong extends PBaseNumber {
+public class PLong extends PBaseNumber {
/**
* Construct with a property name and root instance.
@@ -14,7 +14,7 @@ public class PLong extends PBaseNumber {
* @param root the root query bean instance
*/
public PLong(String name, R root) {
- super(name , root);
+ super(name, root);
}
/**
@@ -24,4 +24,70 @@ public class PLong extends PBaseNumber {
super(name, root, prefix);
}
+ /**
+ * Add bitwise AND expression of the given bit flags to compare with the match/mask.
+ *
+ *
{@code
+ *
+ * // Flags Bulk + Size = Size
+ * // ... meaning Bulk is not set and Size is set
+ *
+ * long selectedFlags = BwFlags.HAS_BULK + BwFlags.HAS_SIZE;
+ * long mask = BwFlags.HAS_SIZE; // Only Size flag set
+ *
+ * bitwiseAnd(selectedFlags, mask)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAnd(long flags, long mask) {
+ expr().bitwiseAnd(_name, flags, mask);
+ return _root;
+ }
+
+ /**
+ * Add expression for ALL of the given bit flags to be set.
+ * {@code
+ *
+ * bitwiseAll(BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAll(long flags) {
+ expr().bitwiseAll(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for ANY of the given bit flags to be set.
+ * {@code
+ *
+ * bitwiseAny(BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAny(long flags) {
+ expr().bitwiseAny(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for the given bit flags to be NOT set.
+ * {@code
+ *
+ * bitwiseNot(BwFlags.HAS_COLOUR)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseNot(long flags) {
+ expr().bitwiseNot(_name, flags);
+ return _root;
+ }
}
diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PShort.java b/ebean-querybean/src/main/java/io/ebean/typequery/PShort.java
index 440a5ab4c..f8ee7bf8a 100644
--- a/ebean-querybean/src/main/java/io/ebean/typequery/PShort.java
+++ b/ebean-querybean/src/main/java/io/ebean/typequery/PShort.java
@@ -5,7 +5,7 @@ package io.ebean.typequery;
*
* @param the root query bean type
*/
-public class PShort extends PBaseNumber {
+public class PShort extends PBaseNumber {
/**
* Construct with a property name and root instance.
@@ -24,4 +24,70 @@ public class PShort extends PBaseNumber {
super(name, root, prefix);
}
+ /**
+ * Add bitwise AND expression of the given bit flags to compare with the match/mask.
+ *
+ *
{@code
+ *
+ * // Flags Bulk + Size = Size
+ * // ... meaning Bulk is not set and Size is set
+ *
+ * short selectedFlags = BwFlags.HAS_BULK + BwFlags.HAS_SIZE;
+ * short mask = BwFlags.HAS_SIZE; // Only Size flag set
+ *
+ * bitwiseAnd(selectedFlags, mask)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAnd(short flags, short mask) {
+ expr().bitwiseAnd(_name, flags, mask);
+ return _root;
+ }
+
+ /**
+ * Add expression for ALL of the given bit flags to be set.
+ * {@code
+ *
+ * bitwiseAll(BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAll(short flags) {
+ expr().bitwiseAll(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for ANY of the given bit flags to be set.
+ * {@code
+ *
+ * bitwiseAny(BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseAny(short flags) {
+ expr().bitwiseAny(_name, flags);
+ return _root;
+ }
+
+ /**
+ * Add expression for the given bit flags to be NOT set.
+ * {@code
+ *
+ * bitwiseNot(BwFlags.HAS_COLOUR)
+ *
+ * }
+ *
+ * @param flags The flags we are looking for
+ */
+ public R bitwiseNot(short flags) {
+ expr().bitwiseNot(_name, flags);
+ return _root;
+ }
}
diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PZonedDateTime.java b/ebean-querybean/src/main/java/io/ebean/typequery/PZonedDateTime.java
new file mode 100644
index 000000000..e233449ef
--- /dev/null
+++ b/ebean-querybean/src/main/java/io/ebean/typequery/PZonedDateTime.java
@@ -0,0 +1,50 @@
+package io.ebean.typequery;
+
+import java.time.ZonedDateTime;
+
+/**
+ * ZonedDateTime property.
+ *
+ * @param the root query bean type
+ */
+public class PZonedDateTime extends PBaseCompareable {
+
+ /**
+ * Construct with a property name and root instance.
+ *
+ * @param name property name
+ * @param root the root query bean instance
+ */
+ public PZonedDateTime(String name, R root) {
+ super(name, root);
+ }
+
+ /**
+ * Construct with additional path prefix.
+ */
+ public PZonedDateTime(String name, R root, String prefix) {
+ super(name, root, prefix);
+ }
+
+ /**
+ * Same as greater than.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R after(ZonedDateTime value) {
+ expr().gt(_name, value);
+ return _root;
+ }
+
+ /**
+ * Same as less than.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R before(ZonedDateTime value) {
+ expr().lt(_name, value);
+ return _root;
+ }
+}
diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java
index c54862151..d5843a612 100644
--- a/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java
+++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java
@@ -2,7 +2,10 @@ package io.ebean.typequery;
import io.ebean.ExpressionList;
import io.ebean.FetchConfig;
+import io.ebean.FetchGroup;
import io.ebeaninternal.api.SpiQueryFetch;
+import io.ebeaninternal.server.querydefn.OrmQueryDetail;
+import io.ebeaninternal.server.querydefn.SpiFetchGroup;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -142,7 +145,34 @@ public abstract class TQAssocBean extends TQProperty {
return _root;
}
- private final SpiQueryFetch spiQuery() {
+ /**
+ * Fetch using the nested FetchGroup.
+ */
+ public R fetch(FetchGroup nestedGroup) {
+ return fetchNested(nestedGroup, FETCH_DEFAULT);
+ }
+
+ /**
+ * Fetch query using the nested FetchGroup.
+ */
+ public R fetchQuery(FetchGroup nestedGroup) {
+ return fetchNested(nestedGroup, FETCH_QUERY);
+ }
+
+ /**
+ * Fetch cache using the nested FetchGroup.
+ */
+ public R fetchCache(FetchGroup nestedGroup) {
+ return fetchNested(nestedGroup, FETCH_CACHE);
+ }
+
+ private R fetchNested(FetchGroup nestedGroup, FetchConfig fetchConfig) {
+ OrmQueryDetail nestedDetail = ((SpiFetchGroup) nestedGroup).underlying();
+ spiQuery().addNested(_name, nestedDetail, fetchConfig);
+ return _root;
+ }
+
+ private SpiQueryFetch spiQuery() {
return (SpiQueryFetch)((TQRootBean) _root).query();
}
diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java
index 654e35e9d..728184cf4 100644
--- a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java
+++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java
@@ -679,7 +679,7 @@ public abstract class TQRootBean {
* Provides us with the ability to explicitly use Postgres
* SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks.
*/
- R withLock(Query.LockType lockType) {
+ public R withLock(Query.LockType lockType) {
query.withLock(lockType);
return root;
}
@@ -693,7 +693,7 @@ public abstract class TQRootBean {
* Provides us with the ability to explicitly use Postgres
* SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks.
*/
- R withLock(Query.LockType lockType, Query.LockWait lockWait) {
+ public R withLock(Query.LockType lockType, Query.LockWait lockWait) {
query.withLock(lockType, lockWait);
return root;
}
diff --git a/ebean-querybean/src/test/java/org/example/domain/Contact.java b/ebean-querybean/src/test/java/org/example/domain/Contact.java
index 00035d80a..2b5fe9f53 100644
--- a/ebean-querybean/src/test/java/org/example/domain/Contact.java
+++ b/ebean-querybean/src/test/java/org/example/domain/Contact.java
@@ -3,6 +3,7 @@ package org.example.domain;
import io.ebean.annotation.DbArray;
import javax.persistence.*;
+import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -11,33 +12,35 @@ import java.util.Map;
* Contact entity bean.
*/
@Entity
-@Table(name="be_contact")
+@Table(name = "be_contact")
public class Contact extends BaseModel {
@DbArray
- List phoneNumbers = new ArrayList();
+ List phoneNumbers = new ArrayList<>();
- @Column(length=50)
+ @Column(length = 50)
String firstName;
- @Column(length=50)
+ @Column(length = 50)
String lastName;
- @Column(length=200)
+ @Column(length = 200)
String email;
- @Column(length=20)
+ @Column(length = 20)
String phone;
- @ManyToOne(optional=false)
+ ZonedDateTime zoneDateTime;
+
+ @ManyToOne(optional = false)
Customer customer;
@OneToMany(mappedBy = "contact")
List notes;
@OneToMany(cascade = CascadeType.PERSIST)
- @MapKey(name="key")
- Map others;
+ @MapKey(name = "key")
+ Map others;
/**
* Default constructor.
@@ -85,6 +88,14 @@ public class Contact extends BaseModel {
this.phone = phone;
}
+ public ZonedDateTime getZoneDateTime() {
+ return zoneDateTime;
+ }
+
+ public void setZoneDateTime(ZonedDateTime zoneDateTime) {
+ this.zoneDateTime = zoneDateTime;
+ }
+
public Customer getCustomer() {
return customer;
}
diff --git a/ebean-querybean/src/test/java/org/querytest/QContactTest.java b/ebean-querybean/src/test/java/org/querytest/QContactTest.java
index ff37f789e..ef0d5a01b 100644
--- a/ebean-querybean/src/test/java/org/querytest/QContactTest.java
+++ b/ebean-querybean/src/test/java/org/querytest/QContactTest.java
@@ -3,13 +3,15 @@ package org.querytest;
import org.example.domain.query.QContact;
import org.junit.Test;
+import java.time.ZonedDateTime;
+
public class QContactTest {
@Test
public void test_oneToManyMap() {
-
new QContact()
.others.fetch()
+ .zoneDateTime.before(ZonedDateTime.now())
.findList();
}
}
diff --git a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java
index 56aec1c51..867290e71 100644
--- a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java
+++ b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java
@@ -71,6 +71,7 @@ public class QCustomerTest {
// not found using other transaction
final Customer foundNot = new QCustomer()
.name.eq("explicitTransaction")
+ .withLock(Query.LockType.SHARE)
.findOne();
assertThat(foundNot).isNull();
diff --git a/ebean-querybean/src/test/java/org/querytest/QOrderTest.java b/ebean-querybean/src/test/java/org/querytest/QOrderTest.java
index 2f470fdb5..db899549c 100644
--- a/ebean-querybean/src/test/java/org/querytest/QOrderTest.java
+++ b/ebean-querybean/src/test/java/org/querytest/QOrderTest.java
@@ -3,9 +3,13 @@ package org.querytest;
import io.ebean.DB;
import io.ebean.FetchGroup;
import io.ebean.test.LoggedSql;
+import org.example.domain.Customer;
import org.example.domain.Order;
+import org.example.domain.otherpackage.PhoneNumber;
import org.example.domain.query.QCustomer;
import org.example.domain.query.QOrder;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
import org.junit.Test;
import java.util.List;
@@ -18,6 +22,26 @@ public class QOrderTest {
private static final QOrder or = QOrder.alias();
+ private static final FetchGroup fgC = QCustomer.forFetchGroup()
+ .select(cu.name, cu.phoneNumber)
+ .buildFetchGroup();
+
+ private static final FetchGroup fgNested1 = QOrder.forFetchGroup()
+ .select(or.status, or.shipDate)
+ .customer.fetch(fgC)
+ .buildFetchGroup();
+
+ private static final FetchGroup fgNested_fetchQuery = QOrder.forFetchGroup()
+ .select(or.status)
+ .customer.fetchQuery(fgC)
+ .buildFetchGroup();
+
+ private static final FetchGroup fgNested_fetchCache = QOrder.forFetchGroup()
+ .select(or.status)
+ .customer.fetchCache(fgC)
+ .buildFetchGroup();
+
+
private static final FetchGroup fg = QOrder.forFetchGroup()
.select(or.status, or.shipDate)
.customer.fetchCache(cu.name, cu.status, cu.registered, cu.comments)
@@ -28,6 +52,20 @@ public class QOrderTest {
.customer.fetch(cu.name)
.buildFetchGroup();
+ private static Order order;
+ private static Customer customer;
+
+ @BeforeClass
+ public static void before() {
+ setupData();
+ }
+
+ @AfterClass
+ public static void after() {
+ DB.delete(order);
+ DB.delete(customer);
+ }
+
@Test
public void fetchCache() {
@@ -76,6 +114,69 @@ public class QOrderTest {
assertThat(sql.get(0)).contains("select t0.id, t0.status, t1.id, t1.name from o_order t0 join be_customer t1 on t1.id = t0.customer_id where");
}
+ @Test
+ public void viaFetchGraph_withNested() {
+
+ DB.getDefault();
+ LoggedSql.start();
+
+ new QOrder()
+ .status.eq(Order.Status.NEW)
+ .select(fgNested1)
+ .findList();
+
+ final List sql = LoggedSql.stop();
+ assertThat(sql).hasSize(1);
+ assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.ship_date, t1.id, t1.name, t1.phone_number from o_order t0 join be_customer t1 on t1.id = t0.customer_id where");
+ }
+
+ @Test
+ public void viaFetchGraph_withNested_fetchQuery() {
+
+ DB.getDefault();
+ LoggedSql.start();
+
+ final Order found = new QOrder()
+ .id.eq(order.getId())
+ .select(fgNested_fetchQuery)
+ .findOne();
+
+ final List sql = LoggedSql.stop();
+
+ // assert fetching customer via fetchQuery
+ assertThat(sql).hasSize(2);
+ assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.customer_id from o_order t0 where t0.id = ?");
+ assertThat(sql.get(1)).contains("select t0.id, t0.name, t0.phone_number from be_customer t0 where t0.id = ?");
+
+ assertThat(found.getCustomer().getPhoneNumber().getMsisdn()).isEqualTo("Ph1");
+ }
+
+
+ @Test
+ public void viaFetchGraph_withNested_fetchCache() {
+
+ DB.getDefault();
+
+ // ensure the customer is loaded in the L2 cache
+ new QCustomer().id.eq(customer.getId()).findOne();
+
+ LoggedSql.start();
+
+ final Order found = new QOrder()
+ .id.eq(order.getId())
+ .select(fgNested_fetchCache) // cache hit for customer
+ .findOne();
+
+ final String msisdn = found.getCustomer().getPhoneNumber().getMsisdn();
+ assertThat(msisdn).isEqualTo("Ph1");
+
+ final List sql = LoggedSql.stop();
+
+ // assert we only hit DB for order
+ assertThat(sql).hasSize(1);
+ assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.customer_id from o_order t0 where t0.id = ?");
+ }
+
@Test
public void select_partial() {
@@ -113,4 +214,15 @@ public class QOrderTest {
}
+ private static void setupData() {
+
+ customer = new Customer();
+ customer.setName("Fred");
+ customer.setPhoneNumber(new PhoneNumber("Ph1"));
+ customer.save();
+
+ order = new Order();
+ order.setCustomer(customer);
+ order.save();
+ }
}
diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml
index 2a346b178..573fbcf3b 100644
--- a/ebean-redis/pom.xml
+++ b/ebean-redis/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
ebean-redis
@@ -16,41 +16,41 @@
redis.clients
jedis
- 3.5.2
+ 3.6.0
io.ebean
ebean-api
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
provided
io.ebean
ebean-core
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
provided
io.ebean
ebean-querybean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
io.ebean
querybean-generator
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
io.ebean
ebean-test
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml
index c32dab0c2..c3ed1f57f 100644
--- a/ebean-test/pom.xml
+++ b/ebean-test/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
ebean test
@@ -29,14 +29,14 @@
io.ebean
ebean-core
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
provided
io.ebean
ebean-ddl-generator
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
diff --git a/ebean/pom.xml b/ebean/pom.xml
index 33c8aeb60..ba312953f 100644
--- a/ebean/pom.xml
+++ b/ebean/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
ebean composite
@@ -22,20 +22,20 @@
io.ebean
ebean-api
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-core
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
io.ebean
ebean-querybean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
diff --git a/kotlin-querybean-generator/pom.xml b/kotlin-querybean-generator/pom.xml
index 7016e2aa4..a556671bb 100644
--- a/kotlin-querybean-generator/pom.xml
+++ b/kotlin-querybean-generator/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
kotlin querybean generator
@@ -12,7 +12,7 @@
kotlin-querybean-generator
- 1.4.21
+ 1.4.31
@@ -29,7 +29,7 @@
io.ebean
ebean-querybean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
@@ -43,7 +43,7 @@
io.ebean
ebean-core
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
@@ -64,7 +64,7 @@
io.ebean
ebean-ddl-generator
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
test
@@ -81,46 +81,47 @@
src/test/kotlin
-
- org.jetbrains.kotlin
- kotlin-maven-plugin
- ${kotlin.version}
-
-
- test-compile
- test-compile
-
- test-compile
-
-
-
- test-kapt
-
- test-kapt
-
-
-
- src/test/kotlin
-
-
-
-
-
-
-
-
-
-
-
-
-
- 1.8
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
org.apache.maven.plugins
maven-compiler-plugin
- 3.2
+ 3.8.1
default-testCompile
@@ -145,7 +146,7 @@
io.ebean
ebean-maven-plugin
- 12.6.6
+ 12.8.2
test
diff --git a/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeMap.java b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeMap.java
index f5676f060..6afc8a6e3 100644
--- a/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeMap.java
+++ b/kotlin-querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeMap.java
@@ -25,10 +25,9 @@ class PropertyTypeMap {
*/
private final PropertyType dbJsonType = new PropertyType("PJson");
- private Map map = new HashMap<>();
+ private final Map map = new HashMap<>();
PropertyTypeMap() {
-
map.put("boolean", new PropertyType("PBoolean"));
map.put("short", new PropertyType("PShort"));
map.put("int", new PropertyType("PInteger"));
@@ -57,7 +56,6 @@ class PropertyTypeMap {
addType(Locale.class);
addType(File.class);
addType(InetAddress.class);
-
map.put(URI.class.getName(), new PropertyType("PUri"));
map.put(URL.class.getName(), new PropertyType("PUrl"));
map.put(UUID.class.getName(), new PropertyType("PUuid"));
@@ -74,7 +72,6 @@ class PropertyTypeMap {
}
private void addJava8Types() {
-
try {
Class.forName("java.time.Instant");
} catch (ClassNotFoundException e) {
@@ -94,6 +91,7 @@ class PropertyTypeMap {
addType(java.time.YearMonth.class);
addType(java.time.ZoneId.class);
addType(java.time.ZoneOffset.class);
+ addType(java.time.ZonedDateTime.class);
}
private void addJodaTypes() {
diff --git a/pom.xml b/pom.xml
index dbd383225..fb82113fa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,12 +4,12 @@
org.avaje
java8-oss
- 2.3
+ 3.1
io.ebean
ebean-parent
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
pom
ebean parent
diff --git a/querybean-generator/pom.xml b/querybean-generator/pom.xml
index 6e7bf295f..1ea632096 100644
--- a/querybean-generator/pom.xml
+++ b/querybean-generator/pom.xml
@@ -4,7 +4,7 @@
ebean-parent
io.ebean
- 12.8.1-SNAPSHOT
+ 12.8.4-SNAPSHOT
querybean generator
diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeMap.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeMap.java
index b60a42cd7..fa35c72fa 100644
--- a/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeMap.java
+++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeMap.java
@@ -25,10 +25,9 @@ class PropertyTypeMap {
*/
private final PropertyType dbJsonType = new PropertyType("PJson");
- private Map map = new HashMap<>();
+ private final Map map = new HashMap<>();
PropertyTypeMap() {
-
map.put("boolean", new PropertyType("PBoolean"));
map.put("short", new PropertyType("PShort"));
map.put("int", new PropertyType("PInteger"));
@@ -57,7 +56,6 @@ class PropertyTypeMap {
map.put("java.lang.Class>", new PropertyType("PClass"));
addType(File.class);
addType(InetAddress.class);
-
map.put(URI.class.getName(), new PropertyType("PUri"));
map.put(URL.class.getName(), new PropertyType("PUrl"));
map.put(UUID.class.getName(), new PropertyType("PUuid"));
@@ -73,7 +71,6 @@ class PropertyTypeMap {
}
private void addJava8Types() {
-
try {
Class.forName("java.time.Instant");
} catch (ClassNotFoundException e) {
@@ -93,6 +90,7 @@ class PropertyTypeMap {
addType(java.time.YearMonth.class);
addType(java.time.ZoneId.class);
addType(java.time.ZoneOffset.class);
+ addType(java.time.ZonedDateTime.class);
}
private void addJodaTypes() {
diff --git a/tests/pom.xml b/tests/pom.xml
new file mode 100644
index 000000000..2c79d71fa
--- /dev/null
+++ b/tests/pom.xml
@@ -0,0 +1,23 @@
+
+
+ 4.0.0
+
+ org.avaje
+ java8-oss
+ 2.3
+
+
+ io.ebean
+ tests
+ 1.0
+ pom
+
+ tests
+ test modules
+
+
+ test-java16
+
+
+
+
diff --git a/tests/test-java16/pom.xml b/tests/test-java16/pom.xml
new file mode 100644
index 000000000..21dfa7852
--- /dev/null
+++ b/tests/test-java16/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+
+
+
+
+
+ io.ebean
+ test-java16
+ 1.0
+
+
+ 3.8.1
+
+
+
+
+ io.ebean
+ ebean
+ 12.8.1-SNAPSHOT
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.3
+
+
+
+ io.ebean
+ ebean-test
+ 12.8.1-SNAPSHOT
+ test
+
+
+
+ org.avaje.composite
+ junit
+ 5.0
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+ 16
+ 16
+ 16
+
+
+ io.ebean
+ querybean-generator
+ 12.8.1-SNAPSHOT
+
+
+
+
+
+
+
+
+
diff --git a/tests/test-java16/src/main/java/org/example/records/Address.java b/tests/test-java16/src/main/java/org/example/records/Address.java
new file mode 100644
index 000000000..2ac531402
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/Address.java
@@ -0,0 +1,7 @@
+package org.example.records;
+
+import javax.persistence.Embeddable;
+
+@Embeddable
+public record Address (String line1, String line2, String city) {
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/BaseModel.java b/tests/test-java16/src/main/java/org/example/records/BaseModel.java
new file mode 100644
index 000000000..b5116e638
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/BaseModel.java
@@ -0,0 +1,35 @@
+package org.example.records;
+
+import io.ebean.Model;
+import io.ebean.annotation.Identity;
+
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.Version;
+
+@Identity(start = 1000, cache = 100)
+@MappedSuperclass
+public class BaseModel extends Model {
+
+ @Id
+ long id;
+
+ @Version
+ long version;
+
+ public long getId() {
+ return id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ public void setVersion(long version) {
+ this.version = version;
+ }
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/Contact.java b/tests/test-java16/src/main/java/org/example/records/Contact.java
new file mode 100644
index 000000000..871862f21
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/Contact.java
@@ -0,0 +1,58 @@
+package org.example.records;
+
+import io.ebean.Model;
+
+import javax.persistence.Embedded;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Version;
+
+@Entity
+public class Contact extends Model {
+
+ @Id
+ private long id;
+
+ @Version
+ private long version;
+
+ private final String name;
+
+ @Embedded(prefix = "home_")
+ private Address homeAddress;
+
+ @Embedded(prefix = "work_")
+ private Address workAddress;
+
+ public Contact(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Address getHomeAddress() {
+ return homeAddress;
+ }
+
+ public void setHomeAddress(Address homeAddress) {
+ this.homeAddress = homeAddress;
+ }
+
+ public Address getWorkAddress() {
+ return workAddress;
+ }
+
+ public void setWorkAddress(Address workAddress) {
+ this.workAddress = workAddress;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/Course.java b/tests/test-java16/src/main/java/org/example/records/Course.java
new file mode 100644
index 000000000..19e8d09fe
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/Course.java
@@ -0,0 +1,33 @@
+package org.example.records;
+
+import io.ebean.annotation.Length;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = "course")
+public class Course extends BaseModel {
+
+ @Length(200)
+ final String name;
+
+ @Length(400)
+ String summary;
+
+ public Course(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getSummary() {
+ return summary;
+ }
+
+ public void setSummary(String summary) {
+ this.summary = summary;
+ }
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/CourseRecordEntity.java b/tests/test-java16/src/main/java/org/example/records/CourseRecordEntity.java
new file mode 100644
index 000000000..dc95a8723
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/CourseRecordEntity.java
@@ -0,0 +1,13 @@
+package org.example.records;
+
+import io.ebean.annotation.Identity;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+@Identity(start = 1000)
+@Entity
+@Table(name="course_rec")
+public record CourseRecordEntity(@Id long id, String name, String notes) {
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/UserRole.java b/tests/test-java16/src/main/java/org/example/records/UserRole.java
new file mode 100644
index 000000000..682e61f4a
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/UserRole.java
@@ -0,0 +1,36 @@
+package org.example.records;
+
+import io.ebean.Model;
+
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Version;
+
+@Entity
+public class UserRole extends Model {
+
+ @EmbeddedId
+ final UserRoleId id;
+
+ String note;
+
+ @Version
+ long version;
+
+ public UserRole(UserRoleId id, String note) {
+ this.id = id;
+ this.note = note;
+ }
+
+ public UserRoleId getId() {
+ return id;
+ }
+
+ public String getNote() {
+ return note;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/UserRoleId.java b/tests/test-java16/src/main/java/org/example/records/UserRoleId.java
new file mode 100644
index 000000000..a0807de0a
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/UserRoleId.java
@@ -0,0 +1,7 @@
+package org.example.records;
+
+import javax.persistence.Embeddable;
+
+@Embeddable
+public record UserRoleId(Integer userId, String roleId) {
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/UserSite.java b/tests/test-java16/src/main/java/org/example/records/UserSite.java
new file mode 100644
index 000000000..a8cf46449
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/UserSite.java
@@ -0,0 +1,51 @@
+package org.example.records;
+
+import io.ebean.Model;
+
+import javax.persistence.*;
+import java.util.UUID;
+
+@IdClass(UserSiteId.class)
+@Entity
+public class UserSite extends Model {
+
+ @Id
+ final UUID userId;
+
+ @Id
+ final UUID siteId;
+
+ String note;
+
+ @Version
+ long version;
+
+ public UserSite(UUID userId, UUID siteId) {
+ this.userId = userId;
+ this.siteId = siteId;
+ }
+
+ public UUID getUserId() {
+ return userId;
+ }
+
+ public UUID getSiteId() {
+ return siteId;
+ }
+
+ public void setNote(String note) {
+ this.note = note;
+ }
+
+ public void setVersion(long version) {
+ this.version = version;
+ }
+
+ public String getNote() {
+ return note;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+}
diff --git a/tests/test-java16/src/main/java/org/example/records/UserSiteId.java b/tests/test-java16/src/main/java/org/example/records/UserSiteId.java
new file mode 100644
index 000000000..36c4dd599
--- /dev/null
+++ b/tests/test-java16/src/main/java/org/example/records/UserSiteId.java
@@ -0,0 +1,8 @@
+package org.example.records;
+
+import javax.persistence.Embeddable;
+import java.util.UUID;
+
+@Embeddable
+public record UserSiteId(UUID userId, UUID siteId) {
+}
diff --git a/tests/test-java16/src/test/java/org/example/records/CourseRecordEntityTest.java b/tests/test-java16/src/test/java/org/example/records/CourseRecordEntityTest.java
new file mode 100644
index 000000000..63e0c7d84
--- /dev/null
+++ b/tests/test-java16/src/test/java/org/example/records/CourseRecordEntityTest.java
@@ -0,0 +1,40 @@
+package org.example.records;
+
+import io.ebean.DB;
+import org.example.records.query.QCourseRecordEntity;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CourseRecordEntityTest {
+
+ @Test
+ void record_insert_withGivenIdValue() {
+ var course = new CourseRecordEntity(42, "SuppliedId", "Record");
+ DB.save(course);
+ assertThat(course.id()).isEqualTo(42);
+
+ var found = new QCourseRecordEntity()
+ .name.startsWith("SuppliedId")
+ .findOneOrEmpty();
+
+ assertThat(found).isPresent();
+ assertThat(found.get().id()).isEqualTo(42L);
+ assertThat(found.get().notes()).isEqualTo("Record");
+ }
+
+ @Test
+ void record_insert_usingGeneratedId() {
+ var course2 = new CourseRecordEntity(0, "Second", "Record with generated id");
+ DB.save(course2);
+ // as using @Identity(start = 1000)
+ assertThat(course2.id()).isEqualTo(1000);
+
+ var second = new QCourseRecordEntity()
+ .name.startsWith("Second")
+ .findOneOrEmpty();
+
+ assertThat(second).isPresent();
+ assertThat(second.get().notes()).isEqualTo("Record with generated id");
+ }
+}
diff --git a/tests/test-java16/src/test/java/org/example/records/DtoQueryUsingRecordsTest.java b/tests/test-java16/src/test/java/org/example/records/DtoQueryUsingRecordsTest.java
new file mode 100644
index 000000000..e64c15b1c
--- /dev/null
+++ b/tests/test-java16/src/test/java/org/example/records/DtoQueryUsingRecordsTest.java
@@ -0,0 +1,59 @@
+package org.example.records;
+
+import io.ebean.DB;
+import org.example.records.query.QCourse;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.example.records.query.QCourse.Alias.id;
+import static org.example.records.query.QCourse.Alias.name;
+
+class DtoQueryUsingRecordsTest {
+
+ @Test
+ void dtoQuery_projectRecords() {
+
+ var course = new Course("Calculus");
+ course.setSummary("Something here");
+ course.save();
+
+ performOrmQuery();
+
+ records_via_SqlDtoQuery(course);
+ records_via_OrmQueryToDtoQuery(course);
+
+ course.delete();
+ }
+
+ private void performOrmQuery() {
+ List courses = new QCourse()
+ .name.startsWith("Calc")
+ .findList();
+
+ assertThat(courses).hasSize(1);
+ }
+
+ private void records_via_SqlDtoQuery(Course course) {
+ List records = DB.findDto(Foo.class, "select id, name from course where name like ?")
+ .setParameter("Calc%")
+ .findList();
+
+ assertThat(records).hasSize(1);
+ assertThat(records.get(0).name()).isEqualTo(course.getName());
+ }
+
+ private void records_via_OrmQueryToDtoQuery(Course course) {
+ List records2 = new QCourse()
+ .select(id, name )
+ .name.startsWith("Calc")
+ .asDto(Foo.class)
+ .findList();
+
+ assertThat(records2).hasSize(1);
+ assertThat(records2.get(0).name()).isEqualTo(course.getName());
+ }
+
+ public record Foo(long id, String name){}
+}
diff --git a/tests/test-java16/src/test/java/org/example/records/RecordAsEmbeddedTest.java b/tests/test-java16/src/test/java/org/example/records/RecordAsEmbeddedTest.java
new file mode 100644
index 000000000..271481468
--- /dev/null
+++ b/tests/test-java16/src/test/java/org/example/records/RecordAsEmbeddedTest.java
@@ -0,0 +1,48 @@
+package org.example.records;
+
+import org.example.records.query.QContact;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.example.records.query.QContact.Alias.homeAddress;
+import static org.example.records.query.QContact.Alias.name;
+
+public class RecordAsEmbeddedTest {
+
+ @Test
+ void insert_query() {
+
+ var contact = new Contact("Rob");
+ contact.setWorkAddress(new Address("45 work", "workling", "wo"));
+ contact.setHomeAddress(new Address("94 home st", "homeline", "wo"));
+
+ contact.save();
+
+ Contact found = new QContact()
+ .id.eq(contact.getId())
+ .findOne();
+
+ assertThat(found.getWorkAddress().toString()).isEqualTo("Address[line1=45 work, line2=workling, city=wo]");
+ assertThat(found.getHomeAddress().toString()).isEqualTo("Address[line1=94 home st, line2=homeline, city=wo]");
+
+ Contact foundPartial = new QContact()
+ .select(name, homeAddress)
+ .id.eq(contact.getId())
+ .findOne();
+
+ // invoke lazy loading on getWorkAddress
+ assertThat(foundPartial.getWorkAddress().toString()).isEqualTo("Address[line1=45 work, line2=workling, city=wo]");
+ assertThat(foundPartial.getHomeAddress().toString()).isEqualTo("Address[line1=94 home st, line2=homeline, city=wo]");
+
+ Contact foundNoLazyLoading = new QContact()
+ .select(name, homeAddress)
+ .setDisableLazyLoading(true)
+ .id.eq(contact.getId())
+ .findOne();
+
+ // no lazy loading on getWorkAddress this time
+ assertThat(foundNoLazyLoading.getWorkAddress()).isNull();
+ assertThat(foundNoLazyLoading.getHomeAddress().toString()).isEqualTo("Address[line1=94 home st, line2=homeline, city=wo]");
+
+ }
+}
diff --git a/tests/test-java16/src/test/java/org/example/records/RecordIdClassTest.java b/tests/test-java16/src/test/java/org/example/records/RecordIdClassTest.java
new file mode 100644
index 000000000..47008c544
--- /dev/null
+++ b/tests/test-java16/src/test/java/org/example/records/RecordIdClassTest.java
@@ -0,0 +1,34 @@
+package org.example.records;
+
+import org.example.records.query.QUserSite;
+import org.junit.jupiter.api.Test;
+
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RecordIdClassTest {
+
+ @Test
+ void insert_query() {
+
+ UUID userId = UUID.randomUUID();
+ UUID siteId = UUID.randomUUID();
+
+ var userSite = new UserSite(userId, siteId);
+ userSite.setNote("HelloIdClass");
+ userSite.save();
+
+ var id = new UserSiteId(userId, siteId);
+
+ UserSite found = new QUserSite()
+ .setId(id)
+ .findOne();
+
+ assertThat(found.getUserId()).isEqualTo(userId);
+ assertThat(found.getSiteId()).isEqualTo(siteId);
+ assertThat(found.getNote()).isEqualTo("HelloIdClass");
+
+
+ }
+}
diff --git a/tests/test-java16/src/test/java/org/example/records/UserRoleTest.java b/tests/test-java16/src/test/java/org/example/records/UserRoleTest.java
new file mode 100644
index 000000000..1b4231b58
--- /dev/null
+++ b/tests/test-java16/src/test/java/org/example/records/UserRoleTest.java
@@ -0,0 +1,38 @@
+package org.example.records;
+
+import org.example.records.query.QUserRole;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class UserRoleTest {
+
+ final UserRoleId id = new UserRoleId(42, "R7");
+
+ @Test
+ void id_equals() {
+ assertThat(id).isEqualTo(new UserRoleId(42, "R7"));
+ }
+
+ @Test
+ void id_notEquals() {
+ assertThat(id).isNotEqualTo(new UserRoleId(43, "R7"));
+ assertThat(id).isNotEqualTo(new UserRoleId(42, "R8"));
+ }
+
+ @Test
+ void insert_query() {
+
+ var userRole = new UserRole(id, "hello");
+ userRole.save();
+
+ UserRole found = new QUserRole()
+ .id.eq(new UserRoleId(42, "R7"))
+ .findOne();
+
+ UserRoleId id1 = found.getId();
+ assertThat(id1).isEqualTo(id);
+ assertThat(id1.userId()).isEqualTo(42);
+ assertThat(id1.roleId()).isEqualTo("R7");
+ }
+}
diff --git a/tests/test-java16/src/test/resources/application-test.yaml b/tests/test-java16/src/test/resources/application-test.yaml
new file mode 100644
index 000000000..029b11023
--- /dev/null
+++ b/tests/test-java16/src/test/resources/application-test.yaml
@@ -0,0 +1,6 @@
+ebean:
+ test:
+ platform: h2
+ ddlMode: dropCreate
+ dbName: foo
+
diff --git a/tests/test-java16/src/test/resources/logback-test.xml b/tests/test-java16/src/test/resources/logback-test.xml
new file mode 100644
index 000000000..ce7ad5968
--- /dev/null
+++ b/tests/test-java16/src/test/resources/logback-test.xml
@@ -0,0 +1,22 @@
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+