mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
45
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc58fc8195 | ||
|
|
1c34c09f3c | ||
|
|
8544712448 | ||
|
|
5e22079366 | ||
|
|
903b0a3929 | ||
|
|
b18bc82b8a | ||
|
|
50de32af29 | ||
|
|
0b18189e62 | ||
|
|
0b6e73d729 | ||
|
|
3108076953 | ||
|
|
7ed4435ccc | ||
|
|
7aef4ff1bd | ||
|
|
5bcad17ae1 | ||
|
|
b1beb0b787 | ||
|
|
585eb2b57b | ||
|
|
2653ec50aa | ||
|
|
cb4432a588 | ||
|
|
0b299edf0d | ||
|
|
cf10246783 | ||
|
|
babba914a0 | ||
|
|
0397140e0b | ||
|
|
82def2710e | ||
|
|
60ad417e1b | ||
|
|
3af1b574fd | ||
|
|
60c8683914 | ||
|
|
72263c0af0 | ||
|
|
f66316138d | ||
|
|
b403942491 | ||
|
|
baa3909c63 | ||
|
|
81f42f8e95 | ||
|
|
0377e3ec5c | ||
|
|
58f6c67d78 | ||
|
|
84f7fc5d81 | ||
|
|
ea02bebc6f | ||
|
|
da874d84a8 | ||
|
|
049cacad1c | ||
|
|
ced8c79458 | ||
|
|
a66edf8077 | ||
|
|
94591954b3 | ||
|
|
e611353163 | ||
|
|
e7811910e9 | ||
|
|
138b587ff7 | ||
|
|
8fc6c4c648 | ||
|
|
ebdc28e1cd | ||
|
|
51f79e769c |
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.29.1</version>
|
||||
<version>11.31.3</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ebean</name>
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>ebean-11.29.1</tag>
|
||||
<tag>ebean-11.31.3</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
@@ -117,7 +117,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-annotation</artifactId>
|
||||
<version>4.3</version>
|
||||
<version>4.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -135,7 +135,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>11.12.1</version>
|
||||
<version>11.13.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -229,7 +229,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-agent</artifactId>
|
||||
<version>11.25.1</version>
|
||||
<version>11.27.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>11.25.1</version>
|
||||
<version>11.27.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -37,4 +39,21 @@ public interface BackgroundExecutor {
|
||||
* </p>
|
||||
*/
|
||||
void executePeriodically(Runnable r, long delay, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Schedules a Runnable for one-shot action that becomes enabled after the given delay.
|
||||
*
|
||||
* @return a ScheduledFuture representing pending completion of the task and
|
||||
* whose get() method will return null upon completion
|
||||
*/
|
||||
ScheduledFuture<?> schedule(Runnable r, long delay, TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Schedules a Callable for one-shot action that becomes enabled after the given delay.
|
||||
*
|
||||
* @return a ScheduledFuture that can be used to extract result or cancel
|
||||
*/
|
||||
<V> ScheduledFuture<V> schedule(Callable<V> c, long delay, TimeUnit unit);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.util.function.Predicate;
|
||||
* String sql = "select id, name from customer where name like :name and status_code = :status";
|
||||
*
|
||||
* List<CustomerDto> beans =
|
||||
* Ebean.findDto(CustomrDto.class, sql)
|
||||
* Ebean.findDto(CustomerDto.class, sql)
|
||||
* .setParameter("name", "Acme%")
|
||||
* .setParameter("status", "ACTIVE")
|
||||
* .findList();
|
||||
|
||||
@@ -128,6 +128,38 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
<D> DtoQuery<D> asDto(Class<D> dtoClass);
|
||||
|
||||
/**
|
||||
* Return the underlying query as an UpdateQuery.
|
||||
* <p>
|
||||
* Typically this is used with query beans to covert a query bean
|
||||
* query into an UpdateQuery like the examples below.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rowsUpdated = new QCustomer()
|
||||
* .name.startsWith("Rob")
|
||||
* .asUpdate()
|
||||
* .set("active", false)
|
||||
* .update();;
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rowsUpdated = new QContact()
|
||||
* .notes.note.startsWith("Make Inactive")
|
||||
* .email.endsWith("@foo.com")
|
||||
* .customer.id.equalTo(42)
|
||||
* .asUpdate()
|
||||
* .set("inactive", true)
|
||||
* .setRaw("email = lower(email)")
|
||||
* .update();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
UpdateQuery<T> asUpdate();
|
||||
|
||||
/**
|
||||
* Execute using "for update" clause which results in the DB locking the record.
|
||||
*/
|
||||
|
||||
@@ -244,6 +244,38 @@ public interface Query<T> {
|
||||
*/
|
||||
<D> DtoQuery<D> asDto(Class<D> dtoClass);
|
||||
|
||||
/**
|
||||
* Convert the query to a UpdateQuery.
|
||||
* <p>
|
||||
* Typically this is used with query beans to covert a query bean
|
||||
* query into an UpdateQuery like the examples below.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rowsUpdated = new QCustomer()
|
||||
* .name.startsWith("Rob")
|
||||
* .asUpdate()
|
||||
* .set("active", false)
|
||||
* .update();;
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* int rowsUpdated = new QContact()
|
||||
* .notes.note.startsWith("Make Inactive")
|
||||
* .email.endsWith("@foo.com")
|
||||
* .customer.id.equalTo(42)
|
||||
* .asUpdate()
|
||||
* .set("inactive", true)
|
||||
* .setRaw("email = lower(email)")
|
||||
* .update();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
UpdateQuery<T> asUpdate();
|
||||
|
||||
/**
|
||||
* Cancel the query execution if supported by the underlying database and
|
||||
* driver.
|
||||
@@ -636,15 +668,13 @@ public interface Query<T> {
|
||||
* .where().eq("status", Status.NEW)
|
||||
* .order().asc("id");
|
||||
*
|
||||
* QueryIterator<Customer> it = query.findIterate();
|
||||
* try {
|
||||
* // use try with resources to ensure QueryIterator is closed
|
||||
*
|
||||
* try (QueryIterator<Customer> it = query.findIterate()) {
|
||||
* while (it.hasNext()) {
|
||||
* Customer customer = it.next();
|
||||
* // do something with customer ...
|
||||
* }
|
||||
* } finally {
|
||||
* // close the underlying resources
|
||||
* it.close();
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
@@ -1329,7 +1359,6 @@ public interface Query<T> {
|
||||
* count:1 orderStatus:COMPLETE
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*/
|
||||
Query<T> setCountDistinct(CountDistinctOrder orderBy);
|
||||
|
||||
|
||||
@@ -154,9 +154,19 @@ public interface UpdateQuery<T> {
|
||||
*/
|
||||
UpdateQuery<T> setProfileLocation(ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Set the label on the update query.
|
||||
*/
|
||||
UpdateQuery<T> setLabel(String label);
|
||||
|
||||
/**
|
||||
* Return the query expression list to add predicates to.
|
||||
*/
|
||||
ExpressionList<T> where();
|
||||
|
||||
/**
|
||||
* Execute the update returning the number of rows updated.
|
||||
*/
|
||||
int update();
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ public class ServerCacheNotification {
|
||||
this.dependentTables = dependentTables;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ts:" + modifyTimestamp + " tables:" + dependentTables;
|
||||
}
|
||||
|
||||
public long getModifyTimestamp() {
|
||||
return modifyTimestamp;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ public class ServerCacheOptions {
|
||||
private int maxIdleSecs;
|
||||
private int maxSecsToLive;
|
||||
private int trimFrequency;
|
||||
private boolean nearCache;
|
||||
|
||||
/**
|
||||
* Construct with no set options.
|
||||
@@ -40,6 +41,14 @@ public class ServerCacheOptions {
|
||||
this.trimFrequency = cacheTuning.trimFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with nearCache option.
|
||||
*/
|
||||
public ServerCacheOptions(boolean nearCache, CacheBeanTuning tuning) {
|
||||
this(tuning);
|
||||
this.nearCache = nearCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any settings from the default settings that have not already been
|
||||
* specifically set.
|
||||
@@ -70,9 +79,33 @@ public class ServerCacheOptions {
|
||||
copy.maxIdleSecs = maxIdleSecs;
|
||||
copy.maxSecsToLive = maxSecsToLive;
|
||||
copy.trimFrequency = trimFrequency;
|
||||
copy.nearCache = this.nearCache;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a copy of this object with nearCache option.
|
||||
*/
|
||||
public ServerCacheOptions copy(boolean nearCache) {
|
||||
ServerCacheOptions copy = copy();
|
||||
copy.nearCache = nearCache;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if nearCache was explicitly turned on.
|
||||
*/
|
||||
public boolean isNearCache() {
|
||||
return nearCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on nearCache option.
|
||||
*/
|
||||
public void setNearCache(boolean nearCache) {
|
||||
this.nearCache = nearCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum cache size.
|
||||
*/
|
||||
|
||||
@@ -366,9 +366,10 @@ public class ServerConfig {
|
||||
private UuidVersion uuidVersion = UuidVersion.VERSION4;
|
||||
|
||||
/**
|
||||
* The UUID state file (for Version 1 UUIDs).
|
||||
* The UUID state file (for Version 1 UUIDs). By default, the file is created in
|
||||
* ${HOME}/.ebean/${servername}-uuid.state
|
||||
*/
|
||||
private String uuidStateFile = "ebean-uuid.state";
|
||||
private String uuidStateFile;
|
||||
|
||||
/**
|
||||
* The clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
@@ -486,6 +487,11 @@ public class ServerConfig {
|
||||
*/
|
||||
private boolean notifyL2CacheInForeground;
|
||||
|
||||
/**
|
||||
* Set to true to support query plan capture.
|
||||
*/
|
||||
private boolean collectQueryPlans;
|
||||
|
||||
/**
|
||||
* The time in millis used to determine when a query is alerted for being slow.
|
||||
*/
|
||||
@@ -594,6 +600,47 @@ public class ServerConfig {
|
||||
return serviceObject.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a service object into configuration such that it can be passed to a plugin.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* JedisPool jedisPool = ..
|
||||
*
|
||||
* serverConfig.putServiceObject(jedisPool);
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public void putServiceObject(Object configObject) {
|
||||
String key = serviceObjectKey(configObject);
|
||||
serviceObject.put(key, configObject);
|
||||
}
|
||||
|
||||
private String serviceObjectKey(Object configObject) {
|
||||
return serviceObjectKey(configObject.getClass());
|
||||
}
|
||||
|
||||
private String serviceObjectKey(Class<?> cls) {
|
||||
String simpleName = cls.getSimpleName();
|
||||
return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by plugins to obtain service objects.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* JedisPool jedisPool = serverConfig.getServiceObject(JedisPool.class);
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param cls The type of the service object to obtain
|
||||
* @return The service object given the class type
|
||||
*/
|
||||
public <P> P getServiceObject(Class<P> cls) {
|
||||
return (P) serviceObject.get(serviceObjectKey(cls));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Jackson JsonFactory to use.
|
||||
* <p>
|
||||
@@ -2832,6 +2879,7 @@ public class ServerConfig {
|
||||
|
||||
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
|
||||
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
|
||||
collectQueryPlans = p.getBoolean("collectQueryPlans", collectQueryPlans);
|
||||
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
|
||||
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
|
||||
notifyL2CacheInForeground = p.getBoolean("notifyL2CacheInForeground", notifyL2CacheInForeground);
|
||||
@@ -3197,6 +3245,20 @@ public class ServerConfig {
|
||||
this.idGeneratorAutomatic = idGeneratorAutomatic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if query plan capture is enabled.
|
||||
*/
|
||||
public boolean isCollectQueryPlans() {
|
||||
return collectQueryPlans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to enable query plan capture.
|
||||
*/
|
||||
public void setCollectQueryPlans(boolean collectQueryPlans) {
|
||||
this.collectQueryPlans = collectQueryPlans;
|
||||
}
|
||||
|
||||
public enum UuidVersion {
|
||||
VERSION4,
|
||||
VERSION1,
|
||||
|
||||
@@ -7,6 +7,11 @@ import java.util.List;
|
||||
*/
|
||||
public interface MetaInfoManager {
|
||||
|
||||
/**
|
||||
* Collect query plans.
|
||||
*/
|
||||
List<MetaQueryPlan> collectQueryPlans(QueryPlanRequest request);
|
||||
|
||||
/**
|
||||
* Visit the metrics resetting and collecting/reporting as desired.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
public interface MetaQueryPlan {
|
||||
|
||||
Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Return a string representation of the query plan hash.
|
||||
*/
|
||||
String getQueryPlanHash();
|
||||
|
||||
String getLabel();
|
||||
|
||||
String getSql();
|
||||
|
||||
String getBind();
|
||||
|
||||
String getPlan();
|
||||
|
||||
long getQueryTimeMicros();
|
||||
|
||||
long getCaptureCount();
|
||||
}
|
||||
@@ -25,6 +25,11 @@ public enum MetricType {
|
||||
* <p>
|
||||
* SqlQuery and SqlUpdate without a label have no metrics collected.
|
||||
*/
|
||||
SQL
|
||||
SQL,
|
||||
|
||||
/**
|
||||
* L2 cache metrics.
|
||||
*/
|
||||
L2
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Request used to capture query plans.
|
||||
*/
|
||||
public class QueryPlanRequest {
|
||||
|
||||
private List<MetaQueryPlan> plans = new ArrayList<>();
|
||||
|
||||
private Connection connection;
|
||||
|
||||
private boolean store;
|
||||
|
||||
private long since;
|
||||
|
||||
private Set<Class<?>> includedBeanTypes;
|
||||
|
||||
private Set<String> includedLabels;
|
||||
|
||||
public List<MetaQueryPlan> getPlans() {
|
||||
return plans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the connection to use to capture the query plans.
|
||||
*/
|
||||
public Connection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection to use to capture the query plans.
|
||||
*/
|
||||
public void setConnection(Connection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the captured query plan is stored.
|
||||
*/
|
||||
public boolean isStore() {
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true to store the captured query plan.
|
||||
*/
|
||||
public void setStore(boolean store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the epoch time after which the query plan was capture (to be included).
|
||||
*/
|
||||
public long getSince() {
|
||||
return since;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the epoch time after which the query plan was captured.
|
||||
* <p>
|
||||
* This is used to only capture plans that have changed since a given time (like the time of last capture).
|
||||
* </p>
|
||||
*
|
||||
* @param since The time after which the query plan was captured to be included
|
||||
*/
|
||||
public void setSince(long since) {
|
||||
this.since = since;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process consume the query plan.
|
||||
*/
|
||||
public void process(MetaQueryPlan plan) {
|
||||
plans.add(plan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bean type should be included in the query plan capture.
|
||||
*/
|
||||
public boolean includeType(Class<?> beanType) {
|
||||
return includedBeanTypes == null || includedBeanTypes.isEmpty() || includedBeanTypes.contains(beanType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the label should be included in the query plan capture.
|
||||
*/
|
||||
public boolean includeLabel(String label) {
|
||||
return includedLabels == null || includedLabels.isEmpty() || includedLabels.contains(label);
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetricType;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebeaninternal.server.profile.DMetricFactory;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetaQueryMetric;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
+11
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
@@ -17,6 +17,16 @@ public interface TimedMetric {
|
||||
*/
|
||||
void add(long micros, long beans);
|
||||
|
||||
/**
|
||||
* Add a time event given the start nanos.
|
||||
*/
|
||||
void addSinceNanos(long startNanos);
|
||||
|
||||
/**
|
||||
* Add a time event given the start nanos and bean count.
|
||||
*/
|
||||
void addSinceNanos(long startNanos, long beans);
|
||||
|
||||
/**
|
||||
* Return true if there are no metrics collected since the last collection.
|
||||
*/
|
||||
+11
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
|
||||
@@ -7,6 +7,16 @@ import io.ebean.meta.MetricVisitor;
|
||||
*/
|
||||
public interface TimedMetricMap {
|
||||
|
||||
/**
|
||||
* Add a time event given the start nanos.
|
||||
*/
|
||||
void addSinceNanos(String key, long startNanos);
|
||||
|
||||
/**
|
||||
* Add a time event given the start nanos and beans.
|
||||
*/
|
||||
void addSinceNanos(String key, long startNanos, int beans);
|
||||
|
||||
/**
|
||||
* Add an execution for the given key.
|
||||
*/
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ebeaninternal.metric;
|
||||
package io.ebean.metric;
|
||||
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
|
||||
@@ -3,7 +3,6 @@ package io.ebeaninternal.api;
|
||||
import io.ebean.Pairs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -749,23 +749,6 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
boolean isDisableLazyLoading();
|
||||
|
||||
/**
|
||||
* Internally set by Ebean when this query must use the DISTINCT keyword.
|
||||
* <p>
|
||||
* This does not exclude/remove the use of the id property.
|
||||
*/
|
||||
void setSqlDistinct(boolean sqlDistinct);
|
||||
|
||||
/**
|
||||
* Return true if this query has been specified by a user or internally by Ebean to use DISTINCT.
|
||||
*/
|
||||
boolean isDistinctQuery();
|
||||
|
||||
/**
|
||||
* Return true if this was internally set to sql distinct (ie. many where predicate).
|
||||
*/
|
||||
boolean isSqlDistinct();
|
||||
|
||||
/**
|
||||
* Return true if this query has been specified by a user to use DISTINCT.
|
||||
*/
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HanaColumnStoreDdl extends AbstractHanaDdl {
|
||||
|
||||
public HanaColumnStoreDdl(DatabasePlatform platform) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import io.ebean.config.PropertiesWrapper;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
|
||||
@@ -12,6 +9,9 @@ import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.DropColumn;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class HanaTableDdl extends BaseTableDdl {
|
||||
|
||||
private final HanaHistoryDdl historyDdl;
|
||||
|
||||
@@ -20,7 +20,6 @@ import io.ebeaninternal.dbmigration.migration.Column;
|
||||
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
|
||||
import io.ebeaninternal.dbmigration.migration.IdentityType;
|
||||
import io.ebeaninternal.dbmigration.model.MTable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -78,7 +77,7 @@ public class PlatformDdl {
|
||||
protected String dropIndexIfExists = "drop index if exists ";
|
||||
|
||||
protected String alterColumn = "alter column";
|
||||
|
||||
|
||||
protected String alterColumnSuffix = "";
|
||||
|
||||
protected String dropUniqueConstraint = "drop constraint";
|
||||
@@ -86,7 +85,7 @@ public class PlatformDdl {
|
||||
protected String addConstraint = "add constraint";
|
||||
|
||||
protected String addColumn = "add column";
|
||||
|
||||
|
||||
protected String addColumnSuffix = "";
|
||||
|
||||
protected String columnSetType = "";
|
||||
@@ -100,11 +99,11 @@ public class PlatformDdl {
|
||||
protected String columnSetNull = "set null";
|
||||
|
||||
protected String updateNullWithDefault = "update ${table} set ${column} = ${default} where ${column} is null";
|
||||
|
||||
|
||||
protected String createTable = "create table";
|
||||
|
||||
|
||||
protected String dropColumn = "drop column";
|
||||
|
||||
|
||||
protected String dropColumnSuffix = "";
|
||||
|
||||
/**
|
||||
@@ -674,12 +673,12 @@ public class PlatformDdl {
|
||||
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the database-specific "create table" command prefix. For HANA this is
|
||||
* either "create column table" or "create row table", for all other databases
|
||||
* it is "create table".
|
||||
*
|
||||
*
|
||||
* @return The "create table" command prefix
|
||||
*/
|
||||
public String getCreateTableCommandPrefix() {
|
||||
|
||||
@@ -10,14 +10,14 @@ import java.util.Map;
|
||||
class CacheChangeBeanUpdate implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> desc;
|
||||
private final Object id;
|
||||
private final String key;
|
||||
private final Map<String, Object> changes;
|
||||
private final boolean updateNaturalKey;
|
||||
private final long version;
|
||||
|
||||
CacheChangeBeanUpdate(BeanDescriptor<?> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
CacheChangeBeanUpdate(BeanDescriptor<?> desc, String key, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
this.desc = desc;
|
||||
this.id = id;
|
||||
this.key = key;
|
||||
this.changes = changes;
|
||||
this.updateNaturalKey = updateNaturalKey;
|
||||
this.version = version;
|
||||
@@ -25,6 +25,6 @@ class CacheChangeBeanUpdate implements CacheChange {
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
desc.cacheApplyBeanUpdate(id, changes, updateNaturalKey, version);
|
||||
desc.cacheApplyBeanUpdate(key, changes, updateNaturalKey, version);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,17 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
class CacheChangeNaturalKeyPut implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> descriptor;
|
||||
private final Object id;
|
||||
private final Object newKey;
|
||||
private final String key;
|
||||
private final String newKey;
|
||||
|
||||
CacheChangeNaturalKeyPut(BeanDescriptor<?> descriptor, Object id, Object newKey) {
|
||||
CacheChangeNaturalKeyPut(BeanDescriptor<?> descriptor, String key, String newKey) {
|
||||
this.descriptor = descriptor;
|
||||
this.id = id;
|
||||
this.key = key;
|
||||
this.newKey = newKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
descriptor.cacheNaturalKeyPut(id, newKey);
|
||||
descriptor.cacheNaturalKeyPut(key, newKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,16 +152,16 @@ public class CacheChangeSet {
|
||||
/**
|
||||
* Update a bean entry.
|
||||
*/
|
||||
public <T> void addBeanUpdate(BeanDescriptor<T> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
public <T> void addBeanUpdate(BeanDescriptor<T> desc, String key, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
touchedTables.add(desc.getBaseTable());
|
||||
entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version));
|
||||
entries.add(new CacheChangeBeanUpdate(desc, key, changes, updateNaturalKey, version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a natural key.
|
||||
*/
|
||||
public <T> void addNaturalKeyPut(BeanDescriptor<T> desc, Object id, Object val) {
|
||||
entries.add(new CacheChangeNaturalKeyPut(desc, id, val));
|
||||
public <T> void addNaturalKeyPut(BeanDescriptor<T> desc, String key, String val) {
|
||||
entries.add(new CacheChangeNaturalKeyPut(desc, key, val));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.annotation.Cache;
|
||||
import io.ebean.annotation.CacheBeanTuning;
|
||||
import io.ebean.annotation.CacheQueryTuning;
|
||||
import io.ebean.cache.QueryCacheEntryValidate;
|
||||
@@ -123,11 +124,15 @@ class DefaultCacheHolder {
|
||||
}
|
||||
|
||||
private ServerCacheOptions getBeanOptions(Class<?> cls) {
|
||||
|
||||
Cache cache = cls.getAnnotation(Cache.class);
|
||||
boolean nearCache = (cache != null && cache.nearCache());
|
||||
|
||||
CacheBeanTuning tuning = cls.getAnnotation(CacheBeanTuning.class);
|
||||
if (tuning != null) {
|
||||
return new ServerCacheOptions(tuning).applyDefaults(beanDefault);
|
||||
return new ServerCacheOptions(nearCache, tuning).applyDefaults(beanDefault);
|
||||
}
|
||||
return beanDefault.copy();
|
||||
return beanDefault.copy(nearCache);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.ebean.ScriptRunner;
|
||||
import io.ebean.migration.ddl.DdlRunner;
|
||||
import io.ebean.migration.runner.ScriptTransform;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.util.UrlHelper;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.IOException;
|
||||
@@ -63,7 +64,7 @@ class DScriptRunner implements ScriptRunner {
|
||||
throw new IllegalArgumentException("resource is null?");
|
||||
}
|
||||
|
||||
try (InputStream inputStream = resource.openStream()) {
|
||||
try (InputStream inputStream = UrlHelper.openNoCache(resource)) {
|
||||
return readContent(new InputStreamReader(inputStream));
|
||||
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import io.ebeaninternal.server.lib.DaemonScheduleThreadPool;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -64,6 +66,46 @@ public class DefaultBackgroundExecutor implements SpiBackgroundExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScheduledFuture<?> schedule(Runnable r, long delay, TimeUnit unit) {
|
||||
final Map<String, String> map = MDC.getCopyOfContextMap();
|
||||
|
||||
if (map == null) {
|
||||
return schedulePool.schedule(r, delay, unit);
|
||||
} else {
|
||||
return schedulePool.schedule(() -> {
|
||||
MDC.setContextMap(map);
|
||||
try {
|
||||
r.run();
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
}, delay, unit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> ScheduledFuture<V> schedule(Callable<V> c, long delay, TimeUnit unit) {
|
||||
final Map<String, String> map = MDC.getCopyOfContextMap();
|
||||
|
||||
if (map == null) {
|
||||
return schedulePool.schedule(c, delay, unit);
|
||||
} else {
|
||||
return schedulePool.schedule(new Callable<V>() {
|
||||
@Override
|
||||
public V call() throws Exception {
|
||||
MDC.setContextMap(map);
|
||||
try {
|
||||
return c.call();
|
||||
} finally {
|
||||
MDC.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}, delay, unit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
pool.shutdown();
|
||||
|
||||
@@ -6,8 +6,10 @@ import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.meta.MetaOrmQueryMetric;
|
||||
import io.ebean.meta.MetaOrmQueryNode;
|
||||
import io.ebean.meta.MetaQueryMetric;
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -23,6 +25,11 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaQueryPlan> collectQueryPlans(QueryPlanRequest request) {
|
||||
return server.collectQueryPlans(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMetrics(MetricVisitor visitor) {
|
||||
server.visitMetrics(visitor);
|
||||
|
||||
@@ -54,13 +54,16 @@ import io.ebean.event.BeanPersistController;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.plugin.Plugin;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.plugin.SpiServer;
|
||||
import io.ebean.text.csv.CsvReader;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import io.ebeaninternal.api.LoadBeanRequest;
|
||||
import io.ebeaninternal.api.LoadManyRequest;
|
||||
import io.ebeaninternal.api.ScopedTransaction;
|
||||
@@ -120,6 +123,8 @@ import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Clock;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -2335,10 +2340,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
@Override
|
||||
public void slowQueryCheck(long timeMicros, int rowCount, SpiQuery<?> query) {
|
||||
if (timeMicros > slowQueryMicros) {
|
||||
if (slowQueryListener != null) {
|
||||
slowQueryListener.process(new SlowQueryEvent(query.getGeneratedSql(), timeMicros / 1000L, rowCount, query.getParentNode()));
|
||||
}
|
||||
if (timeMicros > slowQueryMicros && slowQueryListener != null) {
|
||||
slowQueryListener.process(new SlowQueryEvent(query.getGeneratedSql(), timeMicros / 1000L, rowCount, query.getParentNode()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2425,4 +2428,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
visitor.visitEnd();
|
||||
}
|
||||
|
||||
public List<MetaQueryPlan> collectQueryPlans(QueryPlanRequest request) {
|
||||
Connection connection = null;
|
||||
try {
|
||||
connection = getDataSource().getConnection();
|
||||
request.setConnection(connection);
|
||||
beanDescriptorManager.collectQueryPlans(request);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
||||
} finally {
|
||||
JdbcClose.close(connection);
|
||||
}
|
||||
return request.getPlans();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.SlowQueryListener;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.DbHistorySupport;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebean.event.changelog.ChangeLogListener;
|
||||
import io.ebean.event.changelog.ChangeLogPrepare;
|
||||
import io.ebean.event.changelog.ChangeLogRegister;
|
||||
@@ -87,7 +88,6 @@ import io.ebeanservice.docstore.api.DocStoreFactory;
|
||||
import io.ebeanservice.docstore.api.DocStoreIntegration;
|
||||
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
|
||||
import io.ebeanservice.docstore.none.NoneDocStoreFactory;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.config.TenantCatalogProvider;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -2,8 +2,8 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
import io.ebean.config.TenantSchemaProvider;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -1405,6 +1405,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
|
||||
boolean updateNaturalKey = false;
|
||||
|
||||
String key = beanDescriptor.cacheKey(idValue);
|
||||
|
||||
Map<String, Object> changes = new LinkedHashMap<>();
|
||||
EntityBean bean = getEntityBean();
|
||||
boolean[] dirtyProperties = getDirtyProperties();
|
||||
@@ -1417,7 +1419,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
changes.put(property.getName(), val);
|
||||
if (property.isNaturalKey()) {
|
||||
updateNaturalKey = true;
|
||||
changeSet.addNaturalKeyPut(beanDescriptor, idValue, val);
|
||||
changeSet.addNaturalKeyPut(beanDescriptor, key, val.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1428,7 +1430,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
changes.putAll(collectionChanges);
|
||||
}
|
||||
|
||||
changeSet.addBeanUpdate(beanDescriptor, idValue, changes, updateNaturalKey, getVersion());
|
||||
changeSet.addBeanUpdate(beanDescriptor, key, changes, updateNaturalKey, getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import io.ebean.datasource.DataSourcePool;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.core.bootup;
|
||||
|
||||
import io.ebean.util.StringHelper;
|
||||
import io.ebeaninternal.util.UrlHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -59,7 +60,7 @@ class ManifestReader {
|
||||
try {
|
||||
Enumeration<URL> resources = classLoader.getResources(resourcePath);
|
||||
while (resources.hasMoreElements()) {
|
||||
try (InputStream is = resources.nextElement().openStream()) {
|
||||
try (InputStream is = UrlHelper.openNoCache(resources.nextElement())) {
|
||||
read(new Manifest(is));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.ValuePair;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.annotation.Formula;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
@@ -30,6 +29,7 @@ import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebean.event.readaudit.ReadEvent;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebean.plugin.BeanDocType;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.plugin.ExpressionPath;
|
||||
@@ -1487,30 +1487,33 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
* Return a bean from the bean cache (or null).
|
||||
*/
|
||||
public T cacheBeanGet(Object id, Boolean readOnly, PersistenceContext context) {
|
||||
return cacheHelp.beanCacheGet(id, readOnly, context);
|
||||
return cacheHelp.beanCacheGet(cacheKey(id), readOnly, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a collection of beans from the cache given the ids.
|
||||
*/
|
||||
public void cacheApplyInvalidate(Collection<Object> ids) {
|
||||
cacheHelp.beanCacheApplyInvalidate(ids);
|
||||
List<String> keys = new ArrayList<>(ids.size());
|
||||
for (Object id : ids) {
|
||||
keys.add(cacheKey(id));
|
||||
}
|
||||
cacheHelp.beanCacheApplyInvalidate(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hit the bean cache trying to load a list/batch of entities.
|
||||
* Return the set of entities that were successfully loaded from L2 cache.
|
||||
*/
|
||||
public Set<EntityBeanIntercept> cacheBeanLoadAll(List<EntityBeanIntercept> list, PersistenceContext persistenceContext, int lazyLoadProperty, String propertyName) {
|
||||
return cacheHelp.beanCacheLoadAll(list, persistenceContext, lazyLoadProperty, propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the bean from the cache.
|
||||
*/
|
||||
public boolean cacheBeanLoad(EntityBean bean, EntityBeanIntercept ebi, Object id, PersistenceContext context) {
|
||||
return cacheHelp.beanCacheLoad(bean, ebi, id, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the bean from the cache.
|
||||
*/
|
||||
public boolean cacheBeanLoad(EntityBeanIntercept ebi, PersistenceContext context) {
|
||||
EntityBean bean = ebi.getOwner();
|
||||
Object id = getId(bean);
|
||||
return cacheBeanLoad(bean, ebi, id, context);
|
||||
return cacheHelp.beanCacheLoad(bean, ebi, cacheKey(id), context);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1520,8 +1523,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
return cacheHelp.naturalKeyLookup(context, keys);
|
||||
}
|
||||
|
||||
public void cacheNaturalKeyPut(Object id, Object newKey) {
|
||||
cacheHelp.cacheNaturalKeyPut(id, newKey);
|
||||
public void cacheNaturalKeyPut(String key, String newKey) {
|
||||
cacheHelp.cacheNaturalKeyPut(key, newKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1569,8 +1572,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
/**
|
||||
* Apply the update to the cache.
|
||||
*/
|
||||
public void cacheApplyBeanUpdate(Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
cacheHelp.cacheBeanUpdate(id, changes, updateNaturalKey, version);
|
||||
public void cacheApplyBeanUpdate(String key, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
cacheHelp.cacheBeanUpdate(key, changes, updateNaturalKey, version);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1652,6 +1655,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
return new DeployUpdateParser(this).parse(ormUpdateStatement);
|
||||
}
|
||||
|
||||
public void collectQueryPlans(QueryPlanRequest request) {
|
||||
for (CQueryPlan queryPlan : queryPlanCache.values()) {
|
||||
if (request.includeLabel(queryPlan.getLabel())) {
|
||||
queryPlan.collectQueryPlan(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit all the ORM query plan metrics (includes UpdateQuery with updates and deletes).
|
||||
*/
|
||||
@@ -1981,7 +1992,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
public T createReference(Boolean readOnly, boolean disableLazyLoad, Object id, PersistenceContext pc) {
|
||||
|
||||
if (cacheSharableBeans && !disableLazyLoad && !Boolean.FALSE.equals(readOnly)) {
|
||||
CachedBeanData d = cacheHelp.beanCacheGetData(id);
|
||||
CachedBeanData d = cacheHelp.beanCacheGetData(cacheKey(id));
|
||||
if (d != null) {
|
||||
Object shareableBean = d.getSharableBean();
|
||||
if (shareableBean != null) {
|
||||
@@ -2262,6 +2273,20 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
return (idProperty == null) ? null : idProperty.getValueIntercept(bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache key for the given bean (based on id value).
|
||||
*/
|
||||
public String cacheKeyForBean(EntityBean bean) {
|
||||
return cacheKey(idProperty.getValue(bean));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache key for the given id value.
|
||||
*/
|
||||
public String cacheKey(Object id) {
|
||||
return idBinder.cacheKey(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object beanId(Object bean) {
|
||||
return getId((EntityBean) bean);
|
||||
|
||||
@@ -27,6 +27,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -282,7 +283,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
if (many.isElementCollection()) {
|
||||
// held as part of the bean cache so skip
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
CachedManyIds entry = manyPropGet(parentId, many.getName());
|
||||
@@ -317,7 +318,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
try {
|
||||
// add as JSON to bean cache
|
||||
String asJson = many.jsonWriteCollection(details);
|
||||
Map<String,Object> changes = new HashMap<>();
|
||||
Map<String, Object> changes = new HashMap<>();
|
||||
changes.put(many.getName(), asJson);
|
||||
|
||||
CachedBeanData newData = data.update(changes, data.getVersion());
|
||||
@@ -475,7 +476,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
private Class<?> theClassOf(Collection<EntityBean> beans) {
|
||||
if (beans instanceof List) {
|
||||
return ((List<?>)beans).get(0).getClass();
|
||||
return ((List<?>) beans).get(0).getClass();
|
||||
}
|
||||
return beans.iterator().next().getClass();
|
||||
}
|
||||
@@ -494,20 +495,20 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
|
||||
void beanCachePutAllDirect(Collection<EntityBean> beans) {
|
||||
|
||||
Map<Object,Object> natKeys = null;
|
||||
Map<Object, Object> natKeys = null;
|
||||
if (naturalKey != null) {
|
||||
natKeys = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
Map<Object,Object> map = new LinkedHashMap<>();
|
||||
Map<Object, Object> map = new LinkedHashMap<>();
|
||||
for (EntityBean bean : beans) {
|
||||
CachedBeanData beanData = beanExtractData(desc, bean);
|
||||
Object id = desc.getId(bean);
|
||||
map.put(id, beanData);
|
||||
String key = desc.cacheKeyForBean(bean);
|
||||
map.put(key, beanData);
|
||||
if (naturalKey != null) {
|
||||
Object naturalKey = calculateNaturalKey(beanData);
|
||||
if (naturalKey != null) {
|
||||
natKeys.put(naturalKey, id);
|
||||
natKeys.put(naturalKey, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -525,32 +526,33 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the bean into the bean cache.
|
||||
*/
|
||||
* Put the bean into the bean cache.
|
||||
*/
|
||||
void beanCachePutDirect(EntityBean bean) {
|
||||
|
||||
CachedBeanData beanData = beanExtractData(desc, bean);
|
||||
|
||||
Object id = desc.getId(bean);
|
||||
String key = desc.cacheKeyForBean(bean);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" PUT {}({}) data:{}", cacheName, id, beanData);
|
||||
beanLog.debug(" PUT {}({}) data:{}", cacheName, key, beanData);
|
||||
}
|
||||
getBeanCache().put(id, beanData);
|
||||
getBeanCache().put(key, beanData);
|
||||
|
||||
if (naturalKey != null) {
|
||||
Object naturalKey = calculateNaturalKey(beanData);
|
||||
String naturalKey = calculateNaturalKey(beanData);
|
||||
if (naturalKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(" PUT {}({}, {})", cacheName, naturalKey, id);
|
||||
natLog.debug(" PUT {}({}, {})", cacheName, naturalKey, key);
|
||||
}
|
||||
naturalKeyCache.put(naturalKey, id);
|
||||
naturalKeyCache.put(naturalKey, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object calculateNaturalKey(CachedBeanData beanData) {
|
||||
private String calculateNaturalKey(CachedBeanData beanData) {
|
||||
if (naturalKey.length == 1) {
|
||||
return beanData.getData(naturalKey[0]);
|
||||
Object data = beanData.getData(naturalKey[0]);
|
||||
return (data == null) ? null : data.toString();
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String key : naturalKey) {
|
||||
@@ -563,12 +565,12 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
CachedBeanData beanCacheGetData(Object id) {
|
||||
return (CachedBeanData) getBeanCache().get(id);
|
||||
CachedBeanData beanCacheGetData(String key) {
|
||||
return (CachedBeanData) getBeanCache().get(key);
|
||||
}
|
||||
|
||||
T beanCacheGet(Object id, Boolean readOnly, PersistenceContext context) {
|
||||
T bean = beanCacheGetInternal(id, readOnly, context);
|
||||
T beanCacheGet(String key, Boolean readOnly, PersistenceContext context) {
|
||||
T bean = beanCacheGetInternal(key, readOnly, context);
|
||||
if (bean != null) {
|
||||
setupContext(bean, context);
|
||||
}
|
||||
@@ -578,19 +580,19 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Return a bean from the bean cache.
|
||||
*/
|
||||
private T beanCacheGetInternal(Object id, Boolean readOnly, PersistenceContext context) {
|
||||
private T beanCacheGetInternal(String key, Boolean readOnly, PersistenceContext context) {
|
||||
|
||||
CachedBeanData data = (CachedBeanData) getBeanCache().get(id);
|
||||
CachedBeanData data = (CachedBeanData) getBeanCache().get(key);
|
||||
if (data == null) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - cache miss", cacheName, id);
|
||||
beanLog.trace(" GET {}({}) - cache miss", cacheName, key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET {}({}) - hit", cacheName, id);
|
||||
beanLog.trace(" GET {}({}) - hit", cacheName, key);
|
||||
}
|
||||
return convertToBean(id, readOnly, context, data);
|
||||
return convertToBean(key, readOnly, context, data);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -686,12 +688,12 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
/**
|
||||
* Remove a bean from the cache given its Id.
|
||||
*/
|
||||
void beanCacheApplyInvalidate(Collection<Object> ids) {
|
||||
void beanCacheApplyInvalidate(Collection<String> keys) {
|
||||
if (beanCache != null) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" REMOVE {}({})", cacheName, ids);
|
||||
beanLog.debug(" REMOVE {}({})", cacheName, keys);
|
||||
}
|
||||
beanCache.removeAll(new HashSet<>(ids));
|
||||
beanCache.removeAll(new HashSet<>(keys));
|
||||
}
|
||||
for (BeanPropertyAssocOne<?> imported : propertiesOneImported) {
|
||||
imported.cacheClear();
|
||||
@@ -699,28 +701,77 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the bean from the cache.
|
||||
* Load a batch of entities from L2 bean cache checking the lazy loaded property is loaded.
|
||||
*/
|
||||
boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, Object id, PersistenceContext context) {
|
||||
Set<EntityBeanIntercept> beanCacheLoadAll(List<EntityBeanIntercept> list, PersistenceContext context, int lazyLoadProperty, String propertyName) {
|
||||
|
||||
CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(id);
|
||||
Map<Object, EntityBeanIntercept> ebis = new HashMap<>();
|
||||
for (EntityBeanIntercept ebi : list) {
|
||||
ebis.put(desc.cacheKeyForBean(ebi.getOwner()), ebi);
|
||||
}
|
||||
|
||||
|
||||
Map<Object, Object> hits = getBeanCache().getAll(ebis.keySet());
|
||||
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD ALL {}({}) - got hits ({})", cacheName, ebis.keySet(), hits.size());
|
||||
}
|
||||
|
||||
Set<EntityBeanIntercept> loaded = new HashSet<>();
|
||||
|
||||
Iterator<Map.Entry<Object, Object>> iterator = hits.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<Object, Object> hit = iterator.next();
|
||||
|
||||
Object key = hit.getKey();
|
||||
EntityBeanIntercept ebi = ebis.remove(key);
|
||||
CachedBeanData cacheData = (CachedBeanData) hit.getValue();
|
||||
|
||||
if (lazyLoadProperty > -1 && !cacheData.isLoaded(propertyName)) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss on property({})", cacheName, key, propertyName);
|
||||
}
|
||||
iterator.remove();
|
||||
|
||||
} else {
|
||||
CachedBeanDataToBean.load(desc, ebi.getOwner(), cacheData, context);
|
||||
loaded.add(ebi);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" LOAD {}({}) - hit", cacheName, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ebis.isEmpty() && beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss", cacheName, ebis.keySet());
|
||||
}
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it managed to populate/load the single bean from the cache.
|
||||
*/
|
||||
boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, String key, PersistenceContext context) {
|
||||
|
||||
CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(key);
|
||||
if (cacheData == null) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss", cacheName, id);
|
||||
beanLog.trace(" LOAD {}({}) - cache miss", cacheName, key);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
int lazyLoadProperty = ebi.getLazyLoadPropertyIndex();
|
||||
if (lazyLoadProperty > -1 && !cacheData.isLoaded(ebi.getLazyLoadProperty())) {
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" LOAD {}({}) - cache miss on property({})", cacheName, id, ebi.getLazyLoadProperty());
|
||||
beanLog.trace(" LOAD {}({}) - cache miss on property({})", cacheName, key, ebi.getLazyLoadProperty());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CachedBeanDataToBean.load(desc, bean, cacheData, context);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" LOAD {}({}) - hit", cacheName, id);
|
||||
beanLog.debug(" LOAD {}({}) - hit", cacheName, key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -736,7 +787,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
*/
|
||||
void persistDeleteIds(Collection<Object> ids, CacheChangeSet changeSet) {
|
||||
if (invalidateQueryCache) {
|
||||
changeSet.addInvalidate(desc);
|
||||
changeSet.addInvalidate(desc);
|
||||
} else {
|
||||
queryCacheClear(changeSet);
|
||||
if (beanCache != null) {
|
||||
@@ -833,42 +884,42 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
void cacheNaturalKeyPut(Object id, Object newKey) {
|
||||
void cacheNaturalKeyPut(String key, String newKey) {
|
||||
if (newKey != null) {
|
||||
naturalKeyCache.put(newKey, id);
|
||||
naturalKeyCache.put(newKey, key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply changes to the bean cache entry.
|
||||
*/
|
||||
void cacheBeanUpdate(Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
void cacheBeanUpdate(String key, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
|
||||
ServerCache cache = getBeanCache();
|
||||
CachedBeanData existingData = (CachedBeanData) cache.get(id);
|
||||
CachedBeanData existingData = (CachedBeanData) cache.get(key);
|
||||
if (existingData != null) {
|
||||
long currentVersion = existingData.getVersion();
|
||||
if (version > 0 && version < currentVersion) {
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" REMOVE {}({}) - version conflict old:{} new:{}", cacheName, id, currentVersion, version);
|
||||
beanLog.debug(" REMOVE {}({}) - version conflict old:{} new:{}", cacheName, key, currentVersion, version);
|
||||
}
|
||||
cache.remove(id);
|
||||
cache.remove(key);
|
||||
} else {
|
||||
if (version == 0) {
|
||||
version = currentVersion;
|
||||
}
|
||||
CachedBeanData newData = existingData.update(changes, version);
|
||||
if (beanLog.isDebugEnabled()) {
|
||||
beanLog.debug(" UPDATE {}({}) changes:{}", cacheName, id, changes);
|
||||
beanLog.debug(" UPDATE {}({}) changes:{}", cacheName, key, changes);
|
||||
}
|
||||
cache.put(id, newData);
|
||||
cache.put(key, newData);
|
||||
}
|
||||
|
||||
if (updateNaturalKey) {
|
||||
Object oldKey = calculateNaturalKey(existingData);
|
||||
if (oldKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(".. update {} REMOVE({}) - old key for ({})", cacheName, oldKey, id);
|
||||
natLog.debug(".. update {} REMOVE({}) - old key for ({})", cacheName, oldKey, key);
|
||||
}
|
||||
naturalKeyCache.remove(oldKey);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import io.ebean.event.changelog.ChangeLogListener;
|
||||
import io.ebean.event.changelog.ChangeLogPrepare;
|
||||
import io.ebean.event.changelog.ChangeLogRegister;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.util.AnnotationUtil;
|
||||
import io.ebeaninternal.api.ConcurrencyMode;
|
||||
@@ -1688,6 +1689,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
}
|
||||
}
|
||||
|
||||
public void collectQueryPlans(QueryPlanRequest request) {
|
||||
for (BeanDescriptor<?> desc : immutableDescriptorList) {
|
||||
if (request.includeType(desc.getBeanType())) {
|
||||
desc.collectQueryPlans(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator to sort the BeanDescriptors by name.
|
||||
*/
|
||||
|
||||
@@ -56,11 +56,10 @@ public final class BeanFkeyProperty implements ElPropertyValue {
|
||||
}
|
||||
}
|
||||
|
||||
public BeanFkeyProperty create(String expression, boolean containsMany) {
|
||||
public BeanFkeyProperty create(String expression, boolean pathContainsMany) {
|
||||
int len = expression.length() - name.length() - 1;
|
||||
String prefix = expression.substring(0, len);
|
||||
|
||||
return new BeanFkeyProperty(prefix, name, dbColumn, deployOrder, containsMany);
|
||||
return new BeanFkeyProperty(prefix, name, dbColumn, deployOrder, containsMany || pathContainsMany);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -827,6 +827,13 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value in String format (for bean cache key).
|
||||
*/
|
||||
public String format(Object value) {
|
||||
return scalarType.format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the value for this property from L2 cache entry and set it to the bean.
|
||||
* <p>
|
||||
|
||||
@@ -204,7 +204,9 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
public void initialisePostTarget() {
|
||||
if (childMasterProperty != null) {
|
||||
BeanProperty masterId = childMasterProperty.getTargetDescriptor().getIdProperty();
|
||||
childMasterIdProperty = childMasterProperty.getName() + "." + masterId.getName();
|
||||
if (masterId != null) { // in docstore only, the master-id may be not available
|
||||
childMasterIdProperty = childMasterProperty.getName() + "." + masterId.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -972,8 +974,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
public void setCacheDataValue(EntityBean bean, Object cacheData, PersistenceContext context) {
|
||||
try {
|
||||
String asJson = (String) cacheData;
|
||||
Object collection = jsonReadCollection(asJson);
|
||||
setValue(bean, collection);
|
||||
if (asJson != null && !asJson.isEmpty()) {
|
||||
Object collection = jsonReadCollection(asJson);
|
||||
setValue(bean, collection);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error setting value from L2 cache", e);
|
||||
}
|
||||
@@ -999,7 +1003,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
public String jsonWriteCollection(Object value) throws IOException {
|
||||
StringWriter writer = new StringWriter(300);
|
||||
SpiJsonWriter ctx = descriptor.createJsonWriter(writer);
|
||||
help.jsonWrite(ctx, null, value, false);
|
||||
help.jsonWrite(ctx, null, value, true);
|
||||
ctx.flush();
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
@@ -413,6 +413,11 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String format(Object value) {
|
||||
return targetDescriptor.getIdBinder().cacheKey(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCacheDataValue(EntityBean bean, Object cacheData, PersistenceContext context) {
|
||||
if (cacheData == null) {
|
||||
|
||||
@@ -207,4 +207,10 @@ public interface IdBinder {
|
||||
* Cast or convert the Id value if necessary.
|
||||
*/
|
||||
Object convertId(Object idValue);
|
||||
|
||||
/**
|
||||
* Return a key to use for bean caches given the id value.
|
||||
*/
|
||||
String cacheKey(Object idValue);
|
||||
|
||||
}
|
||||
|
||||
@@ -479,4 +479,20 @@ public final class IdBinderEmbedded implements IdBinder {
|
||||
|
||||
return idValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cacheKey(Object value) {
|
||||
|
||||
EntityBean bean = (EntityBean)value;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (BeanProperty prop : props) {
|
||||
Object val = prop.getValue(bean);
|
||||
if (val != null) {
|
||||
sb.append(prop.format(val));
|
||||
}
|
||||
sb.append("|");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -178,4 +178,10 @@ public final class IdBinderEmpty implements IdBinder {
|
||||
public void writeData(DataOutput dataOutput, Object idValue) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cacheKey(Object bean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -262,4 +262,10 @@ public final class IdBinderSimple implements IdBinder {
|
||||
}
|
||||
return idValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cacheKey(Object value) {
|
||||
return scalarType.format(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package io.ebeaninternal.server.dto;
|
||||
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.QueryPlanMetric;
|
||||
import io.ebeaninternal.api.SpiDtoQuery;
|
||||
import io.ebeaninternal.metric.MetricFactory;
|
||||
import io.ebeaninternal.metric.QueryPlanMetric;
|
||||
|
||||
/**
|
||||
* Request to map a resultSet columns for a query into a DTO bean.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebeaninternal.server.dto;
|
||||
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.metric.QueryPlanMetric;
|
||||
import io.ebeaninternal.metric.TimedMetric;
|
||||
import io.ebean.metric.QueryPlanMetric;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
|
||||
abstract class DtoQueryPlanBase implements DtoQueryPlan {
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package io.ebeaninternal.server.el;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Comparator based on a ElGetValue.
|
||||
*/
|
||||
|
||||
@@ -3,10 +3,10 @@ package io.ebeaninternal.server.el;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.text.StringParser;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebean.util.StringHelper;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.server.type.ScalarType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -18,6 +18,7 @@ import io.ebean.Pairs;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.QueryIterator;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.UpdateQuery;
|
||||
import io.ebean.Version;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import io.ebean.search.Match;
|
||||
@@ -302,6 +303,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return query.asDto(dtoClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateQuery<T> asUpdate() {
|
||||
return query.asUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> setIncludeSoftDeletes() {
|
||||
return query.setIncludeSoftDeletes();
|
||||
|
||||
@@ -11,10 +11,10 @@ import io.ebean.Query;
|
||||
import io.ebeaninternal.api.SpiExpressionList;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
public class FilterExpressionList<T> extends DefaultExpressionList<T> {
|
||||
@@ -89,6 +89,16 @@ public class FilterExpressionList<T> extends DefaultExpressionList<T> {
|
||||
return rootQuery.findSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T findOne() {
|
||||
return rootQuery.findOne();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<T> findOneOrEmpty() {
|
||||
return rootQuery.findOneOrEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> having() {
|
||||
throw new PersistenceException(notAllowedMessage);
|
||||
|
||||
@@ -2,11 +2,11 @@ package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.persist.MultiValueWrapper;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -17,6 +17,7 @@ import io.ebean.Pairs;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.QueryIterator;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.UpdateQuery;
|
||||
import io.ebean.Version;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import io.ebean.search.Match;
|
||||
@@ -349,6 +350,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
|
||||
return exprList.asDto(dtoClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateQuery<T> asUpdate() {
|
||||
return exprList.asUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query<T> setIncludeSoftDeletes() {
|
||||
return exprList.setIncludeSoftDeletes();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.api.ManyWhereJoins;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebean.util.SplitName;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
// Generated from C:/dev/ebean/ebean/src/test/resources/EQL.g4 by ANTLR 4.7.1
|
||||
package io.ebeaninternal.server.grammer.antlr;
|
||||
|
||||
import org.antlr.v4.runtime.Lexer;
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.TokenStream;
|
||||
import org.antlr.v4.runtime.*;
|
||||
import org.antlr.v4.runtime.atn.*;
|
||||
import org.antlr.v4.runtime.Lexer;
|
||||
import org.antlr.v4.runtime.RuntimeMetaData;
|
||||
import org.antlr.v4.runtime.Vocabulary;
|
||||
import org.antlr.v4.runtime.VocabularyImpl;
|
||||
import org.antlr.v4.runtime.atn.ATN;
|
||||
import org.antlr.v4.runtime.atn.ATNDeserializer;
|
||||
import org.antlr.v4.runtime.atn.LexerATNSimulator;
|
||||
import org.antlr.v4.runtime.atn.PredictionContextCache;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.antlr.v4.runtime.misc.*;
|
||||
|
||||
@SuppressWarnings({ "all", "warnings", "unchecked", "unused", "cast" })
|
||||
public class EQLLexer extends Lexer {
|
||||
@@ -292,4 +294,4 @@ public class EQLLexer extends Lexer {
|
||||
_decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
// Generated from C:/dev/ebean/ebean/src/test/resources/EQL.g4 by ANTLR 4.7.1
|
||||
package io.ebeaninternal.server.grammer.antlr;
|
||||
|
||||
import org.antlr.v4.runtime.atn.*;
|
||||
import org.antlr.v4.runtime.NoViableAltException;
|
||||
import org.antlr.v4.runtime.Parser;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.RuntimeMetaData;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.TokenStream;
|
||||
import org.antlr.v4.runtime.Vocabulary;
|
||||
import org.antlr.v4.runtime.VocabularyImpl;
|
||||
import org.antlr.v4.runtime.atn.ATN;
|
||||
import org.antlr.v4.runtime.atn.ATNDeserializer;
|
||||
import org.antlr.v4.runtime.atn.ParserATNSimulator;
|
||||
import org.antlr.v4.runtime.atn.PredictionContextCache;
|
||||
import org.antlr.v4.runtime.dfa.DFA;
|
||||
import org.antlr.v4.runtime.*;
|
||||
import org.antlr.v4.runtime.misc.*;
|
||||
import org.antlr.v4.runtime.tree.*;
|
||||
import org.antlr.v4.runtime.tree.ParseTreeListener;
|
||||
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Iterator;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@SuppressWarnings({ "all", "warnings", "unchecked", "unused", "cast" })
|
||||
public class EQLParser extends Parser {
|
||||
@@ -2822,4 +2832,4 @@ public class EQLParser extends Parser {
|
||||
_decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import io.ebeaninternal.server.querydefn.OrmQueryProperties;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Default implementation of LoadBeanContext.
|
||||
@@ -174,22 +175,19 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.hitCache && context.desc.cacheBeanLoad(ebi, persistenceContext)) {
|
||||
// successfully hit the L2 cache so don't invoke DB lazy loading
|
||||
list.remove(ebi);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.hitCache) {
|
||||
// check each of the beans in the batch to see if they are in the L2 cache.
|
||||
// bean successfully loaded from L2 cache so remove from batch load
|
||||
list.removeIf(batchEbi -> batchEbi != ebi && context.desc.cacheBeanLoad(batchEbi, persistenceContext));
|
||||
Set<EntityBeanIntercept> hits = context.desc.cacheBeanLoadAll(list, persistenceContext, ebi.getLazyLoadPropertyIndex(), ebi.getLazyLoadProperty());
|
||||
|
||||
list.removeAll(hits);
|
||||
if (list.isEmpty() || hits.contains(ebi)) {
|
||||
// successfully hit the L2 cache so don't invoke DB lazy loading
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LoadBeanRequest req = new LoadBeanRequest(this, ebi.getLazyLoadProperty(), context.hitCache);
|
||||
context.desc.getEbeanServer().loadBean(req);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package io.ebeaninternal.server.persist;
|
||||
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.TimedMetricMap;
|
||||
import io.ebeaninternal.api.SpiTransaction;
|
||||
import io.ebeaninternal.metric.MetricFactory;
|
||||
import io.ebeaninternal.metric.TimedMetricMap;
|
||||
import io.ebeaninternal.server.core.PersistRequestCallableSql;
|
||||
import io.ebeaninternal.server.core.PersistRequestOrmUpdate;
|
||||
import io.ebeaninternal.server.core.PersistRequestUpdateSql;
|
||||
|
||||
@@ -84,15 +84,17 @@ class DeleteUnloadedForeignKeys {
|
||||
*/
|
||||
void deleteCascade() {
|
||||
|
||||
for (BeanPropertyAssocOne<?> prop : propList) {
|
||||
Object detailBean = prop.getValue(beanWithForeignKeys);
|
||||
if (beanWithForeignKeys != null) {
|
||||
for (BeanPropertyAssocOne<?> prop : propList) {
|
||||
Object detailBean = prop.getValue(beanWithForeignKeys);
|
||||
|
||||
// if bean exists with a unique id then delete it
|
||||
if (detailBean != null && prop.hasId((EntityBean) detailBean)) {
|
||||
if (deletePermanent) {
|
||||
server.deletePermanent(detailBean, request.getTransaction());
|
||||
} else {
|
||||
server.delete(detailBean, request.getTransaction());
|
||||
// if bean exists with a unique id then delete it
|
||||
if (detailBean != null && prop.hasId((EntityBean) detailBean)) {
|
||||
if (deletePermanent) {
|
||||
server.deletePermanent(detailBean, request.getTransaction());
|
||||
} else {
|
||||
server.delete(detailBean, request.getTransaction());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,10 @@ class SaveManyBeans extends SaveManyBase {
|
||||
}
|
||||
|
||||
private boolean isSaveIntersection() {
|
||||
return transaction.isSaveAssocManyIntersection(many.getIntersectionTableJoin().getTable(), many.getBeanDescriptor().getName());
|
||||
if (!many.isManyToMany()) {
|
||||
return true;
|
||||
}
|
||||
return transaction.isSaveAssocManyIntersection(many.getIntersectionTableJoin().getTable(), many.getBeanDescriptor().rootName());
|
||||
}
|
||||
|
||||
private boolean isModifyListenMode() {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package io.ebeaninternal.server.profile;
|
||||
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebeaninternal.metric.MetricFactory;
|
||||
import io.ebeaninternal.metric.QueryPlanMetric;
|
||||
import io.ebeaninternal.metric.TimedMetric;
|
||||
import io.ebeaninternal.metric.TimedMetricMap;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.QueryPlanMetric;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
import io.ebean.metric.TimedMetricMap;
|
||||
|
||||
/**
|
||||
* Default metric factory implementation.
|
||||
|
||||
@@ -2,9 +2,9 @@ package io.ebeaninternal.server.profile;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
import io.ebean.service.SpiProfileLocationFactory;
|
||||
import io.ebeaninternal.metric.MetricFactory;
|
||||
import io.ebeaninternal.metric.TimedMetric;
|
||||
|
||||
/**
|
||||
* Default implementation of the profile location factory.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebeaninternal.server.profile;
|
||||
|
||||
import io.ebean.meta.MetaQueryMetric;
|
||||
import io.ebeaninternal.metric.QueryPlanCollector;
|
||||
import io.ebean.metric.QueryPlanCollector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -3,9 +3,9 @@ package io.ebeaninternal.server.profile;
|
||||
import io.ebean.meta.MetaQueryMetric;
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.metric.QueryPlanMetric;
|
||||
import io.ebeaninternal.metric.TimedMetric;
|
||||
import io.ebeaninternal.metric.TimedMetricStats;
|
||||
import io.ebean.metric.QueryPlanMetric;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
import io.ebean.metric.TimedMetricStats;
|
||||
|
||||
class DQueryPlanMetric implements QueryPlanMetric {
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebeaninternal.server.profile;
|
||||
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebeaninternal.metric.TimedMetricStats;
|
||||
import io.ebean.metric.TimedMetricStats;
|
||||
|
||||
/**
|
||||
* Snapshot of the current statistics for a Counter or TimeCounter.
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.profile;
|
||||
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.metric.TimedMetric;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.LongAccumulator;
|
||||
@@ -35,6 +35,16 @@ class DTimedMetric implements TimedMetric {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSinceNanos(long startNanos) {
|
||||
add((System.nanoTime() - startNanos) / 1000L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSinceNanos(long startNanos, long beans) {
|
||||
add((System.nanoTime() - startNanos) / 1000L, beans);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value. Usually the value is Time or Bytes etc.
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.profile;
|
||||
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.metric.TimedMetricMap;
|
||||
import io.ebean.metric.TimedMetricMap;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -19,6 +19,16 @@ class DTimedMetricMap implements TimedMetricMap {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSinceNanos(String key, long startNanos) {
|
||||
add(key, (System.nanoTime() - startNanos)/1000L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSinceNanos(String key, long startNanos, int beans) {
|
||||
add(key, (System.nanoTime() - startNanos)/1000L, beans);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(String key, long exeMicros) {
|
||||
map.computeIfAbsent(key, (k) -> new DTimedMetric(metricType, name + key)).add(exeMicros);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebeaninternal.server.profile;
|
||||
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.metric.TimedMetric;
|
||||
import io.ebeaninternal.metric.TimedMetricStats;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
import io.ebean.metric.TimedMetricStats;
|
||||
|
||||
/**
|
||||
* Default profile location that uses stack trace.
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.profile;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.metric.TimedMetric;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
|
||||
/**
|
||||
* ProfileLocation that collects timing metrics.
|
||||
|
||||
@@ -602,7 +602,9 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
|
||||
if (autoTuneProfiling) {
|
||||
profilingListener.collectQueryInfo(objectGraphNode, loadedBeanCount, executionTimeMicros);
|
||||
}
|
||||
queryPlan.executionTime(loadedBeanCount, executionTimeMicros, objectGraphNode);
|
||||
if (queryPlan.executionTime(loadedBeanCount, executionTimeMicros, objectGraphNode)) {
|
||||
queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros);
|
||||
}
|
||||
getTransaction().profileEvent(this);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error updating execution statistics", e);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
class CQueryBindCapture {
|
||||
|
||||
private final double multiplier = 1.3d;
|
||||
|
||||
private final CQueryPlan cQueryPlan;
|
||||
private final QueryPlanLogger planLogger;
|
||||
|
||||
private BindCapture bindCapture;
|
||||
private long queryTimeMicros;
|
||||
private long thresholdMicros;
|
||||
private long captureCount;
|
||||
|
||||
private long lastBindCapture;
|
||||
|
||||
CQueryBindCapture(CQueryPlan cQueryPlan, QueryPlanLogger planLogger) {
|
||||
this.cQueryPlan = cQueryPlan;
|
||||
this.planLogger = planLogger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we should capture the bind values for this query.
|
||||
*/
|
||||
boolean collectFor(long timeMicros) {
|
||||
return (bindCapture == null || timeMicros > thresholdMicros);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the captured bind values that we can use later to collect a query plan.
|
||||
*
|
||||
* @param bindCapture The bind values of the query
|
||||
* @param queryTimeMicros The query execution time
|
||||
*/
|
||||
void setBind(BindCapture bindCapture, long queryTimeMicros) {
|
||||
synchronized (this) {
|
||||
this.bindCapture = bindCapture;
|
||||
this.queryTimeMicros = queryTimeMicros;
|
||||
this.thresholdMicros = Math.round(queryTimeMicros * multiplier);
|
||||
captureCount++;
|
||||
lastBindCapture = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Collect the query plan using already captured bind values.
|
||||
*/
|
||||
void collectQueryPlan(QueryPlanRequest request) {
|
||||
|
||||
if (bindCapture == null || request.getSince() > lastBindCapture) {
|
||||
// no bind capture since the last capture
|
||||
return;
|
||||
}
|
||||
|
||||
final BindCapture last = this.bindCapture;
|
||||
|
||||
DQueryPlanOutput queryPlan = planLogger.logQueryPlan(request.getConnection(), cQueryPlan, last);
|
||||
if (queryPlan != null) {
|
||||
queryPlan.with(queryTimeMicros, captureCount, cQueryPlan.getPlanKey().toString());
|
||||
request.process(queryPlan);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -280,13 +280,8 @@ class CQueryBuilder {
|
||||
if (sqlTree.isSingleProperty()) {
|
||||
request.setInlineCountDistinct();
|
||||
}
|
||||
} else {
|
||||
if (hasMany) {
|
||||
// need to count distinct id's ...
|
||||
query.setSqlDistinct(true);
|
||||
} else {
|
||||
sqlSelect = "select count(*)";
|
||||
}
|
||||
} else if (!hasMany) {
|
||||
sqlSelect = "select count(*)";
|
||||
}
|
||||
|
||||
SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree);
|
||||
@@ -556,6 +551,7 @@ class CQueryBuilder {
|
||||
return rawSqlHandler.buildSql(request, predicates, query.getRawSql().getSql());
|
||||
}
|
||||
|
||||
boolean distinct = query.isDistinct() || select.isSqlDistinct();
|
||||
boolean useSqlLimiter = false;
|
||||
StringBuilder sb = new StringBuilder(500);
|
||||
String dbOrderBy = predicates.getDbOrderBy();
|
||||
@@ -568,7 +564,7 @@ class CQueryBuilder {
|
||||
|
||||
if (!useSqlLimiter) {
|
||||
sb.append("select ");
|
||||
if (query.isDistinctQuery()) {
|
||||
if (distinct) {
|
||||
if (request.isInlineCountDistinct()) {
|
||||
sb.append("count(");
|
||||
}
|
||||
@@ -590,7 +586,7 @@ class CQueryBuilder {
|
||||
if (request.isInlineCountDistinct()) {
|
||||
sb.append(")");
|
||||
}
|
||||
if (query.isDistinctQuery() && dbOrderBy != null && !query.isSingleAttribute()) {
|
||||
if (distinct && dbOrderBy != null && !query.isSingleAttribute()) {
|
||||
// add the orderBy columns to the select clause (due to distinct)
|
||||
sb.append(", ").append(DbOrderByTrim.trim(dbOrderBy));
|
||||
}
|
||||
@@ -692,7 +688,7 @@ class CQueryBuilder {
|
||||
|
||||
if (useSqlLimiter) {
|
||||
// use LIMIT/OFFSET, ROW_NUMBER() or rownum type SQL query limitation
|
||||
SqlLimitRequest r = new OrmQueryLimitRequest(sb.toString(), dbOrderBy, query, dbPlatform);
|
||||
SqlLimitRequest r = new OrmQueryLimitRequest(sb.toString(), dbOrderBy, query, dbPlatform, distinct);
|
||||
return sqlLimiter.limit(r);
|
||||
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.server.rawsql.SpiRawSql;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.config.dbplatform.SqlLimitResponse;
|
||||
import io.ebean.config.dbplatform.SqlLimiter;
|
||||
@@ -9,6 +8,7 @@ import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryLimitRequest;
|
||||
import io.ebeaninternal.server.rawsql.SpiRawSql;
|
||||
import io.ebeaninternal.server.util.BindParamsParser;
|
||||
|
||||
class CQueryBuilderRawSql {
|
||||
@@ -28,7 +28,7 @@ class CQueryBuilderRawSql {
|
||||
|
||||
if (rsql == null) {
|
||||
// this is a ResultSet based RawSql query - just use some placeholder for the SQL
|
||||
return new SqlLimitResponse("--ResultSetBasedRawSql", false);
|
||||
return new SqlLimitResponse(CQueryPlan.RESULT_SET_BASED_RAW_SQL, false);
|
||||
}
|
||||
|
||||
if (!rsql.isParsed()) {
|
||||
@@ -50,7 +50,7 @@ class CQueryBuilderRawSql {
|
||||
SpiQuery<?> query = request.getQuery();
|
||||
if (query.hasMaxRowsOrFirstRow() && sqlLimiter != null) {
|
||||
// wrap with a limit offset or ROW_NUMBER() etc
|
||||
return sqlLimiter.limit(new OrmQueryLimitRequest(sql, orderBy, query, dbPlatform));
|
||||
return sqlLimiter.limit(new OrmQueryLimitRequest(sql, orderBy, query, dbPlatform, rsql.isDistinct() || query.isDistinct()));
|
||||
|
||||
} else {
|
||||
// add back select keyword (it was removed to support sqlQueryLimiter)
|
||||
|
||||
@@ -117,7 +117,9 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
|
||||
|
||||
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
request.slowQueryCheck(executionTimeMicros, rowCount);
|
||||
queryPlan.executionTime(rowCount, executionTimeMicros, null);
|
||||
if (queryPlan.executionTime(rowCount, executionTimeMicros, null)) {
|
||||
queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros);
|
||||
}
|
||||
getTransaction().profileEvent(this);
|
||||
|
||||
return result;
|
||||
|
||||
@@ -2,17 +2,20 @@ package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.SqlLimitResponse;
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
import io.ebeaninternal.api.CQueryPlanKey;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.metric.MetricFactory;
|
||||
import io.ebeaninternal.metric.TimedMetric;
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import io.ebeaninternal.server.core.timezone.DataTimeZone;
|
||||
import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
|
||||
import io.ebeaninternal.server.type.DataBind;
|
||||
import io.ebeaninternal.server.type.DataBindCapture;
|
||||
import io.ebeaninternal.server.type.DataReader;
|
||||
import io.ebeaninternal.server.type.RsetDataReader;
|
||||
import io.ebeaninternal.server.type.ScalarDataReader;
|
||||
@@ -50,6 +53,8 @@ public class CQueryPlan {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(CQueryPlan.class);
|
||||
|
||||
public static final String RESULT_SET_BASED_RAW_SQL = "--ResultSetBasedRawSql";
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final boolean autoTuned;
|
||||
@@ -92,6 +97,8 @@ public class CQueryPlan {
|
||||
|
||||
private final Set<String> dependentTables;
|
||||
|
||||
private final CQueryBindCapture bindCapture;
|
||||
|
||||
/**
|
||||
* Create a query plan based on a OrmQueryRequest.
|
||||
*/
|
||||
@@ -115,6 +122,7 @@ public class CQueryPlan {
|
||||
this.encryptedProps = sqlTree.getEncryptedProps();
|
||||
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
|
||||
this.dependentTables = sqlTree.dependentTables();
|
||||
this.bindCapture = initBindCapture(server.getServerConfig(), query);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,6 +148,15 @@ public class CQueryPlan {
|
||||
this.encryptedProps = sqlTree.getEncryptedProps();
|
||||
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
|
||||
this.dependentTables = (rawSql) ? Collections.emptySet() : sqlTree.dependentTables();
|
||||
this.bindCapture = initBindCapture(server.getServerConfig(), query);
|
||||
}
|
||||
|
||||
private CQueryBindCapture initBindCapture(ServerConfig serverConfig, SpiQuery<?> query) {
|
||||
if (serverConfig.isCollectQueryPlans() && !query.getType().isUpdate()) {
|
||||
return new CQueryBindCapture(this, PlatformQueryPlan.getLogger(serverConfig.getDatabasePlatform().getPlatform()));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String location() {
|
||||
@@ -193,6 +210,16 @@ public class CQueryPlan {
|
||||
return dataBind;
|
||||
}
|
||||
|
||||
private DataBindCapture bindCapture() throws SQLException {
|
||||
DataBindCapture dataBind = DataBindCapture.of(dataTimeZone);
|
||||
if (encryptedProps != null) {
|
||||
for (STreeProperty encryptedProp : encryptedProps) {
|
||||
dataBind.setString(encryptedProp.getEncryptKeyAsString());
|
||||
}
|
||||
}
|
||||
return dataBind;
|
||||
}
|
||||
|
||||
int getAsOfTableCount() {
|
||||
return asOfTableCount;
|
||||
}
|
||||
@@ -263,13 +290,15 @@ public class CQueryPlan {
|
||||
/**
|
||||
* Register an execution time against this query plan;
|
||||
*/
|
||||
void executionTime(long loadedBeanCount, long timeMicros, ObjectGraphNode objectGraphNode) {
|
||||
boolean executionTime(long loadedBeanCount, long timeMicros, ObjectGraphNode objectGraphNode) {
|
||||
|
||||
stats.add(loadedBeanCount, timeMicros, objectGraphNode);
|
||||
if (objectGraphNode != null) {
|
||||
// collect stats based on objectGraphNode for lazy loading reporting
|
||||
server.collectQueryStats(objectGraphNode, loadedBeanCount, timeMicros);
|
||||
}
|
||||
|
||||
return bindCapture != null && bindCapture.collectFor(timeMicros);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,4 +329,22 @@ public class CQueryPlan {
|
||||
public TimedMetric createTimedMetric() {
|
||||
return MetricFactory.get().createTimedMetric(MetricType.ORM, label);
|
||||
}
|
||||
|
||||
void captureBindForQueryPlan(CQueryPredicates predicates, long executionTimeMicros) {
|
||||
try {
|
||||
DataBindCapture capture = bindCapture();
|
||||
predicates.bind(capture);
|
||||
bindCapture.setBind(capture.bindCapture(), executionTimeMicros);
|
||||
|
||||
} catch (SQLException e) {
|
||||
logger.error("Error capturing bind values", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void collectQueryPlan(QueryPlanRequest request) {
|
||||
|
||||
if (!getSql().equals(RESULT_SET_BASED_RAW_SQL) && bindCapture != null) {
|
||||
bindCapture.collectQueryPlan(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.meta.MetaOrmQueryMetric;
|
||||
import io.ebean.meta.MetaOrmQueryOrigin;
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebeaninternal.metric.TimedMetric;
|
||||
import io.ebeaninternal.metric.TimedMetricStats;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
import io.ebean.metric.TimedMetricStats;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -125,8 +125,10 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
|
||||
rowCount = rset.getInt(1);
|
||||
|
||||
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
queryPlan.executionTime(rowCount, executionTimeMicros, query.getParentNode());
|
||||
request.slowQueryCheck(executionTimeMicros, rowCount);
|
||||
if (queryPlan.executionTime(rowCount, executionTimeMicros, query.getParentNode())) {
|
||||
queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros);
|
||||
}
|
||||
getTransaction().profileEvent(this);
|
||||
return rowCount;
|
||||
|
||||
|
||||
@@ -111,7 +111,9 @@ class CQueryUpdate implements SpiProfileTransactionEvent {
|
||||
|
||||
executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
request.slowQueryCheck(executionTimeMicros, rowCount);
|
||||
queryPlan.executionTime(rowCount, executionTimeMicros, null);
|
||||
if (queryPlan.executionTime(rowCount, executionTimeMicros, null)) {
|
||||
queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros);
|
||||
}
|
||||
getTransaction().profileEvent(this);
|
||||
return rowCount;
|
||||
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
|
||||
/**
|
||||
* Captured query plan details.
|
||||
*/
|
||||
class DQueryPlanOutput implements MetaQueryPlan {
|
||||
|
||||
private final Class<?> beanType;
|
||||
private final String label;
|
||||
|
||||
|
||||
private final String sql;
|
||||
|
||||
private final String bind;
|
||||
|
||||
private final String plan;
|
||||
|
||||
private String planHash;
|
||||
private long queryTimeMicros;
|
||||
private long captureCount;
|
||||
|
||||
DQueryPlanOutput(Class<?> beanType, String label, String sql, String bind, String plan) {
|
||||
this.beanType = beanType;
|
||||
this.label = label;
|
||||
this.sql = sql;
|
||||
this.bind = bind;
|
||||
this.plan = plan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryPlanHash() {
|
||||
return planHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated bean.
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getBeanType() {
|
||||
return beanType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query label if set.
|
||||
*/
|
||||
@Override
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sql of query.
|
||||
*/
|
||||
@Override
|
||||
public String getSql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description of the bind values used.
|
||||
*/
|
||||
@Override
|
||||
public String getBind() {
|
||||
return bind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query plan.
|
||||
*/
|
||||
@Override
|
||||
public String getPlan() {
|
||||
return plan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query execution time associated with the capture of bind values used
|
||||
* to build the query plan.
|
||||
*/
|
||||
@Override
|
||||
public long getQueryTimeMicros() {
|
||||
return queryTimeMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total count of times bind capture has occurred. We don't want this to be
|
||||
* massive as that implies a high overhead.
|
||||
*/
|
||||
@Override
|
||||
public long getCaptureCount() {
|
||||
return captureCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return " BeanType:" + ((beanType == null) ? "" : beanType.getSimpleName()) + " planHash:" + planHash + " label:" + label + " queryTimeMicros:" + queryTimeMicros + " captureCount:" + captureCount + "\n SQL:" + sql + "\nBIND:" + bind + "\nPLAN:" + plan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Additionally set the query execution time and the number of bind captures.
|
||||
*/
|
||||
void with(long queryTimeMicros, long captureCount, String planHash) {
|
||||
this.queryTimeMicros = queryTimeMicros;
|
||||
this.captureCount = captureCount;
|
||||
this.planHash = planHash;
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import io.ebean.RowMapper;
|
||||
import io.ebean.SqlRow;
|
||||
import io.ebean.meta.MetricType;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.TimedMetricMap;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.metric.MetricFactory;
|
||||
import io.ebeaninternal.metric.TimedMetricMap;
|
||||
import io.ebeaninternal.server.core.Message;
|
||||
import io.ebeaninternal.server.core.RelationalQueryEngine;
|
||||
import io.ebeaninternal.server.core.RelationalQueryRequest;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
|
||||
public final class PlatformQueryPlan {
|
||||
|
||||
private static QueryPlanLogger explainLogger = new QueryPlanLoggerExplain();
|
||||
|
||||
private static QueryPlanLogger postgresLogger = new QueryPlanLoggerPostgres();
|
||||
|
||||
private static QueryPlanLogger sqlServerLogger = new QueryPlanLoggerSqlServer();
|
||||
|
||||
private static QueryPlanLogger oracleLogger = new QueryPlanLoggerOracle();
|
||||
|
||||
/**
|
||||
* Returns the logger to log query plans for the given platform.
|
||||
*/
|
||||
public static QueryPlanLogger getLogger(Platform platform) {
|
||||
|
||||
switch (platform) {
|
||||
case POSTGRES:
|
||||
return postgresLogger;
|
||||
|
||||
case SQLSERVER:
|
||||
case SQLSERVER16:
|
||||
case SQLSERVER17:
|
||||
return sqlServerLogger;
|
||||
|
||||
case ORACLE:
|
||||
return oracleLogger;
|
||||
|
||||
default:
|
||||
return explainLogger;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public abstract class QueryPlanLogger {
|
||||
|
||||
static final Logger queryPlanLog = LoggerFactory.getLogger(QueryPlanLogger.class);
|
||||
|
||||
public abstract DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind);
|
||||
|
||||
DQueryPlanOutput readQueryPlan(CQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 1; i <= rset.getMetaData().getColumnCount(); i++) {
|
||||
sb.append(rset.getMetaData().getColumnLabel(i)).append("\t");
|
||||
}
|
||||
sb.setLength(sb.length() - 1);
|
||||
readPlanData(sb, rset);
|
||||
|
||||
return createPlan(plan, bind.toString(), sb.toString());
|
||||
}
|
||||
|
||||
protected DQueryPlanOutput createPlan(CQueryPlan plan, String bind, String planString) {
|
||||
return new DQueryPlanOutput(plan.getBeanType(), plan.getLabel(), plan.getSql(), bind, planString);
|
||||
}
|
||||
|
||||
DQueryPlanOutput readQueryPlanBasic(CQueryPlan plan, BindCapture bind, ResultSet rset) throws SQLException {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
readPlanData(sb, rset);
|
||||
return createPlan(plan, bind.toString(), sb.toString().trim());
|
||||
}
|
||||
|
||||
private void readPlanData(StringBuilder sb, ResultSet rset) throws SQLException {
|
||||
while (rset.next()) {
|
||||
sb.append('\n');
|
||||
for (int i = 1; i <= rset.getMetaData().getColumnCount(); i++) {
|
||||
sb.append(rset.getString(i)).append("\t");
|
||||
}
|
||||
sb.setLength(sb.length()-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
/**
|
||||
* A QueryPlanlogger that prefixes "EXPLAIN " to the query. This works for Postgres, H2 and MySql.
|
||||
*/
|
||||
public class QueryPlanLoggerExplain extends QueryPlanLogger {
|
||||
|
||||
@Override
|
||||
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
|
||||
|
||||
try (PreparedStatement explainStmt = conn.prepareStatement("EXPLAIN " + plan.getSql())) {
|
||||
bind.prepare(explainStmt, conn);
|
||||
try (ResultSet rset = explainStmt.executeQuery()) {
|
||||
return readQueryPlan(plan, bind, rset);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
queryPlanLog.error("Could not log query plan", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
/**
|
||||
* A QueryPlanlogger for oracle.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public class QueryPlanLoggerOracle extends QueryPlanLogger {
|
||||
|
||||
@Override
|
||||
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
|
||||
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
try (PreparedStatement explainStmt = conn.prepareStatement("EXPLAIN PLAN FOR " + plan.getSql())) {
|
||||
bind.prepare(explainStmt, conn);
|
||||
explainStmt.execute();
|
||||
}
|
||||
try (ResultSet rset = stmt.executeQuery("select plan_table_output from table(dbms_xplan.display())")) {
|
||||
return readQueryPlan(plan, bind, rset);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
queryPlanLog.error("Could not log query plan", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* A QueryPlanlogger for Postgres that prefixes "EXPLAIN ANALYZE" to the query.
|
||||
*/
|
||||
public class QueryPlanLoggerPostgres extends QueryPlanLogger {
|
||||
|
||||
@Override
|
||||
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
|
||||
|
||||
String explain = "explain analyze " + plan.getSql();
|
||||
try (PreparedStatement explainStmt = conn.prepareStatement(explain)) {
|
||||
bind.prepare(explainStmt, conn);
|
||||
try (ResultSet rset = explainStmt.executeQuery()) {
|
||||
return readQueryPlanBasic(plan, bind, rset);
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
queryPlanLog.error("Could not log query plan: " + explain, e);
|
||||
throw new IllegalStateException("Failed to obtain explain plan: " + explain, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.ebeaninternal.server.query;
|
||||
|
||||
import io.ebeaninternal.server.type.bindcapture.BindCapture;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
/**
|
||||
* A QueryPlanlogger for sqlserver. It will return the plan as XML, which can be opened in
|
||||
* Microsoft SQL Server Management Studio.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public class QueryPlanLoggerSqlServer extends QueryPlanLogger {
|
||||
|
||||
@Override
|
||||
public DQueryPlanOutput logQueryPlan(Connection conn, CQueryPlan plan, BindCapture bind) {
|
||||
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
stmt.execute("set statistics xml on");
|
||||
stmt.execute("begin transaction");
|
||||
try (PreparedStatement explainStmt = conn.prepareStatement(plan.getSql())) {
|
||||
bind.prepare(explainStmt, conn);
|
||||
|
||||
try (ResultSet rset = explainStmt.executeQuery()) {
|
||||
// unfortunately, this will execute the query, so we execute this in a transaction
|
||||
}
|
||||
stmt.execute("rollback transaction");
|
||||
String xml = null;
|
||||
if (explainStmt.getMoreResults()) {
|
||||
try (ResultSet rset = explainStmt.getResultSet()) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (rset.next()) {
|
||||
xml = rset.getString(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return createPlan(plan, bind.toString(), xml);
|
||||
|
||||
} catch (SQLException e) {
|
||||
queryPlanLog.error("Could not log query plan", e);
|
||||
|
||||
} finally {
|
||||
stmt.execute("set statistics xml off");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
queryPlanLog.error("Could not log query plan", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -65,6 +65,13 @@ class SqlTree {
|
||||
this.includeJoins = includeJoins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the query mandates SQL Distinct due to ToMany inclusion.
|
||||
*/
|
||||
boolean isSqlDistinct() {
|
||||
return rootNode.isSqlDistinct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the query includes joins (not valid for rawSql).
|
||||
*/
|
||||
|
||||
@@ -70,6 +70,8 @@ public final class SqlTreeBuilder {
|
||||
|
||||
private SqlTreeNode rootNode;
|
||||
|
||||
private boolean sqlDistinct;
|
||||
|
||||
/**
|
||||
* Construct for RawSql query.
|
||||
*/
|
||||
@@ -172,7 +174,7 @@ public final class SqlTreeBuilder {
|
||||
|
||||
private String buildDistinctOn() {
|
||||
|
||||
if (rawSql || !distinctOnPlatform || !query.isSqlDistinct() || Type.COUNT == query.getType()) {
|
||||
if (rawSql || !distinctOnPlatform || !sqlDistinct || Type.COUNT == query.getType()) {
|
||||
return null;
|
||||
}
|
||||
ctx.startGroupBy();
|
||||
@@ -273,7 +275,7 @@ public final class SqlTreeBuilder {
|
||||
|
||||
if (prefix == null && !rawSql) {
|
||||
if (props.requireSqlDistinct(manyWhereJoins)) {
|
||||
query.setSqlDistinct(true);
|
||||
sqlDistinct = true;
|
||||
}
|
||||
addManyWhereJoins(myJoinList);
|
||||
}
|
||||
@@ -310,7 +312,7 @@ public final class SqlTreeBuilder {
|
||||
// Optional many property for lazy loading query
|
||||
STreePropertyAssocMany lazyLoadMany = (query == null) ? null : query.getLazyLoadMany();
|
||||
boolean withId = !rawNoId && !subQuery && (query == null || query.isWithId());
|
||||
return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, temporalMode, disableLazyLoad);
|
||||
return new SqlTreeNodeRoot(desc, props, myList, withId, includeJoin, lazyLoadMany, temporalMode, disableLazyLoad, sqlDistinct);
|
||||
|
||||
} else if (prop instanceof STreePropertyAssocMany) {
|
||||
return new SqlTreeNodeManyRoot(prefix, (STreePropertyAssocMany) prop, props, myList, temporalMode, disableLazyLoad);
|
||||
@@ -361,7 +363,7 @@ public final class SqlTreeBuilder {
|
||||
// as we are now going to join to the many then we need
|
||||
// to add the distinct to the sql query to stop duplicate
|
||||
// rows...
|
||||
query.setSqlDistinct(true);
|
||||
sqlDistinct = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ interface SqlTreeNode {
|
||||
*/
|
||||
void buildRawSqlSelectChain(List<String> selectChain);
|
||||
|
||||
default boolean isSqlDistinct() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this node includes an aggregation.
|
||||
*/
|
||||
|
||||
@@ -14,14 +14,17 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean {
|
||||
|
||||
private final TableJoin includeJoin;
|
||||
|
||||
private final boolean sqlDistinct;
|
||||
|
||||
/**
|
||||
* Specify for SqlSelect to include an Id property or not.
|
||||
*/
|
||||
SqlTreeNodeRoot(STreeType desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId,
|
||||
TableJoin includeJoin, STreePropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
|
||||
TableJoin includeJoin, STreePropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad, boolean sqlDistinct) {
|
||||
|
||||
super(desc, props, myList, withId, many, temporalMode, disableLazyLoad);
|
||||
this.includeJoin = includeJoin;
|
||||
this.sqlDistinct = sqlDistinct;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,6 +32,11 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSqlDistinct() {
|
||||
return sqlDistinct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the property columns to the buffer.
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@ import io.ebean.QueryIterator;
|
||||
import io.ebean.QueryType;
|
||||
import io.ebean.RawSql;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.UpdateQuery;
|
||||
import io.ebean.Version;
|
||||
import io.ebean.bean.CallStack;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
@@ -145,12 +146,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
*/
|
||||
private boolean distinct;
|
||||
|
||||
/**
|
||||
* Set to true internally by Ebean when it needs the DISTINCT keyword added to the query (id
|
||||
* property still expected).
|
||||
*/
|
||||
private boolean sqlDistinct;
|
||||
|
||||
/**
|
||||
* Set to true if this is a future fetch using background threads.
|
||||
*/
|
||||
@@ -296,6 +291,11 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return server.findDto(dtoClass, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateQuery<T> asUpdate() {
|
||||
return new DefaultUpdateQuery<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BeanDescriptor<T> getBeanDescriptor() {
|
||||
return beanDescriptor;
|
||||
@@ -766,7 +766,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
|
||||
copy.rootTableAlias = rootTableAlias;
|
||||
copy.distinct = distinct;
|
||||
copy.sqlDistinct = sqlDistinct;
|
||||
copy.timeout = timeout;
|
||||
copy.mapKey = mapKey;
|
||||
copy.id = id;
|
||||
@@ -1080,9 +1079,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
if (distinct) {
|
||||
sb.append(",dist:");
|
||||
}
|
||||
if (sqlDistinct) {
|
||||
sb.append(",sqlD:");
|
||||
}
|
||||
if (disableLazyLoading) {
|
||||
sb.append(",disLazy:");
|
||||
}
|
||||
@@ -1629,28 +1625,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return countDistinctOrder != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this query uses SQL DISTINCT either explicitly by the user or internally defined
|
||||
* by ebean.
|
||||
*/
|
||||
@Override
|
||||
public boolean isDistinctQuery() {
|
||||
return distinct || sqlDistinct;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSqlDistinct() {
|
||||
return sqlDistinct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internally set to use SQL DISTINCT on the query but still have id property included.
|
||||
*/
|
||||
@Override
|
||||
public void setSqlDistinct(boolean sqlDistinct) {
|
||||
this.sqlDistinct = sqlDistinct;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<T> getBeanType() {
|
||||
return beanType;
|
||||
|
||||
@@ -48,8 +48,19 @@ public class DefaultUpdateQuery<T> implements UpdateQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateQuery<T> setLabel(String label) {
|
||||
query.setLabel(label);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> where() {
|
||||
return query.where();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update() {
|
||||
return query.update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,14 @@ public class OrmQueryLimitRequest implements SqlLimitRequest {
|
||||
|
||||
private final String sqlOrderBy;
|
||||
|
||||
public OrmQueryLimitRequest(String sql, String sqlOrderBy, SpiQuery<?> ormQuery, DatabasePlatform dbPlatform) {
|
||||
private final boolean distinct;
|
||||
|
||||
public OrmQueryLimitRequest(String sql, String sqlOrderBy, SpiQuery<?> ormQuery, DatabasePlatform dbPlatform, boolean distinct) {
|
||||
this.sql = sql;
|
||||
this.sqlOrderBy = sqlOrderBy;
|
||||
this.ormQuery = ormQuery;
|
||||
this.dbPlatform = dbPlatform;
|
||||
this.distinct = distinct;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,7 +46,7 @@ public class OrmQueryLimitRequest implements SqlLimitRequest {
|
||||
|
||||
@Override
|
||||
public boolean isDistinct() {
|
||||
return ormQuery.isDistinctQuery();
|
||||
return distinct;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -512,8 +512,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
|
||||
}
|
||||
connection = null;
|
||||
active = false;
|
||||
long exeMicros = (System.nanoTime() - startNanos) / 1000L;
|
||||
manager.collectMetricReadOnly(exeMicros);
|
||||
manager.collectMetricReadOnly((System.nanoTime() - startNanos) / 1000L);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user