diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java index ec28b5d14..e6567b8bb 100644 --- a/src/main/java/com/avaje/ebean/EbeanServer.java +++ b/src/main/java/com/avaje/ebean/EbeanServer.java @@ -11,6 +11,7 @@ import javax.persistence.OptimisticLockException; import com.avaje.ebean.annotation.CacheStrategy; import com.avaje.ebean.cache.ServerCacheManager; import com.avaje.ebean.config.ServerConfig; +import com.avaje.ebean.meta.MetaInfoManager; import com.avaje.ebean.text.csv.CsvReader; import com.avaje.ebean.text.json.JsonContext; @@ -117,6 +118,13 @@ public interface EbeanServer { */ public ExpressionFactory getExpressionFactory(); + /** + * Return the MetaInfoManager which is used to get meta data from the EbeanServer + * such as query execution statistics. + */ + public MetaInfoManager getMetaInfoManager(); + + /** * Return the BeanState for a given entity bean. *

diff --git a/src/main/java/com/avaje/ebean/bean/CallStack.java b/src/main/java/com/avaje/ebean/bean/CallStack.java index 66f7a937f..43d17ce0e 100644 --- a/src/main/java/com/avaje/ebean/bean/CallStack.java +++ b/src/main/java/com/avaje/ebean/bean/CallStack.java @@ -1,6 +1,7 @@ package com.avaje.ebean.bean; import java.io.Serializable; +import java.util.Arrays; /** * Represent the call stack (stack trace elements). @@ -34,6 +35,25 @@ public final class CallStack implements Serializable { } this.pathHash = enc(hc); } + + public int hashCode() { + int hc = 0; + for (int i = 0; i < callStack.length; i++) { + hc = 31 * hc + callStack[i].hashCode(); + } + return hc; + } + + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof CallStack)) { + return false; + } + CallStack e = (CallStack) obj; + return Arrays.equals(callStack, e.callStack); + } /** * Return the first element of the call stack. diff --git a/src/main/java/com/avaje/ebean/bean/ObjectGraphNode.java b/src/main/java/com/avaje/ebean/bean/ObjectGraphNode.java index 031f91e22..8cfd0e361 100644 --- a/src/main/java/com/avaje/ebean/bean/ObjectGraphNode.java +++ b/src/main/java/com/avaje/ebean/bean/ObjectGraphNode.java @@ -1,6 +1,7 @@ package com.avaje.ebean.bean; import java.io.Serializable; +import java.util.Objects; /** * Identifies a unique node of an object graph. @@ -66,4 +67,23 @@ public final class ObjectGraphNode implements Serializable { public String toString() { return "origin:" + originQueryPoint + " " + ":" + path + ":" + path; } + + public int hashCode() { + int hc = 31 * originQueryPoint.hashCode(); + hc = 31 * hc + Objects.hashCode(path); + return hc; + } + + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ObjectGraphNode)) { + return false; + } + + ObjectGraphNode e = (ObjectGraphNode) obj; + return Objects.equals(e.path, path) + && e.originQueryPoint.equals(originQueryPoint); + } } diff --git a/src/main/java/com/avaje/ebean/bean/ObjectGraphOrigin.java b/src/main/java/com/avaje/ebean/bean/ObjectGraphOrigin.java index 9c9f97635..d94487df7 100644 --- a/src/main/java/com/avaje/ebean/bean/ObjectGraphOrigin.java +++ b/src/main/java/com/avaje/ebean/bean/ObjectGraphOrigin.java @@ -14,17 +14,20 @@ import java.io.Serializable; */ public final class ObjectGraphOrigin implements Serializable { - private static final long serialVersionUID = 410937765287968707L; + private static final long serialVersionUID = 410937765287968708L; private final CallStack callStack; - private final String key; - private final String beanType; + private final int queryHash; + + private final String key; + public ObjectGraphOrigin(int queryHash, CallStack callStack, String beanType) { this.callStack = callStack; this.beanType = beanType; + this.queryHash = queryHash; this.key = callStack.getOriginKey(queryHash); } @@ -58,4 +61,24 @@ public final class ObjectGraphOrigin implements Serializable { return key + " " + beanType + " " + callStack.getFirstStackTraceElement(); } + public int hashCode() { + int hc = 31 * callStack.hashCode(); + hc = 31 * hc + beanType.hashCode(); + hc = 31 * hc + queryHash; + return hc; + } + + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof ObjectGraphOrigin)) { + return false; + } + + ObjectGraphOrigin e = (ObjectGraphOrigin) obj; + return e.queryHash == queryHash + && e.beanType.equals(beanType) + && e.callStack.equals(callStack); + } } diff --git a/src/main/java/com/avaje/ebean/meta/MetaAutoFetchStatistic.java b/src/main/java/com/avaje/ebean/meta/MetaAutoFetchStatistic.java index e7327d835..1c55e5c48 100644 --- a/src/main/java/com/avaje/ebean/meta/MetaAutoFetchStatistic.java +++ b/src/main/java/com/avaje/ebean/meta/MetaAutoFetchStatistic.java @@ -101,13 +101,13 @@ public class MetaAutoFetchStatistic implements Serializable { private final String path; - private final int exeCount; + private final long exeCount; - private final int totalBeanLoaded; + private final long totalBeanLoaded; - private final int totalMicros; + private final long totalMicros; - public QueryStats(String path, int exeCount, int totalBeanLoaded, int totalMicros) { + public QueryStats(String path, long exeCount, long totalBeanLoaded, long totalMicros) { this.path = path; this.exeCount = exeCount; this.totalBeanLoaded = totalBeanLoaded; @@ -125,21 +125,21 @@ public class MetaAutoFetchStatistic implements Serializable { /** * The number of queries executed. */ - public int getExeCount() { + public long getExeCount() { return exeCount; } /** * The total number of beans loaded by the query. */ - public int getTotalBeanLoaded() { + public long getTotalBeanLoaded() { return totalBeanLoaded; } /** * The total time in microseconds of the queries. */ - public int getTotalMicros() { + public long getTotalMicros() { return totalMicros; } diff --git a/src/main/java/com/avaje/ebean/meta/MetaBeanInfo.java b/src/main/java/com/avaje/ebean/meta/MetaBeanInfo.java new file mode 100644 index 000000000..b991582e0 --- /dev/null +++ b/src/main/java/com/avaje/ebean/meta/MetaBeanInfo.java @@ -0,0 +1,17 @@ +package com.avaje.ebean.meta; + +import java.util.List; + +public interface MetaBeanInfo { + + /** + * Collect the current query plan statistics return the non-empty statistics. + */ + public List collectQueryPlanStatistics(boolean reset); + + /** + * Collect the current query plan statistics return all the statistics (include query plans that haven't had query executions). + */ + public List collectAllQueryPlanStatistics(boolean reset); + +} diff --git a/src/main/java/com/avaje/ebean/meta/MetaBeanQueryPlanStatistic.java b/src/main/java/com/avaje/ebean/meta/MetaBeanQueryPlanStatistic.java new file mode 100644 index 000000000..6980c59c8 --- /dev/null +++ b/src/main/java/com/avaje/ebean/meta/MetaBeanQueryPlanStatistic.java @@ -0,0 +1,79 @@ +package com.avaje.ebean.meta; + + + +/** + * Query execution statistics Meta data. + */ +public interface MetaBeanQueryPlanStatistic { + + /** + * Return the bean type this query plan is for. + */ + public Class getBeanType(); + + /** + * Return true if this query plan was tuned by Autofetch. + */ + public boolean isAutofetchTuned(); + + /** + * Return the query plan hash. + */ + public int getQueryPlanHash(); + + /** + * Return the sql executed. + */ + public String getSql(); + + /** + * Return the total number of queries executed. + */ + public long getExecutionCount(); + + /** + * Return the total number of beans loaded by the queries. + *

+ * This excludes background fetching. + *

+ */ + public long getTotalLoadedBeans(); + + /** + * Return the total time taken by executions of this query. + */ + public long getTotalTimeMicros(); + + /** + * Return the max execution time for this query. + */ + public long getMaxTimeMicros(); + + /** + * Return the time collection started (or was last reset). + */ + public long getCollectionStart(); + + /** + * Return the time of the last query executed using this plan. + */ + public long getLastQueryTime(); + + /** + * Return the average query execution time in microseconds. + *

+ * This excludes background fetching. + *

+ */ + public long getAvgTimeMicros(); + + /** + * Return the average number of bean loaded per query. + *

+ * This excludes background fetching. + *

+ */ + public long getAvgLoadedBeans(); + +} diff --git a/src/main/java/com/avaje/ebean/meta/MetaInfoManager.java b/src/main/java/com/avaje/ebean/meta/MetaInfoManager.java new file mode 100644 index 000000000..aa3ee1f4c --- /dev/null +++ b/src/main/java/com/avaje/ebean/meta/MetaInfoManager.java @@ -0,0 +1,26 @@ +package com.avaje.ebean.meta; + +import java.util.List; + +public interface MetaInfoManager { + + /** + * Return the MetaBeanInfo for a bean type. + */ + public MetaBeanInfo getMetaBeanInfo(Class beanClass); + + /** + * Return all the MetaBeanInfo. + */ + public List getMetaBeanInfoList(); + + /** + * Collect and return the query plan statistics for all the beans. + *

+ * Note that this excludes the query plan statistics where there has been no + * executions (since the last collection with reset). + *

+ */ + public List collectQueryPlanStatistics(boolean reset); + +} diff --git a/src/main/java/com/avaje/ebean/meta/MetaQueryStatistic.java b/src/main/java/com/avaje/ebean/meta/MetaQueryStatistic.java deleted file mode 100644 index 6f0106536..000000000 --- a/src/main/java/com/avaje/ebean/meta/MetaQueryStatistic.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.avaje.ebean.meta; - -import java.io.Serializable; - -import javax.persistence.Entity; - -/** - * Query execution statistics Meta data. - */ -@Entity -public class MetaQueryStatistic implements Serializable { - - private static final long serialVersionUID = -8746524372894472583L; - - boolean autofetchTuned; - - String beanType; - - /** - * The original query plan hash (calculated prior to autofetch tuning). - */ - int origQueryPlanHash; - - /** - * The final query plan hash (calculated after to autofetch tuning). - */ - int finalQueryPlanHash; - - String sql; - - int executionCount; - - int totalLoadedBeans; - - int totalTimeMicros; - - long collectionStart; - - long lastQueryTime; - - int avgTimeMicros; - - int avgLoadedBeans; - - public MetaQueryStatistic() { - - } - - /** - * Create a MetaQueryStatistic. - */ - public MetaQueryStatistic(boolean autofetchTuned, String beanType, int plan, String sql, - int executionCount, int totalLoadedBeans, int totalTimeMicros, long collectionStart, - long lastQueryTime) { - - this.autofetchTuned = autofetchTuned; - this.beanType = beanType; - this.finalQueryPlanHash = plan; - this.sql = sql; - this.executionCount = executionCount; - this.totalLoadedBeans = totalLoadedBeans; - this.totalTimeMicros = totalTimeMicros; - this.collectionStart = collectionStart; - - this.lastQueryTime = lastQueryTime; - this.avgTimeMicros = executionCount == 0 ? 0 : totalTimeMicros / executionCount; - this.avgLoadedBeans = executionCount == 0 ? 0 : totalLoadedBeans / executionCount; - } - - public String toString() { - return "type=" + beanType + " tuned:" + autofetchTuned + " origHash=" + origQueryPlanHash - + " count=" + executionCount + " avgMicros=" + getAvgTimeMicros(); - } - - /** - * Return true if this query plan was built for Autofetch tuned queries. - */ - public boolean isAutofetchTuned() { - return autofetchTuned; - } - - /** - * Return the original query plan hash (calculated prior to autofetch tuning). - *

- * This will return 0 if there is no autofetch profiling or tuning on this - * query. - *

- */ - public int getOrigQueryPlanHash() { - return origQueryPlanHash; - } - - /** - * Return the queryPlanHash value. This is unique for a given query plan. - */ - public int getFinalQueryPlanHash() { - return finalQueryPlanHash; - } - - /** - * Return the bean type. - */ - public String getBeanType() { - return beanType; - } - - /** - * Return the sql executed. - */ - public String getSql() { - return sql; - } - - /** - * Return the total number of queries executed. - */ - public int getExecutionCount() { - return executionCount; - } - - /** - * Return the total number of beans loaded by the queries. - *

- * This excludes background fetching. - *

- */ - public int getTotalLoadedBeans() { - return totalLoadedBeans; - } - - /** - * Return the number of times this query was executed. - */ - public int getTotalTimeMicros() { - return totalTimeMicros; - } - - /** - * Return the time collection started. - */ - public long getCollectionStart() { - return collectionStart; - } - - /** - * Return the time of the last query executed using this plan. - */ - public long getLastQueryTime() { - return lastQueryTime; - } - - /** - * Return the average query execution time in microseconds. - *

- * This excludes background fetching. - *

- */ - public int getAvgTimeMicros() { - return avgTimeMicros; - } - - /** - * Return the average number of bean loaded per query. - *

- * This excludes background fetching. - *

- */ - public int getAvgLoadedBeans() { - return avgLoadedBeans; - } - -} diff --git a/src/main/java/com/avaje/ebean/meta/package-info.java b/src/main/java/com/avaje/ebean/meta/package-info.java new file mode 100644 index 000000000..b637cdbc8 --- /dev/null +++ b/src/main/java/com/avaje/ebean/meta/package-info.java @@ -0,0 +1,4 @@ +/** + * Meta data that can be retrieved for the EbeanServer. + */ +package com.avaje.ebean.meta; \ No newline at end of file diff --git a/src/main/java/com/avaje/ebean/meta/package.html b/src/main/java/com/avaje/ebean/meta/package.html deleted file mode 100644 index 81281f219..000000000 --- a/src/main/java/com/avaje/ebean/meta/package.html +++ /dev/null @@ -1,17 +0,0 @@ - - -Entity Beans for getting "Meta" data from Ebean - - -Entity Beans for getting "Meta" data from Ebean -

-You can query these entity beans to get "meta" data from Ebean. -This includes things like query execution statistics. -

-
-// fetch the meta data that controls autoFetch query tuning
-Query query = Ebean.createQuery(MetaAutoFetchTunedFetch.class);
-List list = query.findList();
-
- - \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/api/BindParams.java b/src/main/java/com/avaje/ebeaninternal/api/BindParams.java index 2b68aa947..f5342779b 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/BindParams.java +++ b/src/main/java/com/avaje/ebeaninternal/api/BindParams.java @@ -3,8 +3,7 @@ package com.avaje.ebeaninternal.api; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -14,22 +13,16 @@ import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam; /** * Parameters used for binding to a statement. *

- * Used by FindByNativeSql and UpdateSql to support ordered and named - * parameters. Note that you can use either ordered OR named parameters. + * Supports ordered or named parameters. *

*/ public class BindParams implements Serializable { private static final long serialVersionUID = 4541081933302086285L; - private ArrayList positionedParameters = new ArrayList(); + private List positionedParameters = new ArrayList(); - private HashMap namedParameters = new HashMap(); - - /** - * Need to create a hash when binding collection values (for in clauses). - */ - private int queryPlanHash = 1; + private Map namedParameters = new LinkedHashMap(); /** * This is the sql. For named parameters this is the sql after the named @@ -38,6 +31,39 @@ public class BindParams implements Serializable { */ private String preparedSql; + public BindParams() { + } + + public int queryBindHash() { + int hc = namedParameters.hashCode(); + for (int i = 0; i < positionedParameters.size(); i++) { + hc = hc * 31 + positionedParameters.get(i).hashCode(); + } + return hc; + } + + /** + * Return the hash that should be included with the query plan. + *

+ * This is to handle binding collections to in clauses. The number of values + * in the collection effects the query (number of bind values) and so must be + * taken into account when calculating the query hash. + *

+ */ + public int getQueryPlanHash() { + int hc = 31; + for (Param param : positionedParameters) { + hc = hc * 31 + param.queryBindCount(); + } + + for (Map.Entry entry : namedParameters.entrySet()) { + hc = hc * 31 + entry.getKey().hashCode(); + hc = hc * 31 + entry.getValue().queryBindCount(); + } + + return hc; + } + /** * Return a deep copy of the BindParams. */ @@ -46,45 +72,12 @@ public class BindParams implements Serializable { for (Param p : positionedParameters) { copy.positionedParameters.add(p.copy()); } - Iterator> it = namedParameters.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = (Map.Entry) it.next(); - copy.namedParameters.put(entry.getKey(), entry.getValue().copy()); + for (Entry entry : namedParameters.entrySet()) { + copy.namedParameters.put(entry.getKey(), entry.getValue().copy()); } return copy; } - public int queryBindHash() { - int hc = namedParameters.hashCode(); - for (int i = 0; i < positionedParameters.size(); i++) { - hc = hc * 31 + positionedParameters.get(i).hashCode(); - } - return hc; - } - - public int hashCode() { - int hc = getClass().hashCode(); - hc = hc * 31 + namedParameters.hashCode(); - for (int i = 0; i < positionedParameters.size(); i++) { - hc = hc * 31 + positionedParameters.get(i).hashCode(); - } - hc = hc * 31 + (preparedSql == null ? 0 : preparedSql.hashCode()); - return hc; - } - - public boolean equals(Object o) { - if (o == null) { - return false; - } - if (o == this) { - return true; - } - if (o instanceof BindParams) { - return hashCode() == o.hashCode(); - } - return false; - } - /** * Return true if there are no bind parameters. */ @@ -131,10 +124,8 @@ public class BindParams implements Serializable { * Set an In Out parameter using position. */ public void setParameter(int position, Object value, int outType) { - - addToQueryPlanHash(String.valueOf(position), value); - Param p = getParam(position); + Param p = getParam(position); p.setInValue(value); p.setOutType(outType); } @@ -144,10 +135,8 @@ public class BindParams implements Serializable { * must use setNullParameter. */ public void setParameter(int position, Object value) { - - addToQueryPlanHash(String.valueOf(position), value); - Param p = getParam(position); + Param p = getParam(position); p.setInValue(value); } @@ -182,10 +171,8 @@ public class BindParams implements Serializable { * Set a named In Out parameter. */ public void setParameter(String name, Object value, int outType) { - - addToQueryPlanHash(name, value); - Param p = getParam(name); + Param p = getParam(name); p.setInValue(value); p.setOutType(outType); } @@ -203,48 +190,22 @@ public class BindParams implements Serializable { */ public Param setParameter(String name, Object value) { - addToQueryPlanHash(name, value); - - Param p = getParam(name); + Param p = getParam(name); p.setInValue(value); return p; } - /** - * For binding collections calculate a hash to be used for the query plan. - */ - private void addToQueryPlanHash(String name, Object value){ - if (value != null){ - if (value instanceof Collection){ - queryPlanHash = queryPlanHash * 31 + name.hashCode(); - queryPlanHash = queryPlanHash * 31 + ((Collection)value).size(); - } - } - } - - /** - * Return the hash that should be included with the query plan. - *

- * This is to handle binding collections to in clauses. The number - * of values in the collection effects the query (number of bind values) - * and so must be taken into account when calculating the query hash. - *

- */ - public int getQueryPlanHash() { - return queryPlanHash; - } - - /** - * Set an encryption key as a bind value. - *

- * Needs special treatment as the value should not be included in a log. - *

- */ - public Param setEncryptionKey(String name, Object value) { - Param p = getParam(name); - p.setEncryptionKey(value); - return p; - } + /** + * Set an encryption key as a bind value. + *

+ * Needs special treatment as the value should not be included in a log. + *

+ */ + public Param setEncryptionKey(String name, Object value) { + Param p = getParam(name); + p.setEncryptionKey(value); + return p; + } /** * Register the named parameter as an Out parameter. @@ -300,9 +261,9 @@ public class BindParams implements Serializable { */ public static final class OrderedList { - final List paramList; + private final List paramList; - final StringBuilder preparedSql; + private final StringBuilder preparedSql; public OrderedList() { this(new ArrayList()); @@ -373,6 +334,16 @@ public class BindParams implements Serializable { public Param() { } + public int queryBindCount() { + if (inValue == null) { + return 0; + } + if (inValue instanceof Collection){ + return ((Collection)inValue).size(); + } + return 1; + } + /** * Create a deep copy of the Param. */ @@ -448,14 +419,14 @@ public class BindParams implements Serializable { this.isInParam = true; } - /** - * Set an encryption key (which can not be logged). - */ - public void setEncryptionKey(Object in) { - this.inValue = in; - this.isInParam = true; - this.encryptionKey = true; - } + /** + * Set an encryption key (which can not be logged). + */ + public void setEncryptionKey(Object in) { + this.inValue = in; + this.isInParam = true; + this.encryptionKey = true; + } /** * Specify that the In parameter is NULL and the specific type that it @@ -506,12 +477,12 @@ public class BindParams implements Serializable { this.textLocation = textLocation; } - /** - * If true do not include this value in a transaction log. - */ - public boolean isEncryptionKey() { - return encryptionKey; - } - + /** + * If true do not include this value in a transaction log. + */ + public boolean isEncryptionKey() { + return encryptionKey; + } + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManager.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManager.java index f2e8cb37f..9cc6836e5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManager.java @@ -224,7 +224,7 @@ public interface AutoFetchManager extends NodeUsageListener { * @param micros * the query executing time in microseconds */ - public void collectQueryInfo(ObjectGraphNode node, int beans, int micros); + public void collectQueryInfo(ObjectGraphNode node, long beans, long micros); /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java index 7c110f5de..8abd76935 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/AutoFetchManagerFactory.java @@ -61,6 +61,7 @@ public class AutoFetchManagerFactory { FileInputStream fi = new FileInputStream(autoFetchFile); ObjectInputStream ois = new ObjectInputStream(fi); AutoFetchManager profListener = (AutoFetchManager) ois.readObject(); + ois.close(); logger.info("AutoFetch deserialized from file ["+autoFetchFile.getAbsolutePath()+"]"); diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManager.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManager.java index 11ee8e651..4e5ec7376 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/DefaultAutoFetchManager.java @@ -527,7 +527,7 @@ public class DefaultAutoFetchManager implements AutoFetchManager, Serializable { * query in which case the parentNode will be null, or a lazy loading query * resulting from traversal of the object graph. */ - public void collectQueryInfo(ObjectGraphNode node, int beans, int micros) { + public void collectQueryInfo(ObjectGraphNode node, long beans, long micros) { if (node != null){ ObjectGraphOrigin origin = node.getOriginQueryPoint(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java index 7aa771f6d..95b8add62 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/Statistics.java @@ -127,7 +127,7 @@ public class Statistics implements Serializable { } - public void collectQueryInfo(ObjectGraphNode node, int beansLoaded, int micros) { + public void collectQueryInfo(ObjectGraphNode node, long beansLoaded, long micros) { synchronized (monitor) { String key = node.getPath(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java b/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java index d0b4bbd03..dcf9e335c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autofetch/StatisticsQuery.java @@ -13,11 +13,11 @@ public class StatisticsQuery implements Serializable { private final String path; - private int exeCount; + private long exeCount; - private int totalBeanLoaded; + private long totalBeanLoaded; - private int totalMicros; + private long totalMicros; public StatisticsQuery(String path){ this.path = path; @@ -27,7 +27,7 @@ public class StatisticsQuery implements Serializable { return new QueryStats(path, exeCount, totalBeanLoaded, totalMicros); } - public void add(int beansLoaded, int micros) { + public void add(long beansLoaded, long micros) { exeCount++; totalBeanLoaded += beansLoaded; totalMicros += micros; diff --git a/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchStatisticFinder.java b/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchStatisticFinder.java deleted file mode 100644 index 6bf429f7d..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchStatisticFinder.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.avaje.ebeaninternal.server.bean; - -import java.util.Iterator; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.bean.BeanCollection; -import com.avaje.ebean.common.BeanList; -import com.avaje.ebean.event.BeanFinder; -import com.avaje.ebean.event.BeanQueryRequest; -import com.avaje.ebean.meta.MetaAutoFetchStatistic; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; -import com.avaje.ebeaninternal.server.autofetch.Statistics; - -/** - * Bean Finder for MetaAutoFetchStatistic. - *

- * This gets the meta data from the AutoFetchManager and creates a copy of that - * data to give back to the caller in the form of MetaAutoFetchStatistic beans. - *

- */ -public class BFAutoFetchStatisticFinder implements BeanFinder { - - - public MetaAutoFetchStatistic find(BeanQueryRequest request) { - SpiQuery query = (SpiQuery)request.getQuery(); - try { - String queryPointKey = (String) query.getId(); - - SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer(); - AutoFetchManager manager = server.getAutoFetchManager(); - - Statistics stats = manager.getStatistics(queryPointKey); - if (stats != null) { - return stats.createPublicMeta(); - } else { - return null; - } - - } catch (Exception e) { - throw new PersistenceException(e); - } - } - - /** - * Only returns Lists at this stage. - */ - public BeanCollection findMany(BeanQueryRequest request) { - - SpiQuery.Type queryType = ((SpiQuery)request.getQuery()).getType(); - if (!queryType.equals(SpiQuery.Type.LIST)) { - throw new PersistenceException("Only findList() supported at this stage."); - } - - SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer(); - AutoFetchManager manager = server.getAutoFetchManager(); - - BeanList list = new BeanList(); - - Iterator it = manager.iterateStatistics(); - while (it.hasNext()) { - Statistics stats = it.next(); - // create a copy for public use - list.add(stats.createPublicMeta()); - } - - String orderBy = request.getQuery().order().toStringFormat(); - if (orderBy == null){ - orderBy = "beanType"; - } - server.sort(list, orderBy); - - - return list; - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchTunedFetchFinder.java b/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchTunedFetchFinder.java deleted file mode 100644 index 598512b09..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/bean/BFAutoFetchTunedFetchFinder.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.avaje.ebeaninternal.server.bean; - -import java.util.Iterator; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.bean.BeanCollection; -import com.avaje.ebean.common.BeanList; -import com.avaje.ebean.event.BeanFinder; -import com.avaje.ebean.event.BeanQueryRequest; -import com.avaje.ebean.meta.MetaAutoFetchTunedQueryInfo; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager; -import com.avaje.ebeaninternal.server.autofetch.TunedQueryInfo; - -/** - * BeanFinder for MetaAutoFetchTunedFetch. - */ -public class BFAutoFetchTunedFetchFinder implements BeanFinder { - - - public MetaAutoFetchTunedQueryInfo find(BeanQueryRequest request) { - - SpiQuery query = (SpiQuery)request.getQuery(); - try { - String queryPointKey = (String)query.getId(); - - SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer(); - AutoFetchManager manager = server.getAutoFetchManager(); - - TunedQueryInfo tunedFetch = manager.getTunedQueryInfo(queryPointKey); - if (tunedFetch != null){ - return tunedFetch.createPublicMeta(); - } else { - return null; - } - - } catch (Exception e){ - throw new PersistenceException(e); - } - } - - /** - * Only returns Lists at this stage. - */ - public BeanCollection findMany(BeanQueryRequest request) { - - SpiQuery.Type queryType = ((SpiQuery)request.getQuery()).getType(); - if (!queryType.equals(SpiQuery.Type.LIST)){ - throw new PersistenceException("Only findList() supported at this stage."); - } - - SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer(); - AutoFetchManager manager = server.getAutoFetchManager(); - - BeanList list = new BeanList(); - - Iterator it = manager.iterateTunedQueryInfo(); - while (it.hasNext()) { - TunedQueryInfo tunedFetch = it.next(); - // create a copy for public use - list.add(tunedFetch.createPublicMeta()); - } - - String orderBy = request.getQuery().order().toStringFormat(); - if (orderBy == null){ - orderBy = "beanType, origQueryPlanHash"; - } - server.sort(list, orderBy); - - - return list; - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/bean/BFQueryStatisticFinder.java b/src/main/java/com/avaje/ebeaninternal/server/bean/BFQueryStatisticFinder.java deleted file mode 100644 index b87df5eb7..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/bean/BFQueryStatisticFinder.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.avaje.ebeaninternal.server.bean; - -import java.util.Iterator; -import java.util.List; - -import javax.persistence.PersistenceException; - -import com.avaje.ebean.bean.BeanCollection; -import com.avaje.ebean.common.BeanList; -import com.avaje.ebean.event.BeanFinder; -import com.avaje.ebean.event.BeanQueryRequest; -import com.avaje.ebean.meta.MetaQueryStatistic; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.api.SpiQuery; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.query.CQueryPlan; - -/** - * BeanFinder for MetaQueryStatistic. - */ -public class BFQueryStatisticFinder implements BeanFinder { - - - public MetaQueryStatistic find(BeanQueryRequest request) { - throw new RuntimeException("Not Supported yet"); - } - - /** - * Only returns Lists at this stage. - */ - public BeanCollection findMany(BeanQueryRequest request) { - - SpiQuery.Type queryType = ((SpiQuery)request.getQuery()).getType(); - if (!queryType.equals(SpiQuery.Type.LIST)){ - throw new PersistenceException("Only findList() supported at this stage."); - } - - BeanList list = new BeanList(); - - SpiEbeanServer server = (SpiEbeanServer) request.getEbeanServer(); - build(list, server); - - String orderBy = request.getQuery().order().toStringFormat(); - if (orderBy == null){ - orderBy = "beanType, origQueryPlanHash, autofetchTuned"; - } - server.sort(list, orderBy); - - return list; - } - - private void build(List list, SpiEbeanServer server) { - - for (BeanDescriptor desc : server.getBeanDescriptors()) { - desc.clearQueryStatistics(); - build(list, desc); - } - } - - private void build(List list, BeanDescriptor desc) { - - Iterator it = desc.queryPlans(); - while (it.hasNext()) { - CQueryPlan queryPlan = (CQueryPlan) it.next(); - list.add(queryPlan.createMetaQueryStatistic(desc.getFullName())); - } - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/bean/package.html b/src/main/java/com/avaje/ebeaninternal/server/bean/package.html deleted file mode 100644 index f77109f52..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/bean/package.html +++ /dev/null @@ -1,8 +0,0 @@ - - -BeanFinders, BeanControllers etc for "meta" beans - - -BeanFinders, BeanControllers etc for "meta" beans - - \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultMetaInfoManager.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultMetaInfoManager.java new file mode 100644 index 000000000..5facae4a2 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultMetaInfoManager.java @@ -0,0 +1,44 @@ +package com.avaje.ebeaninternal.server.core; + +import java.util.ArrayList; +import java.util.List; + +import com.avaje.ebean.meta.MetaBeanInfo; +import com.avaje.ebean.meta.MetaBeanQueryPlanStatistic; +import com.avaje.ebean.meta.MetaInfoManager; + +/** + * DefaultServer based implementation of MetaInfoManager. + */ +public class DefaultMetaInfoManager implements MetaInfoManager { + + private final DefaultServer server; + + public DefaultMetaInfoManager(DefaultServer server) { + this.server = server; + } + + @Override + public MetaBeanInfo getMetaBeanInfo(Class beanClass) { + return server.getBeanDescriptor(beanClass); + } + + @Override + public List getMetaBeanInfoList() { + + return new ArrayList(server.getBeanDescriptors()); + } + + @Override + public List collectQueryPlanStatistics(boolean reset) { + + List list = new ArrayList(); + + for (MetaBeanInfo metaBeanInfo : getMetaBeanInfoList()) { + list.addAll(metaBeanInfo.collectQueryPlanStatistics(reset)); + } + + return list; + } + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java index d4db15306..45f2d1eba 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java @@ -57,6 +57,8 @@ import com.avaje.ebean.config.GlobalProperties; import com.avaje.ebean.config.dbplatform.DatabasePlatform; import com.avaje.ebean.event.BeanPersistController; import com.avaje.ebean.event.BeanQueryAdapter; +import com.avaje.ebean.meta.MetaBeanInfo; +import com.avaje.ebean.meta.MetaInfoManager; import com.avaje.ebean.text.csv.CsvReader; import com.avaje.ebean.text.json.JsonContext; import com.avaje.ebean.text.json.JsonElement; @@ -168,6 +170,8 @@ public final class DefaultServer implements SpiEbeanServer { private final JsonContext jsonContext; + private final MetaInfoManager metaInfoManager; + /** * The MBean name used to register Ebean. */ @@ -208,6 +212,7 @@ public final class DefaultServer implements SpiEbeanServer { */ public DefaultServer(InternalConfiguration config, ServerCacheManager cache) { + this.metaInfoManager = new DefaultMetaInfoManager(this); this.serverCacheManager = cache; this.pstmtBatch = config.getPstmtBatch(); this.databasePlatform = config.getDatabasePlatform(); @@ -298,6 +303,11 @@ public final class DefaultServer implements SpiEbeanServer { public DatabasePlatform getDatabasePlatform() { return databasePlatform; } + + @Override + public MetaInfoManager getMetaInfoManager() { + return metaInfoManager; + } public BackgroundExecutor getBackgroundExecutor() { return backgroundExecutor; @@ -1927,6 +1937,13 @@ public final class DefaultServer implements SpiEbeanServer { return beanDescriptorManager.getBeanDescriptorList(); } + public List getMetaBeanInfoList() { + + List list = new ArrayList(); + list.addAll(getBeanDescriptors()); + return list; + } + public void register(BeanPersistController c) { List> list = beanDescriptorManager.getBeanDescriptorList(); for (int i = 0; i < list.size(); i++) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java index b50c3610d..754876e2f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java @@ -389,4 +389,8 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe } } + public void flushPersistenceContextOnIterate() { + beanDescriptor.flushPersistenceContextOnIterate(persistenceContext); + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index f1cec100c..c3c873b1e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -15,6 +15,9 @@ import java.util.concurrent.ConcurrentHashMap; import javax.persistence.PersistenceException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.avaje.ebean.Query; import com.avaje.ebean.Query.UseIndex; import com.avaje.ebean.SqlUpdate; @@ -33,6 +36,8 @@ import com.avaje.ebean.event.BeanFinder; import com.avaje.ebean.event.BeanPersistController; import com.avaje.ebean.event.BeanPersistListener; import com.avaje.ebean.event.BeanQueryAdapter; +import com.avaje.ebean.meta.MetaBeanInfo; +import com.avaje.ebean.meta.MetaBeanQueryPlanStatistic; import com.avaje.ebean.text.TextException; import com.avaje.ebean.text.json.JsonWriteBeanVisitor; import com.avaje.ebeaninternal.api.SpiEbeanServer; @@ -59,6 +64,7 @@ import com.avaje.ebeaninternal.server.el.ElPropertyDeploy; import com.avaje.ebeaninternal.server.el.ElPropertyValue; import com.avaje.ebeaninternal.server.persist.DmlUtil; import com.avaje.ebeaninternal.server.query.CQueryPlan; +import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot; import com.avaje.ebeaninternal.server.query.SplitName; import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; import com.avaje.ebeaninternal.server.reflect.BeanReflect; @@ -72,13 +78,10 @@ import com.avaje.ebeaninternal.util.SortByClause; import com.avaje.ebeaninternal.util.SortByClause.Property; import com.avaje.ebeaninternal.util.SortByClauseParser; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Describes Beans including their deployment information. */ -public class BeanDescriptor { +public class BeanDescriptor implements MetaBeanInfo { private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class); @@ -1149,6 +1152,27 @@ public class BeanDescriptor { return new DeployUpdateParser(this).parse(ormUpdateStatement); } + @Override + public List collectQueryPlanStatistics(boolean reset) { + return collectQueryPlanStatisticsInternal(reset, false); + } + + @Override + public List collectAllQueryPlanStatistics(boolean reset) { + return collectQueryPlanStatisticsInternal(reset, false); + } + + public List collectQueryPlanStatisticsInternal(boolean reset, boolean collectAll) { + List list = new ArrayList(queryPlanCache.size()); + for (CQueryPlan queryPlan : queryPlanCache.values()) { + Snapshot snapshot = queryPlan.getSnapshot(reset); + if (collectAll || snapshot.getExecutionCount() > 0) { + list.add(snapshot); + } + } + return list; + } + /** * Reset the statistics on all the query plans. */ @@ -2445,5 +2469,13 @@ public class BeanDescriptor { return false; } + + public void flushPersistenceContextOnIterate(PersistenceContext persistenceContext) { + persistenceContext.clear(beanType); + for (int i = 0; i < propertiesMany.length; i++) { + persistenceContext.clear(propertiesMany[i].getBeanDescriptor().getBeanType()); + } + + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java index cd6d7264b..347f1a251 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/loadcontext/DLoadBeanContext.java @@ -126,6 +126,11 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex return context.serverName; } + @Override + public String getFullPath() { + return context.fullPath; + } + @Override public BeanDescriptor getBeanDescriptor() { return context.desc; @@ -171,10 +176,6 @@ public class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContex context.desc.getEbeanServer().loadBean(req); } - @Override - public String getFullPath() { - return context.fullPath; - } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java index 40ab36f72..cdbe59e29 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java @@ -6,6 +6,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; +import java.util.concurrent.TimeUnit; import javax.persistence.PersistenceException; @@ -39,6 +40,7 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties; import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext; import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.server.type.DataReader; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -204,8 +206,6 @@ public class CQuery implements DbReadContext, CancelableQuery { private final CQueryPlan queryPlan; - private long startNano; - private final Mode queryMode; private final boolean autoFetchProfiling; @@ -213,14 +213,17 @@ public class CQuery implements DbReadContext, CancelableQuery { private final ObjectGraphNode autoFetchParentNode; private final AutoFetchManager autoFetchManager; + private final WeakReference autoFetchManagerRef; - private int executionTimeMicros; - private final Boolean readOnly; private final SpiExpressionList filterMany; + private long startNano; + + private long executionTimeMicros; + /** * Create the Sql select based on the request. */ @@ -520,7 +523,7 @@ public class CQuery implements DbReadContext, CancelableQuery { } } - public int getQueryExecutionTimeMicros() { + public long getQueryExecutionTimeMicros() { return executionTimeMicros; } @@ -638,7 +641,7 @@ public class CQuery implements DbReadContext, CancelableQuery { protected void updateExecutionStatistics() { try { long exeNano = System.nanoTime() - startNano; - executionTimeMicros = (int) exeNano / 1000; + executionTimeMicros = TimeUnit.NANOSECONDS.toMicros(exeNano); if (autoFetchProfiling) { autoFetchManager @@ -674,7 +677,7 @@ public class CQuery implements DbReadContext, CancelableQuery { } protected boolean hasNextBean(boolean inForeground) throws SQLException { - + if (!readBeanInternal(inForeground)) { return false; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java index 9f6241cb8..d968faf96 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java @@ -115,7 +115,7 @@ public class CQueryBuilder implements Constants { String sql = s.getSql(); // cache the query plan - queryPlan = new CQueryPlan(sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); + queryPlan = new CQueryPlan(query.getBeanType(), sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); return new CQueryFetchIds(request, predicates, sql, backgroundExecutor); @@ -171,7 +171,7 @@ public class CQueryBuilder implements Constants { } // cache the query plan - queryPlan = new CQueryPlan(sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); + queryPlan = new CQueryPlan(query.getBeanType(), sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); return new CQueryRowCount(request, predicates, sql); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java index ab2f3e2eb..93dcc678d 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorSimple.java @@ -9,38 +9,37 @@ import com.avaje.ebeaninternal.server.core.OrmQueryRequest; /** * QueryIterator that does not require a buffer for secondary queries. - * - * @author rbygrave */ class CQueryIteratorSimple implements QueryIterator { - private final CQuery cquery; - private final OrmQueryRequest request; + private final CQuery cquery; + private final OrmQueryRequest request; - CQueryIteratorSimple(CQuery cquery, OrmQueryRequest request){ - this.cquery = cquery; - this.request = request; - } - - public boolean hasNext() { - try { - return cquery.hasNextBean(true); - } catch (SQLException e){ - throw cquery.createPersistenceException(e); - } - } + CQueryIteratorSimple(CQuery cquery, OrmQueryRequest request) { + this.cquery = cquery; + this.request = request; + } - public T next() { - return cquery.getLoadedBean(); + public boolean hasNext() { + try { + request.flushPersistenceContextOnIterate(); + return cquery.hasNextBean(true); + } catch (SQLException e) { + throw cquery.createPersistenceException(e); } + } - public void close() { - cquery.updateExecutionStatistics(); - cquery.close(); - request.endTransIfRequired(); - } + public T next() { + return cquery.getLoadedBean(); + } - public void remove() { - throw new PersistenceException("Remove not allowed"); - } + public void close() { + cquery.updateExecutionStatistics(); + cquery.close(); + request.endTransIfRequired(); + } + + public void remove() { + throw new PersistenceException("Remove not allowed"); + } } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java index 2df0fcd14..429766d85 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryIteratorWithBuffer.java @@ -10,58 +10,58 @@ import com.avaje.ebeaninternal.server.core.OrmQueryRequest; /** * A QueryIterator that uses a buffer to execute secondary queries periodically. - * - * @author rbygrave */ class CQueryIteratorWithBuffer implements QueryIterator { - private final CQuery cquery; - private final int bufferSize; - private final OrmQueryRequest request; - private final ArrayList buffer; + private final CQuery cquery; + private final int bufferSize; + private final OrmQueryRequest request; + private final ArrayList buffer; - private boolean moreToLoad = true; + private boolean moreToLoad = true; - CQueryIteratorWithBuffer(CQuery cquery, OrmQueryRequest request, int bufferSize) { - this.cquery = cquery; - this.request = request; - this.bufferSize = bufferSize; - this.buffer = new ArrayList(bufferSize); - } + CQueryIteratorWithBuffer(CQuery cquery, OrmQueryRequest request, int bufferSize) { + this.cquery = cquery; + this.request = request; + this.bufferSize = bufferSize; + this.buffer = new ArrayList(bufferSize); + } - public boolean hasNext() { - try { - if (buffer.isEmpty() && moreToLoad) { - // load buffer - int i = -1; - while (moreToLoad && ++i < bufferSize) { - if (cquery.hasNextBean(true)) { - buffer.add(cquery.getLoadedBean()); - } else { - moreToLoad = false; - } - } - // execute secondary queries - request.executeSecondaryQueries(bufferSize); - } - return !buffer.isEmpty(); + public boolean hasNext() { + try { + if (buffer.isEmpty() && moreToLoad) { + // load buffer + request.flushPersistenceContextOnIterate(); - } catch (SQLException e) { - throw cquery.createPersistenceException(e); + int i = -1; + while (moreToLoad && ++i < bufferSize) { + if (cquery.hasNextBean(true)) { + buffer.add(cquery.getLoadedBean()); + } else { + moreToLoad = false; + } } - } + // execute secondary queries + request.executeSecondaryQueries(bufferSize); + } + return !buffer.isEmpty(); - public T next() { - return buffer.remove(0); + } catch (SQLException e) { + throw cquery.createPersistenceException(e); } + } - public void close() { - cquery.updateExecutionStatistics(); - cquery.close(); - request.endTransIfRequired(); - } + public T next() { + return buffer.remove(0); + } - public void remove() { - throw new PersistenceException("Remove not allowed"); - } + public void close() { + cquery.updateExecutionStatistics(); + cquery.close(); + request.endTransIfRequired(); + } + + public void remove() { + throw new PersistenceException("Remove not allowed"); + } } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java index 161e94cef..0f6380346 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java @@ -4,9 +4,9 @@ import java.sql.ResultSet; import java.sql.SQLException; import com.avaje.ebean.config.dbplatform.SqlLimitResponse; -import com.avaje.ebean.meta.MetaQueryStatistic; import com.avaje.ebeaninternal.server.core.OrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot; import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.server.type.DataReader; import com.avaje.ebeaninternal.server.type.RsetDataReader; @@ -51,7 +51,9 @@ public class CQueryPlan { */ private final BeanProperty[] encryptedProps; - private CQueryStats queryStats = new CQueryStats(); + private final CQueryPlanStats stats; + + private final Class beanType; /** * Create a query plan based on a OrmQueryRequest. @@ -59,6 +61,8 @@ public class CQueryPlan { public CQueryPlan(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTree sqlTree, boolean rawSql, String logWhereSql, String luceneQueryDescription) { + this.beanType = request.getBeanDescriptor().getBeanType(); + this.stats = new CQueryPlanStats(this); this.hash = request.getQueryPlanHash(); this.autofetchTuned = request.getQuery().isAutofetchTuned(); if (sqlRes != null){ @@ -77,9 +81,11 @@ public class CQueryPlan { /** * Create a query plan for a raw sql query. */ - public CQueryPlan(String sql, SqlTree sqlTree, + public CQueryPlan(Class beanType, String sql, SqlTree sqlTree, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) { + this.beanType = beanType; + this.stats = new CQueryPlanStats(this); this.hash = 0; this.autofetchTuned = false; this.sql = sql; @@ -90,23 +96,27 @@ public class CQueryPlan { this.encryptedProps = sqlTree.getEncryptedProps(); } - public boolean isLucene() { - return false; + public String toString() { + return beanType+" hash:"+hash; } - public DataReader createDataReader(ResultSet rset){ - - return new RsetDataReader(rset); - } + public Class getBeanType() { + return beanType; + } - public void bindEncryptedProperties(DataBind dataBind) throws SQLException { - if (encryptedProps != null){ - for (int i = 0; i < encryptedProps.length; i++) { - String key = encryptedProps[i].getEncryptKey().getStringValue(); - dataBind.setString(key); - } - } - } + public DataReader createDataReader(ResultSet rset) { + + return new RsetDataReader(rset); + } + + public void bindEncryptedProperties(DataBind dataBind) throws SQLException { + if (encryptedProps != null) { + for (int i = 0; i < encryptedProps.length; i++) { + String key = encryptedProps[i].getEncryptKey().getStringValue(); + dataBind.setString(key); + } + } + } public boolean isAutofetchTuned() { return autofetchTuned; @@ -140,33 +150,33 @@ public class CQueryPlan { * Reset the query statistics. */ public void resetStatistics() { - queryStats = new CQueryStats(); + stats.reset(); } /** * Register an execution time against this query plan; */ - public void executionTime(int loadedBeanCount, int timeMicros) { - // Atomic operation - queryStats = queryStats.add(loadedBeanCount, timeMicros); + public void executionTime(long loadedBeanCount, long timeMicros) { + + stats.add(loadedBeanCount, timeMicros); } + public Snapshot getSnapshot(boolean reset) { + return stats.getSnapshot(reset); + } + /** * Return the current query statistics. */ - public CQueryStats getQueryStats() { - return queryStats; + public CQueryPlanStats getQueryStats() { + return stats; } /** * Return the time this query plan was last used. */ public long getLastQueryTime(){ - return queryStats.getLastQueryTime(); + return stats.getLastQueryTime(); } - public MetaQueryStatistic createMetaQueryStatistic(String beanName) { - return queryStats.createMetaQueryStatistic(beanName, this); - } - } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java new file mode 100644 index 000000000..d492403fe --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java @@ -0,0 +1,154 @@ + +package com.avaje.ebeaninternal.server.query; + +import java.util.concurrent.atomic.AtomicLong; + +import com.avaje.ebean.meta.MetaBeanQueryPlanStatistic; +import com.avaje.ebeaninternal.server.util.LongAdder; + +/** + * Statistics for a specific query plan that can accumulate. + */ +public final class CQueryPlanStats { + + private final CQueryPlan queryPlan; + + private final LongAdder count = new LongAdder(); + + private final LongAdder totalTime = new LongAdder(); + + private final LongAdder totalBeans = new LongAdder(); + + private final AtomicLong maxTime = new AtomicLong(); + + private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis()); + + private long lastQueryTime; + + + public CQueryPlanStats(CQueryPlan queryPlan) { + this.queryPlan = queryPlan; + } + + public void add(long loadedBeanCount, long timeMicros) { + count.increment(); + totalBeans.add(loadedBeanCount); + totalTime.add(timeMicros); + if (timeMicros > maxTime.get()) { + // effectively a high water mark + maxTime.set(timeMicros); + } + lastQueryTime = System.currentTimeMillis(); + } + + public void reset() { + count.reset(); + totalBeans.reset(); + totalTime.reset(); + maxTime.set(0); + startTime.set(System.currentTimeMillis()); + } + + public long getLastQueryTime() { + return lastQueryTime; + } + + public Snapshot getSnapshot(boolean reset) { + // not guaranteed to be consistent - time gaps between getting each value + if (reset) { + return new Snapshot(queryPlan, count.sumThenReset(), totalTime.sumThenReset(), totalBeans.sumThenReset(), maxTime.getAndSet(0), startTime.getAndSet(System.currentTimeMillis()), lastQueryTime); + } + return new Snapshot(queryPlan, count.sum(), totalTime.sum(), totalBeans.sum(), maxTime.get(), startTime.get(), lastQueryTime); + } + + /** + * A snapshot of the current statistics for a query plan. + */ + public static class Snapshot implements MetaBeanQueryPlanStatistic { + + private final CQueryPlan queryPlan; + private final long count; + private final long totalTime; + private final long totalBeans; + private final long maxTime; + private final long startTime; + private final long lastQueryTime; + + public Snapshot(CQueryPlan queryPlan, long count, long totalTime, long totalBeans, long maxTime, long startTime, long lastQueryTime) { + super(); + this.queryPlan = queryPlan; + this.count = count; + this.totalTime = totalTime; + this.totalBeans = totalBeans; + this.maxTime = maxTime; + this.startTime = startTime; + this.lastQueryTime = lastQueryTime; + } + + public String toString() { + return queryPlan+" count:"+count+" time:"+totalTime+" maxTime:"+maxTime+" beans:"+totalBeans+" start:"+startTime+" lastQuery:"+lastQueryTime; + } + + @Override + public Class getBeanType() { + return queryPlan.getBeanType(); + } + + @Override + public long getExecutionCount() { + return count; + } + + @Override + public long getTotalTimeMicros() { + return totalTime; + } + + @Override + public long getTotalLoadedBeans() { + return totalBeans; + } + + @Override + public long getMaxTimeMicros() { + return maxTime; + } + + @Override + public long getCollectionStart() { + return startTime; + } + + @Override + public long getLastQueryTime() { + return lastQueryTime; + } + + @Override + public boolean isAutofetchTuned() { + return queryPlan.isAutofetchTuned(); + } + + + @Override + public int getQueryPlanHash() { + return queryPlan.getHash(); + } + + @Override + public String getSql() { + return queryPlan.getSql(); + } + + @Override + public long getAvgTimeMicros() { + return count < 1 ? 0 : totalTime / count; + } + + @Override + public long getAvgLoadedBeans() { + return count < 1 ? 0 : totalBeans / count; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryStats.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryStats.java deleted file mode 100644 index 616f62b5f..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryStats.java +++ /dev/null @@ -1,77 +0,0 @@ - -package com.avaje.ebeaninternal.server.query; - -import com.avaje.ebean.meta.MetaQueryStatistic; - -/** - * Statistics for query plan that can accumulate. - */ -public final class CQueryStats { - - private final int count; - - private final int totalLoadedBeanCount; - - private final int totalTimeMicros; - - private final long startCollecting; - - private final long lastQueryTime; - - public CQueryStats() { - count = 0; - totalLoadedBeanCount = 0; - totalTimeMicros = 0; - startCollecting = System.currentTimeMillis(); - lastQueryTime = 0; - } - - /** - * Accumulate/Increment the statistics based on the previous statistics. - */ - public CQueryStats(CQueryStats previous, int loadedBeanCount, int timeMicros) { - count = previous.count + 1; - totalLoadedBeanCount = previous.totalLoadedBeanCount + loadedBeanCount; - totalTimeMicros = previous.totalTimeMicros + timeMicros; - startCollecting = previous.startCollecting; - lastQueryTime = System.currentTimeMillis(); - } - - public CQueryStats add(int loadedBeanCount, int timeMicros) { - return new CQueryStats(this, loadedBeanCount, timeMicros); - } - - public int getCount() { - return count; - } - - public int getAverageTimeMicros() { - if (count == 0) { - return 0; - } else { - return totalTimeMicros / count; - } - } - - public int getTotalLoadedBeanCount() { - return totalLoadedBeanCount; - } - - public int getTotalTimeMicros() { - return totalTimeMicros; - } - - public long getStartCollecting() { - return startCollecting; - } - - public long getLastQueryTime() { - return lastQueryTime; - } - - public MetaQueryStatistic createMetaQueryStatistic(String beanName, CQueryPlan qp) { - return new MetaQueryStatistic(qp.isAutofetchTuned(), beanName, qp.getHash(), - qp.getSql(), count, totalLoadedBeanCount, totalTimeMicros, startCollecting, lastQueryTime); - } - -} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java index b11fbfc0a..5c92b11bf 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/RawSqlSelectClauseBuilder.java @@ -83,7 +83,7 @@ public class RawSqlSelectClauseBuilder { SqlTree sqlTree = sqlSelect.getSqlTree(); - CQueryPlan queryPlan = new CQueryPlan(sql, sqlTree, true, includeRowNumColumn, ""); + CQueryPlan queryPlan = new CQueryPlan(query.getBeanType(), sql, sqlTree, true, includeRowNumColumn, ""); CQuery compiledQuery = new CQuery(request, predicates, queryPlan); return compiledQuery; diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 5da53707b..6d6d748a8 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -112,6 +112,7 @@ public class DefaultOrmQuery implements SpiQuery { private OrderBy orderBy; private String loadMode; + private String loadDescription; private String generatedSql; @@ -127,7 +128,7 @@ public class DefaultOrmQuery implements SpiQuery { private String lazyLoadProperty; - private String lazyLoadManyPath; + private String lazyLoadManyPath; /** * Set to true if you want a DISTINCT query. @@ -172,6 +173,7 @@ public class DefaultOrmQuery implements SpiQuery { private boolean usageProfiling = true; private boolean loadBeanCache; + private Boolean useBeanCache; private Boolean useQueryCache; diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java index d60922443..ed6922d38 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DefaultPersistenceContext.java @@ -2,9 +2,7 @@ package com.avaje.ebeaninternal.server.transaction; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import com.avaje.ebean.bean.PersistenceContext; @@ -17,8 +15,9 @@ import com.avaje.ebeaninternal.api.Monitor; * id. *

*

- * PersistenceContext lives on a Transaction and as such is expected to only have - * a single thread accessing it at a time. This is not expected to be used concurrently. + * PersistenceContext lives on a Transaction and as such is expected to only + * have a single thread accessing it at a time. This is not expected to be used + * concurrently. *

*

* Duplicate beans are ones having the same type and unique id value. These are @@ -28,166 +27,172 @@ import com.avaje.ebeaninternal.api.Monitor; */ public final class DefaultPersistenceContext implements PersistenceContext { - /** - * Map used hold caches. One cache per bean type. - */ - private final HashMap typeCache = new HashMap(); + /** + * Map used hold caches. One cache per bean type. + */ + private final HashMap typeCache = new HashMap(); - private final Monitor monitor = new Monitor(); - - /** - * Create a new PersistanceContext. - */ - public DefaultPersistenceContext() { - } + private final Monitor monitor = new Monitor(); - /** - * Set an object into the PersistanceContext. - */ - public void put(Object id, Object bean) { - synchronized (monitor) { - getClassContext(bean.getClass()).put(id, bean); - } - } - - public Object putIfAbsent(Object id, Object bean){ - synchronized (monitor) { - return getClassContext(bean.getClass()).putIfAbsent(id, bean); - } - } + /** + * Create a new PersistanceContext. + */ + public DefaultPersistenceContext() { + } - - - /** - * Return an object given its type and unique id. - */ - public Object get(Class beanType, Object id) { - synchronized (monitor) { - return getClassContext(beanType).get(id); - } + /** + * Set an object into the PersistanceContext. + */ + public void put(Object id, Object bean) { + synchronized (monitor) { + getClassContext(bean.getClass()).put(id, bean); } - - public WithOption getWithOption(Class beanType, Object id) { - synchronized (monitor) { - return getClassContext(beanType).getWithOption(id); + } + + public Object putIfAbsent(Object id, Object bean) { + synchronized (monitor) { + return getClassContext(bean.getClass()).putIfAbsent(id, bean); + } + } + + /** + * Return an object given its type and unique id. + */ + public Object get(Class beanType, Object id) { + synchronized (monitor) { + return getClassContext(beanType).get(id); + } + } + + public WithOption getWithOption(Class beanType, Object id) { + synchronized (monitor) { + return getClassContext(beanType).getWithOption(id); + } + } + + /** + * Return the number of beans of the given type in the persistence context. + */ + public int size(Class beanType) { + synchronized (monitor) { + ClassContext classMap = typeCache.get(beanType.getName()); + return classMap == null ? 0 : classMap.size(); + } + } + + /** + * Clear the PersistenceContext. + */ + public void clear() { + synchronized (monitor) { + typeCache.clear(); + } + } + + public void clear(Class beanType) { + synchronized (monitor) { + ClassContext classMap = typeCache.get(beanType.getName()); + if (classMap != null) { + classMap.clear(); } } + } - /** - * Return the number of beans of the given type in the persistence context. - */ - public int size(Class beanType) { - synchronized (monitor) { - ClassContext classMap = typeCache.get(beanType.getName()); - return classMap == null ? 0 : classMap.size(); - } - } - - /** - * Clear the PersistenceContext. - */ - public void clear() { - synchronized (monitor) { - typeCache.clear(); - } - } - - public void clear(Class beanType) { - synchronized (monitor) { - ClassContext classMap = typeCache.get(beanType.getName()); - if (classMap != null) { - classMap.clear(); - } - } - } - - public void deleted(Class beanType, Object id) { - synchronized (monitor) { - ClassContext classMap = typeCache.get(beanType.getName()); - if (classMap != null && id != null) { - classMap.deleted(id); - } + public void deleted(Class beanType, Object id) { + synchronized (monitor) { + ClassContext classMap = typeCache.get(beanType.getName()); + if (classMap != null && id != null) { + classMap.deleted(id); } } - - public void clear(Class beanType, Object id) { - synchronized (monitor) { - ClassContext classMap = typeCache.get(beanType.getName()); - if (classMap != null && id != null) { - classMap.remove(id); - } - } + } + + public void clear(Class beanType, Object id) { + synchronized (monitor) { + ClassContext classMap = typeCache.get(beanType.getName()); + if (classMap != null && id != null) { + classMap.remove(id); + } } - + } + + public String toString() { + synchronized (monitor) { + return typeCache.toString(); + } + } + + private ClassContext getClassContext(Class beanType) { + + String clsName = beanType.getName(); + ClassContext classMap = typeCache.get(clsName); + if (classMap == null) { + classMap = new ClassContext(); + typeCache.put(clsName, classMap); + } + return classMap; + } + + private static class ClassContext { + + private final Map map = new HashMap(); + + private Set deleteSet; + + private ClassContext() { + } + public String toString() { - synchronized (monitor) { - StringBuilder sb = new StringBuilder(); - Iterator> it = typeCache.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - if (entry.getValue().size() > 0){ - sb.append(entry.getKey()+":"+entry.getValue().size()+"; "); - } - } - return sb.toString(); - } - } - - private ClassContext getClassContext(Class beanType) { - - String clsName = beanType.getName(); - ClassContext classMap = typeCache.get(clsName); - if (classMap == null) { - classMap = new ClassContext(); - typeCache.put(clsName, classMap); - } - return classMap; + return "size:" + map.size(); } - private static class ClassContext { - - private final WeakValueMap map = new WeakValueMap(); - private Set deleteSet; - - private WithOption getWithOption(Object id){ - if (deleteSet != null && deleteSet.contains(id)) { - return WithOption.DELETED; - } - Object bean = map.get(id); - return (bean == null) ? null : new WithOption(bean); - } - - private Object get(Object id){ - return map.get(id); - } - - private Object putIfAbsent(Object id, Object bean){ - return map.putIfAbsent(id, bean); - } - - private void put(Object id, Object b){ - map.put(id, b); - } - - private int size() { - return map.size(); - } - - private void clear(){ - map.clear(); - } - - private Object remove(Object id){ - return map.remove(id); - } - - private void deleted(Object id){ - if (deleteSet == null) { - deleteSet = new HashSet(); - } - deleteSet.add(id); - map.remove(id); + private WithOption getWithOption(Object id) { + if (deleteSet != null && deleteSet.contains(id)) { + return WithOption.DELETED; } + Object bean = map.get(id); + return (bean == null) ? null : new WithOption(bean); } + + private Object get(Object id) { + return map.get(id); + } + + private Object putIfAbsent(Object id, Object bean) { + + Object existingValue = map.get(id); + if (existingValue != null) { + // it is not absent + return existingValue; + } + // put the new value and return null indicating the put was successful + map.put(id, bean); + return null; + } + + private void put(Object id, Object b) { + map.put(id, b); + } + + private int size() { + return map.size(); + } + + private void clear() { + map.clear(); + } + + private Object remove(Object id) { + return map.remove(id); + } + + private void deleted(Object id) { + if (deleteSet == null) { + deleteSet = new HashSet(); + } + deleteSet.add(id); + map.remove(id); + } + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/WeakValueMap.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/WeakValueMap.java deleted file mode 100644 index 51d50bcbf..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/WeakValueMap.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.avaje.ebeaninternal.server.transaction; - -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.Map; - -/** - * A Weak value map designed for use with DefaultPersistenceContext. - *

- * This provides the mechanism where entries in the persistence context will be - * automatically removed when they are not referenced externally. - *

- * - * @author mario, rbygrave - */ -public class WeakValueMap { - - protected final ReferenceQueue refQueue = new ReferenceQueue(); - - /** - * Backing map. - */ - private final Map> backing; - - /** - * Hold the key with the value for expunge purposes. - */ - private static class WeakReferenceWithKey extends WeakReference { - - private final K key; - - public WeakReferenceWithKey(K key, V referent, ReferenceQueue q) { - super(referent, q); - this.key = key; - } - - public K getKey() { - return key; - } - } - - public WeakValueMap() { - this.backing = new HashMap>(); - } - - private WeakReferenceWithKey createReference(K key, V value) { - return new WeakReferenceWithKey(key, value, refQueue); - } - - @SuppressWarnings({ "rawtypes" }) - private void expunge() { - - Reference ref; - - while ((ref = refQueue.poll()) != null) { - backing.remove(((WeakReferenceWithKey) ref).getKey()); - } - } - - /** - * Put the key value pair if there is not already a matching entry. If there - * is an existing entry then return that instead. - */ - public Object putIfAbsent(K key, V value) { - expunge(); - - Reference ref = backing.get(key); - if (ref != null) { - V existingValue = ref.get(); - if (existingValue != null) { - // it is not absent - return existingValue; - } - } - // put the new value and return null - // indicating the put was successful - backing.put(key, createReference(key, value)); - return null; - } - - public void put(K key, V value) { - expunge(); - - backing.put(key, createReference(key, value)); - } - - public V get(K key) { - expunge(); - - Reference v = backing.get(key); - return v == null ? null : v.get(); - } - - public int size() { - expunge(); - - return backing.size(); - } - - public boolean isEmpty() { - expunge(); - - return backing.isEmpty(); - } - - public boolean containsKey(Object key) { - expunge(); - - return backing.containsKey(key); - } - - public V remove(K key) { - expunge(); - - Reference v = backing.remove(key); - return v == null ? null : v.get(); - } - - public void clear() { - expunge(); - backing.clear(); - expunge(); - } - - public String toString() { - expunge(); - - return backing.toString(); - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/util/LongAdder.java b/src/main/java/com/avaje/ebeaninternal/server/util/LongAdder.java new file mode 100644 index 000000000..6ffd97d23 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/util/LongAdder.java @@ -0,0 +1,201 @@ +package com.avaje.ebeaninternal.server.util; + +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + + +import java.util.concurrent.atomic.AtomicLong; +import java.io.Serializable; + +/** + * One or more variables that together maintain an initially zero + * {@code long} sum. When updates (method {@link #add}) are contended + * across threads, the set of variables may grow dynamically to reduce + * contention. Method {@link #sum} (or, equivalently, {@link + * #longValue}) returns the current total combined across the + * variables maintaining the sum. + * + *

This class is usually preferable to {@link AtomicLong} when + * multiple threads update a common sum that is used for purposes such + * as collecting statistics, not for fine-grained synchronization + * control. Under low update contention, the two classes have similar + * characteristics. But under high contention, expected throughput of + * this class is significantly higher, at the expense of higher space + * consumption. + * + *

This class extends {@link Number}, but does not define + * methods such as {@code equals}, {@code hashCode} and {@code + * compareTo} because instances are expected to be mutated, and so are + * not useful as collection keys. + * + *

jsr166e note: This class is targeted to be placed in + * java.util.concurrent.atomic. + * + * @since 1.8 + * @author Doug Lea + */ +public class LongAdder extends Striped64 implements Serializable { + private static final long serialVersionUID = 7249069246863182397L; + + /** + * Version of plus for use in retryUpdate + */ + final long fn(long v, long x) { return v + x; } + + /** + * Creates a new adder with initial sum of zero. + */ + public LongAdder() { + } + + /** + * Adds the given value. + * + * @param x the value to add + */ + public void add(long x) { + Cell[] as; long b, v; HashCode hc; Cell a; int n; + if ((as = cells) != null || !casBase(b = base, b + x)) { + boolean uncontended = true; + int h = (hc = threadHashCode.get()).code; + if (as == null || (n = as.length) < 1 || + (a = as[(n - 1) & h]) == null || + !(uncontended = a.cas(v = a.value, v + x))) + retryUpdate(x, hc, uncontended); + } + } + + /** + * Equivalent to {@code add(1)}. + */ + public void increment() { + add(1L); + } + + /** + * Equivalent to {@code add(-1)}. + */ + public void decrement() { + add(-1L); + } + + /** + * Returns the current sum. The returned value is NOT an + * atomic snapshot; invocation in the absence of concurrent + * updates returns an accurate result, but concurrent updates that + * occur while the sum is being calculated might not be + * incorporated. + * + * @return the sum + */ + public long sum() { + long sum = base; + Cell[] as = cells; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + sum += a.value; + } + } + return sum; + } + + /** + * Resets variables maintaining the sum to zero. This method may + * be a useful alternative to creating a new adder, but is only + * effective if there are no concurrent updates. Because this + * method is intrinsically racy, it should only be used when it is + * known that no threads are concurrently updating. + */ + public void reset() { + internalReset(0L); + } + + /** + * Equivalent in effect to {@link #sum} followed by {@link + * #reset}. This method may apply for example during quiescent + * points between multithreaded computations. If there are + * updates concurrent with this method, the returned value is + * not guaranteed to be the final value occurring before + * the reset. + * + * @return the sum + */ + public long sumThenReset() { + long sum = base; + Cell[] as = cells; + base = 0L; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) { + sum += a.value; + a.value = 0L; + } + } + } + return sum; + } + + /** + * Returns the String representation of the {@link #sum}. + * @return the String representation of the {@link #sum} + */ + public String toString() { + return Long.toString(sum()); + } + + /** + * Equivalent to {@link #sum}. + * + * @return the sum + */ + public long longValue() { + return sum(); + } + + /** + * Returns the {@link #sum} as an {@code int} after a narrowing + * primitive conversion. + */ + public int intValue() { + return (int)sum(); + } + + /** + * Returns the {@link #sum} as a {@code float} + * after a widening primitive conversion. + */ + public float floatValue() { + return (float)sum(); + } + + /** + * Returns the {@link #sum} as a {@code double} after a widening + * primitive conversion. + */ + public double doubleValue() { + return (double)sum(); + } + + private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException { + s.defaultWriteObject(); + s.writeLong(sum()); + } + + private void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + s.defaultReadObject(); + busy = 0; + cells = null; + base = s.readLong(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/util/Stripped64.java b/src/main/java/com/avaje/ebeaninternal/server/util/Stripped64.java new file mode 100644 index 000000000..eed973fb6 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/util/Stripped64.java @@ -0,0 +1,342 @@ +package com.avaje.ebeaninternal.server.util; + +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +import java.util.Random; + +/** + * A package-local class holding common representation and mechanics + * for classes supporting dynamic striping on 64bit values. The class + * extends Number so that concrete subclasses must publicly do so. + */ +abstract class Striped64 extends Number { + /* + * This class maintains a lazily-initialized table of atomically + * updated variables, plus an extra "base" field. The table size + * is a power of two. Indexing uses masked per-thread hash codes. + * Nearly all declarations in this class are package-private, + * accessed directly by subclasses. + * + * Table entries are of class Cell; a variant of AtomicLong padded + * to reduce cache contention on most processors. Padding is + * overkill for most Atomics because they are usually irregularly + * scattered in memory and thus don't interfere much with each + * other. But Atomic objects residing in arrays will tend to be + * placed adjacent to each other, and so will most often share + * cache lines (with a huge negative performance impact) without + * this precaution. + * + * In part because Cells are relatively large, we avoid creating + * them until they are needed. When there is no contention, all + * updates are made to the base field. Upon first contention (a + * failed CAS on base update), the table is initialized to size 2. + * The table size is doubled upon further contention until + * reaching the nearest power of two greater than or equal to the + * number of CPUS. Table slots remain empty (null) until they are + * needed. + * + * A single spinlock ("busy") is used for initializing and + * resizing the table, as well as populating slots with new Cells. + * There is no need for a blocking lock; when the lock is not + * available, threads try other slots (or the base). During these + * retries, there is increased contention and reduced locality, + * which is still better than alternatives. + * + * Per-thread hash codes are initialized to random values. + * Contention and/or table collisions are indicated by failed + * CASes when performing an update operation (see method + * retryUpdate). Upon a collision, if the table size is less than + * the capacity, it is doubled in size unless some other thread + * holds the lock. If a hashed slot is empty, and lock is + * available, a new Cell is created. Otherwise, if the slot + * exists, a CAS is tried. Retries proceed by "double hashing", + * using a secondary hash (Marsaglia XorShift) to try to find a + * free slot. + * + * The table size is capped because, when there are more threads + * than CPUs, supposing that each thread were bound to a CPU, + * there would exist a perfect hash function mapping threads to + * slots that eliminates collisions. When we reach capacity, we + * search for this mapping by randomly varying the hash codes of + * colliding threads. Because search is random, and collisions + * only become known via CAS failures, convergence can be slow, + * and because threads are typically not bound to CPUS forever, + * may not occur at all. However, despite these limitations, + * observed contention rates are typically low in these cases. + * + * It is possible for a Cell to become unused when threads that + * once hashed to it terminate, as well as in the case where + * doubling the table causes no thread to hash to it under + * expanded mask. We do not try to detect or remove such cells, + * under the assumption that for long-running instances, observed + * contention levels will recur, so the cells will eventually be + * needed again; and for short-lived ones, it does not matter. + */ + + /** + * Padded variant of AtomicLong supporting only raw accesses plus CAS. + * The value field is placed between pads, hoping that the JVM doesn't + * reorder them. + * + * JVM intrinsics note: It would be possible to use a release-only + * form of CAS here, if it were provided. + */ + static final class Cell { + volatile long p0, p1, p2, p3, p4, p5, p6; + volatile long value; + volatile long q0, q1, q2, q3, q4, q5, q6; + Cell(long x) { value = x; } + + final boolean cas(long cmp, long val) { + return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long valueOffset; + static { + try { + UNSAFE = getUnsafe(); + Class ak = Cell.class; + valueOffset = UNSAFE.objectFieldOffset + (ak.getDeclaredField("value")); + } catch (Exception e) { + throw new Error(e); + } + } + + } + + /** + * Holder for the thread-local hash code. The code is initially + * random, but may be set to a different value upon collisions. + */ + static final class HashCode { + static final Random rng = new Random(); + int code; + HashCode() { + int h = rng.nextInt(); // Avoid zero to allow xorShift rehash + code = (h == 0) ? 1 : h; + } + } + + /** + * The corresponding ThreadLocal class + */ + static final class ThreadHashCode extends ThreadLocal { + public HashCode initialValue() { return new HashCode(); } + } + + /** + * Static per-thread hash codes. Shared across all instances to + * reduce ThreadLocal pollution and because adjustments due to + * collisions in one table are likely to be appropriate for + * others. + */ + static final ThreadHashCode threadHashCode = new ThreadHashCode(); + + /** Number of CPUS, to place bound on table size */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** + * Table of cells. When non-null, size is a power of 2. + */ + transient volatile Cell[] cells; + + /** + * Base value, used mainly when there is no contention, but also as + * a fallback during table initialization races. Updated via CAS. + */ + transient volatile long base; + + /** + * Spinlock (locked via CAS) used when resizing and/or creating Cells. + */ + transient volatile int busy; + + /** + * Package-private default constructor + */ + Striped64() { + } + + /** + * CASes the base field. + */ + final boolean casBase(long cmp, long val) { + return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val); + } + + /** + * CASes the busy field from 0 to 1 to acquire lock. + */ + final boolean casBusy() { + return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1); + } + + /** + * Computes the function of current and new value. Subclasses + * should open-code this update function for most uses, but the + * virtualized form is needed within retryUpdate. + * + * @param currentValue the current value (of either base or a cell) + * @param newValue the argument from a user update call + * @return result of the update function + */ + abstract long fn(long currentValue, long newValue); + + /** + * Handles cases of updates involving initialization, resizing, + * creating new Cells, and/or contention. See above for + * explanation. This method suffers the usual non-modularity + * problems of optimistic retry code, relying on rechecked sets of + * reads. + * + * @param x the value + * @param hc the hash code holder + * @param wasUncontended false if CAS failed before call + */ + final void retryUpdate(long x, HashCode hc, boolean wasUncontended) { + int h = hc.code; + boolean collide = false; // True if last slot nonempty + for (;;) { + Cell[] as; Cell a; int n; long v; + if ((as = cells) != null && (n = as.length) > 0) { + if ((a = as[(n - 1) & h]) == null) { + if (busy == 0) { // Try to attach new Cell + Cell r = new Cell(x); // Optimistically create + if (busy == 0 && casBusy()) { + boolean created = false; + try { // Recheck under lock + Cell[] rs; int m, j; + if ((rs = cells) != null && + (m = rs.length) > 0 && + rs[j = (m - 1) & h] == null) { + rs[j] = r; + created = true; + } + } finally { + busy = 0; + } + if (created) + break; + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + wasUncontended = true; // Continue after rehash + else if (a.cas(v = a.value, fn(v, x))) + break; + else if (n >= NCPU || cells != as) + collide = false; // At max size or stale + else if (!collide) + collide = true; + else if (busy == 0 && casBusy()) { + try { + if (cells == as) { // Expand table unless stale + Cell[] rs = new Cell[n << 1]; + for (int i = 0; i < n; ++i) + rs[i] = as[i]; + cells = rs; + } + } finally { + busy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h ^= h << 13; // Rehash + h ^= h >>> 17; + h ^= h << 5; + } + else if (busy == 0 && cells == as && casBusy()) { + boolean init = false; + try { // Initialize table + if (cells == as) { + Cell[] rs = new Cell[2]; + rs[h & 1] = new Cell(x); + cells = rs; + init = true; + } + } finally { + busy = 0; + } + if (init) + break; + } + else if (casBase(v = base, fn(v, x))) + break; // Fall back on using base + } + hc.code = h; // Record index for next time + } + + + /** + * Sets base and all cells to the given value. + */ + final void internalReset(long initialValue) { + Cell[] as = cells; + base = initialValue; + if (as != null) { + int n = as.length; + for (int i = 0; i < n; ++i) { + Cell a = as[i]; + if (a != null) + a.value = initialValue; + } + } + } + + // Unsafe mechanics + private static final sun.misc.Unsafe UNSAFE; + private static final long baseOffset; + private static final long busyOffset; + static { + try { + UNSAFE = getUnsafe(); + Class sk = Striped64.class; + baseOffset = UNSAFE.objectFieldOffset + (sk.getDeclaredField("base")); + busyOffset = UNSAFE.objectFieldOffset + (sk.getDeclaredField("busy")); + } catch (Exception e) { + throw new Error(e); + } + } + + /** + * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. + * Replace with a simple call to Unsafe.getUnsafe when integrating + * into a jdk. + * + * @return a sun.misc.Unsafe + */ + private static sun.misc.Unsafe getUnsafe() { + try { + return sun.misc.Unsafe.getUnsafe(); + } catch (SecurityException tryReflectionInstead) {} + try { + return java.security.AccessController.doPrivileged + (new java.security.PrivilegedExceptionAction() { + public sun.misc.Unsafe run() throws Exception { + Class k = sun.misc.Unsafe.class; + for (java.lang.reflect.Field f : k.getDeclaredFields()) { + f.setAccessible(true); + Object x = f.get(null); + if (k.isInstance(x)) + return k.cast(x); + } + throw new NoSuchFieldError("the Unsafe"); + }}); + } catch (java.security.PrivilegedActionException e) { + throw new RuntimeException("Could not initialize intrinsics", + e.getCause()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/tests/basic/TestWeakPersistenceContext.java b/src/test/java/com/avaje/tests/basic/TestWeakPersistenceContext.java deleted file mode 100644 index 1c39a2cd9..000000000 --- a/src/test/java/com/avaje/tests/basic/TestWeakPersistenceContext.java +++ /dev/null @@ -1,70 +0,0 @@ - - -package com.avaje.tests.basic; - -import java.util.List; - -import junit.framework.Assert; - -import org.junit.Test; - -import com.avaje.ebean.BaseTestCase; -import com.avaje.ebean.Ebean; -import com.avaje.ebean.Transaction; -import com.avaje.ebean.bean.PersistenceContext; -import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.tests.model.basic.Order; -import com.avaje.tests.model.basic.ResetBasicData; - -public class TestWeakPersistenceContext extends BaseTestCase { - - @Test - public void testOne() { - - PersistenceContext ctx = inner(); - - System.gc(); - - try { - Thread.sleep(300); - } catch (InterruptedException e) { - e.printStackTrace(); - } - // this is really only a HINT, so no guarantee - // .. but the SUN JVM does do the business - System.gc(); - - // Pass on the SUN JVM - Object o3 = ctx.get(Order.class, 1); - Assert.assertNull("Sun JVM should have GC'ed this bean",o3); - - } - - private PersistenceContext inner() { - - ResetBasicData.reset(); - - Transaction transaction = Ebean.beginTransaction(); - SpiTransaction st = (SpiTransaction)transaction; - PersistenceContext ctx = st.getPersistenceContext(); - - List list = Ebean.find(Order.class) - //.select("id") - .findList(); - - Assert.assertTrue(list.size() > 0); - - Object o1 = ctx.get(Order.class, 1); - - Assert.assertNotNull(o1); - - Ebean.endTransaction(); - - Object o2 = ctx.get(Order.class, 1); - Assert.assertNotNull(o2); - - System.gc(); - - return ctx; - } -}