diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml
index ce3ba3688..0a2a3b777 100644
--- a/ebean-api/pom.xml
+++ b/ebean-api/pom.xml
@@ -4,7 +4,7 @@
+ * Typically holds a representation of the PreparedStatement to perform the
+ * actual cancel.
+ *
+ * For JDBC this translates to calling cancel on the PreparedStatement.
+ *
+ * Note that the QueryIterator holds resources related to the underlying
+ * resultSet and potentially connection and MUST be closed. We should use
+ * QueryIterator in a try with resource block.
+ */
+ @Nonnull
+ QueryIterator
+ * Note that the Stream holds resources related to the underlying
+ * resultSet and potentially connection and MUST be closed. We should use
+ * the Stream in a try with resource block.
+ */
+ @Nonnull
+ Stream
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
- * This must be called from a different thread to the query executor.
- *
diff --git a/ebean-api/src/main/java/io/ebean/QueryIterator.java b/ebean-api/src/main/java/io/ebean/QueryIterator.java
index c158e62c6..6ad523151 100644
--- a/ebean-api/src/main/java/io/ebean/QueryIterator.java
+++ b/ebean-api/src/main/java/io/ebean/QueryIterator.java
@@ -56,7 +56,7 @@ import java.util.Iterator;
*
* @param
+ * 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
- *
* 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.
- *
* 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.
- *
+ * 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).
- *
* Calling this method with batchFlushOnQuery = false means that you can
* execute a query and the batch will not be automatically flushed.
- *
* The default is for this to be true.
- *
* Flushing occurs automatically when:
- *
* Examples of when a developer may wish to use the connection directly are:
* Savepoints, advanced CLOB BLOB use and advanced stored procedure calls.
- *
* 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.
- *
* Technically this is NOT an extension of
* java.util.Collection. The reason being that java.util.Map is not a
* Collection. I realise this makes this name confusing so I apologise for that.
- *
* Returning true means can safely skip cascade save for this bean collection.
- *
* That is, if the collection was not loaded due to filterMany predicates etc
* then make sure the collection is set to empty.
- *
- * This is disallowed for BeanMap.
- *
* For maps this returns the entrySet as we need the keys of the map.
- *
* This will potentially end up as an insert into a intersection table for a
* ManyToMany.
- *
* This will potentially end up as an delete from an intersection table for a
* ManyToMany.
- *
* This is used to ensure only one instance for a given entity type and id is
* used to build object graphs from queries and lazy loading.
- *
* Returns an existing entity bean (if one is already there) and otherwise
* returns null.
- *
* If a bean has been deleted then for the same persistence context is should
* not be able to be fetched from persistence context or L2 cache.
- *
- * This is because the key values may need to be set against the details (so
- * they don't need to be set twice).
- * {@code
*
@@ -392,14 +397,11 @@ public interface Transaction extends AutoCloseable {
*
*
- * This is required for persisting ManyToMany objects. Additions and removals - * become inserts and deletes to the intersection table. - *
- */ -class ModifySet+ * That is, when true beans that do not implement ModifyAwareType are by + * default assumed to be dirty and included in updates. + */ + public boolean isJsonDirtyByDefault() { + return jsonDirtyByDefault; + } + + /** + * Set to false if we want DbJson beans to not be assumed to be dirty. + *
+ * That is, when true beans that do not implement ModifyAwareType are by + * default assumed to be dirty and included in updates. + */ + public void setJsonDirtyByDefault(boolean jsonDirtyByDefault) { + this.jsonDirtyByDefault = jsonDirtyByDefault; + } + /** * Return the name of the Database. */ @@ -896,6 +937,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 +1100,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); @@ -2799,14 +2853,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); @@ -2876,6 +2935,7 @@ public class DatabaseConfig { jsonInclude = p.getEnum(JsonConfig.Include.class, "jsonInclude", jsonInclude); jsonDateTime = p.getEnum(JsonConfig.DateTime.class, "jsonDateTime", jsonDateTime); jsonDate = p.getEnum(JsonConfig.Date.class, "jsonDate", jsonDate); + jsonDirtyByDefault = p.getBoolean("jsonDirtyByDefault", jsonDirtyByDefault); runMigration = p.getBoolean("migration.run", runMigration); ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate); @@ -3096,14 +3156,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; @@ -3176,29 +3239,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;
}
/**
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
+ * 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-api/src/main/java/io/ebean/util/JdbcClose.java b/ebean-api/src/main/java/io/ebean/util/JdbcClose.java
index 92b3f7e28..b72f59c4d 100644
--- a/ebean-api/src/main/java/io/ebean/util/JdbcClose.java
+++ b/ebean-api/src/main/java/io/ebean/util/JdbcClose.java
@@ -66,4 +66,17 @@ public class JdbcClose {
logger.warn("Error on connection rollback", e);
}
}
+
+ /**
+ * Cancels the statement
+ */
+ public static void cancel(Statement stmt) {
+ try {
+ if (stmt != null) {
+ stmt.cancel();
+ }
+ } catch (SQLException e) {
+ logger.warn("Error on cancelling statement", e);
+ }
+ }
}
diff --git a/ebean-autotune/pom.xml b/ebean-autotune/pom.xml
index 5c081a9df..6b6e9511a 100644
--- a/ebean-autotune/pom.xml
+++ b/ebean-autotune/pom.xml
@@ -4,7 +4,7 @@
* For new profiling entries it is useful to compare the profiling against the current * query detail that is specified in the code (as the query might already be manually optimised). - *
*/ private ProfileOrigin createProfileOrigin(ObjectGraphNode origin, SpiQuery> query) { ProfileOrigin profileOrigin = new ProfileOrigin(origin.getOriginQueryPoint(), queryTuningAddVersion, profilingBase, profilingRate); // set the current query detail (fetch group) so that we can compare against profiling for new entries - profileOrigin.setOriginalQuery(query.getDetail().toString()); + profileOrigin.setOriginalQuery(query.getDetail().asString()); return profileOrigin; } @@ -77,7 +75,6 @@ public class ProfileManager implements ProfilingListener { */ @Override public void collectQueryInfo(ObjectGraphNode node, long beans, long micros) { - if (node != null) { ObjectGraphOrigin origin = node.getOriginQueryPoint(); if (origin != null) { @@ -92,11 +89,9 @@ public class ProfileManager implements ProfilingListener { ** This is sent to use from a EntityBeanIntercept when the finalise method * is called on the bean. - *
*/ @Override public void collectNodeUsage(NodeUsageCollector usageCollector) { - ProfileOrigin profileOrigin = getProfileOrigin(usageCollector.getNode().getOriginQueryPoint()); profileOrigin.collectUsageInfo(usageCollector); } @@ -114,16 +109,13 @@ public class ProfileManager implements ProfilingListener { * Collect all the profiling information. */ public AutoTuneCollection profilingCollection(boolean reset) { - AutoTuneCollection req = new AutoTuneCollection(); - for (ProfileOrigin origin : profileMap.values()) { BeanDescriptor> desc = server.getBeanDescriptorById(origin.getOrigin().getBeanType()); if (desc != null) { origin.profilingCollection(desc, req, reset); } } - return req; } diff --git a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/TunedQueryInfo.java b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/TunedQueryInfo.java index 374a8d722..43e66c5af 100644 --- a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/TunedQueryInfo.java +++ b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/TunedQueryInfo.java @@ -65,7 +65,7 @@ public class TunedQueryInfo implements Serializable { @Override public String toString() { - return tunedDetail.toString(); + return tunedDetail.asString(); } } diff --git a/ebean-autotune/src/test/java/io/ebeaninternal/server/autotune/service/ProfileOriginTest.java b/ebean-autotune/src/test/java/io/ebeaninternal/server/autotune/service/ProfileOriginTest.java index c3995d215..b1f982024 100644 --- a/ebean-autotune/src/test/java/io/ebeaninternal/server/autotune/service/ProfileOriginTest.java +++ b/ebean-autotune/src/test/java/io/ebeaninternal/server/autotune/service/ProfileOriginTest.java @@ -27,7 +27,7 @@ public class ProfileOriginTest extends BaseTestCase { OrmQueryDetail detail = po.buildDetail(desc); - assertThat(detail.asStringDebug().trim()).isEqualTo("fetch customer (name)"); + assertThat(detail.asString().trim()).isEqualTo("fetch customer (name)"); } @Test @@ -46,7 +46,7 @@ public class ProfileOriginTest extends BaseTestCase { OrmQueryDetail detail = po.buildDetail(desc); - assertThat(detail.asStringDebug()).isEqualTo("select (orderDate) fetch customer (name)"); + assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (name)"); } @Test @@ -64,7 +64,7 @@ public class ProfileOriginTest extends BaseTestCase { OrmQueryDetail detail = po.buildDetail(desc); - assertThat(detail.asStringDebug().trim()).isEqualTo("select (orderDate,customer)"); + assertThat(detail.asString().trim()).isEqualTo("select (orderDate,customer)"); } @Test @@ -87,7 +87,7 @@ public class ProfileOriginTest extends BaseTestCase { OrmQueryDetail detail = po.buildDetail(desc); - assertThat(detail.asStringDebug()).isEqualTo("select (orderDate) fetch customer (billingAddress)"); + assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (billingAddress)"); } @@ -116,7 +116,7 @@ public class ProfileOriginTest extends BaseTestCase { po.collectUsageInfo(c); OrmQueryDetail detail = po.buildDetail(desc); - assertThat(detail.asStringDebug()).isEqualTo("fetch customer (name,note) fetch customer.billingAddress (line1)"); + assertThat(detail.asString()).isEqualTo("fetch customer (name,note) fetch customer.billingAddress (line1)"); } private NodeUsageCollector node(String path) { diff --git a/ebean-bom/pom.xml b/ebean-bom/pom.xml index e96c248d7..446c823f2 100644 --- a/ebean-bom/pom.xml +++ b/ebean-bom/pom.xml @@ -4,7 +4,7 @@