Compare commits

..
Author SHA1 Message Date
Robin Bygrave f5cfce3f44 [maven-release-plugin] prepare release avaje-ebeanorm-6.13.3 2015-12-09 12:46:18 +13:00
Robin Bygrave 1c6363e85c #490 - Invalid SQL for generating ID from sequence on DB2 2015-12-09 12:44:27 +13:00
Robin Bygrave b8bcbd8af9 #489 - @Draftable - insert does not set 'draft' to true ... so subsequent update fails 2015-12-09 11:06:48 +13:00
Robin Bygrave d70768277b #488 - BeanPropertyAssocManyJsonHelp error when Jackson not in classPath 2015-12-09 11:00:21 +13:00
Robin Bygrave b39b4a2e8c #487 - DDL - @SoftDelete deleted column should have NOT NULL constraint in create table DDL 2015-12-09 10:27:04 +13:00
Robin Bygrave 93b2f4035c #485 - @SoftDelete with join to nullable relation (optional @ManyToOne for example) ... will incorrectly filter out row from result when FK value is null. 2015-12-09 10:12:25 +13:00
Robin Bygrave 31ee0c4582 #486 - Postgres DDL - Add "if exists" to alter table [if exists] drop constraint if exists .... 2015-12-09 10:06:03 +13:00
Robin Bygrave 6c2dad4851 [maven-release-plugin] prepare for next development iteration 2015-12-07 22:20:54 +13:00
Robin Bygrave ed190ede03 [maven-release-plugin] prepare release avaje-ebeanorm-6.13.2 2015-12-07 22:20:25 +13:00
Robin Bygrave f252b80c2d #476 - ENH: Enable ServiceConfig to use alternate ClassLoader - was ServiceConfig add setClassloader api --- part 3: Refactor with ClassLoadConfig 2015-12-07 22:13:27 +13:00
Robin Bygrave 2859ea657e #484 - JDBC4+ drivers do not need Class.forName() registration - removing that call 2015-12-07 21:02:00 +13:00
Robin Bygrave 664b1cbd75 #476 - ENH: Enable ServiceConfig to use alternate ClassLoader - was ServiceConfig add setClassloader api --- part 2 : Use current context classLoader 2015-12-07 17:41:56 +13:00
Robin Bygrave 8bc283fef4 #476 - ENH: Enable ServiceConfig to use alternate ClassLoader - was ServiceConfig add setClassloader api --- part 1 : protected methods 2015-12-07 17:37:59 +13:00
Robin Bygrave 7d85398139 #483 - ENH: Draftable - Add FK from live table back to draft table (for top level @Draftable tables) 2015-12-07 15:21:22 +13:00
Robin Bygrave f42be3d65a #482 - ENH: @Draftable - Save or update is not allowed on a 'live' bean - only draft beans ... add this check when @Draft property is used 2015-12-07 12:09:10 +13:00
Robin Bygrave ff8e5af4e5 #481 - ENH: @Draftable ... add support for transient @Draft boolean draft; property ... used to distinguish a 'live' instance from a 'draft' instance. 2015-12-07 11:11:22 +13:00
Robin Bygrave 526d67eeb4 No effective change - whitespace 2015-12-07 11:02:32 +13:00
Robin Bygrave 374fc8d702 #480 - @History trigger on @Draftable does not exclude the @DraftOnly and @DraftDirty properties - ERROR: column "dirty" of relation "link_history" does not exist Where: PL/pgSQL function link_history_version() 2015-12-07 09:44:34 +13:00
Robin Bygrave 9c6c59afab #479 - Insert of @Draftable with getGeneratedKeys ... PersistenceException executing insert 2015-12-07 09:21:26 +13:00
Robin Bygrave c3eae0d3a5 #477 - findUnique() should throw NonUniqueResultException and not PersistenceException 2015-12-04 11:12:45 +13:00
Robin Bygrave 25ec73364e [maven-release-plugin] prepare for next development iteration 2015-12-03 21:41:41 +13:00
Robin Bygrave c7818e7f23 [maven-release-plugin] prepare release avaje-ebeanorm-6.13.1 2015-12-03 21:41:20 +13:00
Robin Bygrave 8318bbc4ca #118 - Additional: Add query includeSoftDeletes() asDraft() to ExpressionList (for fluid style when added after predicates) 2015-12-03 17:42:26 +13:00
Robin Bygrave 337646a6ce No effective change - javadoc update 2015-12-03 16:47:19 +13:00
Robin Bygrave fa6ee93e5b Bump pom to 6.13.1-SNAPSHOT 2015-12-03 16:35:43 +13:00
Robin Bygrave 0fe691112f Merge branch 'softDelete1' into softDelete2 2015-12-03 00:33:47 +13:00
Robin Bygrave 6946ff07e0 [maven-release-plugin] prepare for next development iteration 2015-12-02 23:48:15 +13:00
Robin Bygrave 7c3d052da0 [maven-release-plugin] prepare release avaje-ebeanorm-6.12.3 2015-12-02 23:47:49 +13:00
Robin Bygrave aea7a7fd65 #118 - ENH: Add support for for Soft Deletes (Logical Deletion) ... 2015-12-02 23:46:26 +13:00
Robin Bygrave f39a684c8e #118 - ENH: Add support for for Soft Deletes (Logical Deletion) ... 2015-12-02 21:38:34 +13:00
Robin Bygrave 3cb5854786 #475 - Unique constraint name not unique when using @Draftable 2015-12-02 07:57:27 +13:00
Robin Bygrave e8192723cc [maven-release-plugin] prepare for next development iteration 2015-12-01 23:25:26 +13:00
Robin Bygrave 3787748eae [maven-release-plugin] prepare release avaje-ebeanorm-6.12.2 2015-12-01 23:25:00 +13:00
Robin Bygrave 0ad52cf6a7 #118 - ENH: Add support for for Soft Deletes (Logical Deletion) ... - initial 2015-12-01 23:21:11 +13:00
Robin Bygrave 51bee836d4 #472 - DDL column ordering for @WhoCreated / @WhoModified. These columns should appear in the DDL with the @WhenCreated/@WhenModified columns. 2015-12-01 10:19:36 +13:00
Robin Bygrave 9193b6b971 No effective change - remove whitespace 2015-12-01 09:32:09 +13:00
Robin Bygrave e48b9fab0f No effective change - spelling in javadoc 2015-12-01 08:08:14 +13:00
Robin Bygrave a14ca24b33 [maven-release-plugin] prepare for next development iteration 2015-11-30 23:25:13 +13:00
Robin Bygrave 761f43d6a1 [maven-release-plugin] prepare release avaje-ebeanorm-6.12.1 2015-11-30 23:24:46 +13:00
Robin Bygrave 737f9a893e Bump pom - 6.12.1-SNAPSHOT 2015-11-30 23:23:58 +13:00
Robin Bygrave 579812d717 #455 - Should not generate "FOR UPDATE" for delete query 2015-11-30 16:50:26 +13:00
Robin Bygrave fae39fe650 #291 - ENH: Add ability to validate a query or property expression - Was Please check column exist on entity 2015-11-30 15:10:22 +13:00
Robin Bygrave debd8ad617 #465 - The encrypt key is logged out in sql statement - when included in where predicate 2015-11-30 12:25:25 +13:00
Robin Bygrave 6450ba219c #469 - Embedded bean made dirty after a prior insert does not trigger dirty state on 'outer' bean 2015-11-27 18:19:59 +13:00
Robin Bygrave a578eda2b1 #467 - Delete bean with unloaded version property throws OptimisticLockException - API Change, return boolean 2015-11-26 16:09:25 +13:00
Robin Bygrave a2ee0bfdbe #467 - Delete bean with unloaded version property throws OptimisticLockException 2015-11-26 15:12:59 +13:00
Robin Bygrave 872f0e21cb #466 - Delete of @Draftable bean ... means also delete matching live bean (aka delete has automatic publish behavior) 2015-11-26 00:49:18 +13:00
Robin Bygrave 8620dbf337 #464 - ENH: Add draftRestore(Class<?> beanType, Object id) ... to refresh a draft from the live (@Draftable feature) 2015-11-25 23:36:17 +13:00
Robin Bygrave 4f51a3b130 #463 - ENH: Add @DraftReset support - property that is set to null on the draft been when it is published (@Draftable feature) 2015-11-25 11:06:14 +13:00
Robin Bygrave 5f194f066a [maven-release-plugin] prepare for next development iteration 2015-11-24 01:04:26 +13:00
Robin Bygrave 31f6e56333 [maven-release-plugin] prepare release avaje-ebeanorm-6.11.1 2015-11-24 01:04:04 +13:00
Robin Bygrave 0efbe061ab #461 - ENH: Add @Draftable support - optimise publish query 2015-11-24 01:01:37 +13:00
Robin Bygrave 555e5ba6fd #461 - ENH: Add @Draftable support - API sans transaction 2015-11-24 01:01:14 +13:00
Robin Bygrave 5d3da60828 #461 - ENH: Add @Draftable support - delete 2015-11-24 00:46:45 +13:00
Robin Bygrave 2ac0d136e6 Bump pom to 6.11.1-SNAPSHOT 2015-11-24 00:02:35 +13:00
Robin Bygrave 97462cb21a #461 - ENH: Add @Draftable support - delete 2015-11-24 00:01:28 +13:00
Robin Bygrave afaacd4eeb #461 - ENH: Add @Draftable support - javadoc 2015-11-23 23:26:09 +13:00
Robin Bygrave 5e8afbe50d #461 - ENH: Add @Draftable support - @DraftDirty 2015-11-23 23:11:15 +13:00
Robin Bygrave db371cbc6a #461 - ENH: Add @Draftable support - publish 2015-11-23 21:28:58 +13:00
Robin Bygrave fd9a5167b1 #461 - ENH: Add @Draftable support - load context 2015-11-23 10:07:09 +13:00
Robin Bygrave 7b76e13bce #461 - ENH: Add @Draftable support - phase 1 2015-11-19 20:44:25 +13:00
Robin Bygrave 42e7103aa6 #458 - Support use of test-ebean.properties without an existing ebean.properties 2015-11-12 10:17:30 +13:00
Robin Bygrave 29ca678f01 No effective change - remove unused code 2015-11-12 09:59:36 +13:00
Robin Bygrave cca6143420 No effective change - code format 2015-11-12 09:48:18 +13:00
Robin Bygrave fe7b110577 #457 - When ebean.properties does not exist do not log error message 2015-11-10 09:47:33 +13:00
Robin Bygrave 16df55eafa [maven-release-plugin] prepare for next development iteration 2015-11-09 14:44:45 +13:00
Robin Bygrave 905a0ce9ec [maven-release-plugin] prepare release avaje-ebeanorm-6.10.4 2015-11-09 14:44:05 +13:00
Robin Bygrave bfa8837e61 #451 - avaje-ebeanorm-jackson - com.fasterxml.jackson.core.JsonParseException: Expecting start_array event but got START_OBJECT at 2015-11-05 16:38:22 +13:00
Robin Bygrave 8d5845b9a2 [maven-release-plugin] prepare for next development iteration 2015-11-04 22:10:38 +13:00
Robin Bygrave 87a7d4958e [maven-release-plugin] prepare release avaje-ebeanorm-6.10.3 2015-11-04 22:10:13 +13:00
Robin Bygrave 511ebd8a29 #450 - @ManyToMany with cascade and jdbc batch - ensure flush occurs 2015-11-03 16:31:27 +13:00
Robin Bygrave 11f4c8d7a3 #449 - Remove whitespace from generated Postgres trigger DDL (as Play evolution not parsing it) 2015-11-03 16:28:00 +13:00
Robin Bygrave d417a92843 [maven-release-plugin] prepare for next development iteration 2015-11-02 16:42:33 +13:00
Robin Bygrave 4bd5e52df3 [maven-release-plugin] prepare release avaje-ebeanorm-6.10.2 2015-11-02 16:42:10 +13:00
Robin Bygrave 788a200d16 #445 - Multiple @*ToMany relations referencing the same type 2015-11-02 14:11:23 +13:00
Robin Bygrave e18d2004dc #448 - ENH: Postgres Support for UUID mapping to native DB UUID type (relating to #430) 2015-11-02 12:57:47 +13:00
Robin Bygrave b326724139 #443 - ENH: Enum mapping - Add annotation @DbEnumValue that can be used to on a enum method which provides DB values 2015-10-30 21:42:21 +13:00
Robin Bygrave fc0b47342b #436 - @DiscriminatorColumn mapped also as @Column leads to NullPointerException 2015-10-29 22:01:28 +13:00
Robin Bygrave ec1a68a133 #435 - Duplicate foreign key name with ManyToMany relations 2015-10-29 17:44:29 +13:00
Robin Bygrave 8ca12521d7 No effective change - reduce debug level 2015-10-29 17:43:54 +13:00
Robin Bygrave 2362ef4130 [maven-release-plugin] prepare for next development iteration 2015-10-29 09:01:17 +13:00
Robin Bygrave 7e233edcc3 [maven-release-plugin] prepare release avaje-ebeanorm-6.10.1 2015-10-29 09:00:55 +13:00
Robin Bygrave a8b98282a1 Bump pom to 6.10.1-SNAPSHOT 2015-10-29 09:00:00 +13:00
Robin Bygrave b4506aa718 #444 - Spring Boot classpath scanning not finding entity beans - part2 - embedded jar scanning 2015-10-29 08:59:06 +13:00
Robin Bygrave 848a3b8888 #444 - Spring Boot classpath scanning not finding entity beans 2015-10-28 15:45:09 +13:00
Robin Bygrave 67c711d75f #442 - @JsonIgnore / @Expose ... not working on @OneToMany or @ManyToMany - was - @Transient field on @MappedSuperclass is not ignored by toJson() 2015-10-28 09:10:19 +13:00
Robin Bygrave 3d62f58930 #439 - @EnumValue("01") stored as integer rather than string - ENum stores strings as numerics? 2015-10-28 08:53:14 +13:00
Robin Bygrave a30948d38d #438 - findPagedList and getTotalRowCount, the query.getAlias() is null 2015-10-28 08:15:15 +13:00
Robin Bygrave a8232d2d8b #441 - ENH: Add support for test-ebean.properties ... for mechanism to configure ebean to run tests 2015-10-27 00:17:47 +13:00
Robin Bygrave d4b8f23a5a Fix test - MySql limitations with delete query syntax 2015-10-26 21:52:35 +13:00
Robin Bygrave bcf2a44187 #430 - ENH: Support for UUID mapping to native DB UUID type (Postgres, H2) 2015-10-26 20:52:16 +13:00
Robin Bygrave f3102ba02c #432 - Delete using Query does not commit 2015-10-22 13:29:21 +13:00
Robin Bygrave 0351eab3a7 #431 - Annotation PrivateOwned seems not enough to update an change in the list of children (fix) 2015-10-22 11:10:41 +13:00
Robin Bygrave 409e2b75ce #431 - Add failing test ... Annotation PrivateOwned seems not enough to update an change in the list of children 2015-10-22 09:27:32 +13:00
Robin Bygrave dc968f16bf Q: Ebean @ManyToMany(cascade=REMOVE) described #429 2015-10-06 14:38:05 +13:00
Robin Bygrave bbbb6482fb [maven-release-plugin] prepare for next development iteration 2015-10-02 13:03:34 +13:00
Robin Bygrave 001968fcf8 [maven-release-plugin] prepare release avaje-ebeanorm-6.9.1 2015-10-02 13:03:07 +13:00
Robin Bygrave 6abcb186a4 Bump pom to 6.9.1-SNAPSHOT 2015-10-02 13:02:13 +13:00
Robin Bygrave f250447fbe Add test for ManyToMany delete scenario 2015-10-01 22:21:47 +13:00
Robin Bygrave bfa86f241b #427 - ENH: Add support for Removing ShutdownManager.shutdownHook ... manually control entire shutdown. 2015-10-01 10:33:09 +13:00
Robin Bygrave 19459b71b4 Fix for #425 - Extra instance of root level bean with findIterate() and fetch of OneToMany relationship - Ebean parses the first bean correctly and the rest not 2015-09-30 23:35:10 +13:00
Robin Bygrave 5e97c3bbbc #425 - Extra instance of root level bean with findIterate() and fetch of OneToMany relationship - Ebean parses the first bean correctly and the rest not 2015-09-30 23:24:53 +13:00
Robin Bygrave 986eadd023 #424 - DDL generation - support turning off lower casing of table and column names DbConstraintNaming, DbConstraintNormalise 2015-09-30 22:44:47 +13:00
Robin Bygrave c6787d0901 Update javadoc 2015-09-30 17:26:30 +13:00
Robin Bygrave 3792e06859 Update javadoc 2015-09-30 17:26:05 +13:00
Robin Bygrave c77446ba24 Update javadoc 2015-09-30 17:25:26 +13:00
Robin Bygrave dba245c517 #421 - ENH: Add support for unsetting the loaded state of a bean property (via BeanState) 2015-09-22 13:21:19 +12:00
Robin Bygrave 489eb483c0 No effective change - parameter name from val -> value 2015-09-22 12:34:30 +12:00
Robin Bygrave 11301846be #422 - ENH: Add jsonBetween() expression 2015-09-22 12:32:46 +12:00
Robin Bygrave d022e4aec8 No effective change - parameter name from val -> value 2015-09-22 11:48:41 +12:00
Robin Bygrave aa788edb68 #421 - ENH: Add support for unsetting the loaded state of a bean property (via BeanState) ... initial test 2015-09-22 09:00:06 +12:00
Robin Bygrave 288adcdf68 Update README 2015-09-21 18:01:31 +12:00
Robin Bygrave ddb07ca202 Update README 2015-09-21 17:59:45 +12:00
Robin Bygrave 524b592e82 [maven-release-plugin] prepare for next development iteration 2015-09-21 17:45:47 +12:00
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 5b8ce8eaa0 WIP AutoTune refactor 2015-09-04 08:48:25 +12:00
Robin Bygrave 5c1de0a886 WIP Autotune refactor 2015-09-02 09:28:17 +12:00
Robin Bygrave 987fe38618 WIP AutoTune refactor 2015-09-01 07:55:25 +12:00
379 changed files with 10975 additions and 4797 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
+1 -10
View File
@@ -5,16 +5,7 @@
Ebean ORM
==============
[http://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
Maven Dependency
----------------
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>4.7.3</version>
</dependency>
[https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
[Maven central - avaje-ebeanorm](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.avaje.ebeanorm%22%20AND%20a%3A%22avaje-ebeanorm%22 "maven central ebeanorm")
+32 -8
View File
@@ -9,7 +9,7 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>6.6.1</version>
<version>6.13.3</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
@@ -61,6 +61,14 @@
<version>1.7.7</version>
</dependency>
<!-- provided scope for Spring boot loader classpath scanning support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>1.2.7.RELEASE</version>
<scope>provided</scope>
</dependency>
<!-- provided scope for JsonNode support -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
@@ -109,10 +117,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>
@@ -133,12 +155,14 @@
<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>
@@ -204,7 +228,7 @@
<configuration>
<classSource>target/test-classes</classSource>
<packages>com.avaje.tests.**</packages>
<transformArgs>debug=2</transformArgs>
<transformArgs>debug=1</transformArgs>
</configuration>
<goals>
<goal>enhance</goal>
@@ -249,11 +273,11 @@
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<doctitle>Ebean 4</doctitle>
<doctitle>Ebean 6</doctitle>
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util:com.avaje.ebean.dbmigration.ddlgeneration:com.avaje.ebean.dbmigration.migration:com.avaje.ebean.dbmigration.migrationreader:com.avaje.ebean.dbmigration.model</excludePackageNames>
<docletArtifact>
<groupId>org.avaje</groupId>
<artifactId>pygments-doclet</artifactId>
@@ -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();
}
@@ -38,6 +38,33 @@ public interface BeanState {
*/
void setDisableLazyLoad(boolean disableLazyLoading);
/**
* Set the loaded state of the property given it's name.
*
* <p>
* Typically this would be used to set the loaded state of a property
* to false to ensure that the specific property is excluded from a
* stateless update.
* </p>
*
* <pre>{@code
*
* // populate a bean via say JSON
* User user = ...;
*
* // set loaded state on the email property to false so that
* // the email property is not included in a stateless update
* Ebean.getBeanState(user).setPropertyLoaded("email", false);
*
* user.update();
*
* }</pre>
*
*
* This will throw an IllegalArgumentException if the property is unknown.
*/
void setPropertyLoaded(String propertyName, boolean loaded);
/**
* For partially populated beans returns the properties that are loaded on the
* bean.
+28 -2
View File
@@ -697,12 +697,31 @@ public final class Ebean {
/**
* Delete the bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* If the bean is configured with <code>@SoftDelete</code> then this will perform a soft
* delete rather than a hard/permanent delete.
* </p>
* <p>
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. Note that, if JDBC batch mode is used then this always returns true.
* </p>
*/
public static void delete(Object bean) throws OptimisticLockException {
serverMgr.getDefaultServer().delete(bean);
public static boolean delete(Object bean) throws OptimisticLockException {
return serverMgr.getDefaultServer().delete(bean);
}
/**
* Delete the bean in permanent fashion (will not use soft delete).
*/
public static boolean deletePermanent(Object bean) throws OptimisticLockException {
return serverMgr.getDefaultServer().deletePermanent(bean);
}
/**
@@ -726,6 +745,13 @@ public final class Ebean {
return serverMgr.getDefaultServer().deleteAll(beans);
}
/**
* Delete permanent all the beans in the Collection (will not use soft delete).
*/
public static int deleteAllPermanent(Collection<?> beans) throws OptimisticLockException {
return serverMgr.getDefaultServer().deleteAllPermanent(beans);
}
/**
* Refresh the values of a bean.
* <p>
+159 -10
View File
@@ -9,6 +9,7 @@ import com.avaje.ebean.text.csv.CsvReader;
import com.avaje.ebean.text.json.JsonContext;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
@@ -106,10 +107,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
@@ -1092,14 +1092,17 @@ public interface EbeanServer {
<T> Map<?, T> findMap(Query<T> query, Transaction transaction);
/**
* Execute the query returning at most one entity bean. This will throw a
* PersistenceException if the query finds more than one result.
* Execute the query returning at most one entity bean or null (if no matching
* bean is found).
* <p>
* This will throw a NonUniqueResultException if the query finds more than one result.
* </p>
* <p>
* Generally you are able to use {@link Query#findUnique()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
*
* @param <T>
* the type of entity bean to fetch.
* @param query
@@ -1107,6 +1110,7 @@ public interface EbeanServer {
* @param transaction
* the transaction to use (can be null).
* @return the list of fetched beans.
* @throws NonUniqueResultException if more than one result was found
*
* @see Query#findUnique()
*/
@@ -1247,16 +1251,52 @@ public interface EbeanServer {
/**
* Delete the bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. Note that, if JDBC batch mode is used then this always returns true.
* </p>
*/
void delete(Object bean) throws OptimisticLockException;
boolean delete(Object bean) throws OptimisticLockException;
/**
* Delete the bean with an explicit transaction.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. However, if JDBC batch mode is used then this always returns true.
* </p>
*/
void delete(Object bean, Transaction transaction) throws OptimisticLockException;
boolean delete(Object bean, Transaction transaction) throws OptimisticLockException;
/**
* Delete a bean permanently without soft delete.
*/
boolean deletePermanent(Object bean) throws OptimisticLockException;
/**
* Delete a bean permanently without soft delete using an explicit transaction.
*/
boolean deletePermanent(Object bean, Transaction transaction) throws OptimisticLockException;
/**
* Delete all the beans in the collection permanently without soft delete.
*/
int deleteAllPermanent(Collection<?> beans) throws OptimisticLockException;
/**
* Delete all the beans in the collection permanently without soft delete using an explicit transaction.
*/
int deleteAllPermanent(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
/**
* Delete the bean given its type and id.
@@ -1457,7 +1497,7 @@ public interface EbeanServer {
void markAsDirty(Object bean);
/**
* Saves the bean using an update. If you know you are updating a bean then it is preferrable to
* Saves the bean using an update. If you know you are updating a bean then it is preferable to
* use this update() method rather than save().
* <p>
* <b>Stateless updates:</b> Note that the bean does not have to be previously fetched to call
@@ -1509,7 +1549,6 @@ public interface EbeanServer {
* @param deleteMissingChildren
* specify false if you do not want 'missing children' of a OneToMany
* or ManyToMany to be automatically deleted.
*/
void update(Object bean, Transaction transaction, boolean deleteMissingChildren) throws OptimisticLockException;
@@ -1820,4 +1859,114 @@ public interface EbeanServer {
*/
JsonContext json();
/**
* Publish a single bean given its type and id returning the resulting live bean.
* <p>
* The values are published from the draft to the live bean.
* </p>
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean
* @param transaction the transaction the publish process should use (can be null)
*/
<T> T publish(Class<T> beanType, Object id, Transaction transaction);
/**
* Publish a single bean given its type and id returning the resulting live bean.
* This will use the current transaction or create one if required.
* <p>
* The values are published from the draft to the live bean.
* </p>
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean
*/
<T> T publish(Class<T> beanType, Object id);
/**
* Publish the beans that match the query returning the resulting published beans.
* <p>
* The values are published from the draft beans to the live beans.
* </p>
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to publish
* @param transaction the transaction the publish process should use (can be null)
*/
<T> List<T> publish(Query<T> query, Transaction transaction);
/**
* Publish the beans that match the query returning the resulting published beans.
* This will use the current transaction or create one if required.
* <p>
* The values are published from the draft beans to the live beans.
* </p>
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to publish
*/
<T> List<T> publish(Query<T> query);
/**
* Restore the draft bean back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
* </p>
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean to restore
* @param transaction the transaction the restore process should use (can be null)
*/
<T> T draftRestore(Class<T> beanType, Object id, Transaction transaction);
/**
* Restore the draft bean back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
* </p>
*
* @param <T> the type of the entity bean
* @param beanType the type of the entity bean
* @param id the id of the entity bean to restore
*/
<T> T draftRestore(Class<T> beanType, Object id);
/**
* Restore the draft beans matching the query back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
* </p>
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to restore
* @param transaction the transaction the restore process should use (can be null)
*/
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
/**
* Restore the draft beans matching the query back to the live state.
* <p>
* The values from the live beans are set back to the draft bean and the
* <code>@DraftDirty</code> and <code>@DraftReset</code> properties are reset.
* </p>
*
* @param <T> the type of the entity bean
* @param query the query used to select the draft beans to restore
*/
<T> List<T> draftRestore(Query<T> query);
/**
* Returns the set of properties/paths that are unknown (do not map to known properties or paths).
* <p>
* Validate the query checking the where and orderBy expression paths to confirm if
* they represent valid properties/path for the given bean type.
* </p>
*/
<T> Set<String> validateQuery(Query<T> query);
}
@@ -36,6 +36,51 @@ 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);
/**
* Between - for the given path in a JSON document.
*/
Expression jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue);
/**
* Equal To - property equal to the given value.
*/
@@ -3,6 +3,7 @@ package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Collection;
@@ -107,6 +108,16 @@ public interface ExpressionList<T> extends Serializable {
*/
Query<T> asOf(Timestamp asOf);
/**
* Execute the query against the draft set of tables.
*/
Query<T> asDraft();
/**
* Execute the query including soft deleted rows.
*/
Query<T> includeSoftDeletes();
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
@@ -183,8 +194,15 @@ public interface ExpressionList<T> extends Serializable {
<K> Map<K, T> findMap(String keyProperty, Class<K> keyType);
/**
* Execute the query returning a single bean.
*
* Execute the query returning a single bean or null (if no matching
* bean is found).
* <p>
* If more than 1 row is found for this query then a NonUniqueResultException is
* thrown.
* </p>
*
* @throws NonUniqueResultException if more than one result was found
*
* @see Query#findUnique()
*/
@Nullable
@@ -345,12 +363,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 value);
/**
* 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 value);
/**
* 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 value);
/**
* 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 value);
/**
* Between - for the given path in a JSON document.
*
* <pre>{@code
*
* where().jsonBetween("content", "orderDate", lowerDateTime, upperDateTime)
*
* }</pre>
*/
ExpressionList<T> jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue);
/**
* Add an Expression to the list.
* <p>
* This returns the list so that add() can be chained.
* </p>
*
*
* <pre>{@code
*
* Query<Customer> query = Ebean.find(Customer.class);
+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);
*
+46 -9
View File
@@ -1,5 +1,6 @@
package com.avaje.ebean;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.text.PathProperties;
import com.avaje.ebean.util.ClassUtil;
import org.jetbrains.annotations.Nullable;
@@ -193,7 +194,31 @@ public abstract class Model {
public void markAsDirty() {
db().markAsDirty(this);
}
/**
* Mark the property as unset or 'not loaded'.
* <p>
* This would be used to specify a property that we did not wish to include in a stateless update.
* </p>
* <pre>{@code
*
* // populate an entity bean from JSON or whatever
* User user = ...;
*
* // mark the email property as 'unset' so that it is not
* // included in a 'stateless update'
* user.markPropertyUnset("email");
*
* user.update();
*
* }</pre>
*
* @param propertyName the name of the property on the bean to be marked as 'unset'
*/
public void markPropertyUnset(String propertyName) {
((EntityBean)this)._ebean_getIntercept().setPropertyLoaded(propertyName, false);
}
/**
* Insert or update this entity depending on its state.
*
@@ -226,12 +251,24 @@ public abstract class Model {
}
/**
* Delete this entity.
* Delete this bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. Note that, if JDBC batch mode is used then this always returns true.
* </p>
*
* @see EbeanServer#delete(Object)
*/
public void delete() {
db().delete(this);
public boolean delete() {
return db().delete(this);
}
/**
@@ -251,8 +288,8 @@ public abstract class Model {
/**
* Perform a delete using this entity against the specified server.
*/
public void delete(String server) {
db(server).delete(this);
public boolean delete(String server) {
return db(server).delete(this);
}
/**
@@ -836,10 +873,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.
*/
+34 -12
View File
@@ -3,6 +3,7 @@ package com.avaje.ebean;
import com.avaje.ebean.text.PathProperties;
import org.jetbrains.annotations.Nullable;
import javax.persistence.NonUniqueResultException;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.List;
@@ -67,9 +68,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 +79,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.
@@ -297,6 +298,16 @@ public interface Query<T> extends Serializable {
*/
Query<T> asOf(Timestamp asOf);
/**
* Execute the query against the draft set of tables.
*/
Query<T> asDraft();
/**
* Execute the query including soft deleted rows.
*/
Query<T> includeSoftDeletes();
/**
* Cancel the query execution if supported by the underlying database and
* driver.
@@ -338,23 +349,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.
@@ -690,7 +701,7 @@ public interface Query<T> extends Serializable {
* Execute the query returning either a single bean or null (if no matching
* bean is found).
* <p>
* If more than 1 row is found for this query then a PersistenceException is
* If more than 1 row is found for this query then a NonUniqueResultException is
* thrown.
* </p>
* <p>
@@ -726,6 +737,8 @@ public interface Query<T> extends Serializable {
* List<OrderDetail> details = order.getDetails();
* ...
* }</pre>
*
* @throws NonUniqueResultException if more than one result was found
*/
@Nullable
T findUnique();
@@ -1313,4 +1326,13 @@ public interface Query<T> extends Serializable {
* </p>
*/
Query<T> setDisableLazyLoading(boolean disableLazyLoading);
/**
* Returns the set of properties or paths that are unknown (do not map to known properties or paths).
* <p>
* Validate the query checking the where and orderBy expression paths to confirm if
* they represent valid properties or paths for the given bean type.
* </p>
*/
Set<String> validate();
}
@@ -9,6 +9,10 @@ import java.lang.annotation.Target;
* For a timestamp property that is set to the datetime when the entity is
* created/inserted.
* <p>
* This is effectively an alias for @WhenCreated which was added as it hints
* towards a better naming convention (WhenCreated, WhenModified).
* </p>
* <p>
* An alternative to using this annotation would be to use insertable=false,
* updateable=false with @Column and have the DB insert the current time
* (default value on the DB column is SYSTIME etc).
@@ -0,0 +1,17 @@
package com.avaje.ebean.annotation;
/**
* Specify the DB storage type used to with <code>@DbEnumValue</code>.
*/
public enum DbEnumType {
/**
* Store values as database INTEGER.
*/
INTEGER,
/**
* Store values as database VARCHAR.
*/
VARCHAR
}
@@ -0,0 +1,44 @@
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;
/**
* Specify a method on an Enum that returns the value that should be stored in the DB.
* <p>
* This is the preferred option for mapping Enum's to DB values (preferred over the JPA
* standard @Enumerated and Ebean's @EnumValue annotations).
* </p>
* <h3>Example:</h3>
* <pre>{@code
*
* public enum Status {
* NEW("N"),
* ACTIVE("A"),
* INACTIVE("I");
*
* String dbValue;
* Status(String dbValue) {
* this.dbValue = dbValue;
* }
*
* @DbEnumValue
* public String getValue() {
* return dbValue;
* }
* }
*
* }</pre>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DbEnumValue {
/**
* Specify the database type used to store the values (VARCHAR or INTEGER).
*/
DbEnumType storage() default DbEnumType.VARCHAR;
}
@@ -0,0 +1,19 @@
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 a boolean property on a @Draftable bean that indicates if the bean instance is a 'draft' or 'live' bean.
* The property is transient and has no underlying DB column.
* <p>
* For beans returned from an <code>asDraft()</code> query this property will be set to true.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Draft {
}
@@ -0,0 +1,20 @@
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 a boolean property on a @Draftable bean that only exists on the 'draft' table
* and is used to detect when a draft has unpublished changes.
* <p>
* This property will automatically have it's value set to true when a draft is saved and
* automatically have it's value set to false when the bean is published.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DraftDirty {
}
@@ -0,0 +1,19 @@
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 a property on a @Draftable bean that only exists on the 'draft' and not the 'live' table.
* <p>
* Typically this would be used on a property that is used as part of application 'workflow' such as
* a publish workflow status or when publish timestamp.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DraftOnly {
}
@@ -0,0 +1,19 @@
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 a property on a @Draftable bean that is set to null on the 'draft bean' on publish.
* <p>
* This is expected to be put on properties that get 'reset' or 'cleared' after a publish.
* These properties might represent a publish comment or publish timestamp.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DraftReset {
}
@@ -0,0 +1,29 @@
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;
/**
* Used to indicate an entity bean that has 'draftable' support.
* <p>
* This means that a second set of tables is created to hold draft versions of
* the rows and that these can then be published which effectively copies/transfers
* the values from the 'draft' table to the 'live' table.
* </p>
* <p>
* Ebean Query supports 'find as draft' which builds the resulting object graph using
* the draft tables. This object graph is typically edited, approved in some application
* specific manor and then published.
* </p>
* <p>
* EbeanServer has a publish method which transfers/copies the draft object graph to
* the 'live' tables.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Draftable {
}
@@ -0,0 +1,20 @@
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;
/**
* Used to indicate an entity bean that has 'draftable' support but it not a 'top level'
* (or root level) bean but instead child related to another @Draftable entity bean.
* <p>
* Relationships to @DraftableElements (@OneToMany, @ManyToMany etc) are automatically
* deemed to have Cascade.ALL for save and delete (as well as orphan removal mode).
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DraftableElement {
}
@@ -25,7 +25,7 @@ import java.lang.annotation.Target;
* }</pre>
* <p>
* This is an alternative to using the JPA standard approach or Ebean's
* {@link EnumMapping} annotation.
* {@link DbEnumValue} annotation.
* </p>
* <p>
* Note that if all the EnumValue values are parsable as Integers then Ebean
@@ -0,0 +1,25 @@
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;
/**
* Used to indicate a property on an entity bean used to control 'soft delete'
* (also known as 'logical delete').
* <p>
* The property should be of type boolean.
* </p>
* <pre>{@code
*
* @SoftDelete
* boolean deleted;
*
* }</pre>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SoftDelete {
}
@@ -8,6 +8,10 @@ import java.lang.annotation.Target;
/**
* For a timestamp property that is set to the datetime when the entity was last
* updated.
* <p>
* This is effectively an alias for @WhenModified which was added as it hints
* towards a better naming convention (WhenCreated, WhenModified).
* </p>
*/
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@@ -11,7 +11,6 @@ import java.lang.annotation.Target;
* This is effectively an alias for @UpdatedTimestamp and added to hint
* towards a better naming convention (WhenCreated, WhenModified).
* </p>
* </p>
*/
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@@ -31,6 +31,16 @@ public interface BeanCollection<E> extends Serializable {
ALL
}
/**
* Add a bean to the list/set with modifyListen notification.
*/
void addBean(E bean);
/**
* Remove a bean to the list/set with modifyListen notification.
*/
void removeBean(E bean);
/**
* Reset the collection back to an empty state ready for reloading.
* <p>
@@ -12,5 +12,5 @@ public interface BeanCollectionAdd {
/**
* Add a loaded bean to the collection.
*/
void addBean(EntityBean bean);
void addEntityBean(EntityBean bean);
}
@@ -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;
@@ -449,7 +449,18 @@ public final class EntityBeanIntercept implements Serializable {
public int getPropertyLength() {
return owner._ebean_getPropertyNames().length;
}
/**
* Set the loaded state of the property given it's name.
*/
public void setPropertyLoaded(String propertyName, boolean loaded) {
int position = findProperty(propertyName);
if (position == -1) {
throw new IllegalArgumentException("Property "+propertyName+" not found");
}
loadedProps[position] = loaded;
}
/**
* Set the property to be treated as unloaded. Used for properties initialised in default
* constructor.
@@ -496,7 +507,7 @@ public final class EntityBeanIntercept implements Serializable {
setDirty(true);
}
private void setChangedProperty(int propertyIndex) {
public void setChangedProperty(int propertyIndex) {
if (changedProps == null) {
changedProps = new boolean[owner._ebean_getPropertyNames().length];
}
@@ -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 {
@@ -60,7 +60,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
}
@SuppressWarnings("unchecked")
public void addBean(EntityBean bean) {
public void addEntityBean(EntityBean bean) {
list.add((E) bean);
}
@@ -206,6 +206,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
list.add(index, element);
}
@Override
public void addBean(E bean) {
add(bean);
}
public boolean add(E o) {
checkReadOnly();
init();
@@ -319,6 +324,13 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
return list.listIterator(index);
}
@Override
public void removeBean(E bean) {
if (list.remove(bean)) {
getModifyHolder().modifyRemoval(bean);
}
}
public E remove(int index) {
checkReadOnly();
init();
@@ -272,6 +272,16 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
map.putAll(puts);
}
@Override
public void addBean(E bean) {
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
}
@Override
public void removeBean(E bean) {
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
}
public E remove(Object key) {
checkReadOnly();
init();
@@ -53,7 +53,7 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
}
@SuppressWarnings("unchecked")
public void addBean(EntityBean bean) {
public void addEntityBean(EntityBean bean) {
set.add((E) bean);
}
@@ -174,6 +174,18 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
return set.hashCode();
}
@Override
public void addBean(E bean) {
add(bean);
}
@Override
public void removeBean(E bean) {
if (set.remove(bean)) {
getModifyHolder().modifyRemoval(bean);
}
}
// -----------------------------------------------------//
// proxy method for map
// -----------------------------------------------------//
@@ -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
}
@@ -0,0 +1,143 @@
package com.avaje.ebean.config;
/**
* Helper to find classes taking into account the context class loader.
*/
public class ClassLoadConfig {
protected final ClassLoaderContext context;
/**
* Construct with the default classLoader search with context classLoader first.
*/
public ClassLoadConfig() {
this(null);
}
/**
* Specify the classLoader to use for class detection and new instance creation.
*/
public ClassLoadConfig(ClassLoader classLoader) {
this.context = new ClassLoaderContext(classLoader);
}
/**
* Return true if the Java.time types are available and should be supported.
*/
public boolean isJavaTimePresent() {
return isPresent("java.time.LocalDate");
}
/**
* Return true if the Joda types are available and should be supported.
*/
public boolean isJodaTimePresent() {
return isPresent("org.joda.time.LocalDateTime");
}
/**
* Return true if javax validation annotations like Size and NotNull are present.
*/
public boolean isJavaxValidationAnnotationsPresent() {
return isPresent("javax.validation.constraints.NotNull");
}
/**
* Return true if Jackson annotations like JsonIgnore are present.
*/
public boolean isJacksonAnnotationsPresent() {
return isPresent("com.fasterxml.jackson.annotation.JsonIgnore");
}
/**
* Return true if Jackson ObjectMapper is present.
*/
public boolean isJacksonObjectMapperPresent() {
return isPresent("com.fasterxml.jackson.databind.ObjectMapper");
}
/**
* Return a new instance of the class using the default constructor.
*/
public Object newInstance(String className) {
try {
Class<?> cls = forName(className);
return cls.newInstance();
} catch (Exception e) {
throw new IllegalArgumentException("Error constructing " + className, e);
}
}
/**
* Return true if the given class is present.
*/
protected boolean isPresent(String className) {
try {
forName(className);
return true;
} catch (Throwable ex) {
// Class or one of its dependencies is not present...
return false;
}
}
/**
* Load a class taking into account a context class loader (if present).
*/
protected Class<?> forName(String name) throws ClassNotFoundException {
return context.forName(name);
}
/**
* Wraps the preferred, caller and context class loaders.
*/
protected class ClassLoaderContext {
/**
* Optional - if set only use this classLoader (no fallback).
*/
protected final ClassLoader preferredLoader;
protected final ClassLoader contextLoader;
protected final ClassLoader callerLoader;
ClassLoaderContext(ClassLoader preferredLoader) {
this.preferredLoader = preferredLoader;
this.callerLoader = ServerConfig.class.getClassLoader();
this.contextLoader = contextLoader();
}
ClassLoader contextLoader() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return (loader != null) ? loader: callerLoader;
}
Class<?> forName(String name) throws ClassNotFoundException {
if (preferredLoader != null) {
// only use the explicitly set classLoader
return classForName(name, preferredLoader);
}
try {
// try the context loader first
return classForName(name, contextLoader);
} catch (ClassNotFoundException e) {
if (callerLoader == contextLoader) {
throw e;
} else {
// fallback to the caller classLoader
return classForName(name, callerLoader);
}
}
}
Class<?> classForName(String name, ClassLoader classLoader) throws ClassNotFoundException {
return Class.forName(name, true, classLoader);
}
}
}
@@ -45,13 +45,29 @@ public class DbConstraintNaming {
protected String ckPrefix = "ck_";
protected String ckSuffix = "";
protected boolean lowerCaseNames = true;
protected MaxLength maxLength;
protected DbConstraintNormalise normalise = new DbConstraintNormalise();
protected DbConstraintNormalise normalise;
/**
* Construct using default of lower case for both table and column names.
*/
public DbConstraintNaming() {
this(true, true);
}
/**
* Construct specifying if lower case should be used (for both table and column names).
*/
public DbConstraintNaming(boolean lowerCase) {
this(lowerCase, lowerCase);
}
/**
* Construct specifying if lower case should be used for both table and column names.
*/
public DbConstraintNaming(boolean lowerCaseTableNames, boolean lowerCaseColumnNames) {
this.normalise = new DbConstraintNormalise(lowerCaseTableNames, lowerCaseColumnNames);
}
/**
@@ -167,7 +183,6 @@ public class DbConstraintNaming {
* quoted identifier characters (",',[,] etc).
*/
public String normaliseTable(String tableName) {
return normalise.normaliseTable(tableName);
}
@@ -175,18 +190,20 @@ public class DbConstraintNaming {
* Normalise the column name by removing any quoted identifier characters (",',[,] etc).
*/
public String normaliseColumn(String tableName) {
return normalise.normaliseColumn(tableName);
}
/**
* Lower case the table or column name checking for quoted identifiers.
* Lower case the table name checking for quoted identifiers.
*/
public String lowerName(String tableName) {
if (lowerCaseNames && normalise.notQuoted(tableName)) {
return tableName.toLowerCase();
}
return tableName;
public String lowerTableName(String tableName) {
return normalise.lowerTableName(tableName);
}
/**
* Lower case the column name checking for quoted identifiers.
*/
public String lowerColumnName(String name) {
return normalise.lowerColumnName(name);
}
}
@@ -8,11 +8,17 @@ public class DbConstraintNormalise {
protected final String[] quotedIdentifiers;
protected boolean lowerCaseTables = true;
protected final boolean lowerCaseTables;
protected boolean lowerCaseColumns = true;
protected final boolean lowerCaseColumns;
public DbConstraintNormalise() {
this(true, true);
}
public DbConstraintNormalise(boolean lowerCaseTables, boolean lowerCaseColumns) {
this.lowerCaseTables = lowerCaseTables;
this.lowerCaseColumns = lowerCaseColumns;
this.quotedIdentifiers = new String[]{"\"", "'", "[", "]", "`"};
}
@@ -46,6 +52,26 @@ public class DbConstraintNormalise {
return columnName;
}
/**
* Lower case the table name checking for quoted identifiers.
*/
public String lowerTableName(String tableName) {
if (lowerCaseTables && notQuoted(tableName)) {
return tableName.toLowerCase();
}
return tableName;
}
/**
* Lower case the column name checking for quoted identifiers.
*/
public String lowerColumnName(String name) {
if (lowerCaseColumns && notQuoted(name)) {
return name.toLowerCase();
}
return name;
}
/**
* Trim off the platform quoted identifier quotes like [ ' and ".
*/
@@ -18,6 +18,14 @@ public final class PropertyMap implements Serializable {
private final LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
/**
* Return properties loaded from test-ebean.properties.
*/
public static Properties testProperties() {
PropertyMap propertyMap = PropertyMapLoader.loadTestProperties();
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
}
public static Properties defaultProperties() {
PropertyMap propertyMap = PropertyMapLoader.loadGlobalProperties();
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
@@ -14,17 +14,34 @@ final class PropertyMapLoader {
private static final Logger logger = LoggerFactory.getLogger(PropertyMapLoader.class);
/**
* Load the <code>test-ebean.properties</code>.
*/
public static PropertyMap loadTestProperties() {
return load(null, "test-ebean.properties");
}
/**
* Load the ebean.properties (and test-ebean.properties if present).
*/
public static PropertyMap loadGlobalProperties() {
boolean loadTestProperties = false;
String fileName = System.getenv("EBEAN_PROPS_FILE");
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName == null) {
loadTestProperties = true;
fileName = "ebean.properties";
}
}
return load(null, fileName);
PropertyMap map = load(null, fileName);
if (loadTestProperties) {
// load test properties if present in classpath
map = load(map, "test-ebean.properties");
}
return map;
}
/**
@@ -39,7 +56,6 @@ final class PropertyMapLoader {
InputStream is = findInputStream(fileName);
if (is == null) {
logger.error(fileName + " not found");
return p;
} else {
return load(p, is);
@@ -47,7 +63,7 @@ final class PropertyMapLoader {
}
/**
* Load the inputstream returning the property map.
* Load the InputStream returning the property map.
*
* @param p
* an existing property map to load into.
@@ -7,14 +7,20 @@ import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.config.dbplatform.DbEncrypt;
import com.avaje.ebean.event.*;
import com.avaje.ebean.event.BeanFindController;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanPostLoad;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.BulkTableEventListener;
import com.avaje.ebean.event.ServerConfigStartup;
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.ebean.meta.MetaInfoManager;
import com.avaje.ebean.util.ClassUtil;
import com.fasterxml.jackson.core.JsonFactory;
import javax.sql.DataSource;
@@ -130,9 +136,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.
@@ -228,13 +234,23 @@ public class ServerConfig {
*/
private DbMigrationConfig migrationConfig = new DbMigrationConfig();
/**
* The ClassLoadConfig used to detect Joda, Java8, Jackson etc and create plugin instances given a className.
*/
private ClassLoadConfig classLoadConfig = new ClassLoadConfig();
/**
* Set to true if the DataSource uses autoCommit.
* <p>
* 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.
*/
@@ -276,9 +292,9 @@ public class ServerConfig {
private boolean updatesDeleteMissingChildren = true;
/**
* Setting to indicate if UUID should be stored as binary(16) or varchar(40).
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
*/
private boolean uuidStoreAsBinary;
private DbUuid dbUuid = DbUuid.AUTO;
private List<BeanFindController> findControllers = new ArrayList<BeanFindController>();
@@ -1144,17 +1160,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;
}
/**
@@ -1220,6 +1236,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>
@@ -1439,19 +1477,18 @@ public class ServerConfig {
this.dbEncrypt = dbEncrypt;
}
/**
* Return true if UUID should be stored as binary(16) (as opposed to varchar(40)).
* Return the DB type used to store UUID.
*/
public boolean isUuidStoreAsBinary() {
return uuidStoreAsBinary;
public DbUuid getDbUuid() {
return dbUuid;
}
/**
* Set to true if UUID should be stored as binary(16) (as opposed to varchar(40)).
* Set the DB type used to store UUID.
*/
public void setUuidStoreAsBinary(boolean uuidStoreAsBinary) {
this.uuidStoreAsBinary = uuidStoreAsBinary;
public void setDbUuid(DbUuid dbUuid) {
this.dbUuid = dbUuid;
}
/**
@@ -1997,6 +2034,22 @@ public class ServerConfig {
this.persistenceContextScope = persistenceContextScope;
}
/**
* Return the ClassLoadConfig which is used to detect Joda, Java8 types etc and also
* create new instances of plugins given a className.
*/
public ClassLoadConfig getClassLoadConfig() {
return classLoadConfig;
}
/**
* Set the ClassLoadConfig which is used to detect Joda, Java8 types etc and also
* create new instances of plugins given a className.
*/
public void setClassLoadConfig(ClassLoadConfig classLoadConfig) {
this.classLoadConfig = classLoadConfig;
}
/**
* Load settings from ebean.properties.
*/
@@ -2014,6 +2067,21 @@ public class ServerConfig {
loadSettings(p);
}
/**
* Load settings from test-ebean.properties and do nothing if the properties is not found.
* <p>
* This is typically used when test-ebean.properties is put into the test class path and used
* to configure Ebean for running tests.
* </p>
*/
public void loadTestProperties() {
Properties properties = PropertyMap.testProperties();
if (!properties.isEmpty()) {
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
loadSettings(p);
}
}
/**
* Return the properties that we used for configuration and were set via a call to loadFromProperties().
*/
@@ -2021,11 +2089,31 @@ public class ServerConfig {
return properties;
}
/**
* Return the instance to use (can be null) for the given plugin.
*
* @param properties the properties
* @param pluginType the type of plugin
* @param key properties key
* @param instance existing instance
*/
@SuppressWarnings("unchecked")
private <T> T createInstance(PropertiesWrapper p, Class<T> pluginType, String key) {
protected <T> T createInstance(PropertiesWrapper properties, Class<T> pluginType, String key, T instance) {
String classname = p.get(key, null);
return classname == null ? null : (T) ClassUtil.newInstance(classname);
if (instance != null) {
return instance;
}
String classname = properties.get(key, null);
return createInstance(pluginType, classname);
}
/**
* Return the instance to use (can be null) for the given plugin.
* @param pluginType the type of plugin
* @param classname the implementation class as per properties
*/
protected <T> T createInstance(Class<T> pluginType, String classname) {
return classname == null ? null : (T) classLoadConfig.newInstance(classname);
}
/**
@@ -2042,8 +2130,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);
}
/**
@@ -2057,28 +2145,29 @@ 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);
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider");
disableClasspathSearch = p.getBoolean("disableClasspathSearch", disableClasspathSearch);
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform");
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager");
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager");
encryptor = createInstance(p, Encryptor.class, "encryptor");
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt");
serverCacheFactory = createInstance(p, ServerCacheFactory.class, "serverCacheFactory");
serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager");
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider", currentUserProvider);
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform", databasePlatform);
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager", encryptKeyManager);
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager", encryptDeployManager);
encryptor = createInstance(p, Encryptor.class, "encryptor", encryptor);
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt", dbEncrypt);
serverCacheFactory = createInstance(p, ServerCacheFactory.class, "serverCacheFactory", serverCacheFactory);
serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager", serverCacheManager);
cacheWarmingDelay = p.getInt("cacheWarmingDelay", cacheWarmingDelay);
classPathReaderClassName = p.get("classpathreader");
@@ -2124,7 +2213,10 @@ public class ServerConfig {
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
uuidStoreAsBinary = p.getBoolean("uuidStoreAsBinary", uuidStoreAsBinary);
dbUuid = p.getEnum(DbUuid.class, "dbuuid", dbUuid);
if (p.getBoolean("uuidStoreAsBinary", false)) {
dbUuid = DbUuid.BINARY;
}
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", lazyLoadBatchSize);
@@ -2146,7 +2238,7 @@ public class ServerConfig {
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention");
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention", null);
return (nc != null) ? nc : namingConvention;
}
@@ -2246,4 +2338,25 @@ public class ServerConfig {
public void setExpressionEqualsWithNullAsNoop(boolean expressionEqualsWithNullAsNoop) {
this.expressionEqualsWithNullAsNoop = expressionEqualsWithNullAsNoop;
}
/**
* Specify how UUID is stored.
*/
public enum DbUuid {
/**
* Store using native UUID in H2 and Postgres.
*/
AUTO,
/**
* Store using DB VARCHAR.
*/
VARCHAR,
/**
* Store using DB BINARY.
*/
BINARY
}
}
@@ -17,7 +17,7 @@ public class DB2SequenceIdGenerator extends SequenceIdGenerator {
*/
public DB2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
super(be, ds, seqName, batchSize);
this.baseSql = "select nextval for " + seqName;
this.baseSql = "values nextval for " + seqName;
this.unionBaseSql = " union " + baseSql;
}
@@ -1,18 +1,16 @@
package com.avaje.ebean.config.dbplatform;
import java.sql.Types;
import javax.sql.DataSource;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.Query;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler;
import com.avaje.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.sql.Types;
/**
* Database platform specific settings.
*/
@@ -54,28 +52,33 @@ public class DatabasePlatform {
*/
protected OnQueryOnly onQueryOnly = OnQueryOnly.ROLLBACK;
/**
* The open quote used by quoted identifiers.
/**
* The open quote used by quoted identifiers.
*/
protected String openQuote = "\"";
/**
* The close quote used by quoted identifiers.
* The close quote used by quoted identifiers.
*/
protected String closeQuote = "\"";
/**
* For limit/offset, row_number etc limiting of SQL queries.
/**
* For limit/offset, row_number etc limiting of SQL queries.
*/
protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter();
/**
* Mapping of JDBC to Database types.
/**
* Mapping of JDBC to Database types.
*/
protected DbTypeMap dbTypeMap = new DbTypeMap();
/**
* Defines DB identity/sequence features.
* Set to true if the DB has native UUID type support.
*/
protected boolean nativeUuidType;
/**
* Defines DB identity/sequence features.
*/
protected DbIdentity dbIdentity = new DbIdentity();
@@ -84,28 +87,28 @@ public class DatabasePlatform {
*/
protected DbHistorySupport historySupport;
/**
* The JDBC type to map booleans to (by default).
/**
* The JDBC type to map booleans to (by default).
*/
protected int booleanDbType = Types.BOOLEAN;
/**
* The JDBC type to map Blob to.
/**
* The JDBC type to map Blob to.
*/
protected int blobDbType = Types.BLOB;
/**
* The JDBC type to map Clob to.
/**
* The JDBC type to map Clob to.
*/
protected int clobDbType = Types.CLOB;
/**
* For Oracle treat empty strings as null.
/**
* For Oracle treat empty strings as null.
*/
protected boolean treatEmptyStringsAsNull;
/**
* The database platform name.
/**
* The database platform name.
*/
protected String name = "generic";
@@ -124,7 +127,7 @@ public class DatabasePlatform {
* The like clause. Can be overridden to disable default escape character.
*/
protected String likeClause = "like ?";
protected DbEncrypt dbEncrypt;
protected boolean idInExpandedForm;
@@ -219,16 +222,12 @@ public class DatabasePlatform {
/**
* Return a DB Sequence based IdGenerator.
*
* @param be
* the BackgroundExecutor that can be used to load the sequence if
* desired
* @param ds
* the DataSource
* @param seqName
* the name of the sequence
* @param batchSize
* the number of sequences that should be loaded
*
* @param be the BackgroundExecutor that can be used to load the sequence if
* desired
* @param ds the DataSource
* @param seqName the name of the sequence
* @param batchSize the number of sequences that should be loaded
*/
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
return null;
@@ -276,9 +275,16 @@ public class DatabasePlatform {
this.historySupport = historySupport;
}
/**
* Return true if the DB supports native UUID.
*/
public boolean isNativeUuidType() {
return nativeUuidType;
}
/**
* Return the mapping of JDBC to DB types.
*
*
* @return the db type map
*/
public DbTypeMap getDbTypeMap() {
@@ -315,7 +321,7 @@ public class DatabasePlatform {
/**
* Return the close quote for quoted identifiers.
*
*
* @return the close quote
*/
public String getCloseQuote() {
@@ -324,7 +330,7 @@ public class DatabasePlatform {
/**
* Return the open quote for quoted identifiers.
*
*
* @return the open quote
*/
public String getOpenQuote() {
@@ -333,7 +339,7 @@ public class DatabasePlatform {
/**
* Return the JDBC type used to store booleans.
*
*
* @return the boolean db type
*/
public int getBooleanDbType() {
@@ -363,7 +369,7 @@ public class DatabasePlatform {
/**
* Return true if empty strings should be treated as null.
*
*
* @return true, if checks if is treat empty strings as null
*/
public boolean isTreatEmptyStringsAsNull() {
@@ -399,7 +405,7 @@ public class DatabasePlatform {
/**
* Return the DB identity/sequence features for this platform.
*
*
* @return the db identity
*/
public DbIdentity getDbIdentity() {
@@ -412,7 +418,7 @@ public class DatabasePlatform {
* <p>
* Basically add the clauses for limit/offset, rownum, row_number().
* </p>
*
*
* @return the sql limiter
*/
public SqlLimiter getSqlLimiter() {
@@ -421,15 +427,12 @@ public class DatabasePlatform {
/**
* Convert backticks to the platform specific open quote and close quote
*
* <p>
* Specific plugins may implement this method to cater for platform specific
* naming rules.
* </p>
*
* @param dbName
* the db name
*
*
* @param dbName the db name
* @return the string
*/
public String convertQuotedIdentifiers(String dbName) {
@@ -8,6 +8,11 @@ package com.avaje.ebean.config.dbplatform;
*/
public class DbType {
/**
* DB native UUID type (H2 and Postgres).
*/
public static final int UUID = 5010;
/**
* Type to map Map content to Postgres HSTORE.
*/
@@ -49,6 +49,8 @@ public class DbTypeMap {
lookup.put("TIME", Types.TIME);
lookup.put("TIMESTAMP", Types.TIMESTAMP);
lookup.put("UUID", DbType.UUID);
// Not standard java.sql.Types
// logical JSON storage types
lookup.put("JSON", DbType.JSON);
@@ -101,6 +103,9 @@ public class DbTypeMap {
put(Types.BLOB, new DbType("blob"));
put(Types.CLOB, new DbType("clob"));
// DB native UUID support (H2 and Postgres)
put(DbType.UUID, new DbType("uuid"));
if (logicalTypes) {
// keep it logical for 2 layer DDL generation
put(DbType.HSTORE, new DbType("hstore"));
@@ -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,8 @@ public class H2Platform extends DatabasePlatform {
this.name = "h2";
this.dbEncrypt = new H2DbEncrypt();
this.platformDdl = new H2Ddl(this.dbTypeMap, dbIdentity);
this.historySupport = new H2HistorySupport();
this.nativeUuidType = true;
// 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 {
}
@@ -23,10 +23,10 @@ public class PostgresPlatform extends DatabasePlatform {
// OnQueryOnly.CLOSE as a performance optimisation on Postgres
this.onQueryOnly = OnQueryOnly.CLOSE;
this.likeClause = "like ? escape''";
this.selectCountWithAlias = true;
this.blobDbType = Types.LONGVARBINARY;
this.clobDbType = Types.VARCHAR;
this.nativeUuidType = true;
this.dbEncrypt = new PostgresDbEncrypt();
this.historySupport = new PostgresHistorySupport();
@@ -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);
}
@@ -98,7 +98,7 @@ public class BaseTableDdl implements TableDdl {
reset();
String tableName = lowerName(createTable.getName());
String tableName = lowerTableName(createTable.getName());
List<Column> columns = createTable.getColumn();
List<Column> pk = determinePrimaryKeyColumns(columns);
@@ -248,7 +248,7 @@ public class BaseTableDdl implements TableDdl {
protected void writeForeignKey(DdlWrite write, String fkName, String tableName, String[] columns, String refTable, String[] refColumns, String indexName) throws IOException {
tableName = lowerName(tableName);
tableName = lowerTableName(tableName);
DdlBuffer fkeyBuffer = write.applyForeignKeys();
alterTableAddForeignKey(fkeyBuffer, fkName, tableName, columns, refTable, refColumns);
@@ -282,7 +282,7 @@ public class BaseTableDdl implements TableDdl {
if (i > 0) {
buffer.append(",");
}
buffer.append(lowerName(columns[i].trim()));
buffer.append(lowerColumnName(columns[i].trim()));
}
buffer.append(")");
}
@@ -367,7 +367,7 @@ public class BaseTableDdl implements TableDdl {
buffer.append(",").newLine();
buffer.append(" constraint ").append(uqName).append(" unique ");
buffer.append("(");
buffer.append(lowerName(column.getName()));
buffer.append(lowerColumnName(column.getName()));
buffer.append(")");
}
@@ -401,14 +401,17 @@ public class BaseTableDdl implements TableDdl {
}
/**
* Convert the table or column name to lower case.
* <p>
* This is passed up to the platformDdl to override as desired.
* Generally lower case with underscore is a good cross database
* choice for column/table names.
* Convert the table lower case.
*/
protected String lowerName(String name) {
return naming.lowerName(name);
protected String lowerTableName(String name) {
return naming.lowerTableName(name);
}
/**
* Convert the column name to lower case.
*/
protected String lowerColumnName(String name) {
return naming.lowerColumnName(name);
}
/**
@@ -176,7 +176,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
String platformType = platformDdl.convert(type, false);
buffer.append(" ");
buffer.append(platformDdl.lowerName(columnName), 29);
buffer.append(platformDdl.lowerColumnName(columnName), 29);
buffer.append(platformType);
}
@@ -232,7 +232,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
List<String> includedColumns = new ArrayList<String>(columns.size());
for (MColumn column : columns) {
if (!column.isHistoryExclude()) {
if (column.isIncludeInHistory()) {
includedColumns.add(column.getName());
}
}
@@ -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();
}
}
@@ -53,6 +53,8 @@ public class PlatformDdl {
protected String identitySuffix = " auto_increment";
protected String alterTableIfExists = "";
protected String dropConstraintIfExists = "drop constraint if exists";
protected String dropIndexIfExists = "drop index if exists ";
@@ -136,7 +138,7 @@ public class PlatformDdl {
String platformType = convert(column.getType(), identityColumn);
buffer.append(" ");
buffer.append(lowerName(column.getName()), 29);
buffer.append(lowerColumnName(column.getName()), 29);
buffer.append(platformType);
if (isTrue(column.isNotnull()) || isTrue(column.isPrimaryKey())) {
buffer.append(" not null");
@@ -150,7 +152,7 @@ public class PlatformDdl {
* Return the drop foreign key clause.
*/
public String alterTableDropForeignKey(String tableName, String fkName) {
return "alter table " + tableName + " " + dropConstraintIfExists + " " + fkName;
return "alter table " + alterTableIfExists + tableName + " " + dropConstraintIfExists + " " + fkName;
}
/**
@@ -254,7 +256,7 @@ public class PlatformDdl {
appendColumns(columns, buffer);
buffer
.append(" references ")
.append(lowerName(refTable));
.append(lowerTableName(refTable));
appendColumns(refColumns, buffer);
appendWithSpace(foreignKeyRestrict, buffer);
@@ -346,7 +348,7 @@ public class PlatformDdl {
if (i > 0) {
buffer.append(",");
}
buffer.append(lowerName(columns[i].trim()));
buffer.append(lowerColumnName(columns[i].trim()));
}
buffer.append(")");
}
@@ -358,16 +360,26 @@ public class PlatformDdl {
}
/**
* Convert the table or column name to lower case.
* Convert the table to lower case.
* <p>
* This is passed up to the platformDdl to override as desired.
* Generally lower case with underscore is a good cross database
* Override as desired. Generally lower case with underscore is a good cross database
* choice for column/table names.
*/
protected String lowerName(String name) {
return naming.lowerName(name);
protected String lowerTableName(String name) {
return naming.lowerTableName(name);
}
/**
* Convert the column name to lower case.
* <p>
* Override as desired. Generally lower case with underscore is a good cross database
* choice for column/table names.
*/
protected String lowerColumnName(String name) {
return naming.lowerColumnName(name);
}
/**
* Null safe Boolean true test.
*/
@@ -13,6 +13,7 @@ public class PostgresDdl extends PlatformDdl {
this.historyDdl = new PostgresHistoryDdl();
this.dropTableCascade = " cascade";
this.columnSetType = "type ";
this.alterTableIfExists = "if exists ";
}
/**
@@ -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();
}
}
@@ -81,12 +81,12 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
appendInsertIntoHistory(apply, historyTable, includedColumns);
apply
.append(" NEW.").append(sysPeriod).append(" = tstzrange(CURRENT_TIMESTAMP,null);").newLine()
.append(" return new;").newLine().newLine();
.append(" return new;").newLine();
apply
.append(" elsif (TG_OP = 'DELETE') then").newLine();
appendInsertIntoHistory(apply, historyTable, includedColumns);
apply
.append(" return old;").newLine().newLine();
.append(" return old;").newLine();
apply
.append(" end if;").newLine()
.append("end;").newLine()
@@ -77,6 +77,9 @@ public class CurrentModel {
ModelBuildBeanVisitor visitor = new ModelBuildBeanVisitor(context);
VisitAllUsing visit = new VisitAllUsing(visitor, server);
visit.visitAllBeans();
// adjust the foreign keys on the 'draft' tables
context.adjustDraftReferences();
}
return model;
}
@@ -35,6 +35,8 @@ public class MColumn {
*/
private AlterColumn alterColumn;
private boolean draftOnly;
public MColumn(Column column) {
this.name = column.getName();
this.type = column.getType();
@@ -63,6 +65,28 @@ public class MColumn {
this.notnull = notnull;
}
/**
* Return a copy of this column used for creating the associated draft table.
*/
public MColumn copyForDraft() {
MColumn copy = new MColumn(name, type);
copy.draftOnly = draftOnly;
copy.checkConstraint = checkConstraint;
copy.checkConstraintName = checkConstraintName;
copy.defaultValue = defaultValue;
copy.references = references;
copy.foreignKeyName = foreignKeyName;
copy.foreignKeyIndex = foreignKeyIndex;
copy.historyExclude = historyExclude;
copy.notnull = notnull;
copy.primaryKey = primaryKey;
copy.identity = identity;
copy.unique = unique;
copy.uniqueOneToOne = uniqueOneToOne;
return copy;
}
public String getName() {
return name;
}
@@ -174,6 +198,28 @@ public class MColumn {
return uniqueOneToOne;
}
/**
* Set the draftOnly status for this column.
*/
public void setDraftOnly(boolean draftOnly) {
this.draftOnly = draftOnly;
}
/**
* Return the draftOnly status for this column.
*/
public boolean isDraftOnly() {
return draftOnly;
}
/**
* Return true if this column should be included in History DB triggers etc.
*/
public boolean isIncludeInHistory() {
return !draftOnly && !historyExclude;
}
public Column createColumn() {
Column c = new Column();
@@ -14,7 +14,7 @@ import java.util.List;
*/
public class MCompoundForeignKey {
private final String name;
private String name;
private final String referenceTable;
private final List<String> columns = new ArrayList<String>();
private final List<String> referenceColumns = new ArrayList<String>();
@@ -47,6 +47,28 @@ public class MCompoundForeignKey {
return fk;
}
/**
* Add a counter to the foreign key and index names to avoid duplication.
*/
public void addNameSuffix(int counter) {
this.name = name + "_" + counter;
this.indexName = indexName + "_" + counter;
}
/**
* Return the foreign key name.
*/
public String getName() {
return name;
}
/**
* Return the index name.
*/
public String getIndexName() {
return indexName;
}
/**
* Return the columns making up the foreign key in order.
*/
@@ -14,6 +14,7 @@ import com.avaje.ebean.dbmigration.migration.UniqueConstraint;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -42,6 +43,17 @@ public class MTable {
*/
private final String name;
/**
* The associated draft table.
*/
private MTable draftTable;
/**
* Marked true for draft tables. These need to have their FK references adjusted
* after all the draft tables have been identified.
*/
private boolean draft;
/**
* Primary key name.
*/
@@ -106,6 +118,27 @@ public class MTable {
*/
private AddColumn addColumn;
/**
* Create a copy of this table structure as a 'draft' table.
*
* Note that both tables contain @DraftOnly MColumns and these are filtered out
* later when creating the CreateTable object.
*/
public MTable createDraftTable() {
draftTable = new MTable(name+"_draft");
draftTable.draft = true;
draftTable.whenCreatedColumn = whenCreatedColumn;
// compoundKeys
// compoundUniqueConstraints
draftTable.identityType = identityType;
for (MColumn col: columns.values()) {
draftTable.addColumn(col.copyForDraft());
}
return draftTable;
}
/**
* Construct for migration.
@@ -163,7 +196,10 @@ public class MTable {
}
for (MColumn column : this.columns.values()) {
createTable.getColumn().add(column.createColumn());
// filter out draftOnly columns from the base table
if (draft || !column.isDraftOnly()) {
createTable.getColumn().add(column.createColumn());
}
}
for (MCompoundForeignKey compoundKey : compoundKeys) {
@@ -270,6 +306,13 @@ public class MTable {
return name;
}
/**
* Return true if this table is a 'Draft' table.
*/
public boolean isDraft() {
return draft;
}
public String getPkName() {
return pkName;
}
@@ -482,4 +525,70 @@ public class MTable {
return (value == 0) ? null : BigInteger.valueOf(value);
}
/**
* Check if there are duplicate foreign keys.
* <p>
* This can occur when an ManyToMany relates back to itself.
* </p>
*/
public void checkDuplicateForeignKeys() {
if (hasDuplicateForeignKeys()) {
int counter = 1;
for (MCompoundForeignKey fk : compoundKeys) {
fk.addNameSuffix(counter++);
}
}
}
/**
* Return true if the foreign key names are not unique.
*/
private boolean hasDuplicateForeignKeys() {
Set<String> fkNames = new HashSet<String>();
for (MCompoundForeignKey fk : compoundKeys) {
if (!fkNames.add(fk.getName())) {
return true;
}
}
return false;
}
/**
* Adjust the references (FK) if it should relate to a draft table.
*/
public void adjustReferences(ModelContainer modelContainer) {
Collection<MColumn> cols = columns.values();
for (MColumn col : cols) {
String references = col.getReferences();
if (references != null) {
String baseTable = extractBaseTable(references);
MTable refBaseTable = modelContainer.getTable(baseTable);
if (refBaseTable.draftTable != null) {
// change references to another associated 'draft' table
String newReferences = deriveReferences(references, refBaseTable.draftTable.getName());
col.setReferences(newReferences);
}
}
}
}
/**
* Return the base table name from references (table.column).
*/
private String extractBaseTable(String references) {
int lastDot = references.lastIndexOf('.');
return references.substring(0,lastDot);
}
/**
* Return the new references using the given draftTableName.
* (The referenced column is the same as before).
*/
private String deriveReferences(String references, String draftTableName) {
int lastDot = references.lastIndexOf('.');
return draftTableName+"."+references.substring(lastDot+1);
}
}
@@ -12,6 +12,7 @@ import com.avaje.ebean.dbmigration.migration.DropIndex;
import com.avaje.ebean.dbmigration.migration.DropTable;
import com.avaje.ebean.dbmigration.migration.Migration;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -38,6 +39,18 @@ public class ModelContainer {
}
/**
* Adjust the FK references on all the draft tables.
*/
public void adjustDraftReferences() {
Collection<MTable> tables = this.tables.values();
for (MTable table : tables) {
if (table.isDraft()) {
table.adjustReferences(this);
}
}
}
/**
* Return the map of all the tables.
*/
@@ -213,8 +226,8 @@ public class ModelContainer {
/**
* Add a table (typically from reading EbeanServer meta data).
*/
public void addTable(MTable table) {
tables.put(table.getName(), table);
public MTable addTable(MTable table) {
return tables.put(table.getName(), table);
}
/**
@@ -5,7 +5,6 @@ import com.avaje.ebean.config.dbplatform.IdType;
import com.avaje.ebean.dbmigration.migration.IdentityType;
import com.avaje.ebean.dbmigration.model.MColumn;
import com.avaje.ebean.dbmigration.model.MTable;
import com.avaje.ebean.dbmigration.model.visitor.BeanPropertyVisitor;
import com.avaje.ebean.dbmigration.model.visitor.BeanVisitor;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -29,7 +28,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
* This creates an MTable and adds it to the model.
* </p>
*/
public BeanPropertyVisitor visitBean(BeanDescriptor<?> descriptor) {
public ModelBuildPropertyVisitor visitBean(BeanDescriptor<?> descriptor) {
if (!descriptor.isInheritanceRoot()) {
return null;
@@ -58,7 +57,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
table.addColumn(new MColumn(discColumn, discDbType, true));
}
return new ModelBuildPropertyVisitor(ctx, table, descriptor.getCompoundUniqueConstraints());
return new ModelBuildPropertyVisitor(ctx, table, descriptor);
}
/**
@@ -3,11 +3,15 @@ package com.avaje.ebean.dbmigration.model.build;
import com.avaje.ebean.config.DbConstraintNaming;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebean.config.dbplatform.DbTypeMap;
import com.avaje.ebean.dbmigration.model.MColumn;
import com.avaje.ebean.dbmigration.model.MTable;
import com.avaje.ebean.dbmigration.model.ModelContainer;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.type.ScalarType;
import java.util.Collection;
import java.util.List;
/**
* The context used during DDL generation.
*/
@@ -31,6 +35,14 @@ public class ModelBuildContext {
this.maxLength = maxLength;
}
/**
* Adjust the foreign key references on any draft tables (that reference other draft tables).
* This is called as a 'second pass' after all the draft tables have been identified.
*/
public void adjustDraftReferences() {
model.adjustDraftReferences();
}
public String primaryKeyName(String tableName) {
return maxLength(constraintNaming.primaryKeyName(tableName), 0);
}
@@ -67,8 +79,8 @@ public class ModelBuildContext {
return maxLength(constraintNaming.checkConstraintName(tableName, columnName), checkCount);
}
public void addTable(MTable table) {
model.addTable(table);
public MTable addTable(MTable table) {
return model.addTable(table);
}
public void addIndex(String indexName, String tableName, String columnName) {
@@ -120,4 +132,49 @@ public class ModelBuildContext {
return dbTypeMap.get(dbType);
}
/**
* Create the draft table for a given table.
*/
public void createDraft(MTable table, boolean draftable) {
MTable draftTable = table.createDraftTable();
draftTable.setPkName(primaryKeyName(draftTable.getName()));
if (draftable) {
// Add a FK from @Draftable live table back to it's draft table)
List<MColumn> pkCols = table.primaryKeyColumns();
if (pkCols.size() == 1) {
// only doing this for single column PK at this stage
MColumn pk = pkCols.get(0);
pk.setReferences(draftTable.getName() + "." + pk.getName());
pk.setForeignKeyName(foreignKeyConstraintName(table.getName(), pk.getName(), 0));
}
}
int fkCount = 0;
int ixCount = 0;
int uqCount = 0;
Collection<MColumn> cols = draftTable.getColumns().values();
for (MColumn col: cols) {
if (col.getForeignKeyName() != null) {
// Note that we adjust the 'references' table later in a second pass
// after we know all the tables that are 'draftable'
//col.setReferences(refTable + "." + refColumn);
col.setForeignKeyName(foreignKeyConstraintName(draftTable.getName(), col.getName(), ++fkCount));
String[] indexCols = {col.getName()};
col.setForeignKeyIndex(foreignKeyIndexName(draftTable.getName(), indexCols, ++ixCount));
}
// adjust the unique constraint names
if (col.getUnique() != null){
col.setUnique(uniqueConstraintName(draftTable.getName(), col.getName(), ++uqCount));
}
if (col.getUniqueOneToOne() != null){
col.setUniqueOneToOne(uniqueConstraintName(draftTable.getName(), col.getName(), ++uqCount));
}
}
addTable(draftTable);
}
}
@@ -35,9 +35,18 @@ public class ModelBuildIntersectionTable {
public void build() {
intersectionTable = createTable();
ctx.addTable(intersectionTable);
MTable existingTable = ctx.addTable(intersectionTable);
if (existingTable != null) {
throw new IllegalStateException("Property " + manyProp.getFullBeanName() + " has duplicate ManyToMany intersection table " + intersectionTable.getName()
+ ". Please use @JoinTable to define unique table to use");
}
buildFkConstraints();
if (manyProp.getTargetDescriptor().isDraftable()) {
ctx.createDraft(intersectionTable, false);
}
}
private void buildFkConstraints() {
@@ -47,6 +56,8 @@ public class ModelBuildIntersectionTable {
BeanDescriptor<?> targetDesc = manyProp.getTargetDescriptor();
buildFkConstraints(targetDesc, tableJoin.columns(), false);
intersectionTable.checkDuplicateForeignKeys();
}
@@ -5,6 +5,7 @@ import com.avaje.ebean.dbmigration.model.MColumn;
import com.avaje.ebean.dbmigration.model.MCompoundForeignKey;
import com.avaje.ebean.dbmigration.model.MTable;
import com.avaje.ebean.dbmigration.model.visitor.BaseTablePropertyVisitor;
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;
@@ -14,6 +15,7 @@ import com.avaje.ebeaninternal.server.deploy.TableJoinColumn;
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
@@ -26,6 +28,8 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
private final MTable table;
private final BeanDescriptor<?> beanDescriptor;
private final IndexSet indexSet = new IndexSet();
private MColumn lastColumn;
@@ -36,11 +40,11 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
private int countCheck;
public ModelBuildPropertyVisitor(ModelBuildContext ctx, MTable table, CompoundUniqueConstraint[] constraints) {
public ModelBuildPropertyVisitor(ModelBuildContext ctx, MTable table, BeanDescriptor<?> beanDescriptor) {
this.ctx = ctx;
this.table = table;
addCompoundUniqueConstraint(constraints);
this.beanDescriptor = beanDescriptor;
addCompoundUniqueConstraint(beanDescriptor.getCompoundUniqueConstraints());
}
/**
@@ -97,6 +101,19 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
compoundKey.setIndexName(null);
}
}
addDraftTable();
}
/**
* Create a 'draft' table that is mostly the same as the base table.
* It has @DraftOnly columns and adjusted primary and foreign keys.
*/
private void addDraftTable() {
if (beanDescriptor.isDraftable() || beanDescriptor.isDraftableElement()) {
// create a 'Draft' table which looks very similar (change PK, FK etc)
ctx.createDraft(table, !beanDescriptor.isDraftableElement());
}
}
@@ -213,6 +230,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
}
MColumn col = new MColumn(p.getDbColumn(), ctx.getColumnDefn(p));
col.setDraftOnly(p.isDraftOnly());
if (p.isId()) {
col.setPrimaryKey(true);
@@ -1,5 +1,6 @@
package com.avaje.ebean.dbmigration.model.visitor;
import com.avaje.ebean.dbmigration.model.build.ModelBuildPropertyVisitor;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
/**
@@ -12,6 +13,6 @@ public interface BeanVisitor {
* Visit a BeanDescriptor and return a PropertyVisitor to use to visit each
* property on the entity bean (return null to skip visiting this bean).
*/
BeanPropertyVisitor visitBean(BeanDescriptor<?> descriptor);
ModelBuildPropertyVisitor visitBean(BeanDescriptor<?> descriptor);
}
@@ -0,0 +1,8 @@
/**
* Generation DDL migration scripts based on changes to the model.
* <p>
* You can use <code>DbMigration</code> to compare the entity bean model to its prior state
* and generate DDL for the differences - adding tables, columns etc.
* </p>
*/
package com.avaje.ebean.dbmigration;
@@ -0,0 +1,13 @@
/**
* Provides a built in change log mechanism and can audit changes.
* <p>
* Although you can build something similar using existing event adapters such as <code>BeanPersistController</code>
* this is built for purpose to reduce the effort and provide optimal performance for auditing or logging changes.
* </p>
* <p>
* By default you can annotate beans with <code>@ChangeLog</code> and associated change events are logged by default
* in a JSON form with appropriate auditing attributes such as who made the changes and ip address of the user etc
* via implementation of <code>ChangeLogPrepare</code>
* </p>
*/
package com.avaje.ebean.event.changelog;
@@ -0,0 +1,8 @@
/**
* Provides Auditing of read events including queries and L2 cache.
* <p>
* Provides a built support for supplied an audit of all the 'read events' for beans annotated
* with <code>@ReadAudit</code>
* </p>
*/
package com.avaje.ebean.event.readaudit;
@@ -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.
@@ -17,8 +17,13 @@ public interface SpiBeanType<T> {
Class<T> getBeanType();
/**
* Return the base table this bean type maps to.
* Return true if the property is a valid known property or path for the given bean type.
*/
boolean isValidExpression(String property);
/**
* Return the base table this bean type maps to.
*/
String getBaseTable();
/**
@@ -0,0 +1,8 @@
/**
* Provides a API for plugins.
* <p>
* Plugins can get and read meta data about the entity beans and enhance or utilise the
* functionality of the EbeanServer.
* </p>
*/
package com.avaje.ebean.plugin;
@@ -9,20 +9,6 @@ import java.util.Arrays;
*/
public class ClassUtil {
/**
* Return a new instance of the class using the default constructor.
*/
public static Object newInstance(String className) {
try {
Class<?> cls = Class.forName(className);
return cls.newInstance();
} catch (Exception e) {
String msg = "Error constructing " + className;
throw new IllegalArgumentException(msg, e);
}
}
/**
* Returns the raw type for the 2nd generic parameter for a subclass.
*/
@@ -1,147 +0,0 @@
package com.avaje.ebeaninternal.api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Wraps the caller and context class loaders.
* <p>
* Helper for ClassUtil.
* </p>
*
* @author rbygrave
*
*/
class ClassLoadContext {
private static final Logger logger = LoggerFactory.getLogger(ClassLoadContext.class);
private final ClassLoader callerLoader;
private final ClassLoader contextLoader;
private final boolean preferContext;
private boolean ambiguous;
public static ClassLoadContext of(Class<?> caller, boolean preferContext) {
return new ClassLoadContext(caller, preferContext);
}
/**
* This constructor is package-private to restrict instantiation to
*/
ClassLoadContext(final Class<?> caller, boolean preferContext) {
if (caller == null){
throw new IllegalArgumentException("caller is null");
}
this.callerLoader = caller.getClassLoader();
this.contextLoader = Thread.currentThread().getContextClassLoader();
this.preferContext = preferContext;
}
public Class<?> forName(String name) throws ClassNotFoundException {
ClassLoader defaultLoader = getDefault(preferContext);
try {
return Class.forName(name, true, defaultLoader);
} catch (ClassNotFoundException e) {
if (callerLoader == defaultLoader) {
throw e;
} else {
return Class.forName(name, true, callerLoader);
}
}
}
/**
* Return the expected class loader to use.
* <p>
* Works on the assumption that the child of the caller or context class
* loader is preferred.
* </p>
*/
public ClassLoader getDefault(boolean preferContext) {
if (contextLoader == null){
if (logger.isDebugEnabled()){
logger.debug("No Context ClassLoader, using "+callerLoader.getClass().getName());
}
return callerLoader;
}
if (contextLoader == callerLoader){
if (logger.isDebugEnabled()){
logger.debug("Context and Caller ClassLoader's same instance of "+contextLoader.getClass().getName());
}
return callerLoader;
}
if (isChild(contextLoader, callerLoader)) {
if (logger.isDebugEnabled()){
logger.debug("Caller ClassLoader "+callerLoader.getClass().getName()
+" child of ContextLoader "+contextLoader.getClass().getName());
}
return callerLoader;
} else if (isChild(callerLoader, contextLoader)) {
if (logger.isDebugEnabled()){
logger.debug("Context ClassLoader "+contextLoader.getClass().getName()
+" child of Caller ClassLoader "+callerLoader.getClass().getName());
}
return contextLoader;
} else {
// ambiguous case, perhaps both null
logger.debug("Ambiguous ClassLoader choice preferContext:"+preferContext
+" Context:"+contextLoader.getClass().getName()+" Caller:"+callerLoader.getClass().getName());
ambiguous = true;
return preferContext ? contextLoader : callerLoader;
}
}
/**
* Return true if the 'default' class loader is ambiguous.
*/
public boolean isAmbiguous() {
return ambiguous;
}
/**
* Return the ClassLoader of the caller.
*/
public ClassLoader getCallerLoader() {
return callerLoader;
}
/**
* Return the Thread Context ClassLoader.
*/
public ClassLoader getContextLoader() {
return contextLoader;
}
/**
* Return the ClassLoader for this class.
*/
public ClassLoader getThisLoader() {
return this.getClass().getClassLoader();
}
/**
* Returns 'true' if 'loader2' is a delegation child of 'loader1' [or if
* 'loader1'=='loader2'].
*/
private boolean isChild(final ClassLoader loader1, ClassLoader loader2) {
for (; loader2 != null; loader2 = loader2.getParent()) {
if (loader2 == loader1) {
return true;
}
}
return false;
}
}
@@ -1,111 +1,65 @@
package com.avaje.ebeaninternal.api;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper to find classes taking into account the context class loader.
*/
public class ClassUtil {
private static final Logger logger = LoggerFactory.getLogger(ClassUtil.class);
/**
* Load a class taking into account a context class loader (if present).
*/
public static Class<?> forName(String name) throws ClassNotFoundException {
return forName(name, null);
}
/**
* Load a class taking into account a context class loader (if present).
*/
public static Class<?> forName(String name, Class<?> caller) throws ClassNotFoundException {
if (caller == null) {
caller = ClassUtil.class;
}
ClassLoadContext ctx = ClassLoadContext.of(caller, true);
return ctx.forName(name);
}
public static ClassLoader getClassLoader(Class<?> caller, boolean preferContext) {
if (caller == null) {
caller = ClassUtil.class;
}
ClassLoadContext ctx = ClassLoadContext.of(caller, preferContext);
ClassLoader classLoader = ctx.getDefault(preferContext);
if (ctx.isAmbiguous()) {
logger.info("Ambigous ClassLoader (Context vs Caller) chosen " + classLoader);
}
return classLoader;
}
/**
* Return true if javax validation annotations like Size and NotNull are present.
*/
public static boolean isJavaxValidationAnnotationsPresent() {
return isPresent("javax.validation.constraints.NotNull", null);
}
/**
* Return true if Jackson annotations like JsonIgnore are present.
*/
public static boolean isJacksonAnnotationsPresent() {
return isPresent("com.fasterxml.jackson.annotation.JsonIgnore", null);
}
/**
* Return true if Jackson ObjectMapper is present.
*/
public static boolean isJacksonObjectMapperPresent() {
return isPresent("com.fasterxml.jackson.databind.ObjectMapper", null);
}
/**
* Return true if the given class is present.
*/
public static boolean isPresent(String className) {
return isPresent(className, null);
}
/**
* Return true if the given class is present.
*/
public static boolean isPresent(String className, Class<?> caller) {
try {
forName(className, caller);
return true;
} catch (Throwable ex) {
// Class or one of its dependencies is not present...
return false;
}
}
/**
* Return a new instance of the class using the default constructor.
*/
public static Object newInstance(String className) {
return newInstance(className, null);
}
/**
* Return a new instance of the class using the default constructor.
*/
public static Object newInstance(String className, Class<?> caller) {
try {
Class<?> cls = forName(className, caller);
Class<?> cls = forName(className);
return cls.newInstance();
} catch (Exception e) {
String msg = "Error constructing " + className;
throw new IllegalArgumentException(msg, e);
}
}
/**
* Load a class taking into account a context class loader (if present).
*/
private static Class<?> forName(String name) throws ClassNotFoundException {
return new ClassLoadContext().forName(name);
}
/**
* Helper to wrap the context and caller classLoaders (to use/try both).
*/
static class ClassLoadContext {
private final ClassLoader contextLoader;
private final ClassLoader callerLoader;
ClassLoadContext() {
this.callerLoader = ClassUtil.class.getClassLoader();
this.contextLoader = contextLoader();
}
ClassLoader contextLoader() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return (loader != null) ? loader: callerLoader;
}
public Class<?> forName(String name) throws ClassNotFoundException {
try {
return Class.forName(name, true, contextLoader);
} catch (ClassNotFoundException e) {
if (callerLoader == contextLoader) {
throw e;
} else {
return Class.forName(name, true, callerLoader);
}
}
}
}
}
@@ -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);
@@ -59,11 +59,6 @@ public class ScopedTransaction implements SpiTransaction {
}
}
@Override
public void endQueryOnly() {
transaction.endQueryOnly();
}
@Override
public String getLogPrefix() {
return transaction.getLogPrefix();
@@ -13,7 +13,6 @@ import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.event.readaudit.ReadAuditLogger;
import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebean.dbmigration.DdlGenerator;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
@@ -56,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>
*/
@@ -72,11 +71,6 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
DdlGenerator getDdlGenerator();
/**
* Return the AutoFetchListener.
*/
AutoFetchManager getAutoFetchManager();
/**
* Clear the query execution statistics.
*/
@@ -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.
@@ -66,4 +66,9 @@ public interface SpiExpression extends Expression {
* the associated request.
*/
void addBindValues(SpiExpressionRequest request);
/**
* Validate all the properties/paths associated with this expression.
*/
void validate(SpiExpressionValidation validation);
}
@@ -67,4 +67,8 @@ public interface SpiExpressionList<T> extends ExpressionList<T> {
*/
void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder);
/**
* Validate all the properties/paths used in this expression list.
*/
void validate(SpiExpressionValidation validation);
}
@@ -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 getJsonHandler();
/**
* Parse the logical property name to the deployment name.
*/
@@ -29,7 +35,12 @@ public interface SpiExpressionRequest {
* Append to the expression sql.
*/
SpiExpressionRequest append(String sql);
/**
* Add an encryption key to bind to this request.
*/
void addBindEncryptKey(Object encryptKey);
/**
* Add a bind value to this request.
*/
@@ -0,0 +1,37 @@
package com.avaje.ebeaninternal.api;
import com.avaje.ebean.plugin.SpiBeanType;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Property expression validation request for a given root bean type.
*/
public class SpiExpressionValidation {
private final SpiBeanType<?> desc;
private final LinkedHashSet<String> unknown = new LinkedHashSet<String>();
public SpiExpressionValidation(SpiBeanType<?> desc) {
this.desc = desc;
}
/**
* Validate that the property expression (path) is valid.
*/
public void validate(String propertyName) {
if (!desc.isValidExpression(propertyName)) {
unknown.add(propertyName);
}
}
/**
* Return the set of properties considered as having unknown paths.
*/
public Set<String> getUnknownProperties() {
return unknown;
}
}
@@ -11,7 +11,8 @@ import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebean.event.readaudit.ReadEvent;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebean.plugin.SpiBeanType;
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;
@@ -22,6 +23,7 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
/**
* Object Relational query - Internal extension to Query object.
@@ -94,6 +96,16 @@ public interface SpiQuery<T> extends Query<T> {
}
enum TemporalMode {
/**
* Includes soft deletes rows in the result.
*/
SOFT_DELETED,
/**
* Query runs against draft tables.
*/
DRAFT,
/**
* Query runs against current data (normal).
*/
@@ -171,6 +183,16 @@ public interface SpiQuery<T> extends Query<T> {
*/
boolean isAsOfQuery();
/**
* Return true if this is a 'As Draft' query.
*/
boolean isAsDraft();
/**
* Return true if this query includes soft deleted rows.
*/
boolean isIncludeSoftDeletes();
/**
* Return the asOf Timestamp which the query should run as.
*/
@@ -186,6 +208,10 @@ public interface SpiQuery<T> extends Query<T> {
*/
List<String> getAsOfTableAlias();
void addSoftDeletePredicate(String softDeletePredicate);
List<String> getSoftDeletePredicates();
/**
* Return a listener that wants to be notified when the bean collection is
* first used.
@@ -345,10 +371,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.
@@ -371,16 +397,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.
@@ -423,14 +449,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);
@@ -441,19 +467,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>
@@ -558,7 +584,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
@@ -568,15 +594,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.
@@ -684,4 +710,10 @@ public interface SpiQuery<T> extends Query<T> {
* Return root table alias set by {@link #alias(String)} command.
*/
String getAlias();
/**
* Validate the query returning the set of properties with unknown paths.
*/
Set<String> validate(SpiBeanType<T> desc);
}
@@ -19,11 +19,6 @@ import com.avaje.ebeaninternal.server.persist.BatchControl;
*/
public interface SpiTransaction extends Transaction {
/**
* End the transaction when had query only use.
*/
void endQueryOnly();
/**
* Return the string prefix with the transactin id and label used in logging.
*/
@@ -1,241 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
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;
import java.util.Iterator;
/**
* 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,587 +0,0 @@
package com.avaje.ebeaninternal.server.autofetch;
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;
import javax.persistence.PersistenceException;
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;
/**
* 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 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;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
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,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 com.avaje.ebean.bean.ObjectGraphOrigin;
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 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;
}
}

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