Compare commits

..
Author SHA1 Message Date
Robin Bygrave 0124f0f254 [maven-release-plugin] prepare release avaje-ebeanorm-6.8.1 2015-09-21 17:45:21 +12:00
Robin Bygrave eddd6a36d6 No effective change - javadoc only 2015-09-21 17:36:13 +12:00
Robin Bygrave 91e309d770 Bump pom version to 6.8.1-SNAPSHOT 2015-09-21 17:03:22 +12:00
Robin Bygrave 5726410f6f #417 - ENH: Add JSON expressions ... path exists and value at path equals value 2015-09-21 16:20:14 +12:00
Robin Bygrave 054f7dc86f #415 - ENH: Add ExplicitJdbcTransaction ... so like AutoCommitJdbcTransaction but using explicit statements to commit and rollback 2015-09-18 22:25:19 +12:00
Robin Bygrave 06b5ebc14e #419 - Support running History DDL scripts in full drop and create ddl execution - detect $$ procedures/triggers in ddl script 2015-09-18 22:19:52 +12:00
Robin Bygrave f6ee82428f Change test - disable this test for MySql - it does not like trying to delete using sub query select from same table 2015-09-18 22:18:37 +12:00
Robin Bygrave e1d45dd79d #419 - Support running History DDL scripts in full drop and create ddl execution - detect $$ procedures/triggers in ddl script 2015-09-18 22:03:26 +12:00
Robin Bygrave 08c455279f #418 - Postgres History DDL generation - invalid update table statement to initialise the sys_period 2015-09-18 21:30:59 +12:00
Robin Bygrave 32dcbc6703 Change test to only run with H2 (but it would also run with db's that use history table views/triggers but not with oracle total recall) 2015-09-18 21:13:19 +12:00
Robin Bygrave c9e733df4e Fix test - incorrect raw sql syntax (breaks in Oracle) 2015-09-18 21:06:54 +12:00
Robin Bygrave 606e1e6d3a #414 - Enh info: @History support for H2 2015-09-18 17:16:46 +12:00
Robin Bygrave fcb50ea13a No effective change - remove whitespace 2015-09-18 11:45:19 +12:00
Robin Bygrave 5b26e77cfd #413 - ENH: Add ability to clear the OrderBy ... so that a default order by can be set in code and then cleared / overwritten 2015-09-18 11:39:47 +12:00
Robin Bygrave 9fc5d8a201 Pom - bump test Postgres JDBC driver version 2015-09-17 11:33:09 +12:00
Robin Bygrave ba78c5e904 [maven-release-plugin] prepare for next development iteration 2015-09-14 16:19:59 +12:00
Robin Bygrave 6627195fc2 [maven-release-plugin] prepare release avaje-ebeanorm-6.7.1 2015-09-14 16:19:33 +12:00
Robin Bygrave b76c4a83ec #411 - AutoTune - add reporting empty tuned profiles, ignore org.avaje.ebean type safe finders, refactor CallStack creation 2015-09-14 16:16:22 +12:00
Robin Bygrave 3dcb82b1f5 gitignore example ebean-autotune.xml 2015-09-14 12:58:58 +12:00
Robin Bygrave ae55a40ebd Remove example ebean-autotune.xml 2015-09-14 12:57:57 +12:00
Robin Bygrave 8ba07419fa Bump pom to 6.7.1-SNAPSHOT 2015-09-14 12:02:26 +12:00
Robin Bygrave 5f8f735bbe #412 - SQL_LOGGER trace() to debug() issue - Unintelligible logging settings 2015-09-14 11:49:16 +12:00
Robin Bygrave 47829f3985 #410 - Require findPagedList() query to specify an order by clause - throw exception if no order by supplied 2015-09-14 11:29:38 +12:00
Robin Bygrave ec4f655312 #410 - Require findPagedList() query to specify an order by clause - throw exception if no order by supplied 2015-09-14 10:39:41 +12:00
Robin Bygrave 04fda0a6b4 #411 - Refactor AutoFetch, rename to "AutoTune", store/load tuning and profiling as XML 2015-09-10 23:22:23 +12:00
Robin Bygrave 0a8c902790 #411 - Refactor AutoFetch, rename to "AutoTune", store/load tuning and profiling as XML 2015-09-10 23:20:57 +12:00
Robin Bygrave 9632279c2f AutoTune update - clean up for query ignore 2015-09-10 22:36:12 +12:00
Robin Bygrave 262d52ed44 Merge AutoTune refactor with master 2015-09-10 10:27:24 +12:00
Robin Bygrave 74330918e1 AutoTune update - .gitignore the profiling output 2015-09-10 10:04:31 +12:00
Robin Bygrave 373598ab26 AutoTune update - config properties for queryTuningFile and profilingFile + logging adjustment 2015-09-10 10:02:17 +12:00
Robin Bygrave 0096477757 AutoTune update - repackage tests and sort profiling xml entries 2015-09-10 09:16:39 +12:00
Robin Bygrave a2a4a15f32 AutoTune update - rename and profiling against original query 2015-09-10 00:18:11 +12:00
Robin Bygrave 71dd0e5728 AutoTune update - rename and profiling against original query 2015-09-10 00:17:49 +12:00
Robin Bygrave 116cc3a3fe AutoTune update - remove resources 2015-09-09 15:21:10 +12:00
Robin Bygrave 92132d6613 [maven-release-plugin] prepare for next development iteration 2015-09-09 00:08:33 +12:00
Robin Bygrave d6e3292a7d [maven-release-plugin] prepare release avaje-ebeanorm-6.6.1 2015-09-09 00:08:12 +12:00
Robin Bygrave 227a1ba032 Bump pom to 6.6.1-SNAPSHOT 2015-09-09 00:07:20 +12:00
Robin Bygrave 953cae6ce8 #407 - ENH: Add example uses of raw() into javadoc for ExpressionList.raw() 2015-09-09 00:06:14 +12:00
Robin Bygrave 98fc739600 #404 - ENH: ExpressionList missing setUseQueryCache() method - need to add it. 2015-09-08 23:55:53 +12:00
Robin Bygrave fc3ab10984 No effective change - javadoc on Ebean 2015-09-08 16:20:56 +12:00
Robin Bygrave 5b8ce8eaa0 WIP AutoTune refactor 2015-09-04 08:48:25 +12:00
Robin Bygrave 97cf8b30f6 Extend test for #322 - using inheritance discriminator with RawSql - extra test with columnMappingIgnore() 2015-09-03 16:29:45 +12:00
Robin Bygrave 7e66e0db1f Extend test for #322 - using inheritance discriminator with RawSql 2015-09-03 15:57:38 +12:00
Robin Bygrave 572bb41b73 Code Cleanup - move diff flatten into DiffHelp class from DiffHelpUpdate #406 2015-09-03 14:31:36 +12:00
Robin Bygrave 088fcfb296 #406 - Remove diff "non flat mode" ... so diff going forward only supports flat mode 2015-09-03 14:29:03 +12:00
Robin Bygrave 54172aec6c #405 - ENH: Add @ReadAudit and associated functionality for auditing reads including queries and L2 cache access 2015-09-03 13:54:52 +12:00
Robin Bygrave 5c1de0a886 WIP Autotune refactor 2015-09-02 09:28:17 +12:00
Robin Bygrave c292da736b #403 - Long property names result in invalid DDL 2015-09-01 08:00:46 +12:00
Robin Bygrave 987fe38618 WIP AutoTune refactor 2015-09-01 07:55:25 +12:00
Robin Bygrave f4ed813c08 No effective change - code format only 2015-08-31 16:16:10 +12:00
Robin Bygrave a358fba31d Bump agent and maven enhancer versions to 4.7.1 2015-08-31 15:37:32 +12:00
Robin Bygrave 76783d525a No effective change - improve error message 2015-08-31 15:36:56 +12:00
Robin Bygrave 3f0f5c7fde #401 - Fix for - Caused by: java.lang.NullPointerException at com.avaje.ebeaninternal.server.changelog.DefaultChangeLogListener.configure(DefaultChangeLogListener.java:62) 2015-08-30 23:38:05 +12:00
Robin Bygrave 6df1058aab [maven-release-plugin] prepare for next development iteration 2015-08-29 01:03:00 +12:00
Robin Bygrave 335c5fb69b [maven-release-plugin] prepare release avaje-ebeanorm-6.5.1 2015-08-29 01:02:37 +12:00
Robin Bygrave 94ed568812 Bump pom to 6.5.1-SNAPSHOT 2015-08-29 01:01:17 +12:00
Robin Bygrave 1c23720605 #398 - ENH: Support delete using a Query instance 2015-08-29 00:58:47 +12:00
Robin Bygrave ccba5d08ef #368 - ENH: Enhance @Index to support multiple columns, unique flag and hence also be able to put on the class (annotation target TYPE) 2015-08-28 21:56:38 +12:00
Robin Bygrave 641ada001e Fix test for CustomerFinder 2015-08-28 20:43:05 +12:00
Robin Bygrave fca80ac649 #399 - ENH: Add new Finder ... to effectively replace Model.Finder 2015-08-28 20:32:40 +12:00
Robin Bygrave 379cc39e38 Initial add of Finder 2015-08-28 07:52:46 +12:00
Robin Bygrave 386d3a37f1 No effective change - format only 2015-08-27 09:37:22 +12:00
Robin Bygrave 37612c6f83 [maven-release-plugin] prepare for next development iteration 2015-08-26 10:48:57 +12:00
257 changed files with 7663 additions and 3906 deletions
+2
View File
@@ -7,6 +7,8 @@
target/
logs/
log/
ebean-autotune.xml
ebean-profiling*.xml
/db
/mydb.db
!src/test/ddl-review/*.sql
+24 -8
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>6.4.1</version>
<version>6.8.1</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
@@ -109,10 +109,24 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1201-jdbc41</version>
<version>9.4-1202-jdbc41</version>
<scope>provided</scope>
</dependency>
<!--<dependency>-->
<!--<groupId>oracle</groupId>-->
<!--<artifactId>oracle-jdbc</artifactId>-->
<!--<version>7.0</version>-->
<!--<scope>test</scope>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>microsoft</groupId>-->
<!--<artifactId>sqlserver-jdbc</artifactId>-->
<!--<version>4.2</version>-->
<!--<scope>test</scope>-->
<!--</dependency>-->
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
@@ -130,15 +144,17 @@
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-agent</artifactId>
<version>4.5.1</version>
<version>4.7.1</version>
<scope>test</scope>
</dependency>
<!-- Provided scope so that the H2HistoryTrigger can live in Ebean core
and not require a separate module for it -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.182</version>
<scope>test</scope>
<version>1.4.189</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -195,7 +211,7 @@
<plugin>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
<version>4.5.1</version>
<version>4.7.1</version>
<executions>
<!-- Not going to enhance Model bean -->
<execution>
@@ -204,7 +220,7 @@
<configuration>
<classSource>target/test-classes</classSource>
<packages>com.avaje.tests.**</packages>
<transformArgs>debug=1</transformArgs>
<transformArgs>debug=2</transformArgs>
</configuration>
<goals>
<goal>enhance</goal>
@@ -1,137 +0,0 @@
package com.avaje.ebean;
/**
* Administrative control of Autofetch during runtime.
*/
public interface AdminAutofetch {
/**
* Return true if profiling is enabled.
*/
boolean isProfiling();
/**
* Set to true to enable profiling.
*/
void setProfiling(boolean enable);
/**
* Return true if autoFetch automatic query tuning is enabled.
*/
boolean isQueryTuning();
/**
* Set to true to enable autoFetch automatic query tuning.
*/
void setQueryTuning(boolean enable);
/**
* Returns the rate which profiling is collected. This is an int between 0 and
* 100.
*/
double getProfilingRate();
/**
* Set the rate at which profiling is collected after the base.
*
* @param rate
* a int between 0 and 100.
*/
void setProfilingRate(double rate);
/**
* Return the number of queries profiled after which profiling is collected at
* a percentage rate.
*/
int getProfilingBase();
/**
* Set a base number of queries to profile per query point.
* <p>
* After this amount of profiling has been obtained profiling is collected at
* the Profiling Percentage rate.
* </p>
*/
void setProfilingBase(int profilingBase);
/**
* Return the minimum number of queries profiled before autoFetch will start
* automatically tuning the queries.
* <p>
* This could be one which means start autoFetch tuning after the first
* profiling information is collected.
* </p>
*/
int getProfilingMin();
/**
* Set the minimum number of queries profiled per query point before autoFetch
* will automatically tune the queries.
* <p>
* Increasing this number will mean more profiling is collected before
* autoFetch starts tuning the query.
* </p>
*/
void setProfilingMin(int autoFetchMinThreshold);
/**
* Fire a garbage collection (hint to the JVM). Assuming garbage collection
* fires this will gather the usage profiling information.
*/
String collectUsageViaGC();
/**
* This will take the current profiling information and update the "tuned
* query detail".
* <p>
* This is done periodically and can also be manually invoked.
* </p>
*
* @return a summary of the updates that occurred
*/
String updateTunedQueryInfo();
/**
* Clear all the tuned query info.
* <p>
* Should only need do this for testing and playing around.
* </p>
*
* @return the amount of tuned query information cleared.
*/
int clearTunedQueryInfo();
/**
* Clear all the profiling information.
* <p>
* This means the profiling information will need to be re-gathered.
* </p>
* <p>
* Should only need do this for testing and playing around.
* </p>
*
* @return the amount of profiled information cleared.
*/
int clearProfilingInfo();
/**
* Clear the query execution statistics.
*/
void clearQueryStatistics();
/**
* Return the number of queries tuned by AutoFetch.
*/
int getTotalTunedQueryCount();
/**
* Return the size of the TuneQuery map.
*/
int getTotalTunedQuerySize();
/**
* Return the size of the profile map.
*/
int getTotalProfileSize();
}
@@ -0,0 +1,14 @@
package com.avaje.ebean;
/**
* Administrative control of AutoTune during runtime.
*/
public interface AutoTune {
/**
* Fire a garbage collection (hint to the JVM). Assuming garbage collection
* fires this will gather remaining usage profiling information.
*/
void collectProfiling();
}
+4 -4
View File
@@ -22,7 +22,7 @@ import com.avaje.ebean.text.json.JsonContext;
/**
* This Ebean object is effectively a singleton that holds a map of registered
* {@link EbeanServer}s. It additionally provides a convenient way to use the
* 'default/primary' EbeanServer.
* 'default' EbeanServer.
* <p>
* If you are using a Dependency Injection framework such as
* <strong>Spring</strong> or <strong>Guice</strong> you will probably
@@ -178,7 +178,7 @@ public final class Ebean {
if (defaultServer == null) {
String msg = "The default EbeanServer has not been defined?";
msg += " This is normally set via the ebean.datasource.default property.";
msg += " Otherwise it should be registered programatically via registerServer()";
msg += " Otherwise it should be registered programmatically via registerServer()";
throw new PersistenceException(msg);
}
return defaultServer;
@@ -217,8 +217,8 @@ public final class Ebean {
/**
* Register a server so we can get it by its name.
*/
private void register(EbeanServer server, boolean isPrimaryServer) {
registerWithName(server.getName(), server, isPrimaryServer);
private void register(EbeanServer server, boolean isDefaultServer) {
registerWithName(server.getName(), server, isDefaultServer);
}
private void registerWithName(String name, EbeanServer server, boolean isDefaultServer) {
+24 -11
View File
@@ -1,13 +1,5 @@
package com.avaje.ebean;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.ServerConfig;
@@ -17,6 +9,13 @@ import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
import org.jetbrains.annotations.Nullable;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Provides the API for fetching and saving beans to a particular DataSource.
* <p>
@@ -107,10 +106,9 @@ public interface EbeanServer {
void shutdown(boolean shutdownDataSource, boolean deregisterDriver);
/**
* Return the AdminAutofetch which is used to control and configure the
* Autofetch service at runtime.
* Return AutoTune which is used to control the AutoTune service at runtime.
*/
AdminAutofetch getAdminAutofetch();
AutoTune getAutoTune();
/**
* Return the name. This is used with {@link Ebean#getServer(String)} to get a
@@ -1114,6 +1112,21 @@ public interface EbeanServer {
@Nullable
<T> T findUnique(Query<T> query, Transaction transaction);
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @param query the query used for the delete
* @param transaction the transaction to use (can be null)
* @param <T> the type of entity bean to fetch.
* @return the number of beans/rows that were deleted
*/
<T> int delete(Query<T> query, Transaction transaction);
/**
* Execute the sql query returning a list of MapBean.
* <p>
@@ -36,6 +36,46 @@ import java.util.Map;
*/
public interface ExpressionFactory {
/**
* Path exists - for the given path in a JSON document.
*/
Expression jsonExists(String propertyName, String path);
/**
* Path does not exist - for the given path in a JSON document.
*/
Expression jsonNotExists(String propertyName, String path);
/**
* Equal to - for the given path in a JSON document.
*/
Expression jsonEqualTo(String propertyName, String path, Object val);
/**
* Not Equal to - for the given path in a JSON document.
*/
Expression jsonNotEqualTo(String propertyName, String path, Object val);
/**
* Greater than - for the given path in a JSON document.
*/
Expression jsonGreaterThan(String propertyName, String path, Object val);
/**
* Greater than or equal to - for the given path in a JSON document.
*/
Expression jsonGreaterOrEqual(String propertyName, String path, Object val);
/**
* Less than - for the given path in a JSON document.
*/
Expression jsonLessThan(String propertyName, String path, Object val);
/**
* Less than or equal to - for the given path in a JSON document.
*/
Expression jsonLessOrEqualTo(String propertyName, String path, Object val);
/**
* Equal To - property equal to the given value.
*/
+158 -17
View File
@@ -107,6 +107,18 @@ public interface ExpressionList<T> extends Serializable {
*/
Query<T> asOf(Timestamp asOf);
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @return the number of beans/rows that were deleted.
*/
int delete();
/**
* Execute the query iterating over the results.
*
@@ -312,6 +324,13 @@ public interface ExpressionList<T> extends Serializable {
*/
Query<T> setUseCache(boolean useCache);
/**
* Set to true to use the query for executing this query.
*
* @see Query#setUseQueryCache(boolean)
*/
Query<T> setUseQueryCache(boolean useCache);
/**
* Add expressions to the having clause.
* <p>
@@ -326,20 +345,125 @@ public interface ExpressionList<T> extends Serializable {
*/
ExpressionList<T> where();
/**
* Path exists - for the given path in a JSON document.
*
* <pre>{@code
*
* where().jsonExists("content", "path.other")
*
* }</pre>
*
* @param propertyName the property that holds a JSON document
* @param path the nested path in the JSON document in dot notation
*/
ExpressionList<T> jsonExists(String propertyName, String path);
/**
* Path does not exist - for the given path in a JSON document.
*
* <pre>{@code
*
* where().jsonNotExists("content", "path.other")
*
* }</pre>
*
* @param propertyName the property that holds a JSON document
* @param path the nested path in the JSON document in dot notation
*/
ExpressionList<T> jsonNotExists(String propertyName, String path);
/**
* Equal to expression for the value at the given path in the JSON document.
*
* <pre>{@code
*
* where().jsonEqualTo("content", "path.other", 34)
*
* }</pre>
*
* @param propertyName the property that holds a JSON document
* @param path the nested path in the JSON document in dot notation
* @param value the value used to test against the document path's value
*/
ExpressionList<T> jsonEqualTo(String propertyName, String path, Object value);
/**
* Not Equal to - for the given path in a JSON document.
*
* <pre>{@code
*
* where().jsonNotEqualTo("content", "path.other", 34)
*
* }</pre>
*
* @param propertyName the property that holds a JSON document
* @param path the nested path in the JSON document in dot notation
* @param value the value used to test against the document path's value
*/
ExpressionList<T> jsonNotEqualTo(String propertyName, String path, Object value);
/**
* Greater than - for the given path in a JSON document.
*
* <pre>{@code
*
* where().jsonGreaterThan("content", "path.other", 34)
*
* }</pre>
*/
ExpressionList<T> jsonGreaterThan(String propertyName, String path, Object val);
/**
* Greater than or equal to - for the given path in a JSON document.
*
* <pre>{@code
*
* where().jsonGreaterOrEqual("content", "path.other", 34)
*
* }</pre>
*/
ExpressionList<T> jsonGreaterOrEqual(String propertyName, String path, Object val);
/**
* Less than - for the given path in a JSON document.
*
* <pre>{@code
*
* where().jsonLessThan("content", "path.other", 34)
*
* }</pre>
*/
ExpressionList<T> jsonLessThan(String propertyName, String path, Object val);
/**
* Less than or equal to - for the given path in a JSON document.
*
* <pre>{@code
*
* where().jsonLessOrEqualTo("content", "path.other", 34)
*
* }</pre>
*/
ExpressionList<T> jsonLessOrEqualTo(String propertyName, String path, Object val);
/**
* Add an Expression to the list.
* <p>
* This returns the list so that add() can be chained.
* </p>
*
* <pre class="code">
* Query&lt;Customer&gt; query = Ebean.createQuery(Customer.class);
*
* <pre>{@code
*
* Query<Customer> query = Ebean.find(Customer.class);
* query.where()
* .like(&quot;name&quot;,&quot;Rob%&quot;)
* .eq(&quot;status&quot;, Customer.ACTIVE);
* List&lt;Customer&gt; list = query.findList();
* .like("name","Rob%")
* .eq("status", Customer.ACTIVE);
*
* List<Customer> list = query.findList();
* ...
* </pre>
*
* }</pre>
*/
ExpressionList<T> add(Expression expr);
@@ -420,33 +544,36 @@ public interface ExpressionList<T> extends Serializable {
* those options such as case insensitive etc.
* </p>
*
* <pre class="code">
* <pre>{@code
*
* // create an example bean and set the properties
* // with the query parameters you want
* Customer example = new Customer();
* example.setName(&quot;Rob%&quot;);
* example.setNotes(&quot;%something%&quot;);
* example.setName("Rob%");
* example.setNotes("%something%");
*
* List&lt;Customer&gt; list = Ebean.find(Customer.class).where()
* // pass the bean into the where() clause
* .exampleLike(example)
* // you can add other expressions to the same query
* .gt(&quot;id&quot;, 2).findList();
* .gt("id", 2).findList();
*
* </pre>
* }</pre>
*
* Similarly you can create an ExampleExpression
*
* <pre>
* <pre>{@code
*
* Customer example = new Customer();
* example.setName(&quot;Rob%&quot;);
* example.setNotes(&quot;%something%&quot;);
* example.setName("Rob%");
* example.setNotes("%something%");
*
* // create a ExampleExpression with more control
* ExampleExpression qbe = new ExampleExpression(example, true, LikeType.EQUAL_TO).includeZeros();
*
* List&lt;Customer&gt; list = Ebean.find(Customer.class).where().add(qbe).findList();
* </pre>
* List<Customer> list = Ebean.find(Customer.class).where().add(qbe).findList();
*
* }</pre>
*/
ExpressionList<T> exampleLike(Object example);
@@ -574,6 +701,14 @@ public interface ExpressionList<T> extends Serializable {
* then they are not translated. logical property name names (not fully
* qualified) will still be translated to their physical name.
* </p>
*
* <h4>Example:</h4>
* <pre>{@code
*
* // use a database function
* raw("add_days(orderDate, 10) < ?", someDate)
*
* }</pre>
*/
ExpressionList<T> raw(String raw, Object value);
@@ -598,6 +733,12 @@ public interface ExpressionList<T> extends Serializable {
* then they are not translated. logical property name names (not fully
* qualified) will still be translated to their physical name.
* </p>
*
* <pre>{@code
*
* raw("orderQty < shipQty")
*
* }</pre>
*/
ExpressionList<T> raw(String raw);
+1 -1
View File
@@ -36,7 +36,7 @@ import java.util.Set;
* .sort(&quot;avgTimeMicros desc&quot;)
* .gt(&quot;executionCount&quot;, 0)
* .gt(&quot;lastQueryTime&quot;, nowMinus24Hrs)
* .eq(&quot;autofetchTuned&quot;, true)
* .eq(&quot;autoTuned&quot;, true)
* .maxRows(10)
* .filter(list);
*
+167
View File
@@ -0,0 +1,167 @@
package com.avaje.ebean;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* Intended to be used as a base class for 'Finder' implementations that can then
* be injected or used as public static fields on the associated entity bean.
* <p>
* These 'finders' are a place to organise all the finder methods for that bean type
* and specific finder methods are expected to be added (find by unique properties etc).
* </p>
*
* <h3>Testing</h3>
* <p>
* For testing the mocki-ebean project has the ability to replace the finder implementation
*
* </p>
* <pre>{@code
*
* public class CustomerFinder extends Finder<Customer> {
*
* public CustomerFinder() {
* super(Customer.class);
* }
*
* // Add your customer finder methods ...
*
* public Customer byName(String name) {
* return query().eq("name", name).findUnique();
* }
*
* public List<Customer> findNew() {
* return query()
* .eq("status", Customer.Status.NEW)
* .orderBy("name")
* .findList()
* }
* }
*
* @Entity
* public class Customer extends BaseModel {
*
* public static final CustomerFinder find = new CustomerFinder();
* ...
*
* }</pre>
*/
public class Finder<I, T> {
/**
* The entity bean type.
*/
private final Class<T> type;
/**
* The name of the EbeanServer, null for the default server.
*/
private final String serverName;
/**
* Create with the type of the entity bean.
*
* <pre>{@code
*
* public class CustomerFinder extends Finder<Customer> {
*
* public CustomerFinder() {
* super(Customer.class);
* }
*
* // ... add extra customer specific finder methods
* }
*
* @Entity
* public class Customer extends BaseModel {
*
* public static final CustomerFinder find = new CustomerFinder();
* ...
*
* }</pre>
*/
public Finder(Class<T> type) {
this.type = type;
this.serverName = null;
}
/**
* Create with the type of the entity bean and specific server name.
*/
public Finder(Class<T> type, String serverName) {
this.type = type;
this.serverName = serverName;
}
/**
* Return the underlying 'default' EbeanServer.
*
* <p>
* This provides full access to the API such as explicit transaction demarcation etc.
*
*/
public EbeanServer db() {
return Ebean.getServer(serverName);
}
/**
* Return typically a different EbeanServer to the default.
* <p>
* This is equivalent to {@link Ebean#getServer(String)}
*
* @param server
* The name of the EbeanServer. If this is null then the default EbeanServer is
* returned.
*/
public EbeanServer db(String server) {
return Ebean.getServer(server);
}
/**
* Creates an entity reference for this ID.
*
* <p>
* Equivalent to {@link EbeanServer#getReference(Class, Object)}
*/
public T ref(I id) {
return db().getReference(type, id);
}
/**
* Retrieves an entity by ID.
*
* <p>
* Equivalent to {@link EbeanServer#find(Class, Object)}
*/
@Nullable
public T byId(I id) {
return db().find(type, id);
}
/**
* Delete a bean by Id.
* <p>
* Equivalent to {@link EbeanServer#delete(Class, Object)}
*/
public void deleteById(I id) {
db().delete(type, id);
}
/**
* Retrieves all entities of the given type.
*/
public List<T> all() {
return query().findList();
}
/**
* Creates a query.
* <p>
* Equivalent to {@link EbeanServer#find(Class)}
*/
public Query<T> query() {
return db().find(type);
}
}
+3 -3
View File
@@ -836,10 +836,10 @@ public abstract class Model {
}
/**
* Create a query with explicit 'Autofetch' use.
* Create a query with explicit 'AutoTune' use.
*/
public Query<T> setAutofetch(boolean autofetch) {
return query().setAutofetch(autofetch);
public Query<T> setAutoTune(boolean autoTune) {
return query().setAutoTune(autoTune);
}
/**
+27 -2
View File
@@ -28,7 +28,7 @@ public final class OrderBy<T> implements Serializable {
* Create an empty OrderBy with no associated query.
*/
public OrderBy() {
this.list = new ArrayList<Property>(2);
this.list = new ArrayList<Property>(3);
}
private OrderBy(List<Property> list) {
@@ -52,7 +52,7 @@ public final class OrderBy<T> implements Serializable {
*/
public OrderBy(Query<T> query, String orderByClause) {
this.query = query;
this.list = new ArrayList<Property>(2);
this.list = new ArrayList<Property>(3);
parse(orderByClause);
}
@@ -83,6 +83,19 @@ public final class OrderBy<T> implements Serializable {
return query;
}
/**
* Return true if the property is known to be contained in the order by clause.
*/
public boolean containsProperty(String propertyName) {
for (int i = 0; i < list.size(); i++) {
if (propertyName.equals(list.get(i).getProperty())) {
return true;
}
}
return false;
}
/**
* Return a copy of this OrderBy with the path trimmed.
*/
@@ -185,6 +198,18 @@ public final class OrderBy<T> implements Serializable {
return list.hashCode();
}
/**
* Clear the orderBy removing any current order by properties.
* <p>
* This is intended to be used when some code creates a query with a
* 'default' order by clause and some other code may clear the 'default'
* order by clause and replace.
* </p>
*/
public void clear() {
list.clear();
}
/**
* A property and its ascending descending order.
*/
+33 -11
View File
@@ -67,9 +67,9 @@ import java.util.Set;
* ...
* }</pre>
*
* <h3>Autofetch</h3>
* <h3>AutoTune</h3>
* <p>
* Ebean has built in support for "Autofetch". This is a mechanism where a query
* Ebean has built in support for "AutoTune". This is a mechanism where a query
* can be automatically tuned based on profiling information that is collected.
* </p>
* <p>
@@ -78,7 +78,7 @@ import java.util.Set;
* more.
* </p>
* <p>
* It is expected that Autofetch will be the default approach for many queries
* It is expected that AutoTune will be the default approach for many queries
* in a system. It is possibly not as useful where the result of a query is sent
* to a remote client or where there is some requirement for "Read Consistency"
* guarantees.
@@ -338,23 +338,23 @@ public interface Query<T> extends Serializable {
ExpressionFactory getExpressionFactory();
/**
* Returns true if this query was tuned by autoFetch.
* Returns true if this query was tuned by autoTune.
*/
boolean isAutofetchTuned();
boolean isAutoTuned();
/**
* Explicitly specify whether to use Autofetch for this query.
* Explicitly specify whether to use AutoTune for this query.
* <p>
* If you do not call this method on a query the "Implicit Autofetch mode" is
* used to determine if Autofetch should be used for a given query.
* If you do not call this method on a query the "Implicit AutoTune mode" is
* used to determine if AutoTune should be used for a given query.
* </p>
* <p>
* Autofetch can add additional fetch paths to the query and specify which
* AutoTune can add additional fetch paths to the query and specify which
* properties are included for each path. If you have explicitly defined some
* fetch paths Autofetch will not remove.
* fetch paths AutoTune will not remove them.
* </p>
*/
Query<T> setAutofetch(boolean autofetch);
Query<T> setAutoTune(boolean autoTune);
/**
* Set the default lazy loading batch size to use.
@@ -366,6 +366,16 @@ public interface Query<T> extends Serializable {
*/
Query<T> setLazyLoadBatchSize(int lazyLoadBatchSize);
/**
* Disable read auditing for this query.
* <p>
* This is intended to be used when the query is not a user initiated query and instead
* part of the internal processing in an application to load a cache or document store etc.
* In these cases we don't want the query to be part of read auditing.
* </p>
*/
Query<T> setDisableReadAuditing();
/**
* Explicitly set a comma delimited list of the properties to fetch on the
* 'main' root level entity bean (aka partial object). Note that '*' means all
@@ -743,6 +753,18 @@ public interface Query<T> extends Serializable {
*/
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @return the number of beans/rows that were deleted.
*/
int delete();
/**
* Return the count of entities this query should return.
* <p>
@@ -10,15 +10,24 @@ import java.lang.annotation.Target;
*
* @author rvbiljouw
*/
@Target({ElementType.FIELD})
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Index {
/**
* Name of the index
*
* @return index name
* Name of the index. If left blank a name is derived using the built in naming convention.
*/
String value() default "";
String name() default "";
/**
* If set true indicates this is a unique index.
*/
boolean unique() default false;
/**
* When placed on the class (rather than field) you can specify the columns
* to include in the index in order.
*/
String[] columnNames() default {};
}
@@ -0,0 +1,15 @@
package com.avaje.ebean.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks an entity bean as being included in read auditing.
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadAudit {
}
@@ -6,7 +6,7 @@ import java.util.Arrays;
/**
* Represent the call stack (stack trace elements).
* <p>
* Used with a query to identify a CallStackQuery for AutoFetch automatic query
* Used with a query to identify a CallStackQuery for AutoTune automatic query
* tuning.
* </p>
* <p>
@@ -26,16 +26,12 @@ public final class CallStack implements Serializable {
private final StackTraceElement[] callStack;
public CallStack(StackTraceElement[] callStack) {
public CallStack(StackTraceElement[] callStack, int zeroHash, int pathHash) {
this.callStack = callStack;
this.zeroHash = enc(callStack[0].hashCode());
int hc = 0;
for (int i = 1; i < callStack.length; i++) {
hc = 31 * hc + callStack[i].hashCode();
}
this.pathHash = enc(hc);
this.zeroHash = enc(zeroHash);
this.pathHash = enc(pathHash);
}
public int hashCode() {
int hc = 0;
for (int i = 0; i < callStack.length; i++) {
@@ -87,8 +83,19 @@ public final class CallStack implements Serializable {
return zeroHash + ":" + pathHash + ":" + callStack[0];
}
/**
* Return the call stack lines appended with the given newLine string.
*/
public String description(String newLine) {
StringBuilder sb = new StringBuilder(400);
for (int i = 0; i < callStack.length; i++) {
sb.append(callStack[i].toString()).append(newLine);
}
return sb.toString();
}
public String getOriginKey(int queryHash) {
return zeroHash + "." + enc(queryHash) + "." + pathHash;
return enc(queryHash)+ "." + zeroHash + "." + pathHash;
}
private static final int radix = 1 << 6;
@@ -1,13 +1,13 @@
package com.avaje.ebean.bean;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Collects profile information for a bean (or reference/proxy bean) at a given
* AutoFetchNode.
* Collects profile information for a bean (or reference/proxy bean) at a given node.
* <p>
* The AutoFetchNode identifies the location of the bean in the object graph.
* The node identifies the location of the bean in the object graph.
* </p>
* <p>
* It has to use a weak reference so as to ensure that it does not stop the
@@ -29,7 +29,7 @@ public final class NodeUsageCollector {
/**
* The properties used at this profile point.
*/
private final HashSet<String> used = new HashSet<String>();
private final Set<String> used = new LinkedHashSet<String>();
/**
* set to true if the bean is modified (setter called)
@@ -105,7 +105,7 @@ public final class NodeUsageCollector {
/**
* Return the set of used properties.
*/
public HashSet<String> getUsed() {
public Set<String> getUsed() {
return used;
}
@@ -9,7 +9,7 @@ public interface NodeUsageListener {
* Collect node usage "profiling" information.
* <p>
* This is the properties that are used for a given bean in the object graph.
* This information is used by autoFetch to tune queries.
* This information is used by autoTune to tune queries.
* </p>
*/
void collectNodeUsage(NodeUsageCollector collector);
@@ -9,7 +9,7 @@ import java.io.Serializable;
* The call stack is included so that the query can have different tuned fetches
* for each unique call stack. For example, a query to fetch a customer could be
* called by three different methods and each can be treated as a separate
* origin point (and autoFetch can tune each one separately).
* origin point (and autoTune can tune each one separately).
* </p>
*/
public final class ObjectGraphOrigin implements Serializable {
@@ -147,8 +147,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
public Collection<E> getActualDetails() {
return map.values();
}
/**
* Returns the map entrySet.
* <p>
@@ -158,7 +157,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
*/
public Collection<?> getActualEntries() {
return map.entrySet();
}
public String toString() {
@@ -0,0 +1,218 @@
package com.avaje.ebean.config;
/**
* Defines the AutoTune behaviour for a EbeanServer.
*/
public class AutoTuneConfig {
private AutoTuneMode mode = AutoTuneMode.DEFAULT_ON;
private String queryTuningFile = "ebean-autotune.xml";
private boolean queryTuning;
private boolean queryTuningAddVersion;
private boolean profiling;
private String profilingFile = "ebean-profiling";
private int profilingBase = 5;
private double profilingRate = 0.01;
private int garbageCollectionWait = 100;
private boolean skipCollectionOnShutdown;
public AutoTuneConfig() {
}
/**
* Return the name of the file that holds the query tuning information.
*/
public String getQueryTuningFile() {
return queryTuningFile;
}
/**
* Set the name of the file that holds the query tuning information.
*/
public void setQueryTuningFile(String queryTuningFile) {
this.queryTuningFile = queryTuningFile;
}
/**
* Return the name of the file that profiling information is written to.
*/
public String getProfilingFile() {
return profilingFile;
}
/**
* Set the name of the file that profiling information is written to.
*/
public void setProfilingFile(String profilingFile) {
this.profilingFile = profilingFile;
}
/**
* Return the mode used when autoTune has not been explicit defined on a
* query.
*/
public AutoTuneMode getMode() {
return mode;
}
/**
* Set the mode used when autoTune has not been explicit defined on a query.
*/
public void setMode(AutoTuneMode mode) {
this.mode = mode;
}
/**
* Return true if the queries are being tuned.
*/
public boolean isQueryTuning() {
return queryTuning;
}
/**
* Set to true if the queries should be tuned by autoTune.
*/
public void setQueryTuning(boolean queryTuning) {
this.queryTuning = queryTuning;
}
/**
* Return true if the version property should be added when the query is
* tuned.
* <p>
* If this is false then the version property will be added when profiling
* detects that the bean is possibly going to be modified.
* </p>
*/
public boolean isQueryTuningAddVersion() {
return queryTuningAddVersion;
}
/**
* Set to true to force the version property to be always added by the query
* tuning.
* <p>
* If this is false then the version property will be added when profiling
* detects that the bean is possibly going to be modified.
* </p>
* <p>
* Generally this is not expected to be turned on.
* </p>
*/
public void setQueryTuningAddVersion(boolean queryTuningAddVersion) {
this.queryTuningAddVersion = queryTuningAddVersion;
}
/**
* Return true if profiling information should be collected.
*/
public boolean isProfiling() {
return profiling;
}
/**
* Set to true if profiling information should be collected.
* <p>
* The profiling information is collected and then used to generate the tuned
* queries for autoTune.
* </p>
*/
public void setProfiling(boolean profiling) {
this.profiling = profiling;
}
/**
* Return the base number of queries to profile before changing to profile
* only a percentage of following queries (profileRate).
*/
public int getProfilingBase() {
return profilingBase;
}
/**
* Set the based number of queries to profile.
*/
public void setProfilingBase(int profilingBase) {
this.profilingBase = profilingBase;
}
/**
* Return the rate (%) of queries to be profiled after the 'base' amount of
* profiling.
*/
public double getProfilingRate() {
return profilingRate;
}
/**
* Set the rate (%) of queries to be profiled after the 'base' amount of
* profiling.
*/
public void setProfilingRate(double profilingRate) {
this.profilingRate = profilingRate;
}
/**
* Return the time in millis to wait after a system gc to collect profiling
* information.
* <p>
* The profiling information is collected on object finalise. As such we
* generally don't want to trigger GC (let the JVM do its thing) but on
* shutdown the autoTune manager will trigger System.gc() and then wait
* (default 100 millis) to hopefully collect profiling information -
* especially for short run unit tests.
* </p>
*/
public int getGarbageCollectionWait() {
return garbageCollectionWait;
}
/**
* Set the time in millis to wait after a System.gc() to collect profiling information.
*/
public void setGarbageCollectionWait(int garbageCollectionWait) {
this.garbageCollectionWait = garbageCollectionWait;
}
/**
* Return true if profiling collection should be skipped on shutdown.
*/
public boolean isSkipCollectionOnShutdown() {
return skipCollectionOnShutdown;
}
/**
* Set to true if profiling collection should be skipped on shutdown.
*/
public void setSkipCollectionOnShutdown(boolean skipCollectionOnShutdown) {
this.skipCollectionOnShutdown = skipCollectionOnShutdown;
}
/**
* Load the settings from the properties file.
*/
public void loadSettings(PropertiesWrapper p) {
queryTuning = p.getBoolean("autoTune.queryTuning", queryTuning);
queryTuningAddVersion = p.getBoolean("autoTune.queryTuningAddVersion", queryTuningAddVersion);
queryTuningFile = p.get("autoTune.queryTuningFile", queryTuningFile);
skipCollectionOnShutdown = p.getBoolean("autoTune.skipCollectionOnShutdown", skipCollectionOnShutdown);
mode = p.getEnum(AutoTuneMode.class, "autoTune.mode", mode);
profiling = p.getBoolean("autoTune.profiling", profiling);
profilingBase = p.getInt("autoTune.profilingBase", profilingBase);
profilingRate = p.getDouble("autoTune.profilingRate", profilingRate);
profilingFile = p.get("autoTune.profilingFile", profilingFile);
}
}
@@ -0,0 +1,32 @@
package com.avaje.ebean.config;
import com.avaje.ebean.Query;
/**
* The mode for determining if AutoTune will be used for a given query when
* {@link Query#setAutoTune(boolean)} has not been explicitly set on a query.
* <p>
* The explicit control of {@link Query#setAutoTune(boolean)} will always take
* precedence. This mode is used when this has not been explicitly set on a
* query.
* </p>
*/
public enum AutoTuneMode {
/**
* Don't implicitly use AutoTune. Must explicitly turn it on.
*/
DEFAULT_OFF,
/**
* Use AutoTune implicitly. Must explicitly turn it off.
*/
DEFAULT_ON,
/**
* Implicitly use AutoTune if the query has not got either select() or join()
* defined.
*/
DEFAULT_ONIFEMPTY
}
@@ -1,245 +0,0 @@
package com.avaje.ebean.config;
/**
* Defines the Autofetch behaviour for a EbeanServer.
*/
public class AutofetchConfig {
private AutofetchMode mode = AutofetchMode.DEFAULT_ONIFEMPTY;
private boolean queryTuning = false;
private boolean queryTuningAddVersion = false;
private boolean profiling = false;
private int profilingMin = 1;
private int profilingBase = 10;
private double profilingRate = 0.05;
private String logDirectory;
private int profileUpdateFrequency = 60;
private int garbageCollectionWait = 100;
private boolean garbageCollectionOnShutdown;
public AutofetchConfig() {
}
/**
* Return the mode used when autofetch has not been explicit defined on a
* query.
*/
public AutofetchMode getMode() {
return mode;
}
/**
* Set the mode used when autofetch has not been explicit defined on a query.
*/
public void setMode(AutofetchMode mode) {
this.mode = mode;
}
/**
* Return true if the queries are being tuned.
*/
public boolean isQueryTuning() {
return queryTuning;
}
/**
* Set to true if the queries should be tuned by autofetch.
*/
public void setQueryTuning(boolean queryTuning) {
this.queryTuning = queryTuning;
}
/**
* Return true if the version property should be added when the query is
* tuned.
* <p>
* If this is false then the version property will be added when profiling
* detects that the bean is possibly going to be modified.
* </p>
*/
public boolean isQueryTuningAddVersion() {
return queryTuningAddVersion;
}
/**
* Set to true to force the version property to be always added by the query
* tuning.
* <p>
* If this is false then the version property will be added when profiling
* detects that the bean is possibly going to be modified.
* </p>
*/
public void setQueryTuningAddVersion(boolean queryTuningAddVersion) {
this.queryTuningAddVersion = queryTuningAddVersion;
}
/**
* Return true if profiling information should be collected.
*/
public boolean isProfiling() {
return profiling;
}
/**
* Set to true if profiling information should be collected.
* <p>
* The profiling information is collected and then used to generate the tuned
* queries for autofetch.
* </p>
*/
public void setProfiling(boolean profiling) {
this.profiling = profiling;
}
/**
* Return the minimum number of queries to profile before autofetch will start
* tuning the queries.
*/
public int getProfilingMin() {
return profilingMin;
}
/**
* Set the minimum number of queries to profile before autofetch will start
* tuning the queries.
*/
public void setProfilingMin(int profilingMin) {
this.profilingMin = profilingMin;
}
/**
* Return the base number of queries to profile before changing to profile
* only a percentage of following queries (profileRate).
*/
public int getProfilingBase() {
return profilingBase;
}
/**
* Set the based number of queries to profile.
*/
public void setProfilingBase(int profilingBase) {
this.profilingBase = profilingBase;
}
/**
* Return the rate (%) of queries to be profiled after the 'base' amount of
* profiling.
*/
public double getProfilingRate() {
return profilingRate;
}
/**
* Set the rate (%) of queries to be profiled after the 'base' amount of
* profiling.
*/
public void setProfilingRate(double profilingRate) {
this.profilingRate = profilingRate;
}
/**
* Return the log directory to put the autofetch log.
*/
public String getLogDirectory() {
return logDirectory;
}
/**
* Set the directory to put the autofetch log in.
*/
public void setLogDirectory(String logDirectory) {
this.logDirectory = logDirectory;
}
/**
* Return the frequency in seconds to update the autofetch tuned queries from
* the profiled information.
*/
public int getProfileUpdateFrequency() {
return profileUpdateFrequency;
}
/**
* Set the frequency in seconds to update the autofetch tuned queries from the
* profiled information.
*/
public void setProfileUpdateFrequency(int profileUpdateFrequency) {
this.profileUpdateFrequency = profileUpdateFrequency;
}
/**
* Return the time in millis to wait after a system gc to collect profiling
* information.
* <p>
* The profiling information is collected on object finalise. As such we
* generally don't want to trigger GC (let the JVM do its thing) but on
* shutdown the autofetch manager will trigger System.gc() and then wait
* (default 100 millis) to hopefully collect profiling information -
* especially for short run unit tests.
* </p>
*/
public int getGarbageCollectionWait() {
return garbageCollectionWait;
}
/**
* Set the time in millis to wait after a System.gc() to collect profiling
* information.
*/
public void setGarbageCollectionWait(int garbageCollectionWait) {
this.garbageCollectionWait = garbageCollectionWait;
}
/**
* Return true if GC should be trigger on shutdown.
* <p>
* Autofetch profiling information is collected as part of garbage collection.
* </p>
*/
public boolean isGarbageCollectionOnShutdown() {
return garbageCollectionOnShutdown;
}
/**
* Set to true if you want GC to trigger on shutdown.
* <p>
* This would be done if you want to try and collect Autofetch profiling information
* on shutdown.
* </p>
*/
public void setGarbageCollectionOnShutdown(boolean garbageCollectionOnShutdown) {
this.garbageCollectionOnShutdown = garbageCollectionOnShutdown;
}
/**
* Load the settings from the properties file.
*/
public void loadSettings(PropertiesWrapper p) {
logDirectory = p.get("autofetch.logDirectory", logDirectory);
queryTuning = p.getBoolean("autofetch.querytuning", queryTuning);
queryTuningAddVersion = p.getBoolean("autofetch.queryTuningAddVersion", queryTuningAddVersion);
garbageCollectionOnShutdown = p.getBoolean("autofetch.garbageCollectionOnShutdown", garbageCollectionOnShutdown);
profiling = p.getBoolean("autofetch.profiling", profiling);
mode = p.getEnum(AutofetchMode.class, "autofetch.implicitmode", mode);
profilingMin = p.getInt("autofetch.profiling.min", profilingMin);
profilingBase = p.getInt("autofetch.profiling.base", profilingBase);
profilingRate = p.getDouble("autofetch.profiling.rate", profilingRate);
profileUpdateFrequency = p.getInt("autofetch.profiling.updatefrequency", profileUpdateFrequency);
}
}
@@ -1,32 +0,0 @@
package com.avaje.ebean.config;
import com.avaje.ebean.Query;
/**
* The mode for determining if Autofetch will be used for a given query when
* {@link Query#setAutofetch(boolean)} has not been explicitly set on a query.
* <p>
* The explicit control of {@link Query#setAutofetch(boolean)} will always take
* precedence. This mode is used when this has not been explicitly set on a
* query.
* </p>
*/
public enum AutofetchMode {
/**
* Don't implicitly use Autofetch. Must explicitly turn it on.
*/
DEFAULT_OFF,
/**
* Use Autofetch implicitly. Must explicitly turn it off.
*/
DEFAULT_ON,
/**
* Implicitly use Autofetch if the query has not got either select() or join()
* defined.
*/
DEFAULT_ONIFEMPTY
}
@@ -11,6 +11,8 @@ import com.avaje.ebean.event.*;
import com.avaje.ebean.event.changelog.ChangeLogListener;
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
import com.avaje.ebean.event.changelog.ChangeLogRegister;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.util.ClassUtil;
import com.fasterxml.jackson.core.JsonFactory;
@@ -128,9 +130,9 @@ public class ServerConfig {
private CurrentUserProvider currentUserProvider;
/**
* Config controlling the autofetch behaviour.
* Config controlling the AutoTune behaviour.
*/
private AutofetchConfig autofetchConfig = new AutofetchConfig();
private AutoTuneConfig autoTuneConfig = new AutoTuneConfig();
/**
* The JSON format used for DateTime types. Default to millis.
@@ -232,7 +234,12 @@ public class ServerConfig {
* Indicates that Ebean should use autoCommit friendly Transactions and TransactionManager.
*/
private boolean autoCommitMode;
/**
* Set to true if transaction begin should be started with explicit statement.
*/
private boolean explicitTransactionBeginMode;
/**
* The data source JNDI name if using a JNDI DataSource.
*/
@@ -299,6 +306,10 @@ public class ServerConfig {
private ChangeLogRegister changeLogRegister;
private ReadAuditLogger readAuditLogger;
private ReadAuditPrepare readAuditPrepare;
private EncryptKeyManager encryptKeyManager;
private EncryptDeployManager encryptDeployManager;
@@ -353,7 +364,6 @@ public class ServerConfig {
private int queryCacheMaxIdleTime = 600;
private int queryCacheMaxTimeToLive = 60*60*6;
private Object objectMapper;
private boolean diffFlatMode = true;
/**
* Set to true if you want eq("someProperty", null) to generate 1=1 rather than "is null" sql expression.
@@ -725,6 +735,41 @@ public class ServerConfig {
this.changeLogIncludeInserts = changeLogIncludeInserts;
}
/**
* Return the ReadAuditLogger to use.
*/
public ReadAuditLogger getReadAuditLogger() {
return readAuditLogger;
}
/**
* Set the ReadAuditLogger to use. If not set the default implementation is used
* which logs the read events in JSON format to a standard named SLF4J logger
* (which can be configured in say logback to log to a separate log file).
*/
public void setReadAuditLogger(ReadAuditLogger readAuditLogger) {
this.readAuditLogger = readAuditLogger;
}
/**
* Return the ReadAuditPrepare to use.
*/
public ReadAuditPrepare getReadAuditPrepare() {
return readAuditPrepare;
}
/**
* Set the ReadAuditPrepare to use.
* <p>
* It is expected that an implementation is used that read user context information
* (user id, user ip address etc) and sets it on the ReadEvent bean before it is sent
* to the ReadAuditLogger.
* </p>
*/
public void setReadAuditPrepare(ReadAuditPrepare readAuditPrepare) {
this.readAuditPrepare = readAuditPrepare;
}
/**
* Return the DB migration configuration.
*/
@@ -1104,17 +1149,17 @@ public class ServerConfig {
}
/**
* Return the configuration for the Autofetch feature.
* Return the configuration for AutoTune.
*/
public AutofetchConfig getAutofetchConfig() {
return autofetchConfig;
public AutoTuneConfig getAutoTuneConfig() {
return autoTuneConfig;
}
/**
* Set the configuration for the Autofetch feature.
* Set the configuration for AutoTune.
*/
public void setAutofetchConfig(AutofetchConfig autofetchConfig) {
this.autofetchConfig = autofetchConfig;
public void setAutoTuneConfig(AutoTuneConfig autoTuneConfig) {
this.autoTuneConfig = autoTuneConfig;
}
/**
@@ -1180,6 +1225,28 @@ public class ServerConfig {
this.autoCommitMode = autoCommitMode;
}
/**
* Return true if transaction begin should be started with explicit statement.
*/
public boolean isExplicitTransactionBeginMode() {
return explicitTransactionBeginMode;
}
/**
* Set to true if transaction begin should be started with explicit statement.
* <p>
* This works for H2 and Postgres but not for Oracle - only use this if you first name
* is Daryl or you have explicitly talked to Rob about this feature.
* </p>
* <p>
* This is generally not expected to be turned on but instead allow transactions to start
* implicitly which is generally the standard approach.
* </p>
*/
public void setExplicitTransactionBeginMode(boolean explicitTransactionBeginMode) {
this.explicitTransactionBeginMode = explicitTransactionBeginMode;
}
/**
* Return a value used to represent TRUE in the database.
* <p>
@@ -2002,8 +2069,8 @@ public class ServerConfig {
/**
* This is broken out for the same reason as above - preserve existing behaviour but let it be overridden.
*/
protected void loadAutofetchSettings(PropertiesWrapper p) {
autofetchConfig.loadSettings(p);
protected void loadAutoTuneSettings(PropertiesWrapper p) {
autoTuneConfig.loadSettings(p);
}
/**
@@ -2017,16 +2084,17 @@ public class ServerConfig {
if (namingConvention != null) {
namingConvention.loadFromProperties(p);
}
if (autofetchConfig == null) {
autofetchConfig = new AutofetchConfig();
if (autoTuneConfig == null) {
autoTuneConfig = new AutoTuneConfig();
}
loadAutofetchSettings(p);
loadAutoTuneSettings(p);
if (dataSourceConfig == null) {
dataSourceConfig = new DataSourceConfig();
}
loadDataSourceSettings(p);
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
@@ -2075,7 +2143,7 @@ public class ServerConfig {
changeLogIncludeInserts = p.getBoolean("changeLogIncludeInserts", changeLogIncludeInserts);
expressionEqualsWithNullAsNoop = p.getBoolean("expressionEqualsWithNullAsNoop", expressionEqualsWithNullAsNoop);
diffFlatMode = p.getBoolean("diffFlatMode", diffFlatMode);
asOfViewSuffix = p.get("asOfViewSuffix", asOfViewSuffix);
asOfSysPeriod = p.get("asOfSysPeriod", asOfSysPeriod);
historyTableSuffix = p.get("historyTableSuffix", historyTableSuffix);
@@ -2188,22 +2256,6 @@ public class ServerConfig {
this.objectMapper = objectMapper;
}
/**
* Return true if diff should return flat properties with dot notation rather than
* embedded beans or reference beans (when the associated bean id is different).
*/
public boolean isDiffFlatMode() {
return diffFlatMode;
}
/**
* Set to true if diff should return flat properties with dot notation rather than
* embedded beans or reference beans (when the associated bean id is different).
*/
public void setDiffFlatMode(boolean diffFlatMode) {
this.diffFlatMode = diffFlatMode;
}
/**
* Return true if eq("someProperty", null) should to generate "1=1" rather than "is null" sql expression.
*/
@@ -55,7 +55,7 @@ public abstract class DbViewHistorySupport implements DbHistorySupport {
public String getAsOfPredicate(String asOfTableAlias, String asOfSysPeriod) {
// (sys_period_start < ? and (sys_period_end is null or sys_period_end > ?));
return "(" + asOfTableAlias + "." + asOfSysPeriod + "_start" + " < ? and (" + asOfTableAlias + "." + asOfSysPeriod + "_end" + " is null or " + asOfTableAlias + "." + asOfSysPeriod + "_end" + " > ?))";
return "(" + asOfTableAlias + "." + asOfSysPeriod + "_start" + " <= ? and (" + asOfTableAlias + "." + asOfSysPeriod + "_end" + " is null or " + asOfTableAlias + "." + asOfSysPeriod + "_end" + " > ?))";
}
/**
@@ -0,0 +1,8 @@
package com.avaje.ebean.config.dbplatform;
/**
* Runtime support for @History with H2.
*/
public class H2HistorySupport extends DbViewHistorySupport {
}
@@ -0,0 +1,127 @@
package com.avaje.ebean.config.dbplatform;
import org.h2.api.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* H2 database trigger used to populate history tables to support the @History feature.
*/
public class H2HistoryTrigger implements Trigger {
private static final Logger logger = LoggerFactory.getLogger(H2HistoryTrigger.class);
/**
* Hardcoding the column and history table suffix for now. Not sure how to get that
* configuration into the trigger instance nicely as it is instantiated by H2.
*/
private static final String SYS_PERIOD_START = "SYS_PERIOD_START";
private static final String SYS_PERIOD_END = "SYS_PERIOD_END";
private static final String HISTORY_SUFFIX = "_history";
/**
* SQL to insert into the history table.
*/
private String insertHistorySql;
/**
* Position of SYS_PERIOD_START column in the Object[].
*/
private int effectStartPosition;
/**
* Position of SYS_PERIOD_END column in the Object[].
*/
private int effectEndPosition;
@Override
public void init(Connection conn, String schemaName, String triggerName, String tableName, boolean before, int type) throws SQLException {
// get the columns for the table
ResultSet rs = conn.getMetaData().getColumns(null, schemaName, tableName, null);
// build the insert into history table SQL
StringBuilder insertSql = new StringBuilder(150);
insertSql.append("insert into ").append(tableName).append(HISTORY_SUFFIX).append(" (");
int count = 0;
List<String> columns = new ArrayList<String>();
while (rs.next()) {
if (++count > 1) {
insertSql.append(",");
}
String columnName = rs.getString("COLUMN_NAME");
if (columnName.equalsIgnoreCase(SYS_PERIOD_START)) {
this.effectStartPosition = count - 1;
} else if (columnName.equalsIgnoreCase(SYS_PERIOD_END)) {
this.effectEndPosition = count - 1;
}
insertSql.append(columnName);
columns.add(columnName);
}
insertSql.append(") values (");
for (int i = 0; i < count; i++) {
if (i > 0) {
insertSql.append(",");
}
insertSql.append("?");
}
insertSql.append(");");
this.insertHistorySql = insertSql.toString();
logger.debug("History table insert sql: {}", insertHistorySql);
}
@Override
public void fire(Connection connection, Object[] oldRow, Object[] newRow) throws SQLException {
if (oldRow != null) {
// a delete or update event
Timestamp now = new Timestamp(System.currentTimeMillis());
oldRow[effectEndPosition] = now;
if (newRow != null) {
// update event. Set the effective start timestamp to now.
newRow[effectStartPosition] = now;
}
if (logger.isDebugEnabled()) {
logger.debug("History insert: {}", Arrays.toString(oldRow));
}
insertIntoHistory(connection, oldRow);
}
}
/**
* Insert the data into the history table.
*/
private void insertIntoHistory(Connection connection, Object[] oldRow) throws SQLException {
PreparedStatement stmt = connection.prepareStatement(insertHistorySql);
try {
for (int i = 0; i < oldRow.length; i++) {
stmt.setObject(i + 1, oldRow[i]);
}
stmt.executeUpdate();
} finally {
stmt.close();
}
}
@Override
public void close() throws SQLException {
}
@Override
public void remove() throws SQLException {
}
}
@@ -15,6 +15,7 @@ public class H2Platform extends DatabasePlatform {
this.name = "h2";
this.dbEncrypt = new H2DbEncrypt();
this.platformDdl = new H2Ddl(this.dbTypeMap, dbIdentity);
this.historySupport = new H2HistorySupport();
// only support getGeneratedKeys with non-batch JDBC
// so generally use SEQUENCE instead of IDENTITY for H2
@@ -5,5 +5,4 @@ package com.avaje.ebean.config.dbplatform;
*/
public class MySqlHistorySupport extends DbViewHistorySupport {
}
@@ -263,6 +263,74 @@ public class DdlGenerator implements SpiEbeanPlugin {
}
}
/**
* Local utility used to detect the end of statements / separate statements.
* This is often just the semicolon character but for trigger/procedures this
* detects the $$ demarcation used in the history DDL generation for MySql and
* Postgres.
*/
static class StatementsSeparator {
ArrayList<String> statements = new ArrayList<String>();
boolean trimDelimiter;
boolean inDbProcedure;
StringBuilder sb = new StringBuilder();
void lineContainsDollars(String line) {
if (inDbProcedure) {
if (trimDelimiter) {
line = line.replace("$$","");
}
endOfStatement(line);
} else {
// MySql style delimiter needs to be trimmed/removed
trimDelimiter = line.equals("delimiter $$");
if (!trimDelimiter) {
sb.append(line).append(" ");
}
}
inDbProcedure = !inDbProcedure;
}
void endOfStatement(String line) {
// end of Db procedure
sb.append(line);
statements.add(sb.toString().trim());
sb = new StringBuilder();
}
void nextLine(String line) {
if (line.contains("$$")) {
lineContainsDollars(line);
return;
}
if (inDbProcedure) {
sb.append(line).append(" ");
return;
}
int semiPos = line.indexOf(';');
if (semiPos == -1) {
sb.append(line).append(" ");
} else if (semiPos == line.length() - 1) {
// semicolon at end of line
endOfStatement(line);
} else {
// semicolon in middle of line
String preSemi = line.substring(0, semiPos);
endOfStatement(preSemi);
sb.append(line.substring(semiPos + 1));
}
}
}
/**
* Break up the sql in reader into a list of statements using the semi-colon
* character;
@@ -271,35 +339,16 @@ public class DdlGenerator implements SpiEbeanPlugin {
try {
BufferedReader br = new BufferedReader(reader);
StatementsSeparator statements = new StatementsSeparator();
ArrayList<String> statements = new ArrayList<String>();
StringBuilder sb = new StringBuilder();
String s;
while ((s = br.readLine()) != null) {
s = s.trim();
int semiPos = s.indexOf(';');
if (semiPos == -1) {
sb.append(s).append(" ");
} else if (semiPos == s.length() - 1) {
// semicolon at end of line
sb.append(s);
statements.add(sb.toString().trim());
sb = new StringBuilder();
} else {
// semicolon in middle of line
String preSemi = s.substring(0, semiPos);
sb.append(preSemi);
statements.add(sb.toString().trim());
sb = new StringBuilder();
sb.append(s.substring(semiPos + 1));
}
statements.nextLine(s);
}
return statements;
return statements.statements;
} catch (IOException e) {
throw new PersistenceException(e);
}
@@ -58,6 +58,7 @@ public class BaseDdlBuffer implements DdlBuffer {
append(" ");
}
}
append(" ");
}
@Override
@@ -324,14 +324,13 @@ public class BaseTableDdl implements TableDdl {
protected void writeCompoundUniqueConstraints(DdlBuffer apply, CreateTable createTable) throws IOException {
String tableName = createTable.getName();
List<UniqueConstraint> uniqueConstraints = createTable.getUniqueConstraint();
for (UniqueConstraint uniqueConstraint : uniqueConstraints) {
String uqName = uniqueConstraint.getName();
String[] columns = toColumnNamesSplit(uniqueConstraint.getColumnNames());
apply
.append(platformDdl.alterTableAddUniqueConstraint(tableName, uniqueConstraint.getName(), columns))
.endOfStatement();
apply.append(",").newLine();
apply.append(" constraint ").append(uqName).append(" unique");
appendColumns(columns, apply);
}
}
@@ -176,7 +176,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
String platformType = platformDdl.convert(type, false);
buffer.append(" ");
buffer.append(platformDdl.lowerName(columnName), 30);
buffer.append(platformDdl.lowerName(columnName), 29);
buffer.append(platformType);
}
@@ -10,6 +10,7 @@ public class H2Ddl extends PlatformDdl {
public H2Ddl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
super(platformTypes, dbIdentity);
this.historyDdl = new H2HistoryDdl();
}
}
@@ -0,0 +1,73 @@
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
import com.avaje.ebean.config.dbplatform.H2HistoryTrigger;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
import com.avaje.ebean.dbmigration.model.MTable;
import java.io.IOException;
import java.util.List;
/**
* H2 history support using DB triggers to maintain a history table.
*/
public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
private static final String TRIGGER_CLASS = H2HistoryTrigger.class.getName();
public H2HistoryDdl() {
}
@Override
protected void dropTriggers(DdlBuffer buffer, String baseTable) throws IOException {
buffer.append("drop trigger ").append(updateTriggerName(baseTable)).endOfStatement();
}
@Override
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
String baseTableName = table.getName();
String historyTableName = historyTableName(baseTableName);
List<String> includedColumns = includedColumnNames(table);
DdlBuffer apply = writer.applyHistory();
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
}
@Override
protected void regenerateHistoryTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
String baseTableName = table.getName();
String historyTableName = historyTableName(baseTableName);
List<String> includedColumns = includedColumnNames(table);
DdlBuffer apply = writer.applyHistory();
apply.append("-- Regenerated ").newLine();
apply.append("-- changes: ").append(update.description()).newLine();
dropTriggers(apply, baseTableName);
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
// put a reverted version into the rollback buffer
update.toRevertedColumns(includedColumns);
DdlBuffer rollback = writer.rollback();
rollback.append("-- Revert regenerated ").newLine();
rollback.append("-- revert changes: ").append(update.description()).newLine();
dropTriggers(rollback, baseTableName);
addCreateTrigger(rollback, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
}
private void addCreateTrigger(DdlBuffer apply, String triggerName, String baseTable, String historyTable, List<String> includedColumns) throws IOException {
// Note that this does not take into account the historyTable name (excepts _history suffix) and
// does not take into account excluded columns (all columns included in history)
apply
.append("create trigger ").append(triggerName).append(" before update,delete on ").append(baseTable)
.append(" for each row call \"" + TRIGGER_CLASS + "\";").newLine();
}
}
@@ -136,7 +136,7 @@ public class PlatformDdl {
String platformType = convert(column.getType(), identityColumn);
buffer.append(" ");
buffer.append(lowerName(column.getName()), 30);
buffer.append(lowerName(column.getName()), 29);
buffer.append(platformType);
if (isTrue(column.isNotnull()) || isTrue(column.isPrimaryKey())) {
buffer.append(" not null");
@@ -40,7 +40,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
.endOfStatement();
if (whenCreatedColumn != null) {
apply.append("update table ").append(baseTableName).append(" set ")
apply.append("update ").append(baseTableName).append(" set ")
.append(sysPeriod).append(" = tstzrange(").append(whenCreatedColumn).append(", null)").endOfStatement();
}
}
@@ -26,6 +26,17 @@ public class MIndex {
this.columns.add(columnName);
}
/**
* Create a multi column non unique index.
*/
public MIndex(String indexName, String tableName, String[] columnNames) {
this.tableName = tableName;
this.indexName = indexName;
for (int i = 0; i < columnNames.length; i++) {
this.columns.add(columnNames[i]);
}
}
public MIndex(CreateIndex createIndex) {
this.indexName = createIndex.getIndexName();
this.tableName = createIndex.getTableName();
@@ -9,6 +9,7 @@ import com.avaje.ebean.dbmigration.migration.DropColumn;
import com.avaje.ebean.dbmigration.migration.DropHistoryTable;
import com.avaje.ebean.dbmigration.migration.DropTable;
import com.avaje.ebean.dbmigration.migration.IdentityType;
import com.avaje.ebean.dbmigration.migration.UniqueConstraint;
import java.math.BigInteger;
import java.util.ArrayList;
@@ -169,6 +170,21 @@ public class MTable {
createTable.getForeignKey().add(compoundKey.createForeignKey());
}
for (MCompoundUniqueConstraint constraint : compoundUniqueConstraints) {
UniqueConstraint uq = new UniqueConstraint();
uq.setName(constraint.getName());
String[] columns = constraint.getColumns();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < columns.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(columns[i]);
}
uq.setColumnNames(sb.toString());
createTable.getUniqueConstraint().add(uq);
}
return createTable;
}
@@ -224,4 +224,12 @@ public class ModelContainer {
indexes.put(indexName, new MIndex(indexName, tableName, columnName));
}
/**
* Add a multi column index.
*/
public void addIndex(String indexName, String tableName, String[] columnNames) {
indexes.put(indexName, new MIndex(indexName, tableName, columnNames));
}
}
@@ -75,6 +75,10 @@ public class ModelBuildContext {
model.addIndex(indexName, tableName, columnName);
}
public void addIndex(String indexName, String tableName, String[] columnNames) {
model.addIndex(indexName, tableName, columnNames);
}
private String maxLength(String constraintName, int indexCount) {
return maxLength.maxLength(constraintName, indexCount);
}
@@ -9,7 +9,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound;
import com.avaje.ebeaninternal.server.deploy.CompoundUniqueContraint;
import com.avaje.ebeaninternal.server.deploy.CompoundUniqueConstraint;
import com.avaje.ebeaninternal.server.deploy.TableJoinColumn;
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
@@ -36,24 +36,39 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
private int countCheck;
public ModelBuildPropertyVisitor(ModelBuildContext ctx, MTable table, CompoundUniqueContraint[] compoundUniqueConstraints) {
public ModelBuildPropertyVisitor(ModelBuildContext ctx, MTable table, CompoundUniqueConstraint[] constraints) {
this.ctx = ctx;
this.table = table;
addCompoundUniqueConstraint(compoundUniqueConstraints);
addCompoundUniqueConstraint(constraints);
}
/**
* Add unique constraints defined via JPA UniqueConstraint annotations.
*/
private void addCompoundUniqueConstraint(CompoundUniqueContraint[] compoundUniqueConstraints) {
private void addCompoundUniqueConstraint(CompoundUniqueConstraint[] constraints) {
if (compoundUniqueConstraints != null) {
for (int i = 0; i < compoundUniqueConstraints.length; i++) {
String[] columns = compoundUniqueConstraints[i].getColumns();
String uqName = determineUniqueConstraintName(columns);
table.addCompoundUniqueConstraint(columns, false, uqName);
if (constraints != null) {
for (int i = 0; i < constraints.length; i++) {
CompoundUniqueConstraint constraint = constraints[i];
String[] columns = constraint.getColumns();
indexSet.add(columns);
if (constraint.isUnique()) {
String uqName = constraint.getName();
if (uqName == null || uqName.trim().isEmpty()) {
uqName = determineUniqueConstraintName(columns);
}
table.addCompoundUniqueConstraint(columns, false, uqName);
} else {
// 'just' an index (not a unique constraint)
String idxName = constraint.getName();
if (idxName == null || idxName.trim().isEmpty()) {
idxName = determineIndexName(columns);
}
ctx.addIndex(idxName, table.getName(), columns);
}
}
}
}
@@ -51,6 +51,10 @@ public class BeanChange {
public BeanChange() {
}
public String toString() {
return "table:" + table + " id:" + id+" values:"+values;
}
/**
* Return the object type (typically table name).
*/
@@ -0,0 +1,37 @@
package com.avaje.ebean.event.readaudit;
/**
* Log that the query was executed
*/
public interface ReadAuditLogger {
/**
* Called when a new query plan is created.
* <p>
* The query plan has the full sql and logging the query plan separately means that each of
* the bean and many read events can log the query plan key and not the full sql (reducing the
* bulk size of the read audit logs).
* </p>
*/
void queryPlan(ReadAuditQueryPlan queryPlan);
/**
* Audit a find bean query that returned a bean.
* <p>
* Finds that did not return a bean are excluded.
* </p>
*/
void auditBean(ReadEvent readBean);
/**
* Audit a find many query that returned some beans.
* <p>
* Finds that did not return any beans are excluded.
* </p>
* <p>
* For large queries executed via findEach() etc the ids are collected in batches
* and logged. Hence the ids list has a maximum size of the batch size.
* </p>
*/
void auditMany(ReadEvent readMany);
}
@@ -0,0 +1,23 @@
package com.avaje.ebean.event.readaudit;
/**
* Set user context information into the read event prior to it being logged.
*/
public interface ReadAuditPrepare {
/**
* Prepare the read event by setting any user context information into the read event such as the
* application user id and ip address.
* <p>
* This method is called prior to the read event being sent to the ReadAuditLogger.
* </p>
* <p>
* Note that for findFutureList() queries prepare() is called early in the foreground thread
* prior to the query executing and at that point the ReadEvent bean only has the bean type
* and no other details (which are populated later when the query is executed in the background
* thread).
* </p>
*/
void prepare(ReadEvent readEvent);
}
@@ -0,0 +1,77 @@
package com.avaje.ebean.event.readaudit;
/**
* A SQL query and associated keys.
* <p>
* This is logged as a separate event so that the
* </p>
*/
public class ReadAuditQueryPlan {
String beanType;
String queryKey;
String sql;
/**
* Construct given the beanType, queryKey and sql.
*/
public ReadAuditQueryPlan(String beanType, String queryKey, String sql) {
this.beanType = beanType;
this.queryKey = queryKey;
this.sql = sql;
}
/**
* Construct for JSON tools.
*/
public ReadAuditQueryPlan() {
}
public String toString() {
return "beanType:" + beanType + " queryKey:" + queryKey + " sql:" + sql;
}
/**
* Return the bean type.
*/
public String getBeanType() {
return beanType;
}
/**
* Set the bean type.
*/
public void setBeanType(String beanType) {
this.beanType = beanType;
}
/**
* Return the query key (relative to the bean type).
*/
public String getQueryKey() {
return queryKey;
}
/**
* Set the query key.
*/
public void setQueryKey(String queryKey) {
this.queryKey = queryKey;
}
/**
* Return the sql statement.
*/
public String getSql() {
return sql;
}
/**
* Set the sql statement.
*/
public void setSql(String sql) {
this.sql = sql;
}
}
@@ -0,0 +1,261 @@
package com.avaje.ebean.event.readaudit;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Read event sent to the ReadEventLogger.
* <p>
* This is a flattened in that it contains either a read bean or list of beans. It is flattened
* in this way to simplify logging and processing and simply means that it either contains an
* id or a list of ids.
* </p>
*/
public class ReadEvent {
/**
* User defined 'source' such as the application name.
*/
protected String source;
/**
* Application user id expected to be optionally populated by ChangeLogPrepare.
*/
protected String userId;
/**
* Application user ip address expected to be optionally populated by ChangeLogPrepare.
*/
protected String userIpAddress;
/**
* Arbitrary user context information expected to be optionally populated by ChangeLogPrepare.
*/
protected Map<String, String> userContext;
/**
* The time the bean change was created.
*/
protected long eventTime;
/**
* The type of the bean(s) read.
*/
protected String beanType;
/**
* The query key (relative to the bean type).
*/
protected String queryKey;
/**
* The bind log when the query was executed.
*/
protected String bindLog;
/**
* The id of the bean read.
*/
protected Object id;
/**
* The ids of the beans read.
*/
protected List<Object> ids;
/**
* Common constructor for single bean and multi-bean read events.
*/
protected ReadEvent(String beanType, String queryKey, String bindLog) {
this.beanType = beanType;
this.queryKey = queryKey;
this.bindLog = bindLog;
this.eventTime = System.currentTimeMillis();
}
/**
* Construct for a single bean read.
*/
public ReadEvent(String beanType, String queryKey, String bindLog, Object id) {
this(beanType, queryKey, bindLog);
this.id = id;
}
/**
* Construct for many beans read.
*/
public ReadEvent(String beanType, String queryKey, String bindLog, List<Object> ids) {
this(beanType, queryKey, bindLog);
this.ids = ids;
}
/**
* Construct for many future list query.
*/
public ReadEvent(String beanType) {
this.beanType = beanType;
this.eventTime = System.currentTimeMillis();
}
/**
* Constructor for JSON tools.
*/
public ReadEvent() {
}
/**
* Return a code that identifies the source of the change (like the name of the application).
*/
public String getSource() {
return source;
}
/**
* Set the source of the change (like the name of the application).
*/
public void setSource(String source) {
this.source = source;
}
/**
* Return the application user Id.
*/
public String getUserId() {
return userId;
}
/**
* Set the application user Id.
* <p>
* This can be set by the ChangeLogListener in the prepare() method which is called
* in the foreground thread.
* </p>
*/
public void setUserId(String userId) {
this.userId = userId;
}
/**
* Return the application users ip address.
*/
public String getUserIpAddress() {
return userIpAddress;
}
/**
* Set the application users ip address.
* <p>
* This can be set by the ChangeLogListener in the prepare() method which is called
* in the foreground thread.
* </p>
*/
public void setUserIpAddress(String userIpAddress) {
this.userIpAddress = userIpAddress;
}
/**
* Return a user context value - anything you set yourself in ChangeLogListener prepare().
*/
public Map<String, String> getUserContext() {
if (userContext == null) {
userContext = new LinkedHashMap<String, String>();
}
return userContext;
}
/**
* Set a user context value (anything you like).
* <p>
* This can be set by the ChangeLogListener in the prepare() method which is called
* in the foreground thread.
* </p>
*/
public void setUserContext(Map<String, String> userContext) {
this.userContext = userContext;
}
/**
* Return the type of bean read.
*/
public String getBeanType() {
return beanType;
}
/**
* Set the type of bean read.
*/
public void setBeanType(String beanType) {
this.beanType = beanType;
}
/**
* Return the query key (relative to the bean type).
*/
public String getQueryKey() {
return queryKey;
}
/**
* Set the query key (relative to the bean type).
*/
public void setQueryKey(String queryKey) {
this.queryKey = queryKey;
}
/**
* Return the bind log used when executing the query.
*/
public String getBindLog() {
return bindLog;
}
/**
* Set the bind log used when executing the query.
*/
public void setBindLog(String bindLog) {
this.bindLog = bindLog;
}
/**
* Return the event date time.
*/
public long getEventTime() {
return eventTime;
}
/**
* Set the event date time.
*/
public void setEventTime(long eventTime) {
this.eventTime = eventTime;
}
/**
* Return the id of the bean read.
*/
public Object getId() {
return id;
}
/**
* Set the id of the bean read.
*/
public void setId(Object id) {
this.id = id;
}
/**
* Return the ids of the beans read.
*/
public List<Object> getIds() {
return ids;
}
/**
* Set the ids of the beans read.
*/
public void setIds(List<Object> ids) {
this.ids = ids;
}
}
@@ -15,9 +15,9 @@ public interface MetaQueryPlanStatistic {
Class<?> getBeanType();
/**
* Return true if this query plan was tuned by Autofetch.
* Return true if this query plan was tuned by AutoTune.
*/
boolean isAutofetchTuned();
boolean isAutoTuned();
/**
* Return a string representation of the query plan hash.
@@ -3,6 +3,7 @@ package com.avaje.ebean.text.json;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -36,6 +37,13 @@ public class EJson {
EJsonWriter.write(object, jsonGenerator);
}
/**
* Write the collection as json array to the jsonGenerator.
*/
public static void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
EJsonWriter.writeCollection(collection, jsonGenerator);
}
/**
* Parse the json and return as a Map additionally specifying if the returned map should
* be modify aware meaning that it can detect when it has been modified.
@@ -37,6 +37,10 @@ class EJsonWriter {
new EJsonWriter(jsonGenerator).writeJson(object);
}
static void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
new EJsonWriter(jsonGenerator).writeCollection(null, collection);
}
private final JsonGenerator jsonGenerator;
private EJsonWriter(JsonGenerator jsonGenerator) {
@@ -53,6 +57,12 @@ class EJsonWriter {
if (object == null) {
writeNull(name);
} else if (object instanceof Number) {
writeNumber(name, (Number) object);
} else if (object instanceof String) {
writeString(name, (String) object);
} else if (object instanceof Map) {
writeMap(name, (Map<Object, Object>) object);
@@ -62,15 +72,9 @@ class EJsonWriter {
} else if (object instanceof Boolean) {
writeBoolean(name, (Boolean) object);
} else if (object instanceof Number) {
writeNumber(name, (Number) object);
} else if (object instanceof Date) {
writeDate(name, (Date) object);
} else if (object instanceof String) {
writeString(name, (String) object);
} else if (object instanceof Map.Entry<?, ?>) {
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object;
writeJson(entry.getKey().toString(), entry.getValue());
@@ -20,7 +20,16 @@ public class HashQueryPlan {
public String toString() {
return planHash+":"+bindCount+(rawSql != null ? ":r" : "");
}
/**
* Return as a partial key. For rawSql hash the sql is part of the key and as such
* needs to be included in order to have a complete key. Typically the MD5 of the sql
* can be used as a shot form proxy for the actual sql.
*/
public String getPartialKey() {
return planHash+"_"+bindCount;
}
public int hashCode() {
int hc = planHash;
hc = hc * 31 + bindCount;
@@ -33,7 +33,7 @@ public interface LoadContext {
void registerSecondaryQueries(SpiQuery<?> query);
/**
* Return the node for a given path which is used by autofetch profiling.
* Return the node for a given path which is used by AutoTune profiling.
*/
ObjectGraphNode getObjectGraphNode(String path);
@@ -11,7 +11,8 @@ import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebean.dbmigration.DdlGenerator;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
@@ -54,7 +55,7 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
/**
* Create an object to represent the current CallStack.
* <p>
* Typically used to identify the origin of queries for Autofetch and object
* Typically used to identify the origin of queries for AutoTune and object
* graph costing.
* </p>
*/
@@ -70,11 +71,6 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
DdlGenerator getDdlGenerator();
/**
* Return the AutoFetchListener.
*/
AutoFetchManager getAutoFetchManager();
/**
* Clear the query execution statistics.
*/
@@ -195,4 +191,14 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
void collectQueryStats(ObjectGraphNode objectGraphNode, long loadedBeanCount, long timeMicros);
/**
* Return the ReadAuditLogger to use for logging all read audit events.
*/
ReadAuditLogger getReadAuditLogger();
/**
* Return the ReadAuditPrepare used to populate the read audit events with
* user context information (user id, user ip address etc).
*/
ReadAuditPrepare getReadAuditPrepare();
}
@@ -19,13 +19,13 @@ public interface SpiExpression extends Expression {
void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins);
/**
* Calculate a hash value used to identify a query for AutoFetch tuning.
* Calculate a hash value used to identify a query for AutoTune tuning.
* <p>
* That is, if the hash changes then the query will be considered different
* from an AutoFetch perspective and get different tuning.
* from an AutoTune perspective and get different tuning.
* </p>
*/
void queryAutoFetchHash(HashQueryPlanBuilder builder);
void queryAutoTuneHash(HashQueryPlanBuilder builder);
/**
* Calculate a hash value for the expression.
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.api;
import java.util.ArrayList;
import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
@@ -10,6 +11,11 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
*/
public interface SpiExpressionRequest {
/**
* Return the DB specific JSON expression handler.
*/
JsonExpressionHandler getJsonHander();
/**
* Parse the logical property name to the deployment name.
*/
@@ -10,7 +10,8 @@ import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebeaninternal.server.autotune.ProfilingListener;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.TableJoin;
@@ -84,7 +85,12 @@ public interface SpiQuery<T> extends Query<T> {
/**
* A subquery used as part of a where clause.
*/
SUBQUERY
SUBQUERY,
/**
* Delete query.
*/
DELETE,
}
enum TemporalMode {
@@ -272,6 +278,11 @@ public interface SpiQuery<T> extends Query<T> {
*/
NaturalKeyBindParam getNaturalKeyBindParam();
/**
* Set the query to be a delete query.
*/
void setDelete();
/**
* Set the query to select the id property only.
*/
@@ -334,10 +345,10 @@ public interface SpiQuery<T> extends Query<T> {
boolean isDetailEmpty();
/**
* Return explicit autoFetch setting or null. If null then not explicitly
* Return explicit AutoTune setting or null. If null then not explicitly
* set so we use the default behaviour.
*/
Boolean isAutofetch();
Boolean isAutoTune();
/**
* Set to true if you want to capture executed secondary queries.
@@ -360,16 +371,16 @@ public interface SpiQuery<T> extends Query<T> {
void logSecondaryQuery(SpiQuery<?> query);
/**
* If return null then no autoFetch profiling for this query. If a
* AutoFetchManager is returned this implies that profiling is turned on for
* this query (and all the objects this query creates).
* If return null then no profiling for this query. If a ProfilingListener is
* returned this implies that profiling is turned on for this query (and all
* the objects this query creates).
*/
AutoFetchManager getAutoFetchManager();
ProfilingListener getProfilingListener();
/**
* This has the effect of turning on autoFetch profiling for this query.
* This has the effect of turning on profiling for this query.
*/
void setAutoFetchManager(AutoFetchManager manager);
void setProfilingListener(ProfilingListener manager);
/**
* Return the origin point for the query.
@@ -412,14 +423,14 @@ public interface SpiQuery<T> extends Query<T> {
/**
* Return false when this is a lazy load or refresh query for a bean.
* <p>
* We just take/copy the data from those beans and don't collect autoFetch
* We just take/copy the data from those beans and don't collect AutoTune
* usage profiling on those lazy load or refresh beans.
* </p>
*/
boolean isUsageProfiling();
/**
* Set to false if this query should not be included in the autoFetch usage
* Set to false if this query should not be included in the AutoTune usage
* profiling information.
*/
void setUsageProfiling(boolean usageProfiling);
@@ -430,19 +441,19 @@ public interface SpiQuery<T> extends Query<T> {
String getName();
/**
* Calculate a hash used by AutoFetch to identify when a query has changed
* Calculate a hash used by AutoTune to identify when a query has changed
* (and hence potentially needs a new tuned query plan to be developed).
* <p>
* Excludes bind values and occurs prior to AutoFetch potentially
* Excludes bind values and occurs prior to AutoTune potentially
* tuning/modifying the query.
* </p>
*/
HashQueryPlan queryAutofetchHash(HashQueryPlanBuilder builder);
HashQueryPlan queryAutoTuneHash(HashQueryPlanBuilder builder);
/**
* Identifies queries that are the same bar the bind variables.
* <p>
* This is used AFTER AutoFetch has potentially tuned the query. This is
* This is used AFTER AutoTune has potentially tuned the query. This is
* used to identify and reused query plans (the final SQL string and
* associated SqlTree object).
* </p>
@@ -547,7 +558,7 @@ public interface SpiQuery<T> extends Query<T> {
String getQuery();
/**
* Replace the query detail. This is used by the autoFetch feature to as a
* Replace the query detail. This is used by the AutoTune feature to as a
* fast way to set the query properties and joins.
* <p>
* Note care must be taken to keep the where, orderBy, firstRows and maxRows
@@ -557,15 +568,15 @@ public interface SpiQuery<T> extends Query<T> {
void setDetail(OrmQueryDetail detail);
/**
* Autofetch tune the detail specifying properties to select on already defined joins
* AutoTune tune the detail specifying properties to select on already defined joins
* and adding extra joins where they are missing.
*/
boolean tuneFetchProperties(OrmQueryDetail detail);
/**
* Set to true if this query has been tuned by autoFetch.
* Set to true if this query has been tuned by autoTune.
*/
void setAutoFetchTuned(boolean autoFetchTuned);
void setAutoTuned(boolean autoTuned);
/**
* Return the query detail.
@@ -634,8 +645,13 @@ public interface SpiQuery<T> extends Query<T> {
int getBufferFetchSizeHint();
/**
* Return true if this is a query executing in the background.
* Return true if read auditing is disabled on this query.
*/
boolean isDisableReadAudit();
/**
* Return true if this is a query executing in the background.
*/
boolean isFutureFetch();
/**
@@ -644,6 +660,16 @@ public interface SpiQuery<T> extends Query<T> {
*/
void setFutureFetch(boolean futureFetch);
/**
* Set the readEvent for future queries (as prepared in foreground thread).
*/
void setFutureFetchAudit(ReadEvent event);
/**
* Read the readEvent for future queries (null otherwise).
*/
ReadEvent getFutureFetchAudit();
/**
* Set the underlying cancelable query (with the PreparedStatement).
*/
@@ -1,244 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.util.Iterator;
import com.avaje.ebean.Query;
import com.avaje.ebean.bean.NodeUsageListener;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.config.AutofetchMode;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
/**
* Collects and manages the the profile information.
* <p>
* The profile information is periodically converted into "tuned query details" -
* which is used to automatically tune the queries that use autoFetch.
* </p>
* <p>
* The "tuned query details" effectively are part of the query that has the
* select() and join() information (but not the where clause, order by, limits
* etc). These are applied to the query when tuneQuery() is called.
* </p>
*/
public interface AutoFetchManager extends NodeUsageListener {
/**
* Set the owning ebean server.
*/
void setOwner(SpiEbeanServer server, ServerConfig serverConfig);
/**
* Clear the query execution statistics.
*/
void clearQueryStatistics();
/**
* Clear all the tuned query info.
* <p>
* Should only need do this for testing and playing around.
* </p>
*/
int clearTunedQueryInfo();
/**
* Clear all the profiling information.
* <p>
* This means the profiling information will need to be re-gathered.
* </p>
* <p>
* Should only need do this for testing and playing around.
* </p>
*/
int clearProfilingInfo();
/**
* On shutdown fire garbage collection and collect statistics. Note that
* usually we add a little delay (100 milliseconds) to give the garbage
* collector plenty of time to do its thing and collect the profile
* information.
*/
void shutdown();
/**
* Return the current tuned fetch information for a given queryPoint key.
*/
TunedQueryInfo getTunedQueryInfo(String queryPointKey);
/**
* Return the current Statistics for a given queryPoint key.
*/
Statistics getStatistics(String queryPointKey);
/**
* Iterate the tuned fetch info.
* <p>
* This should be a read only iteration.
* </p>
*/
Iterator<TunedQueryInfo> iterateTunedQueryInfo();
/**
* Iterate the node usage statistics.
* <p>
* This should be a read only iteration.
* </p>
*/
Iterator<Statistics> iterateStatistics();
/**
* Return true if profiling is enabled.
*/
boolean isProfiling();
/**
* Set to true to enable profiling.
* <p>
* We rely on garbage collection to collect the profiling information. This
* means there is a unknown delay between when a query is executed and when
* we actually collect the usage profile information.
* </p>
* <p>
* Due to this garbage collection delay, when turning off profiling while
* the application is running you should consider calling
* collectUsageViaGC() <em>BEFORE</em> setProfiling(false). This hints to
* the JVM to perform garbage collection, and hopefully collects the
* profiling information.
* </p>
*/
void setProfiling(boolean enable);
/**
* Return true if automatic query tuning is enabled.
*/
boolean isQueryTuning();
/**
* Set to true to enable automatic query tuning.
*/
void setQueryTuning(boolean enable);
/**
* This controls whether autoFetch is used when it has not been explicitly
* set on a query via {@link Query#setAutofetch(boolean)}.
*/
AutofetchMode getMode();
/**
* Set the auto fetch mode used when a query has not had
* {@link Query#setAutofetch(boolean)}.
*/
void setMode(AutofetchMode Mode);
/**
* Return the profiling rate (int between 0 and 100).
*/
double getProfilingRate();
/**
* Set the profiling rate (int between 0 and 100).
*/
void setProfilingRate(double rate);
/**
* Return the max number of queries profiled (per query point).
* <p>
* The number of queries profiled is collected per query point. Once a query
* point has profiled this number of queries it does not profile any more.
* </p>
*/
int getProfilingBase();
/**
* Set a max number of queries to profile per query point.
* <p>
* This number should provide a level of confidence that no more profiling
* is required for this query point.
* </p>
*/
void setProfilingBase(int profilingMax);
/**
* Return the minimum number of queries profiled before autoFetch will start
* automatically tuning the queries.
* <p>
* This could be one which means start autoFetch tuning after the first
* profiling information is collected.
* </p>
*/
int getProfilingMin();
/**
* Set the minimum number of queries profiled per query point before
* autoFetch will automatically tune the queries.
* <p>
* Increasing this number will mean more profiling is collected before
* autoFetch starts tuning the query.
* </p>
*/
void setProfilingMin(int autoFetchMinThreshold);
/**
* Fire a garbage collection (hint to the JVM). Assuming garbage collection
* fires this will gather the usage profiling information.
*/
String collectUsageViaGC(long waitMillis);
/**
* This will take the current profiling information and update the "tuned
* query detail".
* <p>
* This is done periodically and can also be manually invoked.
* </p>
* <p>
* This returns a string summary of the updates that occurred.
* </p>
*/
String updateTunedQueryInfo();
/**
* Called when a query thinks it should be automatically tuned by autoFetch.
* <p>
* This internally checks that autoFetch is enabled, there is a "tuned query
* detail" to tune the query with and that the autoFetchMinThreshold has
* been reached.
* </p>
* <p>
* This will also determine if the query should be profiled.
* </p>
*/
boolean tuneQuery(SpiQuery<?> query);
/**
* Collect query profiling information.
* <p>
* This is for the original query as well as any subsequent lazy loading
* queries that are required as the object graph is traversed.
* </p>
*
* @param node
* the node path in the object graph.
* @param beans
* the number of beans loaded by the query.
* @param micros
* the query executing time in microseconds
*/
void collectQueryInfo(ObjectGraphNode node, long beans, long micros);
/**
* Return the number of queries tuned by AutoFetch.
*/
int getTotalTunedQueryCount();
/**
* Return the size of the TuneQuery map.
*/
int getTotalTunedQuerySize();
/**
* Return the size of the profile map.
*/
int getTotalProfileSize();
}
@@ -1,95 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.resource.ResourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class AutoFetchManagerFactory {
private static final Logger logger = LoggerFactory.getLogger(AutoFetchManagerFactory.class);
public static AutoFetchManager create(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager) {
AutoFetchManagerFactory me = new AutoFetchManagerFactory();
return me.createAutoFetchManager(server, serverConfig, resourceManager);
}
private AutoFetchManager createAutoFetchManager(SpiEbeanServer server, ServerConfig serverConfig, ResourceManager resourceManager){
AutoFetchManager manager = createAutoFetchManager(server.getName(), resourceManager);
manager.setOwner(server, serverConfig);
return manager;
}
private AutoFetchManager createAutoFetchManager(String serverName, ResourceManager resourceManager) {
File autoFetchFile = getAutoFetchFile(serverName, resourceManager);
AutoFetchManager autoFetchManager = null;
boolean readFile = !"false".equalsIgnoreCase(System.getProperty("autofetch.readfromfile"));
if (readFile) {
autoFetchManager = deserializeAutoFetch(autoFetchFile);
}
if (autoFetchManager == null) {
// not deserialized from file so create as empty
// It will be populated automatically by querying the
// database meta data
autoFetchManager = new DefaultAutoFetchManager(autoFetchFile.getAbsolutePath());
}
return autoFetchManager;
}
private AutoFetchManager deserializeAutoFetch(File autoFetchFile) {
try {
if (!autoFetchFile.exists()) {
return null;
}
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()+"]");
return profListener;
} catch (Exception ex) {
logger.error("Error loading autofetch file "+autoFetchFile.getAbsolutePath(), ex);
return null;
}
}
/**
* Return the file name of the autoFetch meta data.
*/
private File getAutoFetchFile(String serverName, ResourceManager resourceManager) {
String fileName = ".ebean."+serverName+".autofetch";
File dir = resourceManager.getAutofetchDirectory();
if (!dir.exists()) {
// automatically create the directory if it does not exist.
// this is probably a fairly reasonable thing to do
if (!dir.mkdirs()) {
String m = "Unable to create directory [" + dir + "] for autofetch file ["+ fileName + "]";
throw new PersistenceException(m);
}
}
return new File(dir, fileName);
}
}
@@ -1,584 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.File;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.PersistenceException;
import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.NodeUsageCollector;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.config.AutofetchConfig;
import com.avaje.ebean.config.AutofetchMode;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The manager of all the usage/query statistics as well as the tuned fetch
* information.
*/
public class DefaultAutoFetchManager implements AutoFetchManager, Serializable {
private static final Logger logger = LoggerFactory.getLogger(DefaultAutoFetchManager.class);
private static final long serialVersionUID = -6826119882781771722L;
@SuppressWarnings("RedundantStringConstructorCall")
private final String statisticsMonitor = new String();
private final String fileName;
/**
* Map of the usage and query statistics gathered.
*/
private final Map<String, Statistics> statisticsMap = new ConcurrentHashMap<String, Statistics>();
/**
* Map of the tuned query details per profile query point.
*/
private final Map<String, TunedQueryInfo> tunedQueryInfoMap = new ConcurrentHashMap<String, TunedQueryInfo>();
private transient long defaultGarbageCollectionWait = 100;
/**
* Left without synchronized for now.
*/
private transient int tunedQueryCount;
/**
* Converted from a 0-100 int to a double. Effectively a percentage rate at
* which to collect profiling information.
*/
private transient double profilingRate = 0.1d;
private transient int profilingBase = 10;
private transient int profilingMin = 1;
private transient boolean profiling;
private transient boolean queryTuning;
private transient boolean queryTuningAddVersion;
private transient boolean garbageCollectionOnShutdown;
private transient AutofetchMode mode;
/**
* Server that owns this Profile Listener.
*/
private transient SpiEbeanServer server;
/**
* The logger.
*/
private transient DefaultAutoFetchManagerLogging logging;
public DefaultAutoFetchManager(String fileName) {
this.fileName = fileName;
}
/**
* Set up this profile listener before it is active.
*/
public void setOwner(SpiEbeanServer server, ServerConfig serverConfig) {
this.server = server;
this.logging = new DefaultAutoFetchManagerLogging(serverConfig, this);
AutofetchConfig autofetchConfig = serverConfig.getAutofetchConfig();
garbageCollectionOnShutdown = autofetchConfig.isGarbageCollectionOnShutdown();
queryTuning = autofetchConfig.isQueryTuning();
queryTuningAddVersion = autofetchConfig.isQueryTuningAddVersion();
profiling = autofetchConfig.isProfiling();
profilingMin = autofetchConfig.getProfilingMin();
profilingBase = autofetchConfig.getProfilingBase();
setProfilingRate(autofetchConfig.getProfilingRate());
defaultGarbageCollectionWait = (long) autofetchConfig.getGarbageCollectionWait();
// determine the mode to use when Query.setAutoFetch() was
// not explicitly set
mode = autofetchConfig.getMode();
if (profiling || queryTuning) {
// log the guts of the autoFetch setup
String msg = "AutoFetch queryTuning[" + queryTuning + "] profiling[" + profiling
+ "] mode[" + mode + "] profiling rate[" + profilingRate
+ "] min[" + profilingMin + "] base[" + profilingBase + "]";
logging.logInfo(msg, null);
// Register a periodic update of the profiling informations
this.logging.init(server);
}
}
public void clearQueryStatistics() {
server.clearQueryStatistics();
}
/**
* Return the number of queries tuned by AutoFetch.
*/
public int getTotalTunedQueryCount(){
return tunedQueryCount;
}
/**
* Return the size of the TuneQuery map.
*/
public int getTotalTunedQuerySize(){
return tunedQueryInfoMap.size();
}
/**
* Return the size of the profile map.
*/
public int getTotalProfileSize(){
return statisticsMap.size();
}
public int clearTunedQueryInfo() {
// reset the rough count as well
tunedQueryCount = 0;
// clear the map...
int size = tunedQueryInfoMap.size();
tunedQueryInfoMap.clear();
return size;
}
public int clearProfilingInfo() {
int size = statisticsMap.size();
statisticsMap.clear();
return size;
}
public void serialize() {
File autoFetchFile = new File(fileName);
try {
FileOutputStream fout = new FileOutputStream(autoFetchFile);
ObjectOutputStream oout = new ObjectOutputStream(fout);
oout.writeObject(this);
oout.flush();
oout.close();
} catch (Exception e) {
String msg = "Error serializing autofetch file";
logging.logError(msg, e);
}
}
/**
* Return the current Tuned query info for a given origin key.
*/
public TunedQueryInfo getTunedQueryInfo(String originKey) {
return tunedQueryInfoMap.get(originKey);
}
/**
* Return the current Statistics for a given originKey key.
*/
public Statistics getStatistics(String originKey) {
return statisticsMap.get(originKey);
}
public Iterator<TunedQueryInfo> iterateTunedQueryInfo() {
return tunedQueryInfoMap.values().iterator();
}
public Iterator<Statistics> iterateStatistics() {
return statisticsMap.values().iterator();
}
public boolean isProfiling() {
return profiling;
}
/**
* When the application is running, BEFORE turning off profiling you
* probably should call collectUsageViaGC() as there is a delay (waiting for
* garbage collection) collecting usage profiling information.
*/
public void setProfiling(boolean profiling) {
this.profiling = profiling;
}
public boolean isQueryTuning() {
return queryTuning;
}
public void setQueryTuning(boolean queryTuning) {
this.queryTuning = queryTuning;
}
public double getProfilingRate() {
return profilingRate;
}
public AutofetchMode getMode() {
return mode;
}
public void setMode(AutofetchMode mode) {
this.mode = mode;
}
public void setProfilingRate(double rate) {
if (rate < 0) {
rate = 0d;
} else if (rate > 1) {
rate = 1d;
}
profilingRate = rate;
}
public int getProfilingBase() {
return profilingBase;
}
public void setProfilingBase(int profilingBase) {
this.profilingBase = profilingBase;
}
public int getProfilingMin() {
return profilingMin;
}
public void setProfilingMin(int profilingMin) {
this.profilingMin = profilingMin;
}
/**
* Shutdown the listener.
* <p>
* We should try to collect the usage statistics by calling a System.gc().
* This is necessary for use with short lived applications where garbage
* collection may not otherwise occur at all.
* </p>
*/
public void shutdown() {
if (garbageCollectionOnShutdown) {
collectUsageViaGC(-1);
serialize();
}
}
/**
* Ask for a System.gc() so that we gather node usage information.
* <p>
* Really only want to do this sparingly but useful just prior to shutdown
* for short run application where garbage collection may otherwise not
* occur at all.
* </p>
* <p>
* waitMillis will do a thread sleep to give the garbage collection a little
* time to do its thing assuming we are shutting down the VM.
* </p>
* <p>
* If waitMillis is -1 then the defaultGarbageCollectionWait is used which
* defaults to 100 milliseconds.
* </p>
*/
public String collectUsageViaGC(long waitMillis) {
System.gc();
try {
if (waitMillis < 0) {
waitMillis = defaultGarbageCollectionWait;
}
Thread.sleep(waitMillis);
} catch (InterruptedException e) {
String msg = "Error while sleeping after System.gc() request.";
logging.logError(msg, e);
return msg;
}
return updateTunedQueryInfo();
}
/**
* Update the tuned fetch plans from the current usage information.
*/
public String updateTunedQueryInfo() {
if (!profiling) {
// we are not collecting any profiling information at
// the moment so don't try updating the tuned query plans.
return "Not profiling";
}
synchronized (statisticsMonitor) {
Counters counters = new Counters();
for (Statistics queryPointStatistics : statisticsMap.values()) {
if (!queryPointStatistics.hasUsage()) {
// no usage statistics collected yet...
counters.incrementNoUsage();
} else {
updateTunedQueryFromUsage(counters, queryPointStatistics);
}
}
String summaryInfo = counters.toString();
if (counters.isInteresting()){
// only log it if its interesting
logging.logSummary(summaryInfo);
}
return summaryInfo;
}
}
private static class Counters {
int newPlan;
int modified;
int unchanged;
int noUsage;
void incrementNoUsage(){
noUsage++;
}
void incrementNew(){
newPlan++;
}
void incrementModified(){
modified++;
}
void incrementUnchanged(){
unchanged++;
}
boolean isInteresting() {
return newPlan > 0 || modified > 0;
}
public String toString() {
return "new["+newPlan+"] modified["+modified+"] unchanged["+unchanged+"] nousage["+noUsage+"]";
}
}
private void updateTunedQueryFromUsage(Counters counters, Statistics statistics) {
ObjectGraphOrigin queryPoint = statistics.getOrigin();
String beanType = queryPoint.getBeanType();
try {
Class<?> beanClass = ClassUtil.forName(beanType, this.getClass());
BeanDescriptor<?> beanDescriptor = server.getBeanDescriptor(beanClass);
if (beanDescriptor != null){
// Determine the fetch plan from the latest statistics.
// Use this to compare with current "tuned fetch plan".
OrmQueryDetail newFetchDetail = statistics.buildTunedFetch(beanDescriptor);
// get the current tuned fetch info...
TunedQueryInfo currentFetch = tunedQueryInfoMap.get(queryPoint.getKey());
if (currentFetch == null) {
// its a new fetch plan, add it.
counters.incrementNew();
currentFetch = statistics.createTunedFetch(newFetchDetail);
logging.logNew(currentFetch);
tunedQueryInfoMap.put(queryPoint.getKey(), currentFetch);
} else if (!currentFetch.isSame(newFetchDetail)) {
// the fetch plan has changed, update it.
counters.incrementModified();
logging.logChanged(currentFetch, newFetchDetail);
currentFetch.setTunedDetail(newFetchDetail);
} else {
// the fetch plan has not changed...
counters.incrementUnchanged();
}
currentFetch.setProfileCount(statistics.getCounter());
}
} catch (ClassNotFoundException e) {
// expected after renaming/moving an entity bean
String msg = e.toString()+" updating autoFetch tuned query for " + beanType
+". It isLikely this bean has been renamed or moved";
logging.logInfo(msg, null);
statisticsMap.remove(statistics.getOrigin().getKey());
}
}
/**
* Return true if we should try to use autoFetch for this query.
*/
private boolean useAutoFetch(SpiQuery<?> query) {
if (query.isLoadBeanCache()){
// when loading the cache don't tune the query
// as we want full objects loaded into the cache
return false;
}
Boolean autoFetch = query.isAutofetch();
if (autoFetch != null) {
// explicitly set...
return autoFetch;
} else {
// determine using implicit mode...
switch (mode) {
case DEFAULT_ON:
return true;
case DEFAULT_OFF:
return false;
case DEFAULT_ONIFEMPTY:
return query.isDetailEmpty();
default:
throw new PersistenceException("Invalid autoFetchMode " + mode);
}
}
}
/**
* Auto tune the query and enable profiling.
*/
public boolean tuneQuery(SpiQuery<?> query) {
if (!queryTuning && !profiling) {
return false;
}
if (!useAutoFetch(query)) {
// not using autoFetch for this query
return false;
}
ObjectGraphNode parentAutoFetchNode = query.getParentNode();
if (parentAutoFetchNode != null) {
// This is a +lazy/+query query with profiling on.
// We continue to collect the profiling information.
query.setAutoFetchManager(this);
return true;
}
// create a query point to identify the query
CallStack stack = server.createCallStack();
ObjectGraphNode origin = query.setOrigin(stack);
// get current "tuned fetch" for this query point
TunedQueryInfo tunedFetch = tunedQueryInfoMap.get(origin.getOriginQueryPoint().getKey());
// get the number of times we have collected profiling information
int profileCount = tunedFetch == null ? 0 : tunedFetch.getProfileCount();
if (profiling) {
// we want more profiling information?
if (tunedFetch == null) {
query.setAutoFetchManager(this);
} else if (profileCount < profilingBase) {
query.setAutoFetchManager(this);
} else if (tunedFetch.isPercentageProfile(profilingRate)) {
query.setAutoFetchManager(this);
}
}
if (queryTuning) {
if (tunedFetch != null && profileCount >= profilingMin) {
// deemed to have enough profiling
// information for automatic tuning
if (tunedFetch.autoFetchTune(query)){
// tunedQueryCount++ not thread-safe, could use AtomicInteger.
// But I'm happy if this statistic is a little wrong
// and this is a VERY HOT method
tunedQueryCount++;
}
return true;
}
}
return false;
}
/**
* Gather query execution statistics. This could either be the originating
* 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, long beans, long micros) {
if (node != null){
ObjectGraphOrigin origin = node.getOriginQueryPoint();
if (origin != null){
Statistics stats = getQueryPointStats(origin);
stats.collectQueryInfo(node, beans, micros);
}
}
}
/**
* Collect usage statistics from a node in the object graph.
* <p>
* This is sent to use from a EntityBeanIntercept when the finalise method
* is called on the bean.
* </p>
*/
public void collectNodeUsage(NodeUsageCollector usageCollector) {
ObjectGraphOrigin origin = usageCollector.getNode().getOriginQueryPoint();
Statistics stats = getQueryPointStats(origin);
if (logger.isTraceEnabled()) {
logger.trace("... NodeUsageCollector " + usageCollector);
}
stats.collectUsageInfo(usageCollector);
if (logger.isTraceEnabled()) {
logger.trace("stats\n" + stats);
}
}
private Statistics getQueryPointStats(ObjectGraphOrigin originQueryPoint) {
synchronized (statisticsMonitor) {
Statistics stats = statisticsMap.get(originQueryPoint.getKey());
if (stats == null) {
stats = new Statistics(originQueryPoint, queryTuningAddVersion);
statisticsMap.put(originQueryPoint.getKey(), stats);
}
return stats;
}
}
public String toString() {
synchronized (statisticsMonitor) {
return statisticsMap.values().toString();
}
}
}
@@ -1,67 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
/**
* Handles the logging aspects for the DefaultAutoFetchListener.
* <p>
* Note that java util logging loggers generally should not be serialised and
* that is one of the main reasons for pulling out the logging to this class.
* </p>
*/
public class DefaultAutoFetchManagerLogging {
private static final Logger logger = LoggerFactory.getLogger(DefaultAutoFetchManagerLogging.class);
private final DefaultAutoFetchManager manager;
private final int updateFreqInSecs;
public DefaultAutoFetchManagerLogging(ServerConfig serverConfig, DefaultAutoFetchManager profileListener) {
this.manager = profileListener;
this.updateFreqInSecs = serverConfig.getAutofetchConfig().getProfileUpdateFrequency();
}
public void init(SpiEbeanServer ebeanServer) {
ebeanServer.getBackgroundExecutor().executePeriodically(new UpdateProfile(), updateFreqInSecs, TimeUnit.SECONDS);
}
private final class UpdateProfile implements Runnable {
public void run() {
manager.updateTunedQueryInfo();
}
}
public void logInfo(String msg, Throwable e) {
logger.info(msg, e);
}
public void logError(String msg, Throwable e) {
logger.error(msg, e);
}
public void logSummary(String summaryInfo) {
String msg = "\"Summary\",\""+summaryInfo+"\",,,,";
logger.debug(msg);
}
public void logChanged(TunedQueryInfo tunedFetch, OrmQueryDetail newQueryDetail) {
String msg = tunedFetch.getLogOutput(newQueryDetail);
logger.debug(msg);
}
public void logNew(TunedQueryInfo tunedFetch) {
String msg = tunedFetch.getLogOutput(null);
logger.debug(msg);
}
}
@@ -1,174 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import com.avaje.ebean.bean.NodeUsageCollector;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebean.text.PathProperties.Props;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
public class Statistics implements Serializable {
private static final long serialVersionUID = -5586783791097230766L;
private final ObjectGraphOrigin origin;
private final boolean queryTuningAddVersion;
private int counter;
private final Map<String, StatisticsQuery> queryStatsMap = new LinkedHashMap<String, StatisticsQuery>();
private final Map<String, StatisticsNodeUsage> nodeUsageMap = new LinkedHashMap<String, StatisticsNodeUsage>();
@SuppressWarnings("RedundantStringConstructorCall")
private final String monitor = new String();
public Statistics(ObjectGraphOrigin origin, boolean queryTuningAddVersion) {
this.origin = origin;
this.queryTuningAddVersion = queryTuningAddVersion;
}
public ObjectGraphOrigin getOrigin() {
return origin;
}
public TunedQueryInfo createTunedFetch(OrmQueryDetail newFetchDetail) {
synchronized (monitor) {
// NB: create a copy of queryPoint allowing garbage
// collection of source...
return new TunedQueryInfo(origin, newFetchDetail, counter);
}
}
/**
* Return the number of times the root query has executed.
* <p>
* This tells us how much profiling we have done for this query.
* For example, after 100 times we may stop collecting more profiling info.
* </p>
*/
public int getCounter() {
return counter;
}
/**
* Return true if this has usage statistics.
*/
public boolean hasUsage() {
synchronized (monitor) {
return !nodeUsageMap.isEmpty();
}
}
public OrmQueryDetail buildTunedFetch(BeanDescriptor<?> rootDesc){
synchronized (monitor) {
if (nodeUsageMap.isEmpty()){
return null;
}
PathProperties pathProps = new PathProperties();
for (StatisticsNodeUsage statsNode : nodeUsageMap.values()) {
statsNode.buildTunedFetch(pathProps, rootDesc);
}
OrmQueryDetail detail = new OrmQueryDetail();
Collection<Props> pathProperties = pathProps.getPathProps();
for (Props props : pathProperties) {
if (!props.isEmpty()){
detail.addFetch(props.getPath(), props.getPropertiesAsString(), null);
}
}
detail.sortFetchPaths(rootDesc);
return detail;
}
}
public void collectQueryInfo(ObjectGraphNode node, long beansLoaded, long micros) {
synchronized (monitor) {
String key = node.getPath();
if (key == null){
key = "";
// this is basically the number of times the root query
// has executed which gives us an indication of how
// much profiling information we have gathered.
counter++;
}
StatisticsQuery stats = queryStatsMap.get(key);
if (stats == null){
stats = new StatisticsQuery(key);
queryStatsMap.put(key, stats);
}
stats.add(beansLoaded, micros);
}
}
/**
* Collect the usage information for from a instance for this node.
*/
public void collectUsageInfo(NodeUsageCollector profile) {
if (!profile.isEmpty()) {
ObjectGraphNode node = profile.getNode();
StatisticsNodeUsage nodeStats = getNodeStats(node.getPath());
nodeStats.publish(profile);
}
}
private StatisticsNodeUsage getNodeStats(String path) {
synchronized (monitor) {
StatisticsNodeUsage nodeStats = nodeUsageMap.get(path);
if (nodeStats == null) {
nodeStats = new StatisticsNodeUsage(path, queryTuningAddVersion);
nodeUsageMap.put(path, nodeStats);
}
return nodeStats;
}
}
public String getUsageDebug() {
synchronized (monitor) {
StringBuilder sb = new StringBuilder();
sb.append("root[").append(origin.getBeanType()).append("] ");
for (StatisticsNodeUsage node : nodeUsageMap.values()) {
sb.append(node.toString()).append("\n");
}
return sb.toString();
}
}
public String getQueryStatDebug() {
synchronized (monitor) {
StringBuilder sb = new StringBuilder();
for (StatisticsQuery queryStat : queryStatsMap.values()) {
sb.append(queryStat.toString()).append("\n");
}
return sb.toString();
}
}
public String toString() {
synchronized (monitor) {
return getUsageDebug();
}
}
}
@@ -1,119 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.Serializable;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.bean.NodeUsageCollector;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.query.SplitName;
/**
* Collects usages statistics for a given node in the object graph.
*/
public class StatisticsNodeUsage implements Serializable {
private static final long serialVersionUID = -1663951463963779547L;
private static final Logger logger = LoggerFactory.getLogger(StatisticsNodeUsage.class);
@SuppressWarnings("RedundantStringConstructorCall")
private final String monitor = new String();
private final String path;
private final boolean queryTuningAddVersion;
private int profileCount;
private int profileUsedCount;
private boolean modified;
private final Set<String> aggregateUsed = new LinkedHashSet<String>();
public StatisticsNodeUsage(String path, boolean queryTuningAddVersion) {
this.path = path;
this.queryTuningAddVersion = queryTuningAddVersion;
}
public void buildTunedFetch(PathProperties pathProps, BeanDescriptor<?> rootDesc) {
synchronized(monitor){
BeanDescriptor<?> desc = rootDesc;
if (path != null){
ElPropertyValue elGetValue = rootDesc.getElGetValue(path);
if (elGetValue == null){
desc = null;
logger.warn("Autofetch: Can't find join for path["+path+"] for "+rootDesc.getName());
} else {
BeanProperty beanProperty = elGetValue.getBeanProperty();
if (beanProperty instanceof BeanPropertyAssoc<?>){
desc = ((BeanPropertyAssoc<?>) beanProperty).getTargetDescriptor();
}
}
}
for (String propName : aggregateUsed) {
BeanProperty beanProp = desc.getBeanPropertyFromPath(propName);
if (beanProp == null){
logger.warn("Autofetch: Can't find property["+propName+"] for "+desc.getName());
} else {
if (beanProp instanceof BeanPropertyAssoc<?>){
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>)beanProp;
String targetIdProp = assocProp.getTargetIdProperty();
String manyPath = SplitName.add(path, assocProp.getName());
pathProps.addToPath(manyPath, targetIdProp);
} else {
//noinspection StatementWithEmptyBody
if (beanProp.isLob() && !beanProp.isFetchEager()) {
// AutoFetch will not include Lob's marked FetchLazy
// (which is the default for Lob's so typical).
} else {
pathProps.addToPath(path, beanProp.getName());
}
}
}
}
if ((modified || queryTuningAddVersion) && desc != null) {
BeanProperty versionProp = desc.getVersionProperty();
if (versionProp != null) {
pathProps.addToPath(path, versionProp.getName());
}
}
}
}
public void publish(NodeUsageCollector profile) {
synchronized(monitor){
HashSet<String> used = profile.getUsed();
profileCount++;
if (!used.isEmpty()){
profileUsedCount++;
aggregateUsed.addAll(used);
}
if (profile.isModified()){
modified = true;
}
}
}
public String toString() {
return "path["+path+"] profileCount["+profileCount+"] used["+profileUsedCount+"] props"+aggregateUsed;
}
}
@@ -1,36 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.Serializable;
/**
* Used to accumulate query execution statistics.
*/
public class StatisticsQuery implements Serializable {
private static final long serialVersionUID = -1133958958072778811L;
private final String path;
private long exeCount;
private long totalBeanLoaded;
private long totalMicros;
public StatisticsQuery(String path){
this.path = path;
}
public void add(long beansLoaded, long micros) {
exeCount++;
totalBeanLoaded += beansLoaded;
totalMicros += micros;
}
public String toString() {
long avgMicros = exeCount == 0 ? 0 : totalMicros / exeCount;
return "queryExe path["+path+"] count[" + exeCount + "] totalBeansLoaded[" + totalBeanLoaded + "] avgMicros["
+ avgMicros + "] totalMicros[" + totalMicros + "]";
}
}
@@ -1,183 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
import java.io.Serializable;
import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
/**
* Holds tuned query information. Is immutable so this represents the tuning at
* a given point in time.
*/
public class TunedQueryInfo implements Serializable {
private static final long serialVersionUID = 7381493228797997282L;
private final ObjectGraphOrigin origin;
/**
* The tuned query details with joins and properties.
*/
private OrmQueryDetail tunedDetail;
/**
* The number of times profiling has been collected for this query point.
*/
private int profileCount;
private Long lastTuneTime = (long) 0;
@SuppressWarnings("RedundantStringConstructorCall")
private final String rateMonitor = new String();
/**
* The number of queries tuned by this object.
* Could use AtomicInteger perhaps.
*/
private transient int tunedCount;
private transient int rateTotal;
private transient int rateHits;
private transient double lastRate;
public TunedQueryInfo(ObjectGraphOrigin queryPoint, OrmQueryDetail tunedDetail, int profileCount) {
this.origin = queryPoint;
this.tunedDetail = tunedDetail;
this.profileCount = profileCount;
}
/**
* Return true if this query should be profiled based on a percentage rate.
*/
public boolean isPercentageProfile(double rate) {
synchronized (rateMonitor) {
if (lastRate != rate) {
// the rate has changed so resetting
lastRate = rate;
rateTotal = 0;
rateHits = 0;
}
rateTotal++;
if (rate > (double) rateHits / rateTotal) {
rateHits++;
return true;
} else {
return false;
}
}
}
/**
* Set the number of times profiling has been collected for this query
* point.
*/
public void setProfileCount(int profileCount) {
// int assignment is atomic
this.profileCount = profileCount;
}
/**
* Set the tuned query detail.
*/
public void setTunedDetail(OrmQueryDetail tunedDetail) {
// assignment is atomic
this.tunedDetail = tunedDetail;
this.lastTuneTime = System.currentTimeMillis();
}
/**
* Return true if the fetches are essentially the same.
*/
public boolean isSame(OrmQueryDetail newQueryDetail) {
return tunedDetail != null && tunedDetail.isAutoFetchEqual(newQueryDetail);
}
/**
* Tune the query by replacing its OrmQueryDetail with a tuned one.
*
* @return true if the query was tuned, otherwise false.
*/
public boolean autoFetchTune(SpiQuery<?> query) {
if (tunedDetail == null) {
return false;
}
boolean tuned;
//Note: tunedDetail is immutable by convention
if (query.isDetailEmpty()) {
tuned = true;
// tune by 'replacement'
query.setDetail(tunedDetail.copy());
} else {
// tune by 'addition'
tuned = query.tuneFetchProperties(tunedDetail);
}
if (tuned){
query.setAutoFetchTuned(true);
// a case for AtomicInteger but good enough for statistics
tunedCount++;
}
return tuned;
}
/**
* Return the time of the last tune.
*/
public Long getLastTuneTime() {
return lastTuneTime;
}
/**
* Return the number of queries tuned by this object.
*/
public int getTunedCount() {
return tunedCount;
}
/**
* Return the number of times profiling has been collected for this query
* point.
*/
public int getProfileCount() {
return profileCount;
}
public OrmQueryDetail getTunedDetail() {
return tunedDetail;
}
public ObjectGraphOrigin getOrigin() {
return origin;
}
public String getLogOutput(OrmQueryDetail newQueryDetail) {
boolean changed = newQueryDetail != null;
StringBuilder sb = new StringBuilder(150);
sb.append( changed ? "\"Changed\",":"\"New\",");
sb.append("\"").append(origin.getBeanType()).append("\",");
sb.append("\"").append(origin.getKey()).append("\",");
if (changed){
sb.append("\"to: ").append(newQueryDetail.toString()).append("\",");
sb.append("\"from: ").append(tunedDetail.toString()).append("\",");
} else {
sb.append("\"to: ").append(tunedDetail.toString()).append("\",");
sb.append("\"\",");
}
sb.append("\"").append(origin.getFirstStackElement()).append("\"");
return sb.toString();
}
public String toString() {
return origin.getBeanType()+" "+origin.getKey()+" " + tunedDetail;
}
}
@@ -1,8 +0,0 @@
<html>
<head>
<title>AutoFetch Implementation</title>
</head>
<body>
AutoFetch Implementation
</body>
</html>
@@ -0,0 +1,115 @@
package com.avaje.ebeaninternal.server.autotune;
import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import java.util.ArrayList;
import java.util.List;
/**
* Profiling information collected.
*/
public class AutoTuneCollection {
List<Entry> entries = new ArrayList<Entry>();
public Entry add(ObjectGraphOrigin origin, OrmQueryDetail detail, String sourceQuery) {
Entry entry = new Entry(origin, detail, sourceQuery);
entries.add(entry);
return entry;
}
public List<Entry> getEntries() {
return entries;
}
/**
* Profiling entry at a given origin point.
*/
public static class Entry {
/**
* Profiling origin point.
*/
private final ObjectGraphOrigin origin;
/**
* The tuned query detail.
*/
private final OrmQueryDetail detail;
/**
* The original/existing query detail.
*/
private final String originalQuery;
/**
* Summary execution statistics for queries related to this origin point.
*/
private final List<EntryQuery> queries = new ArrayList<EntryQuery>();
public Entry(ObjectGraphOrigin origin, OrmQueryDetail detail, String originalQuery) {
this.origin = origin;
this.detail = detail;
this.originalQuery = originalQuery;
}
public void addQuery(EntryQuery entryQuery) {
queries.add(entryQuery);
}
public ObjectGraphOrigin getOrigin() {
return origin;
}
public OrmQueryDetail getDetail() {
return detail;
}
public String getOriginalQuery() {
return originalQuery;
}
public List<EntryQuery> getQueries() {
return queries;
}
}
/**
* Summary query execution statistics for the origin point.
*/
public static class EntryQuery {
final String path;
final long exeCount;
final long totalBeanLoaded;
final long totalMicros;
public EntryQuery(String path, long exeCount, long totalBeanLoaded, long totalMicros) {
this.path = path;
this.exeCount = exeCount;
this.totalBeanLoaded = totalBeanLoaded;
this.totalMicros = totalMicros;
}
/**
* Return the relative path with empty string for the origin query.
*/
public String getPath() {
return path;
}
public long getExeCount() {
return exeCount;
}
public long getTotalBeanLoaded() {
return totalBeanLoaded;
}
public long getTotalMicros() {
return totalMicros;
}
}
}
@@ -0,0 +1,47 @@
package com.avaje.ebeaninternal.server.autotune;
import com.avaje.ebean.AutoTune;
import com.avaje.ebeaninternal.api.SpiQuery;
/**
* Collects and manages the the profile information.
* <p>
* The profile information is periodically converted into "tuned query details" -
* which is used to automatically tune the queries that use AutoTune.
* </p>
* <p>
* The "tuned query details" effectively are part of the query that has the
* select() and join() information (but not the where clause, order by, limits
* etc). These are applied to the query when tuneQuery() is called.
* </p>
*/
public interface AutoTuneService extends AutoTune {
/**
* Load the query tuning information.
*/
void startup();
/**
* Called when a query thinks it should be automatically tuned by AutoTune.
* <p>
* Returns true if the query was tuned.
* </p>
*/
boolean tuneQuery(SpiQuery<?> query);
/**
* Fire a garbage collection (hint to the JVM). Assuming garbage collection
* fires this will gather the usage profiling information.
*/
void collectProfiling();
/**
* On shutdown fire garbage collection and collect statistics. Note that
* usually we add a little delay (100 milliseconds) to give the garbage
* collector plenty of time to do its thing and collect the profile
* information.
*/
void shutdown();
}
@@ -0,0 +1,26 @@
package com.avaje.ebeaninternal.server.autotune;
import com.avaje.ebean.bean.NodeUsageListener;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebeaninternal.api.SpiQuery;
/**
* Profiling listener gets call backs for node usage and the associated query executions.
*/
public interface ProfilingListener extends NodeUsageListener {
/**
* Collect summary statistics for a query executed for the given node.
*
* @param node the node relative to the origin point
* @param beans the number of beans loaded by the query
* @param micros the query execution in microseconds
*/
void collectQueryInfo(ObjectGraphNode node, long beans, long micros);
/**
* Return true if this request should be profiled (based on the
* profiling ratio and collection count for this origin).
*/
boolean isProfileRequest(ObjectGraphNode origin, SpiQuery<?> query);
}
@@ -0,0 +1,150 @@
package com.avaje.ebeaninternal.server.autotune.model;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileDiff" minOccurs="0"/>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileNew" minOccurs="0"/>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileEmpty" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"origin",
"profileDiff",
"profileNew",
"profileEmpty"
})
@XmlRootElement(name = "autotune")
public class Autotune {
protected List<Origin> origin;
protected ProfileDiff profileDiff;
protected ProfileNew profileNew;
protected ProfileEmpty profileEmpty;
/**
* Gets the value of the origin property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the origin property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*
*
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<Origin>();
}
return this.origin;
}
/**
* Gets the value of the profileDiff property.
*
* @return
* possible object is
* {@link ProfileDiff }
*
*/
public ProfileDiff getProfileDiff() {
return profileDiff;
}
/**
* Sets the value of the profileDiff property.
*
* @param value
* allowed object is
* {@link ProfileDiff }
*
*/
public void setProfileDiff(ProfileDiff value) {
this.profileDiff = value;
}
/**
* Gets the value of the profileNew property.
*
* @return
* possible object is
* {@link ProfileNew }
*
*/
public ProfileNew getProfileNew() {
return profileNew;
}
/**
* Sets the value of the profileNew property.
*
* @param value
* allowed object is
* {@link ProfileNew }
*
*/
public void setProfileNew(ProfileNew value) {
this.profileNew = value;
}
/**
* Gets the value of the profileEmpty property.
*
* @return
* possible object is
* {@link ProfileEmpty }
*
*/
public ProfileEmpty getProfileEmpty() {
return profileEmpty;
}
/**
* Sets the value of the profileEmpty property.
*
* @param value
* allowed object is
* {@link ProfileEmpty }
*
*/
public void setProfileEmpty(ProfileEmpty value) {
this.profileEmpty = value;
}
}
@@ -0,0 +1,72 @@
package com.avaje.ebeaninternal.server.autotune.model;
import javax.xml.bind.annotation.XmlRegistry;
/**
* This object contains factory methods for each
* Java content interface and Java element interface
* generated in the com.avaje.ebeaninternal.server.autotune.model package.
* <p>An ObjectFactory allows you to programatically
* construct new instances of the Java representation
* for XML content. The Java representation of XML
* content can consist of schema derived interfaces
* and classes representing the binding of schema
* type definitions, element declarations and model
* groups. Factory methods for each of these are
* provided in this class.
*
*/
@XmlRegistry
public class ObjectFactory {
/**
* Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.avaje.ebeaninternal.server.autotune.model
*
*/
public ObjectFactory() {
}
/**
* Create an instance of {@link ProfileNew }
*
*/
public ProfileNew createProfileNew() {
return new ProfileNew();
}
/**
* Create an instance of {@link Origin }
*
*/
public Origin createOrigin() {
return new Origin();
}
/**
* Create an instance of {@link ProfileEmpty }
*
*/
public ProfileEmpty createProfileEmpty() {
return new ProfileEmpty();
}
/**
* Create an instance of {@link Autotune }
*
*/
public Autotune createAutotune() {
return new Autotune();
}
/**
* Create an instance of {@link ProfileDiff }
*
*/
public ProfileDiff createProfileDiff() {
return new ProfileDiff();
}
}
@@ -0,0 +1,171 @@
package com.avaje.ebeaninternal.server.autotune.model;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element name="callStack" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
* &lt;/sequence>
* &lt;attribute name="key" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="beanType" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="detail" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;attribute name="original" type="{http://www.w3.org/2001/XMLSchema}string" />
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"callStack"
})
@XmlRootElement(name = "origin")
public class Origin {
protected String callStack;
@XmlAttribute(name = "key", required = true)
protected String key;
@XmlAttribute(name = "beanType")
protected String beanType;
@XmlAttribute(name = "detail")
protected String detail;
@XmlAttribute(name = "original")
protected String original;
/**
* Gets the value of the callStack property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getCallStack() {
return callStack;
}
/**
* Sets the value of the callStack property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setCallStack(String value) {
this.callStack = value;
}
/**
* Gets the value of the key property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getKey() {
return key;
}
/**
* Sets the value of the key property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setKey(String value) {
this.key = value;
}
/**
* Gets the value of the beanType property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getBeanType() {
return beanType;
}
/**
* Sets the value of the beanType property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setBeanType(String value) {
this.beanType = value;
}
/**
* Gets the value of the detail property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getDetail() {
return detail;
}
/**
* Sets the value of the detail property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setDetail(String value) {
this.detail = value;
}
/**
* Gets the value of the original property.
*
* @return
* possible object is
* {@link String }
*
*/
public String getOriginal() {
return original;
}
/**
* Sets the value of the original property.
*
* @param value
* allowed object is
* {@link String }
*
*/
public void setOriginal(String value) {
this.original = value;
}
}
@@ -0,0 +1,69 @@
package com.avaje.ebeaninternal.server.autotune.model;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"origin"
})
@XmlRootElement(name = "profileDiff")
public class ProfileDiff {
protected List<Origin> origin;
/**
* Gets the value of the origin property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the origin property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*
*
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<Origin>();
}
return this.origin;
}
}
@@ -0,0 +1,69 @@
package com.avaje.ebeaninternal.server.autotune.model;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"origin"
})
@XmlRootElement(name = "profileEmpty")
public class ProfileEmpty {
protected List<Origin> origin;
/**
* Gets the value of the origin property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the origin property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*
*
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<Origin>();
}
return this.origin;
}
}
@@ -0,0 +1,69 @@
package com.avaje.ebeaninternal.server.autotune.model;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
/**
* <p>Java class for anonymous complex type.
*
* <p>The following schema fragment specifies the expected content contained within this class.
*
* <pre>
* &lt;complexType>
* &lt;complexContent>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
* &lt;sequence>
* &lt;element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
* &lt;/sequence>
* &lt;/restriction>
* &lt;/complexContent>
* &lt;/complexType>
* </pre>
*
*
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
"origin"
})
@XmlRootElement(name = "profileNew")
public class ProfileNew {
protected List<Origin> origin;
/**
* Gets the value of the origin property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the origin property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getOrigin().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Origin }
*
*
*/
public List<Origin> getOrigin() {
if (origin == null) {
origin = new ArrayList<Origin>();
}
return this.origin;
}
}
@@ -0,0 +1,2 @@
@javax.xml.bind.annotation.XmlSchema(namespace = "http://ebean-orm.github.io/xml/ns/autotune", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.avaje.ebeaninternal.server.autotune.model;
@@ -0,0 +1,14 @@
package com.avaje.ebeaninternal.server.autotune.service;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.autotune.AutoTuneService;
public class AutoTuneServiceFactory {
public static AutoTuneService create(SpiEbeanServer server, ServerConfig serverConfig) {
return new DefaultAutoTuneService(server, serverConfig);
}
}
@@ -0,0 +1,57 @@
package com.avaje.ebeaninternal.server.autotune.service;
import com.avaje.ebeaninternal.server.autotune.model.Autotune;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Reads a profiling xml document.
*/
public class AutoTuneXmlReader {
/**
* Read and return a Profiling from an xml file.
*/
public Autotune read(File file) {
try {
return readFile(file);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
protected Autotune readFile(File file) throws IOException {
if (!file.exists()) {
return new Autotune();
}
FileInputStream is = new FileInputStream(file);
try {
return read(is);
} finally {
is.close();
}
}
/**
* Read and return a Profiling from an xml document.
*/
public Autotune read(InputStream is) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Autotune.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
return (Autotune) unmarshaller.unmarshal(is);
} catch (JAXBException e) {
throw new IllegalStateException(e);
}
}
}
@@ -0,0 +1,32 @@
package com.avaje.ebeaninternal.server.autotune.service;
import com.avaje.ebeaninternal.server.autotune.model.Autotune;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import java.io.File;
/**
* Simple writer for output of the AutoTune Profiling as an XML document.
*/
public class AutoTuneXmlWriter {
/**
* Write Profiling to a file as xml.
*/
public void write(Autotune profiling, File file) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Autotune.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(profiling, file);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,178 @@
package com.avaje.ebeaninternal.server.autotune.service;
import com.avaje.ebean.bean.CallStack;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.config.AutoTuneConfig;
import com.avaje.ebean.config.AutoTuneMode;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.autotune.ProfilingListener;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import javax.persistence.PersistenceException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
*
*/
public class BaseQueryTuner {
private final boolean queryTuning;
private final boolean profiling;
private final AutoTuneMode mode;
/**
* Map of the tuned query details per profile query point.
*/
private final Map<String, TunedQueryInfo> tunedQueryInfoMap = new ConcurrentHashMap<String, TunedQueryInfo>();
private final SpiEbeanServer server;
private final ProfilingListener profilingListener;
/**
* Flag set true when there is no profiling or query tuning.
*/
private final boolean skipAll;
public BaseQueryTuner(AutoTuneConfig config, SpiEbeanServer server, ProfilingListener profilingListener) {
this.server = server;
this.profilingListener = profilingListener;
this.mode = config.getMode();
this.queryTuning = config.isQueryTuning();
this.profiling = config.isProfiling();
this.skipAll = !queryTuning && !profiling;
}
/**
* Load the tuned query information.
*/
public void load(String key, TunedQueryInfo queryInfo) {
tunedQueryInfoMap.put(key, queryInfo);
}
/**
* Return the detail currently used for tuning.
* This returns null if there is currently no matching tuning.
*/
public OrmQueryDetail get(String key) {
TunedQueryInfo info = tunedQueryInfoMap.get(key);
return (info == null) ? null : info.getTunedDetail();
}
/**
* Auto tune the query and enable profiling.
*/
public boolean tuneQuery(SpiQuery<?> query) {
if (skipAll || !tunableQuery(query)) {
return false;
}
if (!useTuning(query)) {
if (profiling) {
profiling(query, server.createCallStack());
}
return false;
}
if (query.getParentNode() != null) {
// This is a +lazy/+query query with profiling on.
// We continue to collect the profiling information.
query.setProfilingListener(profilingListener);
return true;
}
// create a query point to identify the query
CallStack stack = server.createCallStack();
ObjectGraphNode origin = query.setOrigin(stack);
if (profiling) {
if (profilingListener.isProfileRequest(origin, query)) {
// collect more profiling based on profiling rate etc
query.setProfilingListener(profilingListener);
}
}
if (queryTuning) {
// get current "tuned fetch" for this query point
TunedQueryInfo tuneInfo = tunedQueryInfoMap.get(origin.getOriginQueryPoint().getKey());
return tuneInfo != null && tuneInfo.tuneQuery(query);
}
return false;
}
/**
* Return false for row count, find ids, subQuery, delete and Versions queries.
* <p>
* These queries are not applicable for autoTune in that they don't have a select/fetch (fetch group).
* </p>
* <p>
* We also exclude queries that are explicitly set to load the L2 bean cache as we want full beans
* in that case.
* </p>
*/
private boolean tunableQuery(SpiQuery<?> query) {
SpiQuery.Type type = query.getType();
switch (type) {
case ROWCOUNT:
case ID_LIST:
case DELETE:
case SUBQUERY:
return false;
default:
// not using autoTune when explicitly loading the l2 bean cache
// or when using Versions query
return !query.isLoadBeanCache() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
}
}
private void profiling(SpiQuery<?> query, CallStack stack) {
// create a query point to identify the query
ObjectGraphNode origin = query.setOrigin(stack);
if (profilingListener.isProfileRequest(origin, query)) {
// collect more profiling based on profiling rate etc
query.setProfilingListener(profilingListener);
}
}
/**
* Return true if we should try to tune this query.
*/
private boolean useTuning(SpiQuery<?> query) {
Boolean autoTune = query.isAutoTune();
if (autoTune != null) {
// explicitly set...
return autoTune;
} else {
// determine using implicit mode...
switch (mode) {
case DEFAULT_ON:
return true;
case DEFAULT_OFF:
return false;
case DEFAULT_ONIFEMPTY:
return query.isDetailEmpty();
default:
throw new PersistenceException("Invalid AutoTuneMode " + mode);
}
}
}
/**
* Return the keys as a set.
*/
public Set<String> keySet() {
return tunedQueryInfoMap.keySet();
}
}
@@ -0,0 +1,300 @@
package com.avaje.ebeaninternal.server.autotune.service;
import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.config.AutoTuneConfig;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.autotune.AutoTuneCollection;
import com.avaje.ebeaninternal.server.autotune.AutoTuneService;
import com.avaje.ebeaninternal.server.autotune.model.Autotune;
import com.avaje.ebeaninternal.server.autotune.model.Origin;
import com.avaje.ebeaninternal.server.autotune.model.ProfileDiff;
import com.avaje.ebeaninternal.server.autotune.model.ProfileEmpty;
import com.avaje.ebeaninternal.server.autotune.model.ProfileNew;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetailParser;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Implementation of the AutoTuneService which is comprised of profiling and query tuning.
*/
public class DefaultAutoTuneService implements AutoTuneService {
private static final Logger logger = LoggerFactory.getLogger(DefaultAutoTuneService.class);
private final long defaultGarbageCollectionWait;
private final boolean skipCollectionOnShutdown;
private final BaseQueryTuner queryTuner;
private final ProfileManager profileManager;
private final boolean profiling;
private final boolean queryTuning;
private final String tuningFile;
private final String profilingFile;
private final String serverName;
public DefaultAutoTuneService(SpiEbeanServer server, ServerConfig serverConfig) {
AutoTuneConfig config = serverConfig.getAutoTuneConfig();
this.queryTuning = config.isQueryTuning();
this.profiling = config.isProfiling();
this.tuningFile = config.getQueryTuningFile();
this.profilingFile = config.getProfilingFile();
this.serverName = server.getName();
this.profileManager = new ProfileManager(config, server);
this.queryTuner = new BaseQueryTuner(config, server, profileManager);
this.skipCollectionOnShutdown = config.isSkipCollectionOnShutdown();
this.defaultGarbageCollectionWait = (long) config.getGarbageCollectionWait();
}
/**
* Load the query tuning information from it's data store.
*/
@Override
public void startup() {
if (queryTuning) {
File file = new File(tuningFile);
if (!file.exists()) {
logger.warn("AutoTune file {} not found - no automatic tuning will be applied", file.getAbsolutePath());
} else {
AutoTuneXmlReader reader = new AutoTuneXmlReader();
Autotune profiling = reader.read(file);
logger.info("AutoTune loading {} tuning entries", profiling.getOrigin().size());
for (Origin origin : profiling.getOrigin()) {
queryTuner.load(origin.getKey(), createTunedQueryInfo(origin));
}
}
}
}
@NotNull
private TunedQueryInfo createTunedQueryInfo(Origin origin) {
OrmQueryDetail detail = new OrmQueryDetailParser(origin.getDetail()).parse();
return new TunedQueryInfo(detail);
}
private void saveProfiling(boolean reset) {
Autotune document = new Autotune();
AutoTuneCollection autoTuneCollection = profileManager.profilingCollection(reset);
List<AutoTuneCollection.Entry> entries = autoTuneCollection.getEntries();
// count "new" and "diff" profiling entries
AtomicInteger newCounter = new AtomicInteger();
AtomicInteger diffCounter = new AtomicInteger();
Set<String> profileKeys = new HashSet<String>();
for (AutoTuneCollection.Entry entry : entries) {
saveProfilingEntry(document, entry, newCounter, diffCounter);
profileKeys.add(entry.getOrigin().getKey());
}
// report the origin keys that we didn't collect any profiling on
Set<String> tunerKeys = queryTuner.keySet();
for (String tuneKey : tunerKeys) {
if (!profileKeys.contains(tuneKey)) {
ProfileEmpty profileEmpty = document.getProfileEmpty();
if (profileEmpty == null) {
profileEmpty = new ProfileEmpty();
document.setProfileEmpty(profileEmpty);
}
Origin emptyOrigin = new Origin();
emptyOrigin.setKey(tuneKey);
profileEmpty.getOrigin().add(emptyOrigin);
}
}
int totalNew = newCounter.get();
int totalDiff = diffCounter.get();
if (totalNew == 0 && totalDiff == 0) {
logger.info("No new or diff entries for profiling server:{}", serverName);
} else {
sortDocument(document);
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
String now = df.format(new Date());
// write the file with serverName and now suffix as we can output the profiling many times
File file = new File(profilingFile + "-" + serverName + "-" + now + ".xml");
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
writer.write(document, file);
logger.info("writing new:{} diff:{} profiling entries for server:{}", totalNew, totalDiff, serverName);
}
}
/**
* Set the diff and new entries by bean type followed by key.
*/
private void sortDocument(Autotune document) {
ProfileDiff profileDiff = document.getProfileDiff();
if (profileDiff != null) {
Collections.sort(profileDiff.getOrigin(), new OriginNameKeySort());
}
ProfileNew profileNew = document.getProfileNew();
if (profileNew != null) {
Collections.sort(profileNew.getOrigin(), new OriginNameKeySort());
}
ProfileEmpty profileEmpty = document.getProfileEmpty();
if (profileEmpty != null) {
Collections.sort(profileEmpty.getOrigin(), new OriginKeySort());
}
}
/**
* Comparator sort by bean type then key.
*/
class OriginNameKeySort implements Comparator<Origin> {
@Override
public int compare(Origin o1, Origin o2) {
int comp = o1.getBeanType().compareTo(o2.getBeanType());
if (comp == 0) {
comp = o1.getKey().compareTo(o2.getKey());
}
return comp;
}
}
/**
* Comparator sort by bean type then key.
*/
class OriginKeySort implements Comparator<Origin> {
@Override
public int compare(Origin o1, Origin o2) {
return o1.getKey().compareTo(o2.getKey());
}
}
private void saveProfilingEntry(Autotune document, AutoTuneCollection.Entry entry, AtomicInteger newCount, AtomicInteger diffCount) {
ObjectGraphOrigin point = entry.getOrigin();
OrmQueryDetail profileDetail = entry.getDetail();
// compare with the existing query tuning entry
OrmQueryDetail tuneDetail = queryTuner.get(point.getKey());
if (tuneDetail == null) {
// New entry
newCount.incrementAndGet();
ProfileNew profileNew = document.getProfileNew();
if (profileNew == null) {
profileNew = new ProfileNew();
document.setProfileNew(profileNew);
}
Origin origin = createOrigin(entry, point);
origin.setOriginal(entry.getOriginalQuery());
profileNew.getOrigin().add(origin);
} else if (!tuneDetail.isAutoTuneEqual(profileDetail)) {
// Diff entry
diffCount.incrementAndGet();
Origin origin = createOrigin(entry, point);
origin.setOriginal(tuneDetail.toString());
ProfileDiff diff = document.getProfileDiff();
if (diff == null) {
diff = new ProfileDiff();
document.setProfileDiff(diff);
}
diff.getOrigin().add(origin);
}
}
/**
* Create the XML Origin bean for the given entry and ObjectGraphOrigin.
*/
@NotNull
private Origin createOrigin(AutoTuneCollection.Entry entry, ObjectGraphOrigin point) {
Origin origin = new Origin();
origin.setKey(point.getKey());
origin.setBeanType(point.getBeanType());
origin.setDetail(entry.getDetail().toString());
origin.setCallStack(point.getCallStack().description("\n"));
return origin;
}
/**
* Shutdown the listener.
* <p>
* We should try to collect the usage statistics by calling a System.gc().
* This is necessary for use with short lived applications where garbage
* collection may not otherwise occur at all.
* </p>
*/
@Override
public void shutdown() {
if (profiling && !skipCollectionOnShutdown) {
collectProfiling(-1);
saveProfiling(false);
}
}
/**
* Ask for a System.gc() so that we gather node usage information.
* <p>
* Really only want to do this sparingly but useful just prior to shutdown
* for short run application where garbage collection may otherwise not
* occur at all.
* </p>
* <p>
* waitMillis will do a thread sleep to give the garbage collection a little
* time to do its thing assuming we are shutting down the VM.
* </p>
* <p>
* If waitMillis is -1 then the defaultGarbageCollectionWait is used which
* defaults to 100 milliseconds.
* </p>
*/
@Override
public void collectProfiling() {
collectProfiling(-1);
}
public void collectProfiling(long waitMillis) {
System.gc();
try {
if (waitMillis < 0) {
waitMillis = defaultGarbageCollectionWait;
}
Thread.sleep(waitMillis);
} catch (InterruptedException e) {
logger.warn("Error while sleeping after System.gc() request.", e);
}
}
/**
* Auto tune the query and enable profiling.
*/
@Override
public boolean tuneQuery(SpiQuery<?> query) {
return queryTuner.tuneQuery(query);
}
}
@@ -0,0 +1,131 @@
package com.avaje.ebeaninternal.server.autotune.service;
import com.avaje.ebean.bean.NodeUsageCollector;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.config.AutoTuneConfig;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.autotune.AutoTuneCollection;
import com.avaje.ebeaninternal.server.autotune.ProfilingListener;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages the collection of object graph usage profiling.
*/
public class ProfileManager implements ProfilingListener {
private final boolean queryTuningAddVersion;
/**
* Converted from a 0-100 int to a double. Effectively a percentage rate at
* which to collect profiling information.
*/
private final double profilingRate;
private final int profilingBase;
/**
* Map of the usage and query statistics gathered.
*/
private final Map<String, ProfileOrigin> profileMap = new ConcurrentHashMap<String, ProfileOrigin>();
private final Object monitor = new Object();
private final SpiEbeanServer server;
public ProfileManager(AutoTuneConfig config, SpiEbeanServer server) {
this.server = server;
this.profilingRate = config.getProfilingRate();
this.profilingBase = config.getProfilingBase();
this.queryTuningAddVersion = config.isQueryTuningAddVersion();
}
@Override
public boolean isProfileRequest(ObjectGraphNode origin, SpiQuery<?> query) {
ProfileOrigin profileOrigin = profileMap.get(origin.getOriginQueryPoint().getKey());
if (profileOrigin == null) {
profileMap.put(origin.getOriginQueryPoint().getKey(), createProfileOrigin(origin, query));
return true;
} else {
return profileOrigin.isProfile();
}
}
/**
* Create the profile origin noting the query detail currently being used.
* <p>
* For new profiling entries it is useful to compare the profiling against the current
* query detail that is specified in the code (as the query might already be manually optimised).
* </p>
*/
private ProfileOrigin createProfileOrigin(ObjectGraphNode origin, SpiQuery<?> query) {
ProfileOrigin profileOrigin = new ProfileOrigin(origin.getOriginQueryPoint(), queryTuningAddVersion, profilingBase, profilingRate);
// set the current query detail (fetch group) so that we can compare against profiling for new entries
profileOrigin.setOriginalQuery(query.getDetail().toString());
return profileOrigin;
}
/**
* Gather query execution statistics. This could either be the originating
* 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, long beans, long micros) {
if (node != null) {
ObjectGraphOrigin origin = node.getOriginQueryPoint();
if (origin != null) {
ProfileOrigin stats = getProfileOrigin(origin);
stats.collectQueryInfo(node, beans, micros);
}
}
}
/**
* Collect usage statistics from a node in the object graph.
* <p>
* This is sent to use from a EntityBeanIntercept when the finalise method
* is called on the bean.
* </p>
*/
public void collectNodeUsage(NodeUsageCollector usageCollector) {
ProfileOrigin profileOrigin = getProfileOrigin(usageCollector.getNode().getOriginQueryPoint());
profileOrigin.collectUsageInfo(usageCollector);
}
private ProfileOrigin getProfileOrigin(ObjectGraphOrigin originQueryPoint) {
synchronized (monitor) {
ProfileOrigin stats = profileMap.get(originQueryPoint.getKey());
if (stats == null) {
stats = new ProfileOrigin(originQueryPoint, queryTuningAddVersion, profilingBase, profilingRate);
profileMap.put(originQueryPoint.getKey(), stats);
}
return stats;
}
}
/**
* Collect all the profiling information.
*/
public AutoTuneCollection profilingCollection(boolean reset) {
AutoTuneCollection req = new AutoTuneCollection();
for (ProfileOrigin origin : profileMap.values()) {
BeanDescriptor<?> desc = server.getBeanDescriptorById(origin.getOrigin().getBeanType());
if (desc != null) {
origin.profilingCollection(desc, req, reset);
}
}
return req;
}
}
@@ -0,0 +1,177 @@
package com.avaje.ebeaninternal.server.autotune.service;
import com.avaje.ebean.bean.NodeUsageCollector;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.ObjectGraphOrigin;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebean.text.PathProperties.Props;
import com.avaje.ebeaninternal.server.autotune.AutoTuneCollection;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public class ProfileOrigin {
private static final long RESET_COUNT = -1000000000L;
private final ObjectGraphOrigin origin;
private final boolean queryTuningAddVersion;
private final int profilingBase;
private final double profilingRate;
private final Map<String, ProfileOriginQuery> queryStatsMap = new ConcurrentHashMap<String, ProfileOriginQuery>();
private final Map<String, ProfileOriginNodeUsage> nodeUsageMap = new ConcurrentHashMap<String, ProfileOriginNodeUsage>();
private final Object monitor = new Object();
private final AtomicLong requestCount = new AtomicLong();
private final AtomicLong profileCount = new AtomicLong();
private String originalQuery;
public ProfileOrigin(ObjectGraphOrigin origin, boolean queryTuningAddVersion, int profilingBase, double profilingRate) {
this.origin = origin;
this.queryTuningAddVersion = queryTuningAddVersion;
this.profilingBase = profilingBase;
this.profilingRate = profilingRate;
}
public String getOriginalQuery() {
return originalQuery;
}
public void setOriginalQuery(String originalQuery) {
this.originalQuery = originalQuery;
}
/**
* Return true if this query should be profiled based on a percentage rate.
*/
public boolean isProfile() {
long count = requestCount.incrementAndGet();
if (count < profilingBase) {
return true;
}
long hits = profileCount.get();
if (profilingRate > (double) hits / count) {
profileCount.incrementAndGet();
return true;
} else {
return false;
}
}
/**
* Collect profiling information with the option to reset the underlying profiling detail.
*/
public void profilingCollection(BeanDescriptor<?> rootDesc, AutoTuneCollection req, boolean reset) {
synchronized (monitor) {
if (nodeUsageMap.isEmpty()) {
return;
}
OrmQueryDetail detail = buildDetail(rootDesc);
AutoTuneCollection.Entry entry = req.add(origin, detail, originalQuery);
Collection<ProfileOriginQuery> values = queryStatsMap.values();
for (ProfileOriginQuery queryEntry : values) {
entry.addQuery(queryEntry.createEntryQuery(reset));
}
if (reset) {
nodeUsageMap.clear();
if (requestCount.get() > RESET_COUNT) {
requestCount.set(profilingBase);
profileCount.set(0);
}
}
}
}
private OrmQueryDetail buildDetail(BeanDescriptor<?> rootDesc) {
PathProperties pathProps = new PathProperties();
for (ProfileOriginNodeUsage statsNode : nodeUsageMap.values()) {
statsNode.buildTunedFetch(pathProps, rootDesc, queryTuningAddVersion);
}
OrmQueryDetail detail = new OrmQueryDetail();
Collection<Props> pathProperties = pathProps.getPathProps();
for (Props props : pathProperties) {
if (!props.isEmpty()) {
detail.addFetch(props.getPath(), props.getPropertiesAsString(), null);
}
}
detail.sortFetchPaths(rootDesc);
return detail;
}
/**
* Return the origin.
*/
public ObjectGraphOrigin getOrigin() {
return origin;
}
/**
* Collect query execution summary statistics.
* <p>
* This can give us a quick overview into bad lazy loading areas etc.
* </p>
*/
public void collectQueryInfo(ObjectGraphNode node, long beansLoaded, long micros) {
String key = node.getPath();
if (key == null) {
key = "";
}
ProfileOriginQuery stats = queryStatsMap.get(key);
if (stats == null) {
// a race condition but we don't care
stats = new ProfileOriginQuery(key);
queryStatsMap.put(key, stats);
}
stats.add(beansLoaded, micros);
}
/**
* Collect the usage information for from a instance for this node.
*/
public void collectUsageInfo(NodeUsageCollector profile) {
//logger.info("COLLECT USAGE {}", profile.toString());
if (!profile.isEmpty()) {
ProfileOriginNodeUsage nodeStats = getNodeStats(profile.getNode().getPath());
nodeStats.collectUsageInfo(profile);
}
}
private ProfileOriginNodeUsage getNodeStats(String path) {
synchronized (monitor) {
// handle null paths as using ConcurrentHashMap
path = (path == null) ? "" : path;
ProfileOriginNodeUsage nodeStats = nodeUsageMap.get(path);
if (nodeStats == null) {
nodeStats = new ProfileOriginNodeUsage(path);
nodeUsageMap.put(path, nodeStats);
}
return nodeStats;
}
}
}
@@ -0,0 +1,114 @@
package com.avaje.ebeaninternal.server.autotune.service;
import com.avaje.ebean.bean.NodeUsageCollector;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.query.SplitName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Collects usages statistics for a given node in the object graph.
*/
public class ProfileOriginNodeUsage {
private static final Logger logger = LoggerFactory.getLogger(ProfileOriginNodeUsage.class);
private final Object monitor = new Object();
private final String path;
private int profileCount;
private int profileUsedCount;
private boolean modified;
private final Set<String> aggregateUsed = new LinkedHashSet<String>();
public ProfileOriginNodeUsage(String path) {
// handle null paths as using ConcurrentHashMap
this.path = "".equals(path) ? null : path;
}
protected void buildTunedFetch(PathProperties pathProps, BeanDescriptor<?> rootDesc, boolean addVersionProperty) {
synchronized (monitor) {
BeanDescriptor<?> desc = rootDesc;
if (path != null) {
ElPropertyValue elGetValue = rootDesc.getElGetValue(path);
if (elGetValue == null) {
desc = null;
logger.warn("AutoTune: Can't find join for path[" + path + "] for " + rootDesc.getName());
} else {
BeanProperty beanProperty = elGetValue.getBeanProperty();
if (beanProperty instanceof BeanPropertyAssoc<?>) {
desc = ((BeanPropertyAssoc<?>) beanProperty).getTargetDescriptor();
}
}
}
for (String propName : aggregateUsed) {
BeanProperty beanProp = desc.getBeanPropertyFromPath(propName);
if (beanProp == null) {
logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName());
} else {
if (beanProp instanceof BeanPropertyAssoc<?>) {
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProp;
String targetIdProp = assocProp.getTargetIdProperty();
String manyPath = SplitName.add(path, assocProp.getName());
pathProps.addToPath(manyPath, targetIdProp);
} else {
//noinspection StatementWithEmptyBody
if (beanProp.isLob() && !beanProp.isFetchEager()) {
// AutoTune will not include Lob's marked FetchLazy
// (which is the default for Lob's so typical).
} else {
pathProps.addToPath(path, beanProp.getName());
}
}
}
}
if ((modified || addVersionProperty) && desc != null) {
BeanProperty versionProp = desc.getVersionProperty();
if (versionProp != null) {
pathProps.addToPath(path, versionProp.getName());
}
}
}
}
/**
* Collect usage from a node.
*/
protected void collectUsageInfo(NodeUsageCollector profile) {
synchronized (monitor) {
Set<String> used = profile.getUsed();
profileCount++;
if (!used.isEmpty()) {
profileUsedCount++;
aggregateUsed.addAll(used);
}
if (profile.isModified()) {
modified = true;
}
}
}
public String toString() {
return "path[" + path + "] profileCount[" + profileCount + "] used[" + profileUsedCount + "] props" + aggregateUsed;
}
}
@@ -0,0 +1,43 @@
package com.avaje.ebeaninternal.server.autotune.service;
import com.avaje.ebeaninternal.server.autotune.AutoTuneCollection;
import com.avaje.ebeaninternal.server.util.LongAdder;
import java.io.Serializable;
/**
* Used to accumulate query execution statistics for paths relative to the origin query.
*/
public class ProfileOriginQuery implements Serializable {
private static final long serialVersionUID = -1133958958072778811L;
private final String path;
private final LongAdder exeCount = new LongAdder();
private final LongAdder totalBeanLoaded = new LongAdder();
private final LongAdder totalMicros = new LongAdder();
public ProfileOriginQuery(String path) {
this.path = path;
}
public void add(long beansLoaded, long micros) {
exeCount.increment();
totalBeanLoaded.add(beansLoaded);
totalMicros.add(micros);
}
public AutoTuneCollection.EntryQuery createEntryQuery(boolean reset){
if (reset) {
return new AutoTuneCollection.EntryQuery(path, exeCount.sumThenReset(), totalBeanLoaded.sumThenReset(), totalMicros.sumThenReset());
} else {
return new AutoTuneCollection.EntryQuery(path, exeCount.sum(), totalBeanLoaded.sum(), totalMicros.sum());
}
}
}
@@ -0,0 +1,56 @@
package com.avaje.ebeaninternal.server.autotune.service;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import java.io.Serializable;
/**
* Holds tuned query information. Is immutable so this represents the tuning at
* a given point in time.
*/
public class TunedQueryInfo implements Serializable {
private final OrmQueryDetail tunedDetail;
public TunedQueryInfo(OrmQueryDetail tunedDetail) {
this.tunedDetail = tunedDetail;
}
/**
* Return the tuned detail (for comparison with profiling information).
*/
public OrmQueryDetail getTunedDetail() {
return tunedDetail;
}
/**
* Tune the query by replacing its OrmQueryDetail with a tuned one.
*
* @return true if the query was tuned, otherwise false.
*/
public boolean tuneQuery(SpiQuery<?> query) {
if (tunedDetail == null) {
return false;
}
boolean tuned;
if (query.isDetailEmpty()) {
tuned = true;
// tune by 'replacement'
query.setDetail(tunedDetail.copy());
} else {
// tune by 'addition'
tuned = query.tuneFetchProperties(tunedDetail);
}
if (tuned) {
query.setAutoTuned(true);
}
return tuned;
}
public String toString() {
return tunedDetail.toString();
}
}
@@ -93,6 +93,7 @@ public class ChangeJsonBuilder {
* For insert and update write the new/old values.
*/
protected void writeBeanValues(JsonGenerator gen, BeanChange bean) throws IOException {
if (bean.getType() != ChangeType.DELETE) {
gen.writeFieldName("values");
gen.writeStartObject();
@@ -9,7 +9,6 @@ import com.avaje.ebean.plugin.SpiServerPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import java.util.Properties;
@@ -59,9 +58,11 @@ public class DefaultChangeLogListener implements ChangeLogListener, SpiServerPlu
jsonBuilder = new ChangeJsonBuilder(server.json());
Properties properties = server.getServerConfig().getProperties();
String bufferSize = properties.getProperty("ebean.changeLog.bufferSize");
if (bufferSize != null) {
defaultBufferSize = Integer.parseInt(bufferSize);
if (properties != null) {
String bufferSize = properties.getProperty("ebean.changeLog.bufferSize");
if (bufferSize != null) {
defaultBufferSize = Integer.parseInt(bufferSize);
}
}
}
@@ -78,17 +79,17 @@ public class DefaultChangeLogListener implements ChangeLogListener, SpiServerPlu
@Override
public void log(ChangeSet changeSet) {
try {
List<BeanChange> changes = changeSet.getChanges();
for (int i = 0; i < changes.size(); i++) {
// log each bean change as a separate log entry
BeanChange beanChange = changes.get(i);
List<BeanChange> changes = changeSet.getChanges();
for (int i = 0; i < changes.size(); i++) {
// log each bean change as a separate log entry
BeanChange beanChange = changes.get(i);
try {
StringWriter writer = new StringWriter(getBufferSize(beanChange));
jsonBuilder.writeBeanJson(writer, beanChange, changeSet, i);
changeLog.info(writer.toString());
} catch (Exception e) {
logger.error("Exception logging beanChange " + beanChange.toString(), e);
}
} catch (IOException e) {
logger.error("Exception sending changeSet " + changeSet.toString(), e);
}
}
@@ -13,6 +13,8 @@ import com.avaje.ebean.event.TransactionEventListener;
import com.avaje.ebean.event.changelog.ChangeLogListener;
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
import com.avaje.ebean.event.changelog.ChangeLogRegister;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.util.ClassPathSearchMatcher;
import org.slf4j.Logger;
@@ -69,10 +71,14 @@ public class BootupClasses implements ClassPathSearchMatcher {
private Class<?> changeLogPrepareClass;
private Class<?> changeLogListenerClass;
private Class<?> changeLogRegisterClass;
private Class<?> readAuditPrepareClass;
private Class<?> readAuditLoggerClass;
private ChangeLogPrepare changeLogPrepare;
private ChangeLogListener changeLogListener;
private ChangeLogRegister changeLogRegister;
private ReadAuditPrepare readAuditPrepare;
private ReadAuditLogger readAuditLogger;
public BootupClasses() {
}
@@ -192,6 +198,17 @@ public class BootupClasses implements ClassPathSearchMatcher {
}
public void addChangeLogInstances(ServerConfig serverConfig) {
readAuditPrepare = serverConfig.getReadAuditPrepare();
readAuditLogger = serverConfig.getReadAuditLogger();
if (readAuditPrepare == null && readAuditPrepareClass != null) {
readAuditPrepare = (ReadAuditPrepare)create(readAuditPrepareClass, false);
}
if (readAuditLogger == null && readAuditLoggerClass != null) {
readAuditLogger = (ReadAuditLogger)create(readAuditLoggerClass, false);
}
changeLogListener = serverConfig.getChangeLogListener();
changeLogRegister = serverConfig.getChangeLogRegister();
changeLogPrepare = serverConfig.getChangeLogPrepare();
@@ -262,6 +279,14 @@ public class BootupClasses implements ClassPathSearchMatcher {
return changeLogRegister;
}
public ReadAuditPrepare getReadAuditPrepare() {
return readAuditPrepare;
}
public ReadAuditLogger getReadAuditLogger() {
return readAuditLogger;
}
public List<BeanQueryAdapter> getBeanQueryAdapters() {
// add class registered BeanQueryAdapter to the already created instances
for (Class<?> cls : beanQueryAdapterList) {
@@ -440,6 +465,15 @@ public class BootupClasses implements ClassPathSearchMatcher {
interesting = true;
}
if (ReadAuditPrepare.class.isAssignableFrom(cls)) {
readAuditPrepareClass = cls;
interesting = true;
}
if (ReadAuditLogger.class.isAssignableFrom(cls)) {
readAuditLoggerClass = cls;
interesting = true;
}
return interesting;
}
@@ -0,0 +1,14 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.bean.CallStack;
/**
* Creates CallStack based on the stack trace.
*/
public interface CallStackFactory {
/**
* Create and return the CallStack given the stack trace elements.
*/
CallStack createCallStack(StackTraceElement[] finalTrace);
}
@@ -421,7 +421,7 @@ public class DefaultBeanLoader {
query.select("id," + embeddedBeanPropertyName);
}
// don't collect autoFetch usage profiling information
// don't collect AutoTune usage profiling information
// as we just copy the data out of these fetched beans
// and put the data into the original bean
query.setUsageProfiling(false);
@@ -0,0 +1,27 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.bean.CallStack;
/**
* Default CallStackFactory where the Hash function for StackTraceElement includes the line number.
*/
public class DefaultCallStackFactory implements CallStackFactory {
@Override
public CallStack createCallStack(StackTraceElement[] finalTrace) {
return new CallStack(finalTrace, finalTrace[0].hashCode(), pathHash(finalTrace));
}
/**
* Return the hash code for the path excluding the first element.
*/
private int pathHash(StackTraceElement[] callStack) {
int hc = 0;
for (int i = 1; i < callStack.length; i++) {
hc = 31 * hc + callStack[i].hashCode();
}
return hc;
}
}
@@ -24,20 +24,16 @@ import com.avaje.ebeaninternal.server.lib.sql.SimpleDataSourceAlert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Default Server side implementation of ServerFactory.
@@ -50,7 +46,7 @@ public class DefaultContainer implements SpiContainer {
private final JndiDataSourceLookup jndiDataSourceFactory;
private final AtomicInteger serverId = new AtomicInteger(1);
// private final AtomicInteger serverId = new AtomicInteger(1);
public DefaultContainer(ContainerConfig containerConfig) {
@@ -119,7 +115,7 @@ public class DefaultContainer implements SpiContainer {
ServerCacheManager cacheManager = getCacheManager(serverConfig);
int uniqueServerId = serverId.incrementAndGet();
// int uniqueServerId = serverId.incrementAndGet();
SpiBackgroundExecutor bgExecutor = createBackgroundExecutor(serverConfig);
XmlConfigLoader xmlConfigLoader = new XmlConfigLoader(null);
@@ -131,18 +127,18 @@ public class DefaultContainer implements SpiContainer {
cacheManager.init(server);
if (serverConfig.isRegisterJmxMBeans()) {
MBeanServer mbeanServer;
ArrayList<?> list = MBeanServerFactory.findMBeanServer(null);
if (list.size() == 0) {
// probably not running in a server
mbeanServer = MBeanServerFactory.createMBeanServer();
} else {
// use the first MBeanServer
mbeanServer = (MBeanServer) list.get(0);
}
server.registerMBeans(mbeanServer, uniqueServerId);
}
// if (serverConfig.isRegisterJmxMBeans()) {
// MBeanServer mbeanServer;
// ArrayList<?> list = MBeanServerFactory.findMBeanServer(null);
// if (list.size() == 0) {
// // probably not running in a server
// mbeanServer = MBeanServerFactory.createMBeanServer();
// } else {
// // use the first MBeanServer
// mbeanServer = (MBeanServer) list.get(0);
// }
// server.registerMBeans(mbeanServer, uniqueServerId);
// }
// generate and run DDL if required
// if there are any other tasks requiring action in their plugins, do them as well
@@ -12,8 +12,11 @@ import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.EncryptKeyManager;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.dbmigration.DdlGenerator;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.plugin.SpiBeanType;
import com.avaje.ebean.plugin.SpiServer;
@@ -33,8 +36,7 @@ import com.avaje.ebeaninternal.api.SpiQuery.Type;
import com.avaje.ebeaninternal.api.SpiSqlQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.TransactionEventTable;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebean.dbmigration.DdlGenerator;
import com.avaje.ebeaninternal.server.autotune.AutoTuneService;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -43,7 +45,6 @@ import com.avaje.ebeaninternal.server.deploy.DeployNamedQuery;
import com.avaje.ebeaninternal.server.deploy.DeployNamedUpdate;
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
import com.avaje.ebeaninternal.server.el.ElFilter;
import com.avaje.ebeaninternal.server.jmx.MAdminAutofetch;
import com.avaje.ebeaninternal.server.lib.ShutdownManager;
import com.avaje.ebeaninternal.server.query.CQuery;
import com.avaje.ebeaninternal.server.query.CQueryEngine;
@@ -69,9 +70,6 @@ import com.avaje.ebeaninternal.util.ParamTypeHelper.TypeInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.ArrayList;
@@ -95,20 +93,22 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private static final int IGNORE_LEADING_ELEMENTS = 5;
private static final String AVAJE_EBEAN = Ebean.class.getName().substring(0, 15);
private static final String COM_AVAJE_EBEAN = "com.avaje.ebean";
private static final String ORG_AVAJE_EBEAN = "org.avaje.ebean";
private final ServerConfig serverConfig;
private final String serverName;
private final DatabasePlatform databasePlatform;
private final AdminAutofetch adminAutofetch;
private final TransactionManager transactionManager;
private final TransactionScopeManager transactionScopeManager;
private final CallStackFactory callStackFactory = new DefaultCallStackFactory();
private final int maxCallStack;
/**
@@ -130,9 +130,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final BeanDescriptorManager beanDescriptorManager;
private final DiffHelp diffHelp;
private final AutoTuneService autoTuneService;
private final AutoFetchManager autoFetchManager;
private final ReadAuditPrepare readAuditPrepare;
private final ReadAuditLogger readAuditLogger;
private final CQueryEngine cqueryEngine;
@@ -152,21 +154,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final MetaInfoManager metaInfoManager;
/**
* The MBean name used to register Ebean.
*/
private String mbeanName;
/**
* The default PersistenceContextScope used if it is not explicitly set on a query.
*/
private final PersistenceContextScope defaultPersistenceContextScope;
/**
* The MBeanServer Ebean is registered with.
*/
private MBeanServer mbeanServer;
/**
* Flag set when the server has shutdown.
*/
@@ -211,7 +203,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.backgroundExecutor = config.getBackgroundExecutor();
this.serverName = serverConfig.getName();
this.diffHelp = new DiffHelp(serverConfig.isDiffFlatMode());
this.lazyLoadBatchSize = serverConfig.getLazyLoadBatchSize();
this.queryBatchSize = serverConfig.getQueryBatchSize();
this.cqueryEngine = config.getCQueryEngine();
@@ -235,8 +226,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.queryEngine = config.createOrmQueryEngine();
this.relationalQueryEngine = config.createRelationalQueryEngine();
this.autoFetchManager = config.createAutoFetchManager(this);
this.adminAutofetch = new MAdminAutofetch(autoFetchManager);
this.autoTuneService = config.createAutoTuneService(this);
this.readAuditPrepare = config.getReadAuditPrepare();
this.readAuditLogger = config.getReadAuditLogger();
this.beanLoader = new DefaultBeanLoader(this);
this.jsonContext = config.createJsonContext(this);
@@ -253,6 +245,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private void configureServerPlugins() {
autoTuneService.startup();
for (SpiServerPlugin plugin : serverPlugins) {
plugin.configure(this);
}
@@ -345,12 +339,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return ddlGenerator;
}
public AdminAutofetch getAdminAutofetch() {
return adminAutofetch;
@Override
public AutoTune getAutoTune() {
return autoTuneService;
}
public AutoFetchManager getAutoFetchManager() {
return autoFetchManager;
@Override
public ReadAuditPrepare getReadAuditPrepare() {
return readAuditPrepare;
}
@Override
public ReadAuditLogger getReadAuditLogger() {
return readAuditLogger;
}
/**
@@ -373,41 +374,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
public void start() {
}
public void registerMBeans(MBeanServer mbeanServer, int uniqueServerId) {
this.mbeanServer = mbeanServer;
this.mbeanName = "Ebean:server=" + serverName + uniqueServerId;
ObjectName autofetchName;
try {
autofetchName = new ObjectName(mbeanName + ",key=AutoFetch");
} catch (Exception e) {
String msg = "Failed to register the JMX beans for Ebean server [" + serverName + "].";
logger.error(msg, e);
return;
}
try {
mbeanServer.registerMBean(adminAutofetch, autofetchName);
} catch (InstanceAlreadyExistsException e) {
// tomcat webapp reloading
String msg = "JMX beans for Ebean server [" + serverName + "] already registered. Will try unregister/register" + e.getMessage();
logger.warn(msg);
try {
mbeanServer.unregisterMBean(autofetchName);
mbeanServer.registerMBean(adminAutofetch, autofetchName);
} catch (Exception ae) {
String amsg = "Unable to unregister/register the JMX beans for Ebean server [" + serverName + "].";
logger.error(amsg, ae);
}
} catch (Exception e) {
String msg = "Error registering MBean[" + mbeanName + "]";
logger.error(msg, e);
}
}
/**
* Shutting down via JVM Shutdown hook.
*/
@@ -439,16 +405,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return;
}
shutdownPlugins();
try {
if (mbeanServer != null) {
mbeanServer.unregisterMBean(new ObjectName(mbeanName + ",key=AutoFetch"));
}
} catch (Exception e) {
logger.error("Error unregistering Ebean " + mbeanName, e);
}
// shutdown autofetch profile collection
autoFetchManager.shutdown();
autoTuneService.shutdown();
// shutdown background threads
backgroundExecutor.shutdown();
// shutdown DataSource (if its an Ebean one)
@@ -560,7 +518,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
BeanDescriptor<?> desc = getBeanDescriptor(a.getClass());
return diffHelp.diff(a, b, desc);
return DiffHelp.diff(a, b, desc);
}
/**
@@ -1100,7 +1058,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
public <T> SpiOrmQueryRequest<T> createQueryRequest(BeanDescriptor<T> desc, SpiQuery<T> query, Transaction t) {
if (desc.isAutoFetchTunable() && !query.isSqlSelect() && !autoFetchManager.tuneQuery(query)) {
if (desc.isAutoTunable() && !query.isSqlSelect() && !autoTuneService.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
query.setDefaultSelectClause();
@@ -1108,13 +1066,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (query.selectAllForLazyLoadProperty()) {
// we need to select all properties to ensure the lazy load property
// was included (was not included by default or via autofetch).
// was included (was not included by default or via autoTune).
if (logger.isDebugEnabled()) {
logger.debug("Using selectAllForLazyLoadProperty");
}
}
// if determine cost and no origin for Autofetch
// if determine cost and no origin for AutoTune
if (query.getParentNode() == null) {
query.setOrigin(createCallStack());
}
@@ -1338,6 +1296,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
public <T> int delete(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.DELETE, query, t);
try {
request.initTransIfRequired();
return request.delete();
} finally {
request.endTransIfRequired();
}
}
public <T> FutureRowCount<T> findFutureRowCount(Query<T> q, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) q).copy();
@@ -1382,6 +1351,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
// FutureList query always run in it's own persistence content
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
if (!spiQuery.isDisableReadAudit()) {
BeanDescriptor<T> desc = beanDescriptorManager.getBeanDescriptor(spiQuery.getBeanType());
desc.readAuditFutureList(spiQuery);
}
// Create a new transaction solely to execute the findList() at some future time
Transaction newTxn = createTransaction();
CallableQueryList<T> call = new CallableQueryList<T>(this, spiQuery, newTxn);
@@ -1392,7 +1366,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction, int pageIndex, int pageSize) {
return new LimitOffsetPagedList<T>(this, (SpiQuery<T>)query, pageIndex, pageSize);
}
@@ -2060,7 +2034,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
// find the first non-avaje stackElement
for (; startIndex < stackTrace.length; startIndex++) {
if (!stackTrace[startIndex].getClassName().startsWith(AVAJE_EBEAN)) {
if (!stackTrace[startIndex].getClassName().startsWith(COM_AVAJE_EBEAN)) {
break;
}
}
for (; startIndex < stackTrace.length; startIndex++) {
if (!stackTrace[startIndex].getClassName().startsWith(ORG_AVAJE_EBEAN)) {
break;
}
}
@@ -2080,10 +2059,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
throw new RuntimeException("StackTraceElement size 0? stack: " + Arrays.toString(stackTrace));
}
return new CallStack(finalTrace);
return callStackFactory.createCallStack(finalTrace);
}
@Override
public JsonContext json() {
// immutable thread safe so return shared instance
@@ -1,14 +1,15 @@
package com.avaje.ebeaninternal.server.core;
import java.util.LinkedHashMap;
import java.util.Map;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import com.avaje.ebeaninternal.util.ValueUtil;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Helper to perform a diff given two beans of the same type.
@@ -18,10 +19,7 @@ import com.avaje.ebeaninternal.util.ValueUtil;
*/
public class DiffHelp {
private final boolean flatMode;
public DiffHelp(boolean flatMode) {
this.flatMode = flatMode;
private DiffHelp() {
}
/**
@@ -34,123 +32,73 @@ public class DiffHelp {
* This intentionally does not include as OneToMany or ManyToMany properties.
* </p>
*/
public Map<String, ValuePair> diff(Object newBean, Object oldBean, BeanDescriptor<?> desc) {
public static Map<String, ValuePair> diff(Object newBean, Object oldBean, BeanDescriptor<?> desc) {
if (!(newBean instanceof EntityBean)) {
throw new IllegalArgumentException("First bean expected to be an enhanced EntityBean? bean:"+newBean);
throw new IllegalArgumentException("First bean expected to be an enhanced EntityBean? bean:" + newBean);
}
if (oldBean != null) {
if (!(oldBean instanceof EntityBean)) {
throw new IllegalArgumentException("Second bean expected to be an enhanced EntityBean? bean:"+oldBean);
throw new IllegalArgumentException("Second bean expected to be an enhanced EntityBean? bean:" + oldBean);
}
if (!newBean.getClass().isAssignableFrom(oldBean.getClass())) {
throw new IllegalArgumentException("Second bean not assignable to the first bean?");
}
}
if (oldBean == null) {
return ((EntityBean) newBean)._ebean_getIntercept().getDirtyValues();
}
Map<String, ValuePair> map = new LinkedHashMap<String, ValuePair>();
diff(null, map, (EntityBean) newBean, (EntityBean) oldBean, desc);
return map;
}
public void diff(String prefix, Map<String, ValuePair> map, EntityBean newBean, EntityBean oldBean, BeanDescriptor<?> desc) {
if (oldBean == null) {
return ((EntityBean) newBean)._ebean_getIntercept().getDirtyValues();
}
// check the simple properties
BeanProperty[] base = desc.propertiesBaseScalar();
for (int i = 0; i < base.length; i++) {
Object newVal = (newBean == null) ? null : base[i].getValue(newBean);
Object oldVal = (oldBean == null) ? null : base[i].getValue(oldBean);
if (!ValueUtil.areEqual(newVal, oldVal)) {
String propName = (prefix == null) ? base[i].getName() : prefix + base[i].getName();
map.put(propName, new ValuePair(newVal, oldVal));
}
}
return desc.diff((EntityBean) newBean, (EntityBean) oldBean);
}
diffAssocOne(prefix, newBean, oldBean, desc, map);
diffEmbedded(prefix, newBean, oldBean, desc, map);
}
/**
* Check the Embedded bean properties for differences.
* <p>
* If ANY of the properties are different then the whole Embedded bean is
* determined to be different as is added to the map.
* </p>
*/
private void diffEmbedded(String prefix, EntityBean newBean, EntityBean oldBean, BeanDescriptor<?> desc, Map<String, ValuePair> map) {
/**
* Flattens an existing diff map converting assoc one beans into the associated id changes.
*/
public static Map<String, ValuePair> flatten(Map<String, ValuePair> values, BeanDescriptor<?> desc) {
BeanPropertyAssocOne<?>[] emb = desc.propertiesEmbedded();
Map<String, ValuePair> flattened = null;
for (int i = 0; i < emb.length; i++) {
EntityBean newVal = (EntityBean)emb[i].getValue(newBean);
EntityBean oldVal = (EntityBean)emb[i].getValue(oldBean);
if (!isBothNull(newVal, oldVal)) {
String propName = (prefix == null) ? emb[i].getName() : prefix + emb[i].getName();
if (isDiffNull(newVal, oldVal)) {
// one of the embedded beans is null
if (flatMode) {
BeanDescriptor<?> embDesc = emb[i].getTargetDescriptor();
diff(emb[i].getName()+".", map, newVal, oldVal, embDesc);
} else {
map.put(propName, new ValuePair(newVal, oldVal));
}
} else {
// recursively diff into the embedded bean
BeanDescriptor<?> embDesc = emb[i].getTargetDescriptor();
diff(emb[i].getName()+".", map, newVal, oldVal, embDesc);
}
}
}
}
/**
* If the properties are different by null OR if the id value is different,
* then add the Assoc One bean to the map.
*/
private void diffAssocOne(String prefix, EntityBean newBean, EntityBean oldBean, BeanDescriptor<?> desc, Map<String, ValuePair> map) {
BeanPropertyAssocOne<?>[] ones = desc.propertiesOne();
for (int i = 0; i < ones.length; i++) {
Object newVal = ones[i].getValue(newBean);
Object oldVal = ones[i].getValue(oldBean);
if (!isBothNull(newVal, oldVal)) {
BeanDescriptor<?> oneDesc = ones[i].getTargetDescriptor();
Object newId = (newVal == null) ? null : oneDesc.getId((EntityBean)newVal);
Object oldId = (oldVal == null) ? null : oneDesc.getId((EntityBean)oldVal);
if (!ValueUtil.areEqual(newId, oldId)) {
String propName = (prefix == null) ? ones[i].getName() : prefix + ones[i].getName();
// the ids are different
if (flatMode) {
String idName = oneDesc.getIdProperty().getName();
map.put(propName + "." + idName, new ValuePair(newId, oldId));
} else {
map.put(propName, new ValuePair(newVal, oldVal));
Iterator<Map.Entry<String, ValuePair>> iterator = values.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, ValuePair> entry = iterator.next();
BeanProperty beanProperty = desc.getBeanProperty(entry.getKey());
if (beanProperty instanceof BeanPropertyAssocMany) {
// filter out assoc many bean properties
iterator.remove();
} else if (beanProperty instanceof BeanPropertyAssocOne) {
BeanPropertyAssocOne assoc = (BeanPropertyAssocOne)beanProperty;
if (!assoc.isEmbedded()) {
// flatten for assoc one beans
if (flattened == null) {
flattened = new LinkedHashMap<String, ValuePair>();
}
flattenToId(flattened, entry, beanProperty, assoc);
iterator.remove();
}
}
}
}
}
private boolean isBothNull(Object newVal, Object oldVal) {
return newVal == null && oldVal == null;
}
if (flattened != null) {
values.putAll(flattened);
}
private boolean isDiffNull(Object newVal, Object oldVal) {
if (newVal == null) {
return oldVal != null;
} else {
return oldVal == null;
}
}
return values;
}
private static void flattenToId(Map<String, ValuePair> flattened, Map.Entry<String, ValuePair> entry, BeanProperty beanProperty, BeanPropertyAssocOne assoc) {
BeanDescriptor<?> oneDesc = assoc.getTargetDescriptor();
ValuePair value = entry.getValue();
Object newId = value.getNewValue() == null ? null : oneDesc.getId((EntityBean)value.getNewValue());
Object oldId = value.getOldValue() == null ? null : oneDesc.getId((EntityBean)value.getOldValue());
String propName = beanProperty.getName() + "." + oneDesc.getIdProperty().getName();
flattened.put(propName, new ValuePair(newId, oldId));
}
}
@@ -1,102 +0,0 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Helper to perform a 'diff' for an insert.
* <p>
* This intentionally does not include any OneToMany or ManyToMany properties.
* </p>
*/
public class DiffHelpInsert {
private DiffHelpInsert() {
}
/**
* Return a map of the differences between a and b.
* <p>
* A and B must be of the same type. B can be null, in which case the 'dirty
* diff' of a is returned.
* </p>
* <p>
* This intentionally does not include as OneToMany or ManyToMany properties.
* </p>
*/
public static Map<String, ValuePair> diff(EntityBean newBean, BeanDescriptor<?> desc) {
Map<String, ValuePair> map = new LinkedHashMap<String, ValuePair>();
diff(null, map, newBean, desc);
return map;
}
private static void diff(String prefix, Map<String, ValuePair> map, EntityBean newBean, BeanDescriptor<?> desc) {
// check the simple properties
BeanProperty[] base = desc.propertiesBaseScalar();
for (int i = 0; i < base.length; i++) {
Object newVal = (newBean == null) ? null : base[i].getValue(newBean);
if (newVal != null) {
String propName = (prefix == null) ? base[i].getName() : prefix + base[i].getName();
map.put(propName, new ValuePair(newVal, null));
}
}
diffAssocOne(prefix, newBean, desc, map);
diffEmbedded(prefix, newBean, desc, map);
}
/**
* Check the Embedded bean properties for differences.
* <p>
* If ANY of the properties are different then the whole Embedded bean is
* determined to be different as is added to the map.
* </p>
*/
private static void diffEmbedded(String prefix, EntityBean newBean, BeanDescriptor<?> desc, Map<String, ValuePair> map) {
BeanPropertyAssocOne<?>[] emb = desc.propertiesEmbedded();
for (int i = 0; i < emb.length; i++) {
EntityBean newVal = (EntityBean) emb[i].getValue(newBean);
if (newVal != null) {
String propName = (prefix == null) ? emb[i].getName() : prefix + emb[i].getName();
BeanDescriptor<?> embDesc = emb[i].getTargetDescriptor();
diff(propName + ".", map, newVal, embDesc);
}
}
}
/**
* If the properties are different by null OR if the id value is different,
* then add the Assoc One bean to the map.
*/
private static void diffAssocOne(String prefix, EntityBean newBean, BeanDescriptor<?> desc, Map<String, ValuePair> map) {
BeanPropertyAssocOne<?>[] ones = desc.propertiesOne();
for (int i = 0; i < ones.length; i++) {
Object newVal = ones[i].getValue(newBean);
if (newVal != null) {
BeanDescriptor<?> oneDesc = ones[i].getTargetDescriptor();
Object newId = oneDesc.getId((EntityBean)newVal);
if (newId != null) {
String propName = (prefix == null) ? ones[i].getName() : prefix + ones[i].getName();
String idName = oneDesc.getIdProperty().getName();
map.put(propName + "." + idName, new ValuePair(newId, null));
}
}
}
}
}
@@ -9,15 +9,17 @@ import com.avaje.ebean.config.dbplatform.DbHistorySupport;
import com.avaje.ebean.event.changelog.ChangeLogListener;
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
import com.avaje.ebean.event.changelog.ChangeLogRegister;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebean.plugin.SpiServerPlugin;
import com.avaje.ebean.text.json.JsonContext;
import com.avaje.ebeaninternal.api.SpiBackgroundExecutor;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManagerFactory;
import com.avaje.ebeaninternal.server.autotune.AutoTuneService;
import com.avaje.ebeaninternal.server.autotune.service.AutoTuneServiceFactory;
import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogListener;
import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogPrepare;
import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogRegister;
import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogListener;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeaninternal.server.deploy.DeployOrmXml;
@@ -32,11 +34,12 @@ import com.avaje.ebeaninternal.server.persist.DefaultPersister;
import com.avaje.ebeaninternal.server.query.CQueryEngine;
import com.avaje.ebeaninternal.server.query.DefaultOrmQueryEngine;
import com.avaje.ebeaninternal.server.query.DefaultRelationalQueryEngine;
import com.avaje.ebeaninternal.server.resource.ResourceManager;
import com.avaje.ebeaninternal.server.resource.ResourceManagerFactory;
import com.avaje.ebeaninternal.server.readaudit.DefaultReadAuditLogger;
import com.avaje.ebeaninternal.server.readaudit.DefaultReadAuditPrepare;
import com.avaje.ebeaninternal.server.text.json.DJsonContext;
import com.avaje.ebeaninternal.server.transaction.AutoCommitTransactionManager;
import com.avaje.ebeaninternal.server.transaction.DefaultTransactionScopeManager;
import com.avaje.ebeaninternal.server.transaction.ExplicitTransactionManager;
import com.avaje.ebeaninternal.server.transaction.ExternalTransactionScopeManager;
import com.avaje.ebeaninternal.server.transaction.JtaTransactionManager;
import com.avaje.ebeaninternal.server.transaction.TransactionManager;
@@ -55,8 +58,6 @@ import java.util.Map;
/**
* Used to extend the ServerConfig with additional objects used to configure and
* construct an EbeanServer.
*
* @author rbygrave
*/
public class InternalConfiguration {
@@ -68,8 +69,6 @@ public class InternalConfiguration {
private final DeployInherit deployInherit;
private final ResourceManager resourceManager;
private final DeployOrmXml deployOrmXml;
private final TypeManager typeManager;
@@ -106,8 +105,8 @@ public class InternalConfiguration {
private final List<SpiServerPlugin> plugins = new ArrayList<SpiServerPlugin>();
public InternalConfiguration(XmlConfig xmlConfig, ClusterManager clusterManager,
ServerCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses) {
ServerCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses) {
this.jsonFactory = serverConfig.getJsonFactory();
this.xmlConfig = xmlConfig;
@@ -120,8 +119,7 @@ public class InternalConfiguration {
this.typeManager = new DefaultTypeManager(serverConfig, bootupClasses);
this.resourceManager = ResourceManagerFactory.createResourceManager(serverConfig);
this.deployOrmXml = new DeployOrmXml(resourceManager.getResourceSource());
this.deployOrmXml = new DeployOrmXml();
this.deployInherit = new DeployInherit(bootupClasses);
this.deployCreateProperties = new DeployCreateProperties(typeManager);
@@ -157,7 +155,7 @@ public class InternalConfiguration {
*/
public <T> T plugin(T maybePlugin) {
if (maybePlugin instanceof SpiServerPlugin) {
plugins.add((SpiServerPlugin)maybePlugin);
plugins.add((SpiServerPlugin) maybePlugin);
}
return maybePlugin;
}
@@ -191,29 +189,67 @@ public class InternalConfiguration {
return plugin((listener != null) ? listener : new DefaultChangeLogListener());
}
/**
* Return the ReadAuditLogger implementation to use.
*/
public ReadAuditLogger getReadAuditLogger() {
ReadAuditLogger found = bootupClasses.getReadAuditLogger();
return plugin(found != null ? found : new DefaultReadAuditLogger());
}
/**
* Return the ReadAuditPrepare implementation to use.
*/
public ReadAuditPrepare getReadAuditPrepare() {
ReadAuditPrepare found = bootupClasses.getReadAuditPrepare();
return plugin(found != null ? found : new DefaultReadAuditPrepare());
}
/**
* For 'As Of' queries return the number of bind variables per predicate.
*/
private Binder getBinder(TypeManager typeManager, DatabasePlatform databasePlatform) {
JsonExpressionHandler jsonHandler = getJsonExpressionHandler(databasePlatform);
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
if (historySupport == null) {
return new Binder(typeManager, 0, false);
return new Binder(typeManager, 0, false, jsonHandler);
}
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause());
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause(), jsonHandler);
}
/**
* Return the JSON expression handler for the given database platform.
*/
private JsonExpressionHandler getJsonExpressionHandler(DatabasePlatform databasePlatform) {
String name = databasePlatform.getName();
if ("postgres".equalsIgnoreCase(name)) {
return new PostgresJsonExpression();
}
if ("oracle".equalsIgnoreCase(name)) {
return new OracleJsonExpression();
}
return new NotSupportedJsonExpression();
}
/**
* Create the TransactionManager taking into account autoCommit mode.
*/
private TransactionManager createTransactionManager() {
if (serverConfig.isExplicitTransactionBeginMode()) {
return new ExplicitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
}
if (isAutoCommitMode()) {
return new AutoCommitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
}
return new TransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
}
/**
* Return true if autoCommit mode is on.
*/
@@ -235,8 +271,8 @@ public class InternalConfiguration {
return xmlConfig;
}
public AutoFetchManager createAutoFetchManager(SpiEbeanServer server) {
return AutoFetchManagerFactory.create(server, serverConfig, resourceManager);
public AutoTuneService createAutoTuneService(SpiEbeanServer server) {
return AutoTuneServiceFactory.create(server, serverConfig);
}
public RelationalQueryEngine createRelationalQueryEngine() {
@@ -251,7 +287,6 @@ public class InternalConfiguration {
return new DefaultPersister(server, binder, beanDescriptorManager);
}
public ServerCacheManager getCacheManager() {
return cacheManager;
}
@@ -288,10 +323,6 @@ public class InternalConfiguration {
return deployInherit;
}
public ResourceManager getResourceManager() {
return resourceManager;
}
public DeployOrmXml getDeployOrmXml() {
return deployOrmXml;
}
@@ -0,0 +1,17 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.expression.Op;
/**
* Adds the db platform specific json expression.
*/
public interface JsonExpressionHandler {
/**
* Write the db platform specific json expression.
*/
void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value);
}
@@ -0,0 +1,15 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.expression.Op;
/**
* Not supported JSON expression handler.
*/
public class NotSupportedJsonExpression implements JsonExpressionHandler {
@Override
public void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
throw new RuntimeException("JSON expressions only supported on Postgres and Oracle");
}
}
@@ -0,0 +1,24 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.expression.Op;
/**
* Postgres JSON expression handler
*/
public class OracleJsonExpression implements JsonExpressionHandler {
@Override
public void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
if (operator == Op.EXISTS) {
request.append("json_exists(").append(propName).append(", '$.").append(path).append("')");
} else if (operator == Op.NOT_EXISTS) {
request.append("not json_exists(").append(propName).append(", '$.").append(path).append("')");
} else {
request.append("json_value(").append(propName).append(", '$.").append(path).append("')");
request.append(operator.bind());
}
}
}

Some files were not shown because too many files have changed in this diff Show More