Compare commits

...
Author SHA1 Message Date
Rob Bygrave 4bd9e67703 [maven-release-plugin] prepare release ebean-11.10.4 2018-02-14 15:29:12 +13:00
Rob Bygrave 2a986ab590 #1260 - Mark query.setForUpdate(true) as deprecated - migrate to query.forUpdate() 2018-02-14 15:28:13 +13:00
Rob Bygrave 7c477827fe #1259 - Throw explicit mapping error when an Enum is mapped to both ORDINAL and STRING 2018-02-14 12:39:35 +13:00
Rob Bygrave b2967dec1e #1257 - ServerConfig with register = true, defaultServer = false ... fails as it tries to create the default server 2018-02-13 16:22:52 +13:00
Rob Bygrave a95fd063ae #1256 - Query where many isNotEmpty() ... on ManyToMany with SoftDelete produces incorrect SQL (Part 2) 2018-02-13 14:52:18 +13:00
Rob Bygrave 17a54e3804 No effective change - extra tests for DeployPropertyParser 2018-02-13 14:09:08 +13:00
Rob Bygrave f1b2a09b30 Testing - modify .travis to run mysql as service (bump MySql version to 5.7) 2018-02-10 01:16:40 +13:00
Rob Bygrave e4d9e4f2de Testing - modify .travis to run mysql as service (bump MySql version to 5.7) 2018-02-10 01:04:19 +13:00
Rob Bygrave 6474f306b5 #1255 - Regression on raw LIKE expression with slashes on Postgres
Related update for Sqlite
2018-02-10 00:57:19 +13:00
Rob Bygrave f1c2f134ad #1255 - Regression on raw LIKE expression with slashes on Postgres
Related update for Sqlite
2018-02-10 00:56:05 +13:00
Rob Bygrave 0f7158ea18 Testing - modify .travis to run mysql as service 2018-02-10 00:34:34 +13:00
Rob Bygrave 3fdb76d18f Testing - modify .travis to run mysql as service 2018-02-10 00:31:39 +13:00
Rob Bygrave b1027a2f25 Testing - modify .travis to run mysql as docker container 2018-02-10 00:18:33 +13:00
Rob Bygrave e5a1f9f7cc Testing - modify .travis adding docker service 2018-02-10 00:07:27 +13:00
Rob Bygrave f78f24136a [maven-release-plugin] prepare for next development iteration 2018-02-09 23:42:42 +13:00
Rob Bygrave 5b2aa0c74d [maven-release-plugin] prepare release ebean-11.10.3 2018-02-09 23:42:28 +13:00
Rob Bygrave 175714046a #1254 - SpringJdbcTransactionManager inifinite recursion #1254
Yeah, nah. This is the real fix. Expose:

   SpiTransaction TransctionManager.getInScope();

... and have ebean-spring-txn SpringJdbcTransactionManager call it instead of getActive().
2018-02-09 23:03:07 +13:00
Rob Bygrave 73b2cc55f3 #1254 - SpringJdbcTransactionManager inifinite recursion #1254
Fix. Note tested in ebean-spring-txn.
2018-02-09 22:22:08 +13:00
Rob Bygrave cebe375856 No effective change - initial docker testing setup for oracle and sqlserver 2018-02-09 21:31:40 +13:00
Rob Bygrave 2f47235018 #1255 - Regression on raw LIKE expression with slashes on Postgres 2018-02-09 21:28:39 +13:00
Rob Bygrave 17abd6d977 #1255 - Regression on raw LIKE expression with slashes on Postgres
Tests
2018-02-09 21:25:36 +13:00
Rob Bygrave cc5f3689f3 No effective change - modify tests for SqlServer 2018-02-09 21:10:18 +13:00
Rob Bygrave abcc651e7f #1253 - Query where many isNotEmpty() ... on ManyToMany with SoftDelete produces incorrect SQL
Fix
2018-02-09 15:00:58 +13:00
Rob Bygrave caa36262c1 #1253 - Query where many isNotEmpty() ... on ManyToMany with SoftDelete produces incorrect SQL
Failing test
2018-02-09 15:00:29 +13:00
Rob Bygrave 66b6dba921 [maven-release-plugin] prepare for next development iteration 2018-02-07 16:54:44 +13:00
Rob Bygrave fc01f230f3 [maven-release-plugin] prepare release ebean-11.10.2 2018-02-07 16:54:31 +13:00
Rob Bygrave 7fe3e657d7 No effective change - put default test db back to default to h2 2018-02-07 16:49:49 +13:00
Rob Bygrave 9bade12c9d #1251 - Delete cascade with 11+ and Postgres throws exception
Fix
2018-02-07 16:47:59 +13:00
Rob Bygrave 7a816ae2da #1251 - Delete cascade with 11+ and Postgres throws exception
Failing test
2018-02-07 16:40:12 +13:00
Rob Bygrave b83412281e #1251 - Delete cascade with 11+ and Postgres throws exception
Failing test
2018-02-07 16:39:58 +13:00
Rob Bygrave 478bba292d #1250 - Add explicit coverage for Enum with abstract method - no new issue 2018-02-07 14:01:11 +13:00
Rob Bygrave 77a4e684bb [maven-release-plugin] prepare for next development iteration 2018-02-02 23:39:51 +13:00
Rob Bygrave a8254c4295 [maven-release-plugin] prepare release ebean-11.10.1 2018-02-02 23:39:38 +13:00
Rob Bygrave 2a5d0084ef #1249 - Bump db-migration to 11.3.1 ... with fix for not running empty sql statements (empty strings) 2018-02-02 23:38:11 +13:00
Rob Bygrave f9404639d7 Fix test for H2 select for update - my bad. 2018-02-02 23:36:12 +13:00
Rob Bygrave bbef5a6bbb #1243 - Add IDENTITY support for Oracle platform (since 12c Oracle supports auto generated identity) 2018-02-02 23:18:23 +13:00
Rob Bygrave 4645fd9725 Cleanup tests for Oracle 2018-02-02 23:17:21 +13:00
Rob Bygrave 823cb45221 Cleanup tests for Oracle 2018-02-02 23:17:03 +13:00
Rob Bygrave b2bbb05b83 #1248 - ENH: Additionally read "db" system property to determine default database (to use when testing with docker against different databases) 2018-02-02 20:49:39 +13:00
Rob Bygrave 5ff71324d0 #1247 - Unique index or primary key violation - when jdbc batch cascading to PrivateOwned child with unique constraint 2018-02-02 20:43:55 +13:00
Rob Bygrave 851f1c98b0 #1245 - Refactor internals - move Transaction ProfileStream creation
Stops the profileId parameter pollution, enables to potentially use transaction label
and runtime toggling of collecting transaction profiling detail.
2018-01-31 14:02:50 +13:00
Rob Bygrave a92e620f7e #1245 - Refactor internals - move logic to TransactionManager from DefaultServer 2018-01-31 13:36:23 +13:00
Rob Bygrave f9cd955f7a #1245 - Refactor internals - Add to TransactionManager externalRemoveTransaction() and externalBeginTransaction()
These provide a better/tidier way for external transaction managers (like ebean-spring-txn) to begin and end externally
managed transactions.
2018-01-31 13:28:42 +13:00
Rob Bygrave 71f66005e7 Merge branch 'master' of github.com:ebean-orm/ebean 2018-01-31 11:57:10 +13:00
Rob Bygrave b682293a32 #1245 - Refactor internals - move logic to TransactionManager from DefaultServer 2018-01-31 11:56:38 +13:00
Rob Bygrave 5bd7853ce8 #1245 - Refactor internals - move TransactionMangerScope to be part of TransactionManager 2018-01-31 11:30:11 +13:00
André CamiloandRob Bygrave 276c56161a DiscriminatorColumn length should be 31 (#1244) 2018-01-31 08:32:21 +13:00
Rob Bygrave 6b01821623 #1242 - Bump avaje-classpath-scanner dependency to 3.1.1 2018-01-26 11:51:28 +13:00
Rob Bygrave 8495ccced6 #1241 - ENH: Support using ebean_db system property to control docker container for testing 2018-01-24 19:17:14 +13:00
Rob Bygrave 2d929837c4 No effective change - Update testing for profile location enhancement (#1227) 2018-01-24 19:11:59 +13:00
Rob Bygrave 6754992b1c [maven-release-plugin] prepare for next development iteration 2018-01-22 23:02:52 +13:00
Rob Bygrave 08b7f4a95f [maven-release-plugin] prepare release ebean-11.9.1 2018-01-22 23:02:37 +13:00
Rob Bygrave 07e2605a30 #1240 - ENH: Add support for @Transactional skipCache and label 2018-01-22 22:57:35 +13:00
Rob Bygrave 9a0b742f14 #1240 - ENH: Add support for @Transactional skipCache and label 2018-01-22 15:07:50 +13:00
Rob Bygrave bce745e88d #1240 - ENH: Add support for @Transactional skipCache and label 2018-01-22 15:04:13 +13:00
Rob Bygrave 51d2e4c600 ENH: Add ProfileLocation - Part 2 - performance metrics for transactions with label and profileLocation 2018-01-22 14:54:27 +13:00
Rob Bygrave 4e33027fdf Bump to 11.9.1-SNAPSHOT, add test dependency for logback 2018-01-19 11:10:18 +13:00
Rob Bygrave 0a809b0fb3 #1239 - When Transaction.setSkipCache(true) ... Query with useQueryCache is still use L2 query cache 2018-01-19 10:46:49 +13:00
Rob Bygrave 13ddb915c8 No effective change - add javadoc 2018-01-19 09:38:50 +13:00
Rob Bygrave 07cdf64ece #1238 - Refactor MetaInfoManager - collect query plan stats, fix collection of stats on UpdateQuery 2018-01-19 01:07:37 +13:00
Rob Bygrave b9ae76d465 #1237 - ENH: Add ProfileLocation - support for relating performance metrics for queries and transactions back to source line of code 2018-01-18 18:10:03 +13:00
Rob Bygrave 82f5c21d26 No effective change - Improve exception error message when property is not found on a bean type
Typically using IDE enhancement plugin with incremental compilation where part of an inheritance
hierarchy has been changed but not all related classes recompiled and re-enhanced.
2018-01-12 01:27:24 +13:00
Rob Bygrave 14105eb1eb #1236 - ENH: Add Transaction.current() ... as alternative to obtain the current transaction 2018-01-11 22:34:31 +13:00
Rob Bygrave bab27f25ca [maven-release-plugin] prepare for next development iteration 2018-01-09 23:19:09 +13:00
Rob Bygrave eed8d69715 [maven-release-plugin] prepare release ebean-11.8.1 2018-01-09 23:18:54 +13:00
Rob Bygrave 7b93df2df7 Bump pom version to 11.8.1-SNAPSHOT 2018-01-09 23:17:53 +13:00
Rob Bygrave bb205ced54 #1235 - Thread deadlock on L2 cache load of many property 2018-01-09 23:16:35 +13:00
Rob Bygrave 0d4c50e19c #1234 - Remove deprecated CacheMode.QUERY_ONLY (migrate to CacheMode.GET) 2018-01-09 19:56:55 +13:00
Rob Bygrave 8cff85fdda #1233 - Remove deprecated CacheMode.RECACHE (migrate to CacheMode.PUT) 2018-01-09 19:50:26 +13:00
Rob Bygrave 932986a6b5 #1232 - Performance: Add support for a second read-only DataSource (for implicit query-only transactions) 2018-01-09 16:35:22 +13:00
Rob Bygrave 01539091ef #1231 - Performance: Only use CallStack when using AutoTune (automatic query tuning or profiling) 2018-01-09 13:28:57 +13:00
Rob Bygrave 7e69f71b51 #1230 - Add DB Migration non strict mode - IllegalArgumentException: non-null column has no default value 2018-01-07 15:33:18 +13:00
Rob Bygrave 58d6b2c24e Merge branch 'master' of github.com:ebean-orm/ebean 2018-01-06 12:52:38 +13:00
Rob Bygrave 3c7ef7d46e #1229 - Implement equals and hashCode on ModifyAwareMap, ModifyAwareList and ModifyAwareSet 2018-01-06 12:51:50 +13:00
Rob Bygrave db5db26fca No effective change - rename parameter fix typo 2018-01-06 12:21:14 +13:00
rob bygrave 7110f4a59b No effective change - remove unused HashQueryPlan etc 2017-12-28 16:17:00 +13:00
rob bygrave 9f91d8bcf9 No effective change - modify specific test to run with Postgres specific syntax 2017-12-27 23:23:01 +13:00
Rob Bygrave ac97444ad1 No effective change - add test for @Aggregation with order by clause 2017-12-27 13:49:46 +13:00
Rob Bygrave f6e9afcab6 #1226 - Version range in dependency makes sbt resolve very slow. Change slf4j-api dependency to provided scope.
Part 2, bring in new version of avaje-classpath-scanner which also has slf4j-api as provided scope.
2017-12-27 13:48:22 +13:00
Rob Bygrave a7226079fe [maven-release-plugin] prepare for next development iteration 2017-12-14 00:00:04 +13:00
Rob Bygrave fc915010d9 [maven-release-plugin] prepare release ebean-11.7.1 2017-12-13 23:59:49 +13:00
Rob Bygrave c1818bc309 #1226 - Version range in dependency makes sbt resolve very slow. Change slf4j-api dependency to provided scope. 2017-12-13 22:51:23 +13:00
Rob Bygrave 4a4134ee93 #1227 - NPE when using L2 Query cache with findOne() or findOneOrEmpty() 2017-12-13 22:19:25 +13:00
Rob Bygrave e07bd8f736 #1223 - Support mapping @DbArray to a List or Set of enums 2017-12-13 21:15:19 +13:00
Rob Bygrave 4d0b29624e #1222 - Extend L2 ServerCache API with putAll(), getAll() and removeAll() methods 2017-12-13 19:58:28 +13:00
Rob Bygrave c7f4b56dd1 #1225 - ENH: Extend L2 server cache (near caching) for clearing "near caches" 2017-12-13 02:09:10 +13:00
Rob Bygrave 69c045443c #1218 - ENH: Bump to migration 11.2.1 with support for options patchInsertOn and patchResetChecksumOn 2017-11-23 19:05:35 +13:00
Rob Bygrave fd2d083971 #1217 - L2 query cache not caching empty result set 2017-11-22 22:03:35 +13:00
Rob Bygrave b9380aa5b8 No effective change - add property test for ServerConfig jsonDateTime 2017-11-22 21:21:09 +13:00
Rob Bygrave 3a1368a342 [maven-release-plugin] prepare for next development iteration 2017-11-19 20:34:54 +13:00
Rob Bygrave 0e9fc31531 [maven-release-plugin] prepare release ebean-11.6.1 2017-11-19 20:34:39 +13:00
Rob Bygrave 5132107748 Bump to 11.6.1-SNAPSHOT 2017-11-19 20:33:27 +13:00
Rob Bygrave 7ff3d703f7 #1215 - ENH: Add Query setBeanCacheMode(CacheMode) ... with a view to it replacing the existing setUseCache(boolean) and setLoadBeanCache(boolean) 2017-11-19 20:16:40 +13:00
Rob Bygrave ef91c643ed #1214 - ENH: Add inPairs() expression - support L2 cache hits for complex natural key with findList() and in pairs expression 2017-11-19 16:47:32 +13:00
Rob Bygrave f0ddf6c7e6 #1213 - NPE with TxType SUPPORTS 2017-11-19 01:51:03 +13:00
Rob Bygrave fcff571664 #1212 - ENH: Add query findSingleAttribute() 2017-11-18 22:36:08 +13:00
Rob Bygrave d5555ed9a9 #1211 - ENH: Support L2 cache complex natural keys include findList() queries 2017-11-18 22:20:42 +13:00
Rob Bygrave c5a11aa5a8 No effective change - format and javadoc 2017-11-18 10:48:37 +13:00
Rob Bygrave d1099c8309 Merge branch 'master' of github.com:ebean-orm/ebean 2017-11-15 01:02:35 +13:00
Rob Bygrave 7ff8758797 No effective change - fix javadoc 2017-11-15 01:02:11 +13:00
Koen De GrooteandRob Bygrave f1ebe7929b Internal refactor / tidy - Suggestions from intellij code analysis. (#1209) 2017-11-14 18:19:38 +13:00
Rob Bygrave 0056568bc7 [maven-release-plugin] prepare for next development iteration 2017-11-13 23:18:10 +13:00
Rob Bygrave 4b72924529 [maven-release-plugin] prepare release ebean-11.5.1 2017-11-13 23:17:54 +13:00
Rob Bygrave a75ed676f8 Bump pom version to 11.5.1-SNAPSHOT 2017-11-13 23:16:38 +13:00
Rob Bygrave c3438b888d #995 - @OneToMany with constraints on the many side 2017-11-13 22:43:56 +13:00
Rob Bygrave 56be274acc No effective change - change internal use of ArrayList to List 2017-11-13 22:42:39 +13:00
Rob Bygrave 84bb4ff78c #1207 - Regression added in 11.4.1 with NPE on @transactional(type = TxType.SUPPORTS) 2017-11-13 21:33:39 +13:00
Rob Bygrave 4054eda1f6 #1207 - Regression added in 11.4.1 with NPE on @transactional(type = TxType.SUPPORTS) 2017-11-13 21:32:52 +13:00
Rob Bygrave a4b9354e43 #1206 - NPE when using @Encrypted with Enum property that is null 2017-11-13 19:12:57 +13:00
Rob Bygrave 06e033f23f #1181 - ENH: Add @FetchPreference support - Secondary query generation is based on order of fetch statements 2017-11-08 23:20:08 +13:00
Rob Bygrave 156f505394 #1181 - ENH: Add @FetchPreference support - Secondary query generation is based on order of fetch statements 2017-11-08 23:19:58 +13:00
Rob Bygrave 9b2b2f7787 #1205 - ENH: Add support for raw expression array binding with Postgres - e.g. raw(" foo = any(?) ", collection) 2017-11-08 00:56:13 +13:00
Rob Bygrave e68dae0baa Update ebean-annotation to 3.3 and tests to use 11.5.1 agent 2017-11-07 22:57:04 +13:00
Rob Bygrave 2cf864954f #1204 - Check presence of JAXB and javax @PostConstruct (which may not be present in minimal Java 9) 2017-11-07 20:08:10 +13:00
Rob Bygrave 2b94487573 #1202 - Automatically remove old query plans from Ebean's internal query plan cache 2017-11-06 22:59:42 +13:00
Rob Bygrave fcf730ec39 [maven-release-plugin] prepare for next development iteration 2017-11-06 14:49:08 +13:00
Rob Bygrave 7d343455ac [maven-release-plugin] prepare release ebean-11.4.1 2017-11-06 14:48:55 +13:00
Rob Bygrave 21e0b748a6 #1199 - Remove deprecated findUnique() method - please migrate to findOne() 2017-11-03 03:35:19 +13:00
Rob Bygrave 2fab733e4e Update agent and maven plugin for 11.4.1 enhancement (@Transactional and PropertyChangeListener changes) 2017-11-03 03:30:53 +13:00
Rob Bygrave 7260a1b6eb #1198 - Remove support for PropertyChangeListener from entity beans 2017-11-03 03:08:43 +13:00
Rob Bygrave 434a005366 #1198 - Remove support for PropertyChangeListener from entity beans 2017-11-03 03:05:59 +13:00
Rob Bygrave ff67ef60b0 #1197 - Refactor how @Transactional methods internally managed 2017-11-03 02:13:56 +13:00
Rob Bygrave 0a1747d8e3 #1194 - Change from using Jetbrains @Nullable to ... class retention based javax annotation @Nonnull 2017-11-02 15:56:15 +13:00
Rob Bygrave f36499f71f No effective change - remove use of jetbrains @NotNull in tests 2017-11-02 15:26:35 +13:00
rob bygrave da818f17c2 #1190 - MySql throwing DataIntegrityException when it should throw the more specific DuplicateKeyException
Ah, remove the 23000 SQLState to just use the error codes for MySql.
2017-11-02 01:16:52 +13:00
rob bygrave 10eb2bc21f #1193 - For RawSql ignore the Postgres cast double colon like ::text 2017-11-02 00:03:38 +13:00
rob bygrave 66fe278e3f #1192 - findNative() with @Embedded beans ... the embedded bean isn't loaded properly 2017-11-01 23:03:57 +13:00
rob bygrave 10607385e0 #1191 - Trim logged SQL removing new line chars (for sql from SqlUpdate) 2017-11-01 22:00:55 +13:00
rob bygrave db6562df76 #1190 - MySql throwing DataIntegrityException when it should throw the more specific DuplicateKeyException 2017-11-01 21:54:48 +13:00
rob bygrave c0ec87d4cd #1189 - ExpressionList.isNull and SoftDelete 2017-11-01 21:48:23 +13:00
rob bygrave 0dbba7a211 #1189 - ExpressionList.isNull and SoftDelete 2017-11-01 21:26:15 +13:00
rob bygrave e75670f207 #1188 - When using inheritance getReference() on a leaf type ... should not hit the DB 2017-11-01 20:42:18 +13:00
rob bygrave fb8db14f3a #1186 - Tidy, remove H2MultiValueBind for now 2017-10-31 22:14:01 +13:00
rob bygrave c00b1849ef Merge branch 'feature/1186-MultiValueBind' 2017-10-31 22:11:30 +13:00
rob bygrave 1e1c518641 #1186 - ENH: Support using TableValues / Array for select prop in (?) queries
Use Postgres Array ANY and ALL without select unnest
2017-10-31 22:09:35 +13:00
Rob Bygrave d5b42f9ba1 [maven-release-plugin] prepare for next development iteration 2017-10-31 10:52:44 +13:00
Rob Bygrave bda564d2c8 [maven-release-plugin] prepare release ebean-11.3.1 2017-10-31 10:52:31 +13:00
Rob Bygrave dce124cabd #1187 - Fix connection leak regression introduced in 11.2.3 for findEach() with invalid SQL 2017-10-31 10:50:20 +13:00
Rob Bygrave f5cfcbd56e #1187 - Fix connection leak regression introduced in 11.2.3 for findEach() with invalid SQL 2017-10-31 10:49:35 +13:00
rob bygrave 5bccdccbd6 #1186 - ENH: Support using TableValues / Array for select prop in (?) queries 2017-10-31 02:53:34 +13:00
rob bygrave 6e62b72fb9 #1186 - ENH: Support using TableValues / Array for select prop in (?) queries 2017-10-31 02:53:20 +13:00
rob bygrave 89dac3820b #1182 - Error when applied disjunction to filterMany 2017-10-30 22:06:57 +13:00
rob bygrave ea836a0078 No effective change - rename variable 2017-10-30 21:29:57 +13:00
rob bygrave 1ffd7c7720 git push origin masterMerge branch 'OndrejPacay-feature/1179_bugCollectionL2CacheNotInvalidatedAfterExternalChange' 2017-10-30 21:25:43 +13:00
rob bygrave 387582543c Merge branch 'feature/1179_bugCollectionL2CacheNotInvalidatedAfterExternalChange' of https://github.com/OndrejPacay/ebean into OndrejPacay-feature/1179_bugCollectionL2CacheNotInvalidatedAfterExternalChange 2017-10-30 21:25:30 +13:00
rob bygrave a215878a7c #1183 - ENH: Add support for transaction profiling 2017-10-30 21:21:12 +13:00
rob bygrave dbedb65922 #1183 - ENH: Add support for transaction profiling 2017-10-30 21:19:58 +13:00
Rob Bygrave 1d52b3a818 #1183 - Support transaction profiling, adjust output format 2017-10-27 00:27:21 +13:00
Rob Bygrave 6ce2b91fff #1183 - Support transaction profiling, move away from binary encoding 2017-10-26 23:32:54 +13:00
Rob Bygrave 55d66ae851 #1183 - Support transaction profiling, move away from binary encoding 2017-10-26 23:32:44 +13:00
Rob Bygrave 37d001f754 #1185 - Modify JDBC batch so it does not "auto flush" on non-Id non-generated property 2017-10-26 00:03:58 +13:00
rob bygrave c121618c0c #1183 - ENH: Add support for transaction profiling 2017-10-25 01:31:09 +13:00
rob bygrave 7ebef51859 #1183 - ENH: Add support for transaction profiling 2017-10-25 01:07:00 +13:00
rob bygrave 093f82a87f #1178 - Make the new changeLogAsync property default to true in ServerConfig 2017-10-19 22:26:13 +13:00
Ondrej Pacay b60f1f978f Bug Collection IDs L2 cache not cleared on external sql modification of the table #1179 2017-10-19 01:06:43 +02:00
Roland PramlandRob Bygrave 6ac146cabe Fix beanchange deser (#1178)
Fix / support JSON marshalling of BeanChange + add changeLogAsync option to ServerConfig
2017-10-19 09:40:27 +13:00
Roland PramlandRob Bygrave 2263d31f46 Improved the performance of findAnnotation (#1177) 2017-10-19 09:34:57 +13:00
rob bygrave 4a66d0be6b [maven-release-plugin] prepare for next development iteration 2017-10-19 01:26:28 +13:00
rob bygrave e68e496207 [maven-release-plugin] prepare release ebean-11.2.3 2017-10-19 01:26:12 +13:00
rob bygrave 088110385f Bump ebean-migration dependency to 11.1.1 2017-10-19 01:19:51 +13:00
rob bygrave a355f66561 #1176 - Allow script in extra-ddl.xml ... to not have trailing semi-colon 2017-10-19 01:11:14 +13:00
rob bygrave 00fdb632d7 #1173 - Ebean server should read ebean.mf entity-packages to locate entity beans 2017-10-18 23:58:22 +13:00
rob bygrave a54c4157e6 Refactor as part of - Ensure that Ebean.currentTransaction is available in lifecycle handlers (#1166) #1175 2017-10-18 23:27:59 +13:00
Rob Bygrave a89a9f0202 #1166 - Refactor to use beginServerTransaction() to make Ebean.currentTransaction available in lifecycle handlers 2017-10-17 01:07:48 +13:00
Rob Bygrave 331f49824f #1172 - Refactor - remove internal unused DefaultOrmQuery logSecondaryQuery (as we use LoggedSqlCollector) 2017-10-16 23:49:23 +13:00
Rob Bygrave 94a857d345 #1171 - ENH: Add Finder.update() method ... returning a UpdateQuery<T> 2017-10-16 23:25:56 +13:00
Rob Bygrave 1005f177fd #1170 - ENH: Add isIn() methods as alias to in() on ExpressionList (for use with Kotlin where in is a keyword) 2017-10-16 23:13:49 +13:00
Rob Bygrave 96a4b93c75 #1167 - BUG: query.setId(...).delete() does not work 2017-10-16 22:58:48 +13:00
Rob Bygrave 5869187368 [maven-release-plugin] prepare for next development iteration 2017-10-16 22:26:39 +13:00
Rob Bygrave eac50b8f5b [maven-release-plugin] prepare release ebean-11.2.2 2017-10-16 22:26:21 +13:00
Rob Bygrave 6e2712ea49 #1169 - Memory leak - DefaultTransactionThreadLocal holding reference to transaction 2017-10-16 22:14:48 +13:00
Rob Bygrave f6e5446dab #1169 - Memory leak - DefaultTransactionThreadLocal holding reference to transaction - test case 2017-10-16 22:13:41 +13:00
Koen De GrooteandRob Bygrave 4879d44da4 Bit of cleanup suggestions from intellij. (#1168) 2017-10-16 20:16:30 +13:00
rob bygrave 2c5bf98d0b Merge branch 'master' of github.com:ebean-orm/ebean 2017-10-09 21:13:32 +13:00
rob bygrave 45a571bd97 #1165 - ENH: Add to Finder - currentTransaction() and flush() methods ... (as useful shortcuts) 2017-10-09 21:13:12 +13:00
Roland PramlandRob Bygrave d0ca75c25b FIX: connection leak in query-iterator (#1164) 2017-10-09 20:46:02 +13:00
Roland PramlandRob Bygrave fb1c7667cc FIX: order by must be added in findEach also (#1163) 2017-10-09 20:28:36 +13:00
Roland PramlandRob Bygrave 1909ba1557 Fix like escaping for DB2, Oracle and MySql (#1162)
* Extended test case to show problem

* FIX: Proper escaping of backslash in like/contains queries

* Had to replace "like ?" assertions in tests with "like " as mysql will do a "like binary ? escape'|'" now to be case sensitive
2017-10-06 09:46:56 +13:00
Roland PramlandRob Bygrave e4eca4bbe9 FIX: Oto relations must match datatype (#1161) 2017-10-06 09:44:06 +13:00
Roland PramlandRob Bygrave b2438ecd71 FIX: do not generate unique-constraint for PK as it is useless (and oracle does not accept this) (#1160) 2017-10-06 09:43:30 +13:00
Roland PramlandRob Bygrave ed08567d06 FIX: bundle symbolic name in manifest (#1159) 2017-10-06 09:42:42 +13:00
Roland PramlandRob Bygrave bbe8abcc2e Can handle one to many & many to one relations in docstore only entities (#1158) 2017-10-06 09:42:05 +13:00
Roland PramlandRob Bygrave 1651d5e119 Misc platform fixes (#1157)
* Oracle does not return row count - so we probably cannot detect Optimistic locking

* DB2 wants a varchar null value (other platfroms are happy with this, too)

* FIX Test for DB2 platform
2017-10-05 21:51:09 +13:00
Roland PramlandRob Bygrave 64bf6d6a35 commit transaction only if active (#1156) 2017-10-05 11:04:12 +13:00
Roland PramlandRob Bygrave 47b0a917a2 No effective code change - fix imports and override annotations (#1155) 2017-10-05 10:55:48 +13:00
rob bygrave 2fe1a349c3 [maven-release-plugin] prepare for next development iteration 2017-10-05 01:36:46 +13:00
rob bygrave f168a8fd0d [maven-release-plugin] prepare release ebean-11.2.1 2017-10-05 01:36:30 +13:00
rob bygrave dda98c1dda Bump to 11.2.1-SNAPSHOT 2017-10-05 01:35:30 +13:00
rob bygrave 88a13c5416 #1154 - Remove deprecated TxCallable and TxRunnable - migrate to use Callable and Runnable 2017-10-05 01:24:53 +13:00
rob bygrave 16b8f5da35 Refactor EbeanServerFactory internals - use SpiContainerFactory interface and service loader 2017-10-05 01:12:07 +13:00
rob bygrave 3471f6d206 Refactor EbeanServerFactory internals - use SpiContainerShutdown interface for container shutdown mechanism 2017-10-05 00:57:40 +13:00
rob bygrave 0bf4c84bc8 #1153 - Refactor move SplitName into io.ebean.util 2017-10-05 00:41:32 +13:00
rob bygrave d366336ee5 #1153 - Refactor move ExtraTypeFactory out of io.ebean.plugin into io.ebeaninternal 2017-10-05 00:38:42 +13:00
rob bygrave bf3f8ecff5 Refactor plugin.BeanType addInheritanceWhere() to just take Query and not SpiQuery 2017-10-05 00:35:12 +13:00
rob bygrave 7524903aea #1152 - Refactor EJson internals extracting io.ebean.service.SpiJsonService 2017-10-05 00:29:43 +13:00
rob bygrave 90cf3fddc9 #1151 - Refactor add util AnnotationUtil ... moving common annotation searching methods there 2017-10-05 00:00:44 +13:00
rob bygrave 8e0f9f7591 Refactor - remove unused methods from StringHelper 2017-10-04 23:35:57 +13:00
rob bygrave 24936c0a41 Refactor - remove unused class ClassUtil 2017-10-04 23:34:13 +13:00
rob bygrave f02a552cdf #1150 - Refactor convert RawSql and RawSqlBuilder into interfaces and push the parsers into io.ebeaninternal 2017-10-04 23:03:12 +13:00
rob bygrave 5a5cb7ffbd #1149 - Refactor move PersistenceContextUtil into ... io.ebeaninternal 2017-10-04 21:12:39 +13:00
rob bygrave f85fa772bf #1147 - Refactor move DbMigration internals into io.ebeaninternal - change to use DbMigration.create() - move DbMigrationFactory.create() method 2017-10-04 21:06:55 +13:00
rob bygrave 9f9ec8ef9f #1147 - Refactor move DbMigration internals into io.ebeaninternal - change to use DbMigration.create() - move IdentityType handling into internal from DbIdentity 2017-10-04 20:49:04 +13:00
rob bygrave 62707caf64 #1147 - Refactor move DbMigration internals into io.ebeaninternal - change to use DbMigration.create() - move PlatformDdl into internal 2017-10-04 20:30:57 +13:00
rob bygrave c6fc7a032d #1147 - Refactor move DbMigration internals into io.ebeaninternal - change to use DbMigration.create() - DbOffline & ServerConfig 2017-10-04 19:44:29 +13:00
rob bygrave 10faa1fae6 #1147 - Refactor move DbMigration internals into io.ebeaninternal - change to use DbMigration.create() - initial move 2017-10-04 19:36:27 +13:00
Rob Bygrave 764f5ce4d4 [maven-release-plugin] prepare for next development iteration 2017-10-03 23:20:50 +13:00
Rob Bygrave 65ba231fc0 [maven-release-plugin] prepare release ebean-11.1.1 2017-10-03 23:20:35 +13:00
Rob Bygrave 1da2382e45 No effective change - javadoc only 2017-10-03 15:38:17 +13:00
Rob Bygrave a86af94a8c Reduce logging level only - no functional change 2017-10-03 15:35:21 +13:00
Rob Bygrave 8301cb634e #1027 - ENH: Add a Slow query event listener ... to log or listen to queries that exceed some defined execution time 2017-10-03 15:04:30 +13:00
Rob Bygrave 721654d033 #1140 - Error loading embedded instance
The issue was due to the loaded status not being recursively set on embedded
beans after inserts. The test case invoked lazy loading due to that which
lead to the issue.
2017-10-03 12:47:29 +13:00
Rob Bygrave fe1817ef02 No effective change - update test 2017-10-03 12:34:56 +13:00
Rob Bygrave fc996659f2 #1139 - NoSuchMethodError: javax.persistence.Embedded.prefix()Ljava/lang/String; 2017-10-03 11:02:38 +13:00
rob bygrave 368d22baa6 #1145 - Refactor @ChangeLog based on JSON documents for 'new values' and 'old values' 2017-10-03 01:31:37 +13:00
rob bygrave daf3d97266 #1144 - Refactor internals - move generated property setting into PersistRequestBean 2017-10-03 00:30:34 +13:00
rob bygrave f2179fca2d #1123 - 11.1.1 API change - moved PersistBatch, Platform, TxIsolation and TxType to io.ebean.annotation package 2017-09-15 21:48:53 +12:00
rob bygrave 2c436c3af5 #1123 - 11.1.1 API change - moved PersistBatch, Platform, TxIsolation and TxType to io.ebean.annotation package 2017-09-15 21:48:37 +12:00
Roland PramlandRob Bygrave 8d334ff2e8 Feature/contitional test runner (#1132)
* FIX: warnings (add generics, overrides, fix imports) - should be no effecive code change

* replaced deprecated methods by default methods

* FIX: more compiler warnings

* FIX more warnings - no code changes

* FIX: deprecated call to JsonParseException

* Remove unused code, check unused variables

* ADD: ContitonaltestRunner to skip tests for certain platforms

* Use annotations to control on which platforms a test shoud run

* FIX: compile errors
2017-09-15 21:03:03 +12:00
rob bygrave f3e4696498 [maven-release-plugin] prepare for next development iteration 2017-09-14 01:03:21 +12:00
rob bygrave 221260fa44 [maven-release-plugin] prepare release ebean-10.4.7 2017-09-14 01:03:07 +12:00
rob bygrave f8e7a79e9a #1131 - Migration broken in 10.4.6 due different classpath-scanner version 2017-09-14 00:56:37 +12:00
Roland PramlandRob Bygrave b66f18bfd6 Fix warnings 2 (#1130)
* FIX: warnings (add generics, overrides, fix imports) - should be no effecive code change

* replaced deprecated methods by default methods

* FIX: more compiler warnings

* FIX more warnings - no code changes

* FIX: deprecated call to JsonParseException

* Remove unused code, check unused variables
2017-09-14 00:32:48 +12:00
Roland PramlandRob Bygrave ac6708e0d0 Fix warnings (#1129)
* FIX: warnings (add generics, overrides, fix imports) - should be no effecive code change

* replaced deprecated methods by default methods

* FIX: more compiler warnings

* FIX more warnings - no code changes
2017-09-14 00:29:01 +12:00
rob bygrave a5f46c095d [maven-release-plugin] prepare for next development iteration 2017-09-13 23:02:39 +12:00
rob bygrave 3ac7ea6e60 [maven-release-plugin] prepare release ebean-10.4.6 2017-09-13 23:02:25 +12:00
rob bygrave b2e0271ce7 #1128 - Allow "2 word SQL" like TRUNCATE t, ANALYSE t etc 2017-09-13 21:40:17 +12:00
rob bygrave c7a7651602 #1127 - Remove the ebean.query.globallimit of 1 million "backstop" 2017-09-13 21:22:03 +12:00
rob bygrave 1e6678cbb4 #1126 - Regression bug in 10.4.5 with new line character use with SqlQuery, SqlUpdate and CallableSql 2017-09-13 21:14:19 +12:00
Roland PramlandRob Bygrave a6e4e181bf Feature/cache improvements 2 (#1067)
* New Mode to control queryCache behavior

* Collections form the cache have to be read only

* findCount not queried in query cache

* return modifiable collections from the cache

* FIX: Compile errors

* No effective code change: reformat code / javadoc
2017-09-12 23:40:18 +12:00
Roland PramlandRob Bygrave b8e1dffca2 replaced findUnique by "default" method in interfaces. (#1124) 2017-09-12 23:20:04 +12:00
rob bygrave 7e8ed72b7b #1125 - ENH: Add a SpiContainerBootup API ... to enable automatic startup of docker containers (for testing usually) 2017-09-12 22:44:37 +12:00
rob bygrave ec8dd30a04 [maven-release-plugin] prepare for next development iteration 2017-09-11 23:12:43 +12:00
rob bygrave f81d5c9f3a [maven-release-plugin] prepare release ebean-10.4.5 2017-09-11 23:12:29 +12:00
rob bygrave 74b24b1db1 #1120 - Bump classpath scanner dependency to 2.2.4 (fix for NPE on empty directory) 2017-09-11 23:09:28 +12:00
rob bygrave 179ef8c8a0 No effective change - javadoc update on Ebean 2017-09-10 21:23:37 +12:00
rob bygrave f77f3c3d6e #1119 - Deprecate TxRunnable and TxCallable - migrate to plan JDK Runnable and Callable. Add explicit executeCall() method 2017-09-10 20:47:26 +12:00
Roland PramlandRob Bygrave 9a7646195c Featur/dbmigration 3 (#1118)
* ADD: more test classes for Softdelete and History

* update reference models after adding test classes

* DbMigration:Improved History generation for sqlserver

* FIX: When DB supports Sql2011 platform, we must not alter the history-table (as this is handled by DB itself)

* DbMigration: When dropping a table, drop the sequence also.

* DbMigration: dropColumn is handled by platformDdl now, as SqlServerDdl needs to delete dependent objects (default constraints...) first

* FIX: DbMigration is not executed on history table

* DbMigration: can drop comment now.

* No effective code change: completed ObjectFactory / fixed imports

* FIX: drop table comment

* Committed the reference models
2017-09-10 15:26:01 +12:00
rob bygrave 84d3578e33 No effective change - add a test for saveAll() with existing batch mode on
Assert that it does not flush the batch.  We want the batch to continue and flush normally.
2017-09-09 14:14:57 +12:00
rob bygrave ef05b81014 #1117 - Refactor - moved DbDefault and DbMigration annotations to ebean-annotation 2017-09-09 13:27:09 +12:00
rob bygrave 726c682984 #1116 - Remove references to annotation EbeanDDL which is no longer needed 2017-09-09 13:18:39 +12:00
rob bygrave 0c22a99fdf #1116 - Remove references to annotation EbeanDDL which is no longer needed 2017-09-09 13:12:40 +12:00
rob bygrave c062c8e001 #1054 Java 9 - adding "Automatic-Module-Name" to MANIFEST.MF initial step
Update dependency to ebean-migration 10.2.1, avaje-classpath-scanner and avaje-datasource to avoid package name clash
2017-09-09 01:53:47 +12:00
rob bygrave 06f3c000f5 #1115 - Trim SQL for SqlUpdate, SqlQuery and CallableSql - also replace \n with space 2017-09-08 23:45:28 +12:00
rob bygrave 8c0232fdb8 #1114 - Deprecate findUnique() - please migrate to findOne() - update tests 2017-09-08 22:48:46 +12:00
rob bygrave 2470486f20 #1114 - Deprecate findUnique() - please migrate to findOne() 2017-09-08 22:37:50 +12:00
rob bygrave 8fbf0374dd No effective change - tidy around Db migration changes 2017-09-08 22:02:56 +12:00
Roland PramlandRob Bygrave dd8fff0277 Feature/ddl migration (#1111)
* REFACTOR: use functions from ddl-help

* NEW: Default values are written to DDL

* fix testcase for sqlserver as default constraints will get names in future

* Reading DbMigration

* ADD: DbmIgrationGenerateTest

* Tables must be created first

* Sanity checks and some getters

* DdlMigration gets applied now

* ADD: Testcase and reference migration scripts

* no effective code changes: add comments
2017-09-08 21:47:33 +12:00
Rob Bygrave 73b3d1d977 [maven-release-plugin] prepare for next development iteration 2017-09-07 00:41:49 +12:00
Rob Bygrave 2a76ae2192 [maven-release-plugin] prepare release ebean-10.4.4 2017-09-07 00:41:36 +12:00
Rob Bygrave 39931a3a2c No effective change - add test example of markUnset on version property (to avoid occ check) 2017-09-07 00:40:19 +12:00
Rob Bygrave 58eb49617b #1113 - SqlUpdate with leading spaces leads to Invalid event detection (which table was update for L2 cache invalidation etc) 2017-09-07 00:36:52 +12:00
Rob Bygrave 9b621d2ec7 #1112 - Enhance JDBC batch behavior when cascade persist is turned off (manual master/detail batch persisting) 2017-09-06 23:57:54 +12:00
Roland PramlandRob Bygrave f2e8b652fa FIX: DDL process comment change correctly & drop references when index is dropped (#1109) 2017-09-01 20:45:06 +12:00
Roland PramlandRob Bygrave f342d9d561 API-CHANGE: DbMigration returns which version was generated (#1108) 2017-09-01 20:42:02 +12:00
Roland PramlandRob Bygrave 8009bdf099 REFACTOR: Created a "splitNames" in StringHelper and use it in various places. Also wrote some tests for StringHelper. (#1107) 2017-09-01 20:40:39 +12:00
Roland PramlandRob Bygrave 7336ce81bd FIX: Do not add AssocOne-generatedProperties if they should not. (#1106) 2017-09-01 20:38:13 +12:00
Roland PramlandRob Bygrave f805bec8f2 FIX: Using correct datatype for null value when json is serialized to varchar column (#1105) 2017-09-01 20:36:41 +12:00
Roland PramlandRob Bygrave 46cdc84e6b Refactor/findannotation (#1104)
* refactor: renamed findAnnotation method that searches recursive to findAnnotationRecursive

* CHG: searching namedQueries & indices also recursive in superclasses

* FIX: Using the AnnotationBase methods on several places and documented them
2017-09-01 20:25:10 +12:00
Roland PramlandRob Bygrave e908d16ba8 Pr/add tests (#1103)
* ADD: Testcase for Sqlserver-6.2.0 driver

* CHG: Removed Row-limit - so we need no special handling when sqlserver-platform generates a "select top 100" statement

* ADD: additional asserts to TestQueryCache

* CHG: Ignore test for sqlserver
2017-09-01 20:17:56 +12:00
rob bygrave 96e3f4138f #1064 - Query table alias not used in update() or delete() queries 2017-08-31 21:29:08 +12:00
rob bygrave 977dd52890 No effective change - code reformat only 2017-08-31 21:10:13 +12:00
Roland PramlandRob Bygrave a9b7bb2175 FIX: Passing MDC through background-executor (#1102) 2017-08-31 21:09:04 +12:00
rob bygrave f83c7fdac8 #1100 - Refactor OrmQueryPlanKey to use string expression for where 2017-08-31 02:11:49 +12:00
rob bygrave a695fb59b2 [maven-release-plugin] prepare for next development iteration 2017-08-31 01:50:20 +12:00
901 changed files with 23395 additions and 7901 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ ebean-profiling*.xml
/db
/mydb.db
!src/test/ddl-review/*.sql
profiling/
# Intellij project files
*.iml
+11
View File
@@ -9,16 +9,27 @@ git:
addons:
postgresql: "9.6"
# apt:
# packages:
# - mysql-server-5.7
# - mysql-client-core-5.7
# - mysql-client-5.7
services:
- docker
- postgresql
- mysql
before_script:
- ./.travis/setup_database
- mysql -u root -e 'CREATE DATABASE test_ebean;'
- mysql -u root -e "CREATE USER 'test_ebean'@'localhost' IDENTIFIED BY 'test';"
- mysql -u root -e "GRANT ALL ON test_ebean.* TO 'test_ebean'@'localhost';"
env:
- EBEAN_DB=h2
- EBEAN_DB=pg
# - EBEAN_DB=mysql
install: true
+44 -32
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>10.4.3</version>
<version>11.10.4</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -17,7 +17,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-10.4.3</tag>
<tag>ebean-11.10.4</tag>
</scm>
<profiles>
@@ -72,10 +72,26 @@
</profiles>
<dependencies>
<!--
Projects are expected to explicit depend on version
of slf4j that they want to use
-->
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>13.0</version>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
<scope>provided</scope>
</dependency>
<!--
Class retention Nonnull and Nullable annotations
to assist with IDE auto-completion with Ebean API
-->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jsr305</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -87,43 +103,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource-api</artifactId>
<version>1.1</version>
<version>3.6</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner-api</artifactId>
<version>2.2</version>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-classpath-scanner</artifactId>
<version>2.2.2</version>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-dbmigration</artifactId>
<version>10.1.10</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>[1.7.1,1.7.99)</version>
<artifactId>ebean-migration</artifactId>
<version>11.3.1</version>
</dependency>
<dependency>
@@ -200,6 +198,20 @@
<!--<scope>test</scope>-->
<!--</dependency>-->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-docker-run</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-agentloader</artifactId>
@@ -210,7 +222,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>10.4.1</version>
<version>11.9.1</version>
<scope>test</scope>
</dependency>
@@ -219,7 +231,7 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.193</version>
<version>1.4.196</version>
<scope>provided</scope>
</dependency>
@@ -283,7 +295,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>10.4.1</version>
<version>11.9.1</version>
<executions>
<execution>
<id>test</id>
@@ -340,7 +352,7 @@
<overview>src/main/java/io/ebean/overview.html</overview>
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
<excludePackageNames>io.ebeaninternal.*:com.avaje.ebean.util:io.ebean.dbmigration.ddlgeneration:io.ebean.dbmigration.migration:io.ebean.dbmigration.migrationreader:io.ebean.dbmigration.model</excludePackageNames>
<excludePackageNames>io.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
<docletArtifact>
<groupId>org.avaje</groupId>
<artifactId>pygments-doclet</artifactId>
-11
View File
@@ -1,6 +1,5 @@
package io.ebean;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.Set;
@@ -99,16 +98,6 @@ public interface BeanState {
*/
void setReadOnly(boolean readOnly);
/**
* Add a propertyChangeListener.
*/
void addPropertyChangeListener(PropertyChangeListener listener);
/**
* Remove a propertyChangeListener.
*/
void removePropertyChangeListener(PropertyChangeListener listener);
/**
* Advanced - Used to programmatically build a partially or fully loaded
* entity bean. First create an entity bean via
+93
View File
@@ -0,0 +1,93 @@
package io.ebean;
/**
* Enum to control the different cache modes for queryCache and beanCache.
* <h3>Bean cache</h3>
* <p>
* The bean cache is automatically used by default on <code>@Cache</code> beans for
* the following queries:
* </p>
* <ul>
* <li>findOne() by id</li>
* <li>findOne() by natural key(s)</li>
* <li>findList() by ids</li>
* </ul>
* <p>
* Bean caching needs to be explicitly turned on for queries that are findList() by natural keys.
* </p>
* <h3>Query cache</h3>
* <p>
* For query cache use note that you must be careful, what you do with the returned collection.
* By default the returned collections are read only and you will get an exception if you try
* to change them.
* If you add ".setReadOnly(false)" to your query, you'll get a collection that is a clone from the
* one in the cache. That means, changing does not affect the cache.
* </p>
*
* @author Roland Praml, FOCONIS AG
*/
public enum CacheMode {
/**
* Do not use cache.
*/
OFF(false, false),
/**
* Use the cache and store a result when needed.
*/
ON(true, true),
/**
* Only used for bean caching.
* <p>
* The bean cache is automatically used by default on <code>@Cache</code> beans for
* the following queries:
* </p>
* <ul>
* <li>findOne() by id</li>
* <li>findOne() by natural key(s)</li>
* <li>findList() by ids</li>
* </ul>
* <p>
* Bean caching needs to be explicitly turned on for queries that are findList() by natural keys.
* </p>
*/
AUTO(true, true),
/**
* Do not read from cache, but put beans into the cache and invalidate parts of the cache as necessary.
* <p>
* Use this on a query if you want to get the fresh value from database and put it into the cache.
*/
PUT(false, true),
/**
* GET only from the cache.
* <p>
* This mode does not put entries into the cache or invalidate parts of the cache.
*/
GET(true, false);
private boolean get;
private boolean put;
CacheMode(boolean get, boolean put) {
this.get = get;
this.put = put;
}
/**
* Return true if value is read from cache.
*/
public boolean isGet() {
return get;
}
/**
* Return true if a newly loaded value (from database) is put into the cache.
*/
public boolean isPut() {
return put;
}
}
+6 -9
View File
@@ -2,7 +2,7 @@ package io.ebean;
import io.ebeanservice.docstore.api.DocQueryRequest;
import io.ebeanservice.docstore.api.RawDoc;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.List;
@@ -56,7 +56,7 @@ public interface DocumentStore {
* Return the bean by fetching it's content from the document store.
* If the document is not found null is returned.
* <p>
* Typically this is called indirectly by findUnique() on the query.
* Typically this is called indirectly by findOne() on the query.
* </p>
* <pre>{@code
*
@@ -64,7 +64,7 @@ public interface DocumentStore {
* server.find(Customer.class)
* .setUseDocStore(true)
* .setId(42)
* .findUnique();
* .findOne();
*
* }</pre>
*/
@@ -123,11 +123,8 @@ public interface DocumentStore {
* server.find(Order.class)
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEach(new QueryEachConsumer<Order>() {
* @Override
* public void accept(Order bean) {
* // process the bean
* }
* .findEach((Order order) -> {
* // process the bean ...
* });
*
* }</pre>
@@ -151,7 +148,7 @@ public interface DocumentStore {
* server.find(Order.class)
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEachWhile(new QueryEachWhileConsumer<Order>() {
* .findEachWhile(new Predicate<Order>() {
* @Override
* public void accept(Order bean) {
* // process the bean
+31 -35
View File
@@ -1,19 +1,21 @@
package io.ebean;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
/**
@@ -129,8 +131,7 @@ public final class Ebean {
private final ConcurrentHashMap<String, EbeanServer> concMap = new ConcurrentHashMap<>();
/**
* Cache for synchronized read, creation and put. Protected by the monitor
* object.
* Cache for synchronized read, creation and put. Protected by the monitor object.
*/
private final HashMap<String, EbeanServer> syncMap = new HashMap<>();
@@ -144,14 +145,7 @@ public final class Ebean {
private ServerManager() {
try {
// skipDefaultServer is set by EbeanServerFactory
// ... when it is creating the primaryServer
if (PrimaryServer.isSkip()) {
// primary server being created by EbeanServerFactory
// ... so we should not try and create it here
logger.debug("PrimaryServer.isSkip()");
} else {
if (!PrimaryServer.isSkip()) {
// look to see if there is a default server defined
String defaultName = PrimaryServer.getDefaultServerName();
logger.debug("defaultName:{}", defaultName);
@@ -289,7 +283,7 @@ public final class Ebean {
}
/**
* Backdoor for registering a mock implementation of EbeanServer as the default server.
* Backdoor for registering a mock implementation of EbeanServer as the default server.
*/
protected static EbeanServer mock(String name, EbeanServer server, boolean defaultServer) {
EbeanServer originalPrimaryServer = serverMgr.defaultServer;
@@ -371,9 +365,10 @@ public final class Ebean {
/**
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
* <p>
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
* {@link #execute(TxScope, TxRunnable)}.
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
* {@link #execute(TxScope, Runnable)}.
* </p>
*
* <h3>REQUIRES_NEW example:</h3>
* <pre>{@code
* // Start a new transaction. If there is a current transaction
@@ -822,7 +817,7 @@ public final class Ebean {
* }</pre>
* <p>
* If you want more control over the query then you can use createQuery() and
* Query.findUnique();
* Query.findOne();
* </p>
* <pre>{@code
*
@@ -843,7 +838,8 @@ public final class Ebean {
*
* // traverse the object graph...
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* Customer customer = order.getCustomer();
* Address shippingAddress = customer.getShippingAddress();
* List<OrderDetail> details = order.getDetails();
@@ -959,7 +955,7 @@ public final class Ebean {
* predicates, order by, limits etc.
* </p>
* <p>
* You then use findList(), findSet(), findMap() and findUnique() to execute
* You then use findList(), findSet(), findMap() and findOne() to execute
* the query and return the collection or bean.
* </p>
* <p>
@@ -991,7 +987,7 @@ public final class Ebean {
* Query<Order> query = Ebean.createQuery(Order.class, eql);
* query.setParameter("orderId", 2);
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* // This is the same as:
*
@@ -1000,7 +996,7 @@ public final class Ebean {
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findUnique();
* .findOne();
*
* }</pre>
*
@@ -1181,20 +1177,20 @@ public final class Ebean {
*
* }</pre>
*/
public static void execute(TxScope scope, TxRunnable r) {
public static void execute(TxScope scope, Runnable r) {
serverMgr.getDefaultServer().execute(scope, r);
}
/**
* Execute a TxRunnable in a Transaction with the default scope.
* Execute a Runnable in a Transaction with the default scope.
* <p>
* The default scope runs with REQUIRED and by default will rollback on any
* exception (checked or runtime).
* </p>
* <pre>{@code
*
* Ebean.execute(new TxRunnable() {
* public void run() {
* Ebean.execute(() -> {
*
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
@@ -1203,17 +1199,17 @@ public final class Ebean {
*
* Ebean.save(u1);
* Ebean.save(u2);
* }
*
* });
*
* }</pre>
*/
public static void execute(TxRunnable r) {
public static void execute(Runnable r) {
serverMgr.getDefaultServer().execute(r);
}
/**
* Execute a TxCallable in a Transaction with an explicit scope.
* Execute a Callable in a Transaction with an explicit scope.
* <p>
* The scope can control the transaction type, isolation and rollback
* semantics.
@@ -1223,7 +1219,7 @@ public final class Ebean {
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* Ebean.execute(scope, new TxCallable<String>() {
* Ebean.executeCall(scope, new Callable<String>() {
* public String call() {
* User u1 = Ebean.find(User.class, 1);
* ...
@@ -1233,12 +1229,12 @@ public final class Ebean {
*
* }</pre>
*/
public static <T> T execute(TxScope scope, TxCallable<T> c) {
return serverMgr.getDefaultServer().execute(scope, c);
public static <T> T executeCall(TxScope scope, Callable<T> c) {
return serverMgr.getDefaultServer().executeCall(scope, c);
}
/**
* Execute a TxCallable in a Transaction with the default scope.
* Execute a Callable in a Transaction with the default scope.
* <p>
* The default scope runs with REQUIRED and by default will rollback on any
* exception (checked or runtime).
@@ -1249,8 +1245,8 @@ public final class Ebean {
* </p>
* <pre>{@code
*
* Ebean.execute(new TxCallable<String>() {
* public String call() {
* Ebean.executeCall(() -> {
*
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
@@ -1261,13 +1257,13 @@ public final class Ebean {
* Ebean.save(u2);
*
* return u1.getEmail();
* }
*
* });
*
* }</pre>
*/
public static <T> T execute(TxCallable<T> c) {
return serverMgr.getDefaultServer().execute(c);
public static <T> T executeCall(Callable<T> c) {
return serverMgr.getDefaultServer().executeCall(c);
}
/**
+43 -48
View File
@@ -1,12 +1,15 @@
package io.ebean;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.meta.MetaInfoManager;
import io.ebean.plugin.SpiServer;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
@@ -16,6 +19,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -238,7 +242,7 @@ public interface EbeanServer {
* Query<Order> query = Ebean.createQuery(Order.class, ormQuery);
* query.setParameter("orderId", 2);
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* // This is the same as:
*
@@ -247,7 +251,7 @@ public interface EbeanServer {
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findUnique();
* .findOne();
*
* }</pre>
*
@@ -265,7 +269,7 @@ public interface EbeanServer {
* predicates, order by, limits etc.
* </p>
* <p>
* You then use findList(), findSet(), findMap() and findUnique() to execute
* You then use findList(), findSet(), findMap() and findOne() to execute
* the query and return the collection or bean.
* </p>
* <p>
@@ -285,7 +289,7 @@ public interface EbeanServer {
* .fetch("details")
* .fetch("detail.product", "name")
* .setId(2)
* .findUnique();
* .findOne();
*
* // find some new orders ... with firstRow/maxRows
* List<Order> orders =
@@ -560,8 +564,8 @@ public interface EbeanServer {
* Start a transaction typically specifying REQUIRES_NEW or REQUIRED semantics.
* <p>
* <p>
* Note that this provides an try finally alternative to using {@link #execute(TxScope, TxCallable)} or
* {@link #execute(TxScope, TxRunnable)}.
* Note that this provides an try finally alternative to using {@link #executeCall(TxScope, Callable)} or
* {@link #execute(TxScope, Runnable)}.
* </p>
* <p>
* <h3>REQUIRES_NEW example:</h3>
@@ -694,7 +698,7 @@ public interface EbeanServer {
* <p>
* <p>
* If you want more control over the query then you can use createQuery() and
* Query.findUnique();
* Query.findOne();
* </p>
* <p>
* <pre>{@code
@@ -714,7 +718,7 @@ public interface EbeanServer {
* query.fetch("details.product", "name");
*
*
* Order order = query.findUnique();
* Order order = query.findOne();
*
* // traverse the object graph...
*
@@ -730,6 +734,7 @@ public interface EbeanServer {
* @param beanType the type of entity bean to fetch
* @param id the id value
*/
@Nullable
<T> T find(Class<T> beanType, Object id);
/**
@@ -773,6 +778,7 @@ public interface EbeanServer {
* @param beanType the type of entity bean
* @param id the id value
*/
@Nonnull
<T> T getReference(Class<T> beanType, Object id);
/**
@@ -788,6 +794,7 @@ public interface EbeanServer {
*
* @see Query#findIds()
*/
@Nonnull
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
/**
@@ -807,6 +814,7 @@ public interface EbeanServer {
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
@Nonnull
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
@@ -843,7 +851,7 @@ public interface EbeanServer {
* Execute the query visiting the each bean one at a time.
* <p>
* Compared to findEach() this provides the ability to stop processing the query
* results early by returning false for the QueryEachWhileConsumer.
* results early by returning false for the Predicate.
* </p>
* <p>
* Unlike findList() this is suitable for processing a query that will return
@@ -883,6 +891,7 @@ public interface EbeanServer {
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
/**
@@ -908,6 +917,7 @@ public interface EbeanServer {
* @return the list of fetched beans.
* @see Query#findList()
*/
@Nonnull
<T> List<T> findList(Query<T> query, Transaction transaction);
/**
@@ -923,6 +933,7 @@ public interface EbeanServer {
* @return a Future object for the row count query
* @see Query#findFutureCount()
*/
@Nonnull
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
/**
@@ -938,6 +949,7 @@ public interface EbeanServer {
* @return a Future object for the list of Id's
* @see Query#findFutureIds()
*/
@Nonnull
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
/**
@@ -954,6 +966,7 @@ public interface EbeanServer {
* @return a Future object for the list result of the query
* @see Query#findFutureList()
*/
@Nonnull
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
/**
@@ -985,6 +998,7 @@ public interface EbeanServer {
* @return The PagedList
* @see Query#findPagedList()
*/
@Nonnull
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
/**
@@ -1010,6 +1024,7 @@ public interface EbeanServer {
* @return the set of fetched beans.
* @see Query#findSet()
*/
@Nonnull
<T> Set<T> findSet(Query<T> query, Transaction transaction);
/**
@@ -1026,6 +1041,7 @@ public interface EbeanServer {
* @return the map of fetched beans.
* @see Query#findMap()
*/
@Nonnull
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
/**
@@ -1058,6 +1074,7 @@ public interface EbeanServer {
* @return the list of values for the selected property
* @see Query#findSingleAttributeList()
*/
@Nonnull
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
/**
@@ -1085,18 +1102,9 @@ public interface EbeanServer {
/**
* Similar to findOne() but returns an Optional (rather than nullable).
*/
@Nonnull
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
/**
* Synonym for findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Nullable
<T> T findUnique(Query<T> query, Transaction transaction);
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
@@ -1138,6 +1146,7 @@ public interface EbeanServer {
* @return the list of fetched MapBean.
* @see SqlQuery#findList()
*/
@Nonnull
List<SqlRow> findList(SqlQuery query, Transaction transaction);
/**
@@ -1166,7 +1175,7 @@ public interface EbeanServer {
* result.
* </p>
* <p>
* Generally you are able to use {@link SqlQuery#findUnique()} rather than
* Generally you are able to use {@link SqlQuery#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
@@ -1174,21 +1183,11 @@ public interface EbeanServer {
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the fetched MapBean or null if none was found.
* @see SqlQuery#findUnique()
* @see SqlQuery#findOne()
*/
@Nullable
SqlRow findOne(SqlQuery query, Transaction transaction);
/**
* Synonym for findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Nullable
SqlRow findUnique(SqlQuery query, Transaction transaction);
/**
* Either Insert or Update the bean depending on its state.
* <p>
@@ -1585,7 +1584,7 @@ public interface EbeanServer {
int execute(CallableSql callableSql, Transaction transaction);
/**
* Execute a TxRunnable in a Transaction with an explicit scope.
* Execute a Runnable in a Transaction with an explicit scope.
* <p>
* The scope can control the transaction type, isolation and rollback
* semantics.
@@ -1596,7 +1595,7 @@ public interface EbeanServer {
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* ebeanServer.execute(scope, new TxRunnable() {
* ebeanServer.execute(scope, new Runnable() {
* public void run() {
* User u1 = Ebean.find(User.class, 1);
* ...
@@ -1605,10 +1604,10 @@ public interface EbeanServer {
*
* }</pre>
*/
void execute(TxScope scope, TxRunnable runnable);
void execute(TxScope scope, Runnable runnable);
/**
* Execute a TxRunnable in a Transaction with the default scope.
* Execute a Runnable in a Transaction with the default scope.
* <p>
* The default scope runs with REQUIRED and by default will rollback on any
* exception (checked or runtime).
@@ -1616,8 +1615,8 @@ public interface EbeanServer {
* <p>
* <pre>{@code
*
* ebeanServer.execute(new TxRunnable() {
* public void run() {
* ebeanServer.execute(() -> {
*
* User u1 = ebeanServer.find(User.class, 1);
* User u2 = ebeanServer.find(User.class, 2);
*
@@ -1626,12 +1625,12 @@ public interface EbeanServer {
*
* ebeanServer.save(u1);
* ebeanServer.save(u2);
* }
*
* });
*
* }</pre>
*/
void execute(TxRunnable runnable);
void execute(Runnable runnable);
/**
* Execute a TxCallable in a Transaction with an explicit scope.
@@ -1645,7 +1644,7 @@ public interface EbeanServer {
* // set specific transactional scope settings
* TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
*
* ebeanServer.execute(scope, new TxCallable<String>() {
* ebeanServer.executeCall(scope, new Callable<String>() {
* public String call() {
* User u1 = ebeanServer.find(User.class, 1);
* ...
@@ -1655,7 +1654,7 @@ public interface EbeanServer {
*
* }</pre>
*/
<T> T execute(TxScope scope, TxCallable<T> callable);
<T> T executeCall(TxScope scope, Callable<T> callable);
/**
* Execute a TxCallable in a Transaction with the default scope.
@@ -1664,13 +1663,9 @@ public interface EbeanServer {
* exception (checked or runtime).
* </p>
* <p>
* This is basically the same as TxRunnable except that it returns an Object
* (and you specify the return type via generics).
* </p>
* <p>
* <pre>{@code
*
* ebeanServer.execute(new TxCallable<String>() {
* ebeanServer.executeCall(new Callable<String>() {
* public String call() {
* User u1 = ebeanServer.find(User.class, 1);
* User u2 = ebeanServer.find(User.class, 2);
@@ -1687,7 +1682,7 @@ public interface EbeanServer {
*
* }</pre>
*/
<T> T execute(TxCallable<T> callable);
<T> T executeCall(Callable<T> callable);
/**
* Return the manager of the server cache ("L2" cache).
+10 -17
View File
@@ -1,13 +1,14 @@
package io.ebean;
import io.ebean.common.SpiContainer;
import io.ebean.config.ContainerConfig;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.server.lib.ShutdownManager;
import io.ebean.service.SpiContainer;
import io.ebean.service.SpiContainerFactory;
import javax.persistence.PersistenceException;
import java.lang.reflect.Constructor;
import java.util.Iterator;
import java.util.Properties;
import java.util.ServiceLoader;
/**
* Creates EbeanServer instances.
@@ -30,8 +31,6 @@ import java.util.Properties;
public class EbeanServerFactory {
private static final String DEFAULT_CONTAINER = "io.ebeaninternal.server.core.DefaultContainer";
private static SpiContainer container;
/**
@@ -66,10 +65,8 @@ public class EbeanServerFactory {
EbeanServer server = createInternal(config);
if (config.isDefaultServer()) {
PrimaryServer.setSkip(true);
}
if (config.isRegister()) {
PrimaryServer.setSkip(true);
Ebean.register(server, config.isDefaultServer());
}
@@ -99,7 +96,7 @@ public class EbeanServerFactory {
* </p>
*/
public static synchronized void shutdown() {
ShutdownManager.shutdown();
container.shutdown();
}
@@ -135,14 +132,10 @@ public class EbeanServerFactory {
*/
protected static SpiContainer createContainer(ContainerConfig containerConfig) {
String implClassName = System.getProperty("ebean.container", DEFAULT_CONTAINER);
try {
Class<?> cls = Class.forName(implClassName);
Constructor<?> constructor = cls.getConstructor(ContainerConfig.class);
return (SpiContainer) constructor.newInstance(containerConfig);
} catch (Exception ex) {
throw new RuntimeException(ex);
Iterator<SpiContainerFactory> factories = ServiceLoader.load(SpiContainerFactory.class).iterator();
if (factories.hasNext()) {
return factories.next().create(containerConfig);
}
throw new IllegalStateException("Service loader didn't find a SpiContainerFactory?");
}
}
@@ -246,6 +246,11 @@ public interface ExpressionFactory {
*/
Expression icontains(String propertyName, String value);
/**
* In expression using pairs of value objects.
*/
Expression inPairs(Pairs pairs);
/**
* In - property has a value in the array of values.
*/
+91 -15
View File
@@ -5,8 +5,9 @@ import io.ebean.search.MultiMatch;
import io.ebean.search.TextCommonTerms;
import io.ebean.search.TextQueryString;
import io.ebean.search.TextSimple;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.NonUniqueResultException;
import java.sql.Timestamp;
import java.util.Collection;
@@ -192,6 +193,7 @@ public interface ExpressionList<T> {
*
* @see Query#findList()
*/
@Nonnull
List<T> findList();
/**
@@ -199,6 +201,7 @@ public interface ExpressionList<T> {
*
* @see Query#findIds()
*/
@Nonnull
<A> List<A> findIds();
/**
@@ -214,6 +217,7 @@ public interface ExpressionList<T> {
*
* @see Query#findSet()
*/
@Nonnull
Set<T> findSet();
/**
@@ -221,6 +225,7 @@ public interface ExpressionList<T> {
*
* @see Query#findMap()
*/
@Nonnull
<K> Map<K, T> findMap();
/**
@@ -253,8 +258,27 @@ public interface ExpressionList<T> {
*
* @return the list of values for the selected property
*/
@Nonnull
<A> List<A> findSingleAttributeList();
/**
* Execute a query returning a single value of a single property/column.
* <p>
* <pre>{@code
*
* String name =
* Ebean.find(Customer.class)
* .select("name")
* .where().eq("id", 42)
* .findSingleAttribute();
*
* }</pre>
*/
default <A> A findSingleAttribute() {
List<A> list = findSingleAttributeList();
return !list.isEmpty() ? list.get(0) : null;
}
/**
* Execute the query returning a single bean or null (if no matching
* bean is found).
@@ -264,7 +288,7 @@ public interface ExpressionList<T> {
* </p>
*
* @throws NonUniqueResultException if more than one result was found
* @see Query#findUnique()
* @see Query#findOne()
*/
@Nullable
T findOne();
@@ -272,18 +296,9 @@ public interface ExpressionList<T> {
/**
* Execute the query returning an optional bean.
*/
@Nonnull
Optional<T> findOneOrEmpty();
/**
* Synonym for findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Nullable
T findUnique();
/**
* Execute find row count query in a background thread.
* <p>
@@ -294,6 +309,7 @@ public interface ExpressionList<T> {
*
* @return a Future object for the row count query
*/
@Nonnull
FutureRowCount<T> findFutureCount();
/**
@@ -306,6 +322,7 @@ public interface ExpressionList<T> {
*
* @return a Future object for the list of Id's
*/
@Nonnull
FutureIds<T> findFutureIds();
/**
@@ -318,6 +335,7 @@ public interface ExpressionList<T> {
*
* @return a Future object for the list result of the query
*/
@Nonnull
FutureList<T> findFutureList();
/**
@@ -349,6 +367,7 @@ public interface ExpressionList<T> {
* @return The PagedList
* @see Query#findPagedList()
*/
@Nonnull
PagedList<T> findPagedList();
/**
@@ -358,6 +377,7 @@ public interface ExpressionList<T> {
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
List<Version<T>> findVersions();
/**
@@ -367,11 +387,13 @@ public interface ExpressionList<T> {
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
/**
* Add some filter predicate expressions to the many property.
*/
@Nonnull
ExpressionList<T> filterMany(String prop);
/**
@@ -435,18 +457,37 @@ public interface ExpressionList<T> {
Query<T> setMapKey(String mapKey);
/**
* Set to true to use the query for executing this query.
* Set to true when this query should use the bean cache.
* <p>
* This is now the same as setUseBeanCache(CacheMode.ON) and will be deprecated.
* </p>
*
* @see Query#setUseCache(boolean)
*/
Query<T> setUseCache(boolean useCache);
/**
* Set to true to use the query for executing this query.
* Set the mode to use the bean cache when executing this query.
*
* @see Query#setBeanCacheMode(CacheMode)
*/
Query<T> setBeanCacheMode(CacheMode beanCacheMode);
/**
* Set the {@link CacheMode} to use the query cache for executing this query.
*
* @see Query#setUseQueryCache(boolean)
*/
Query<T> setUseQueryCache(boolean useCache);
Query<T> setUseQueryCache(CacheMode useCache);
/**
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
*
* @see Query#setUseQueryCache(CacheMode)
*/
default Query<T> setUseQueryCache(boolean enabled) {
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
}
/**
* Set to true if this query should execute against the doc store.
@@ -780,6 +821,11 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> icontains(String propertyName, String value);
/**
* In expression using pairs of value objects.
*/
ExpressionList<T> inPairs(Pairs pairs);
/**
* In - using a subQuery.
*/
@@ -795,6 +841,36 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> in(String propertyName, Collection<?> values);
/**
* In - using a subQuery.
*
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
*/
default ExpressionList<T> isIn(String propertyName, Query<?> subQuery) {
return in(propertyName, subQuery);
}
/**
* In - property has a value in the array of values.
*
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
*/
default ExpressionList<T> isIn(String propertyName, Object... values) {
return in(propertyName, values);
}
/**
* In - property has a value in the collection of values.
*
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
*/
default ExpressionList<T> isIn(String propertyName, Collection<?> values) {
return in(propertyName, values);
}
/**
* Not In - property has a value in the array of values.
*/
+42 -2
View File
@@ -1,6 +1,7 @@
package io.ebean;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
@@ -27,7 +28,7 @@ import java.util.List;
* // Add your customer finder methods ...
*
* public Customer byName(String name) {
* return query().eq("name", name).findUnique();
* return query().eq("name", name).findOne();
* }
*
* public List<Customer> findNew() {
@@ -92,6 +93,20 @@ public class Finder<I, T> {
this.serverName = serverName;
}
/**
* Return the current transaction.
*/
public Transaction currentTransaction() {
return db().currentTransaction();
}
/**
* Flush the JDBC batch on the current transaction.
*/
public void flush() {
db().flush();
}
/**
* Return the underlying 'default' EbeanServer.
* <p>
@@ -118,6 +133,7 @@ public class Finder<I, T> {
* <p>
* Equivalent to {@link EbeanServer#getReference(Class, Object)}
*/
@Nonnull
public T ref(I id) {
return db().getReference(type, id);
}
@@ -144,10 +160,33 @@ public class Finder<I, T> {
/**
* Retrieves all entities of the given type.
*/
@Nonnull
public List<T> all() {
return query().findList();
}
/**
* Creates an update query.
*
* <pre>{@code
*
* int rows =
* finder.update()
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* <p>
* Equivalent to {@link EbeanServer#update(Class)}
*/
public UpdateQuery<T> update() {
return db().update(type);
}
/**
* Creates a query.
* <p>
@@ -170,4 +209,5 @@ public class Finder<I, T> {
public Query<T> query(String ormQuery) {
return db().createQuery(type, ormQuery);
}
}
+3
View File
@@ -1,5 +1,6 @@
package io.ebean;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.concurrent.Future;
@@ -109,11 +110,13 @@ public interface PagedList<T> {
*
* }</pre>
*/
@Nonnull
Future<Integer> getFutureCount();
/**
* Return the list of entities for this page.
*/
@Nonnull
List<T> getList();
/**
+220
View File
@@ -0,0 +1,220 @@
package io.ebean;
import java.util.ArrayList;
import java.util.List;
/**
* Holds a list of value object pairs.
* <p>
* This feature is to enable use of L2 cache with complex natural keys with findList() queries in cases where the
* IN clause is not a single property but instead a pair of properties.
* </p>
* <p>
* These queries can have predicates that can be translated into a list of complex natural keys such that the L2
* cache can be hit with these keys to obtain some or all of the beans from L2 cache rather than the DB.
* </p>
* <pre>{@code
*
* // where a bean is annotated with a complex
* // natural key made of several properties
* @Cache(naturalKey = {"store","code","sku"})
*
*
* Pairs pairs = new Pairs("sku", "code");
* pairs.add("sj2", 1000);
* pairs.add("sj2", 1001);
* pairs.add("pf3", 1000);
*
* List<OCachedNatKeyBean3> list = Ebean.find(OCachedNatKeyBean3.class)
* .where()
* .eq("store", "def")
* .inPairs(pairs) // IN clause with 'pairs' of values
* .orderBy("sku desc")
*
* // query expressions cover the natural key properties
* // so we can choose to hit the L2 bean cache if we want
* .setUseCache(true)
* .findList();
*
* }</pre>
* <h3>Important implementation Note</h3>
* <p>
* When binding many pairs of values we want to be able to utilise a DB index (as this type of query usually means the
* pairs are a unique key/index or part of a unique key/index and highly selective). Currently we know we can do this
* on any DB that supports expression/formula based indexes.
* using a DB string concatenation formula
* </p>
* <p>
* This means, the implementation converts the list of pairs into a list of strings via concatenation and we use a
* DB concatenation formula to match. We see SQL like:
* </p>
* <pre>{@code sql
*
* ...
* where t0.store = ? and (t0.sku||'-'||t0.code) in (?, ? )
*
* // bind values like: "sj2-1000", "pf3-1000"
*
* }</pre>
* <p>
* We often create a DB expression index to match the DB concat formula like:
* </p>
* <pre>{@code sql
*
* create index ix_name on table_name ((sku || '-' || code));
*
* }</pre>
*/
public class Pairs {
private final String property0;
private final String property1;
private final List<Entry> entries = new ArrayList<>();
/**
* Character between the values when combined via DB varchar concatenation.
*/
private String concatSeparator = "-";
/**
* Optional suffix added to DB varchar concatenation formula.
*/
private String concatSuffix;
/**
* Create with 2 property names.
*
* @param property0 The property of the first value
* @param property1 The property of the second value
*/
public Pairs(String property0, String property1) {
this.property0 = property0;
this.property1 = property1;
}
/**
* Add a pair of value objects.
* <p>
* Both values are expected to be immutable with equals and hashCode implementations.
* </p>
*
* @param a Value of the first property
* @param b Value of the second property
*/
public Pairs add(Object a, Object b) {
entries.add(new Entry(a, b));
return this;
}
/**
* Return the first property name.
*/
public String getProperty0() {
return property0;
}
/**
* Return the second property name.
*/
public String getProperty1() {
return property1;
}
/**
* Return all the value pairs.
*/
public List<Entry> getEntries() {
return entries;
}
/**
* Return the separator character used with DB varchar concatenation to combine the 2 values.
*/
public String getConcatSeparator() {
return concatSeparator;
}
/**
* Set the separator character used with DB varchar concatenation to combine the 2 values.
*/
public Pairs setConcatSeparator(String concatSeparator) {
this.concatSeparator = concatSeparator;
return this;
}
/**
* Return a suffix used with DB varchar concatenation to combine the 2 values.
*/
public String getConcatSuffix() {
return concatSuffix;
}
/**
* Add a suffix used with DB varchar concatenation to combine the 2 values.
*/
public Pairs setConcatSuffix(String concatSuffix) {
this.concatSuffix = concatSuffix;
return this;
}
public String toString() {
return "p0:" + property0 + " p1:" + property1 + " entries:" + entries;
}
/**
* A pair of 2 value objects.
* <p>
* Used to support inPairs() expression.
*/
public static class Entry {
private final Object a;
private final Object b;
/**
* Create with values for property0 and property1 respectively.
*
* @param a Value of the first property
* @param b Value of the second property
*/
public Entry(Object a, Object b) {
this.a = a;
this.b = b;
}
public String toString() {
return "{" + a + "," + b + "}";
}
/**
* Return the value for the first property.
*/
public Object getA() {
return a;
}
/**
* Return the value for the second property.
*/
public Object getB() {
return b;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry that = (Entry) o;
return a.equals(that.a) && b.equals(that.b);
}
@Override
public int hashCode() {
int result = a.hashCode();
result = 92821 * result + b.hashCode();
return result;
}
}
}
@@ -59,6 +59,8 @@ class PrimaryServer {
private static String determineDefaultServerName() {
String defaultServerName = System.getenv("EBEAN_DB");
defaultServerName = System.getProperty("db", defaultServerName);
defaultServerName = System.getProperty("ebean_db", defaultServerName);
if (isEmpty(defaultServerName)) {
defaultServerName = System.getProperty("datasource.default");
if (isEmpty(defaultServerName)) {
@@ -74,6 +76,7 @@ class PrimaryServer {
if (defaultServerName == null) {
defaultServerName = "db";
}
System.setProperty("ebean_db", defaultServerName);
return defaultServerName;
}
@@ -0,0 +1,47 @@
package io.ebean;
/**
* A location for profiling transactions and queries.
* <p>
* Typically represents a class method in the form of class file and line of code that started
* the transaction or invoked the query.
* </p>
*/
public interface ProfileLocation {
/**
* Create and return a new ProfileLocation.
*/
static ProfileLocation create() {
return XServiceProvider.profileLocationFactory().create();
}
/**
* Create and return a new ProfileLocation with a given lineNumber and label.
*/
static ProfileLocation create(int lineNumber, String label) {
return XServiceProvider.profileLocationFactory().create(lineNumber, label);
}
/**
* Create and return a new ProfileLocation with a given location.
*/
static ProfileLocation createAt(String location) {
return XServiceProvider.profileLocationFactory().createAt(location);
}
/**
* Obtain the location description.
*/
String obtain();
/**
* Return a short version of the location description.
*/
String shortDescription();
/**
* Add execution time.
*/
void add(long executionTime);
}
+87 -34
View File
@@ -1,7 +1,7 @@
package io.ebean;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.NonUniqueResultException;
import java.sql.Timestamp;
import java.util.List;
@@ -215,11 +215,6 @@ public interface Query<T> {
SKIPLOCKED
}
/**
* Return the RawSql that was set to use for this query.
*/
RawSql getRawSql();
/**
* Set RawSql to use for this query.
*/
@@ -602,6 +597,7 @@ public interface Query<T> {
*
* @see EbeanServer#findIds(Query, Transaction)
*/
@Nonnull
<A> List<A> findIds();
/**
@@ -642,6 +638,7 @@ public interface Query<T> {
*
* }</pre>
*/
@Nonnull
QueryIterator<T> findIterate();
/**
@@ -669,8 +666,7 @@ public interface Query<T> {
* </p>
* <p>
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the QueryEachConsumer (SAM) interface which is better suited to use
* with Java8 closures.
* iterator uses the Consumer interface which is better suited to use with Java8 closures.
* </p>
* <pre>{@code
*
@@ -699,8 +695,7 @@ public interface Query<T> {
* </p>
* <p>
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the QueryEachWhileConsumer (SAM) interface which is better suited to use
* with Java8 closures.
* iterator uses the Predicate (SAM) interface which is better suited to use with Java8 closures.
* </p>
* <pre>{@code
*
@@ -740,6 +735,7 @@ public interface Query<T> {
*
* @see EbeanServer#findList(Query, Transaction)
*/
@Nonnull
List<T> findList();
/**
@@ -758,6 +754,7 @@ public interface Query<T> {
*
* @see EbeanServer#findSet(Query, Transaction)
*/
@Nonnull
Set<T> findSet();
/**
@@ -780,6 +777,7 @@ public interface Query<T> {
*
* @see EbeanServer#findMap(Query, Transaction)
*/
@Nonnull
<K> Map<K, T> findMap();
/**
@@ -812,8 +810,24 @@ public interface Query<T> {
*
* @return the list of values for the selected property
*/
@Nonnull
<A> List<A> findSingleAttributeList();
/**
* Execute a query returning a single value of a single property/column.
* <p>
* <pre>{@code
*
* String name =
* Ebean.find(Customer.class)
* .select("name")
* .where().eq("id", 42)
* .findSingleAttribute();
*
* }</pre>
*/
<A> A findSingleAttribute();
/**
* Execute the query returning either a single bean or null (if no matching
* bean is found).
@@ -831,7 +845,7 @@ public interface Query<T> {
* Product product =
* ebeanServer.find(Product.class)
* .where().eq("sku", "aa113")
* .findUnique();
* .findOne();
* ...
* }</pre>
* <p>
@@ -845,7 +859,7 @@ public interface Query<T> {
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch("details")
* .findUnique();
* .findOne();
*
* // the order details were eagerly loaded
* List<OrderDetail> details = order.getDetails();
@@ -860,18 +874,9 @@ public interface Query<T> {
/**
* Execute the query returning an optional bean.
*/
@Nonnull
Optional<T> findOneOrEmpty();
/**
* Synonym for findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Nullable
T findUnique();
/**
* Return versions of a @History entity bean.
* <p>
@@ -884,6 +889,7 @@ public interface Query<T> {
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
List<Version<T>> findVersions();
/**
@@ -893,6 +899,7 @@ public interface Query<T> {
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
/**
@@ -930,6 +937,7 @@ public interface Query<T> {
*
* @return a Future object for the row count query
*/
@Nonnull
FutureRowCount<T> findFutureCount();
/**
@@ -942,6 +950,7 @@ public interface Query<T> {
*
* @return a Future object for the list of Id's
*/
@Nonnull
FutureIds<T> findFutureIds();
/**
@@ -953,6 +962,7 @@ public interface Query<T> {
*
* @return a Future object for the list result of the query
*/
@Nonnull
FutureList<T> findFutureList();
/**
@@ -982,6 +992,7 @@ public interface Query<T> {
*
* @return The PagedList
*/
@Nonnull
PagedList<T> findPagedList();
/**
@@ -1028,7 +1039,7 @@ public interface Query<T> {
Query<T> setParameter(int position, Object value);
/**
* Set the Id value to query. This is used with findUnique().
* Set the Id value to query. This is used with findOne().
* <p>
* You can use this to have further control over the query. For example adding
* fetch joins.
@@ -1039,7 +1050,7 @@ public interface Query<T> {
* ebeanServer.find(Order.class)
* .setId(1)
* .fetch("details")
* .findUnique();
* .findOne();
*
* // the order details were eagerly fetched
* List<OrderDetail> details = order.getDetails();
@@ -1294,7 +1305,7 @@ public interface Query<T> {
*
* // Assuming sku is unique for products...
*
* Map<?,Product> productMap =
* Map<String,Product> productMap =
* ebeanServer.find(Product.class)
* // use sku for keys...
* .setMapKey("sku")
@@ -1309,22 +1320,60 @@ public interface Query<T> {
/**
* Set this to false to not use the bean cache.
* <p>
* This method is now superseded by {@link #setBeanCacheMode(CacheMode)}
* which provides more explicit options controlled bean cache use.
* </p>
* <p>
* This method is likely to be deprecated in the future with migration
* over to setUseBeanCache().
* </p>
*/
default Query<T> setUseCache(boolean useCache) {
return setBeanCacheMode(useCache ? CacheMode.ON : CacheMode.OFF);
}
/**
* Set the mode to use the bean cache when executing this query.
* <p>
* By default "find by id" and "find by natural key" will use the bean cache
* when bean caching is enabled. Setting this to false means that the query
* will not use the bean cache and instead hit the database.
* </p>
* <p>
* In the case of other queries (findList(), findEach() etc) then setting this to
* false beans that the if lazy loading is invoked that lazy loading will not try
* to use the bean cache.
* By default findList() with natural keys will not use the bean cache. In that
* case we need to explicitly use the bean cache.
* </p>
*/
Query<T> setUseCache(boolean useCache);
Query<T> setBeanCacheMode(CacheMode beanCacheMode);
/**
* Set this to true to use the query cache.
* Set the {@link CacheMode} to use the query for executing this query.
*/
Query<T> setUseQueryCache(boolean useQueryCache);
Query<T> setUseQueryCache(CacheMode queryCacheMode);
/**
* Calls {@link #setUseQueryCache(CacheMode)} with <code>ON</code> or <code>OFF</code>.
*/
default Query<T> setUseQueryCache(boolean enabled) {
return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF);
}
/**
* Set an id to identify this query for profiling purposes.
* <p>
* The profileId is expected to be unique for a given bean type.
* </p>
* <p>
* Note that the profileId is treated as a short internally and has a MAX value of 32,767.
* </p>
*/
Query<T> setProfileId(int profileId);
/**
* Set the profile location of this query. This is used to relate query execution metrics
* back to a location like a specific line of code.
*/
Query<T> setProfileLocation(ProfileLocation profileLocation);
/**
* Set to true if this query should execute against the doc store.
@@ -1340,8 +1389,9 @@ public interface Query<T> {
Query<T> setReadOnly(boolean readOnly);
/**
* When set to true all the beans from this query are loaded into the bean
* cache.
* Will be deprecated - migrate to use setBeanCacheMode(CacheMode.RECACHE).
* <p>
* When set to true all the beans from this query are loaded into the bean cache.
*/
Query<T> setLoadBeanCache(boolean loadBeanCache);
@@ -1386,9 +1436,12 @@ public interface Query<T> {
String getGeneratedSql();
/**
* Deprecated - migrate to forUpdate().
*
* executed the select with "for update" which should lock the record
* "on read"
*/
@Deprecated
Query<T> setForUpdate(boolean forUpdate);
/**
+1 -572
View File
@@ -1,16 +1,5 @@
package io.ebean;
import io.ebean.util.CamelCaseHelper;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Used to build object graphs based on a raw SQL statement (rather than
* generated by Ebean).
@@ -169,566 +158,6 @@ import java.util.Map;
* Note that lazy loading also works with object graphs built with RawSql.
* </p>
*/
public final class RawSql implements Serializable {
public interface RawSql {
private static final long serialVersionUID = 1L;
private final ResultSet resultSet;
private final Sql sql;
private final ColumnMapping columnMapping;
/**
* Construct with a ResultSet and properties that the columns map to.
* <p>
* The properties listed in the propertyNames must be in the same order as the columns in the
* resultSet.
* <p>
* When a query executes this RawSql object then it will close the resultSet.
*/
public RawSql(ResultSet resultSet, String... propertyNames) {
this.resultSet = resultSet;
this.sql = null;
this.columnMapping = new ColumnMapping(propertyNames);
}
protected RawSql(ResultSet resultSet, Sql sql, ColumnMapping columnMapping) {
this.resultSet = resultSet;
this.sql = sql;
this.columnMapping = columnMapping;
}
/**
* Return the Sql either unparsed or in parsed (broken up) form.
*/
public Sql getSql() {
return sql;
}
/**
* Return the key;
*/
public Key getKey() {
boolean parsed = sql != null && sql.parsed;
String unParsedSql = (sql == null) ? "" : sql.unparsedSql;
return new Key(parsed, unParsedSql, columnMapping);
}
/**
* Return the resultSet if this is a ResultSet based RawSql.
*/
public ResultSet getResultSet() {
return resultSet;
}
/**
* Return the column mapping for the SQL columns to bean properties.
*/
public ColumnMapping getColumnMapping() {
return columnMapping;
}
/**
* Represents the sql part of the query. For parsed RawSql the sql is broken
* up so that Ebean can insert extra WHERE and HAVING expressions into the
* SQL.
*/
public static final class Sql implements Serializable {
private static final long serialVersionUID = 1L;
private final boolean parsed;
private final String unparsedSql;
private final String preFrom;
private final String preWhere;
private final boolean andWhereExpr;
private final String preHaving;
private final boolean andHavingExpr;
private final String orderByPrefix;
private final String orderBy;
private final boolean distinct;
/**
* Construct for unparsed SQL.
*/
protected Sql(String unparsedSql) {
this.parsed = false;
this.unparsedSql = unparsedSql;
this.preFrom = null;
this.preHaving = null;
this.preWhere = null;
this.andHavingExpr = false;
this.andWhereExpr = false;
this.orderByPrefix = null;
this.orderBy = null;
this.distinct = false;
}
/**
* Construct for parsed SQL.
*/
protected Sql(String unparsedSql, String preFrom, String preWhere, boolean andWhereExpr,
String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) {
this.unparsedSql = unparsedSql;
this.parsed = true;
this.preFrom = preFrom;
this.preHaving = preHaving;
this.preWhere = preWhere;
this.andHavingExpr = andHavingExpr;
this.andWhereExpr = andWhereExpr;
this.orderByPrefix = orderByPrefix;
this.orderBy = orderBy;
this.distinct = distinct;
}
@Override
public String toString() {
if (!parsed) {
return "unparsed[" + unparsedSql + "]";
}
return "select[" + preFrom + "] preWhere[" + preWhere + "] preHaving[" + preHaving + "] orderBy[" + orderBy + "]";
}
public boolean isDistinct() {
return distinct;
}
/**
* Return true if the SQL is left completely unmodified.
* <p>
* This means Ebean can't add WHERE or HAVING expressions into the query -
* it will be left completely unmodified.
* </p>
*/
public boolean isParsed() {
return parsed;
}
/**
* Return the SQL when it is unparsed.
*/
public String getUnparsedSql() {
return unparsedSql;
}
/**
* Return the SQL prior to FROM clause.
*/
public String getPreFrom() {
return preFrom;
}
/**
* Return the SQL prior to WHERE clause.
*/
public String getPreWhere() {
return preWhere;
}
/**
* Return true if there is already a WHERE clause and any extra where
* expressions start with AND.
*/
public boolean isAndWhereExpr() {
return andWhereExpr;
}
/**
* Return the SQL prior to HAVING clause.
*/
public String getPreHaving() {
return preHaving;
}
/**
* Return true if there is already a HAVING clause and any extra having
* expressions start with AND.
*/
public boolean isAndHavingExpr() {
return andHavingExpr;
}
/**
* Return the 'order by' keywords.
* This can contain additional keywords, for example 'order siblings by' as Oracle syntax.
*/
public String getOrderByPrefix() {
return (orderByPrefix == null) ? "order by" : orderByPrefix;
}
/**
* Return the SQL ORDER BY clause.
*/
public String getOrderBy() {
return orderBy;
}
}
/**
* Defines the column mapping for raw sql DB columns to bean properties.
*/
public static final class ColumnMapping implements Serializable {
private static final long serialVersionUID = 1L;
private final LinkedHashMap<String, Column> dbColumnMap;
private final Map<String, String> propertyMap;
private final Map<String, Column> propertyColumnMap;
private final boolean parsed;
private final boolean immutable;
/**
* Construct from parsed sql where the columns have been identified.
*/
protected ColumnMapping(List<Column> columns) {
this.immutable = false;
this.parsed = true;
this.propertyMap = null;
this.propertyColumnMap = null;
this.dbColumnMap = new LinkedHashMap<>();
for (Column c : columns) {
dbColumnMap.put(c.getDbColumnKey(), c);
}
}
/**
* Construct for unparsed sql.
*/
protected ColumnMapping() {
this.immutable = false;
this.parsed = false;
this.propertyMap = null;
this.propertyColumnMap = null;
this.dbColumnMap = new LinkedHashMap<>();
}
/**
* Construct for ResultSet use.
*/
protected ColumnMapping(String... propertyNames) {
this.immutable = false;
this.parsed = false;
this.propertyMap = null;
this.dbColumnMap = new LinkedHashMap<>();
int pos = 0;
for (String prop : propertyNames) {
dbColumnMap.put(prop, new Column(pos++, prop, null, prop));
}
propertyColumnMap = dbColumnMap;
}
/**
* Construct an immutable ColumnMapping based on collected information.
*/
protected ColumnMapping(boolean parsed, LinkedHashMap<String, Column> dbColumnMap) {
this.immutable = true;
this.parsed = parsed;
this.dbColumnMap = dbColumnMap;
HashMap<String, Column> pcMap = new HashMap<>();
HashMap<String, String> pMap = new HashMap<>();
for (Column c : dbColumnMap.values()) {
pMap.put(c.getPropertyName(), c.getDbColumn());
pcMap.put(c.getPropertyName(), c);
}
this.propertyMap = Collections.unmodifiableMap(pMap);
this.propertyColumnMap = Collections.unmodifiableMap(pcMap);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ColumnMapping that = (ColumnMapping) o;
return dbColumnMap.equals(that.dbColumnMap);
}
@Override
public int hashCode() {
return dbColumnMap.hashCode();
}
/**
* Return true if the property is mapped.
*/
public boolean contains(String property) {
return this.propertyColumnMap.containsKey(property);
}
/**
* Creates an immutable copy of this ColumnMapping.
*
* @throws IllegalStateException when a propertyName has not been defined for a column.
*/
protected ColumnMapping createImmutableCopy() {
for (Column c : dbColumnMap.values()) {
c.checkMapping();
}
return new ColumnMapping(parsed, dbColumnMap);
}
protected void columnMapping(String dbColumn, String propertyName) {
if (immutable) {
throw new IllegalStateException("Should never happen");
}
if (!parsed) {
int pos = dbColumnMap.size();
dbColumnMap.put(dbColumn, new Column(pos, dbColumn, null, propertyName));
} else {
Column column = dbColumnMap.get(dbColumn);
if (column == null) {
String msg = "DB Column [" + dbColumn + "] not found in mapping. Expecting one of [" + dbColumnMap.keySet() + "]";
throw new IllegalArgumentException(msg);
}
column.setPropertyName(propertyName);
}
}
/**
* Returns true if the Columns where supplied by parsing the sql select
* clause.
* <p>
* In the case where the columns where parsed then we can do extra checks on
* the column mapping such as, is the column a valid one in the sql and
* whether all the columns in the sql have been mapped.
* </p>
*/
public boolean isParsed() {
return parsed;
}
/**
* Return the number of columns in this column mapping.
*/
public int size() {
return dbColumnMap.size();
}
/**
* Return the column mapping.
*/
protected Map<String, Column> mapping() {
return dbColumnMap;
}
/**
* Return the mapping by DB column.
*/
public Map<String, String> getMapping() {
return propertyMap;
}
/**
* Return the index position by bean property name.
*/
public int getIndexPosition(String property) {
Column c = propertyColumnMap.get(property);
return c == null ? -1 : c.getIndexPos();
}
/**
* Return an iterator of the Columns.
*/
public Iterator<Column> getColumns() {
return dbColumnMap.values().iterator();
}
/**
* Modify any column mappings with the given table alias to have the path prefix.
* <p>
* For example modify all mappings with table alias "c" to have the path prefix "customer".
* </p>
* <p>
* For the "Root type" you don't need to specify a tableAliasMapping.
* </p>
*/
public void tableAliasMapping(String tableAlias, String path) {
String startMatch = tableAlias + ".";
for (Map.Entry<String, Column> entry : dbColumnMap.entrySet()) {
if (entry.getKey().startsWith(startMatch)) {
entry.getValue().tableAliasMapping(path);
}
}
}
/**
* A Column of the RawSql that is mapped to a bean property (or ignored).
*/
public static class Column implements Serializable {
private static final long serialVersionUID = 1L;
private final int indexPos;
private final String dbColumn;
private final String dbAlias;
private String propertyName;
/**
* Construct a Column.
*/
public Column(int indexPos, String dbColumn, String dbAlias) {
this(indexPos, dbColumn, dbAlias, derivePropertyName(dbAlias, dbColumn));
}
private Column(int indexPos, String dbColumn, String dbAlias, String propertyName) {
this.indexPos = indexPos;
this.dbColumn = dbColumn;
this.dbAlias = dbAlias;
if (propertyName == null && dbAlias != null) {
this.propertyName = dbAlias;
} else {
this.propertyName = propertyName;
}
}
protected static String derivePropertyName(String dbAlias, String dbColumn) {
if (dbAlias != null) {
return CamelCaseHelper.toCamelFromUnderscore(dbAlias);
}
int dotPos = dbColumn.indexOf('.');
if (dotPos > -1) {
dbColumn = dbColumn.substring(dotPos + 1);
}
return CamelCaseHelper.toCamelFromUnderscore(dbColumn);
}
private void checkMapping() {
if (propertyName == null) {
String msg = "No propertyName defined (Column mapping) for dbColumn [" + dbColumn + "]";
throw new IllegalStateException(msg);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Column that = (Column) o;
if (indexPos != that.indexPos) return false;
if (!dbColumn.equals(that.dbColumn)) return false;
if (dbAlias != null ? !dbAlias.equals(that.dbAlias) : that.dbAlias != null) return false;
return propertyName != null ? propertyName.equals(that.propertyName) : that.propertyName == null;
}
@Override
public int hashCode() {
int result = indexPos;
result = 92821 * result + dbColumn.hashCode();
result = 92821 * result + (dbAlias != null ? dbAlias.hashCode() : 0);
result = 92821 * result + (propertyName != null ? propertyName.hashCode() : 0);
return result;
}
@Override
public String toString() {
return dbColumn + "->" + propertyName;
}
/**
* Return the index position of this column.
*/
public int getIndexPos() {
return indexPos;
}
/**
* Return the DB column alias if specified otherwise DB column.
* This is used as the key for mapping a column to a logical property.
*/
public String getDbColumnKey() {
return (dbAlias != null) ? dbAlias : dbColumn;
}
/**
* Return the DB column name including table alias (if it has one).
*/
public String getDbColumn() {
return dbColumn;
}
/**
* Return the bean property this column is mapped to.
*/
public String getPropertyName() {
return propertyName;
}
/**
* Set the property name mapped to this db column.
*/
private void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
/**
* Prepend the path to the property name.
* <p/>
* For example if path is "customer" then "name" becomes "customer.name".
*/
public void tableAliasMapping(String path) {
if (path != null) {
propertyName = path + "." + propertyName;
}
}
}
}
/**
* A key for the RawSql object using for the query plan.
*/
public static final class Key {
private final boolean parsed;
private final ColumnMapping columnMapping;
private final String unParsedSql;
Key(boolean parsed, String unParsedSql, ColumnMapping columnMapping) {
this.parsed = parsed;
this.unParsedSql = unParsedSql;
this.columnMapping = columnMapping;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key that = (Key) o;
return parsed == that.parsed
&& columnMapping.equals(that.columnMapping)
&& unParsedSql.equals(that.unParsedSql);
}
@Override
public int hashCode() {
int result = (parsed ? 1 : 0);
result = 92821 * result + columnMapping.hashCode();
result = 92821 * result + unParsedSql.hashCode();
return result;
}
}
}
+11 -55
View File
@@ -1,8 +1,5 @@
package io.ebean;
import io.ebean.RawSql.ColumnMapping;
import io.ebean.RawSql.Sql;
import java.sql.ResultSet;
/**
@@ -14,18 +11,7 @@ import java.sql.ResultSet;
*
* @see RawSql
*/
public class RawSqlBuilder {
/**
* Special property name assigned to a DB column that should be ignored.
*/
public static final String IGNORE_COLUMN = "$$_IGNORE_COLUMN_$$";
private final ResultSet resultSet;
private final Sql sql;
private final ColumnMapping columnMapping;
public interface RawSqlBuilder {
/**
* Create and return a RawSql object based on the resultSet and list of properties the columns in
@@ -34,8 +20,8 @@ public class RawSqlBuilder {
* The properties listed in the propertyNames must be in the same order as the columns in the
* resultSet.
*/
public static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
return new RawSql(resultSet, propertyNames);
static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
return XServiceProvider.rawSql().resultSet(resultSet, propertyNames);
}
/**
@@ -43,10 +29,8 @@ public class RawSqlBuilder {
* modified - so no additional WHERE or HAVING expressions can be added to
* this query.
*/
public static RawSqlBuilder unparsed(String sql) {
Sql s = new Sql(sql);
return new RawSqlBuilder(s, new ColumnMapping());
static RawSqlBuilder unparsed(String sql) {
return XServiceProvider.rawSql().unparsed(sql);
}
/**
@@ -62,19 +46,8 @@ public class RawSqlBuilder {
* correct column names are entered into the mapping.
* </p>
*/
public static RawSqlBuilder parse(String sql) {
Sql sql2 = DRawSqlParser.parse(sql);
String select = sql2.getPreFrom();
ColumnMapping mapping = DRawSqlColumnsParser.parse(select);
return new RawSqlBuilder(sql2, mapping);
}
private RawSqlBuilder(Sql sql, ColumnMapping columnMapping) {
this.sql = sql;
this.columnMapping = columnMapping;
this.resultSet = null;
static RawSqlBuilder parse(String sql) {
return XServiceProvider.rawSql().parsed(sql);
}
/**
@@ -87,17 +60,12 @@ public class RawSqlBuilder {
* @param dbColumn the DB column that we are mapping to a bean property
* @param propertyName the bean property that we are mapping the DB column to.
*/
public RawSqlBuilder columnMapping(String dbColumn, String propertyName) {
columnMapping.columnMapping(dbColumn, propertyName);
return this;
}
RawSqlBuilder columnMapping(String dbColumn, String propertyName);
/**
* Ignore this DB column. It is not mapped to any bean property.
*/
public RawSqlBuilder columnMappingIgnore(String dbColumn) {
return columnMapping(dbColumn, IGNORE_COLUMN);
}
RawSqlBuilder columnMappingIgnore(String dbColumn);
/**
* Modify any column mappings with the given table alias to have the path prefix.
@@ -108,24 +76,12 @@ public class RawSqlBuilder {
* For the "Root type" you don't need to specify a tableAliasMapping.
* </p>
*/
public RawSqlBuilder tableAliasMapping(String tableAlias, String path) {
columnMapping.tableAliasMapping(tableAlias, path);
return this;
}
RawSqlBuilder tableAliasMapping(String tableAlias, String path);
/**
* Create the immutable RawSql object. Do this after all the column mapping
* has been defined.
*/
public RawSql create() {
return new RawSql(resultSet, sql, columnMapping.createImmutableCopy());
}
/**
* Return the internal parsed Sql object (for testing).
*/
protected Sql getSql() {
return sql;
}
RawSql create();
}
+5 -5
View File
@@ -1,5 +1,7 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;
@@ -43,6 +45,7 @@ public interface SqlQuery extends Serializable {
/**
* Execute the query returning a list.
*/
@Nonnull
List<SqlRow> findList();
/**
@@ -71,18 +74,15 @@ public interface SqlQuery extends Serializable {
* PersistenceException.
* </p>
*/
@Nullable
SqlRow findOne();
/**
* Execute the query returning an optional row.
*/
@Nonnull
Optional<SqlRow> findOneOrEmpty();
/**
* Synonym for findOne().
*/
SqlRow findUnique();
/**
* The same as bind for named parameters.
*/
+26
View File
@@ -1,6 +1,7 @@
package io.ebean;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.config.DocStoreConfig;
import io.ebean.config.ServerConfig;
@@ -12,6 +13,23 @@ import java.sql.Connection;
*/
public interface Transaction extends AutoCloseable {
/**
* Return the current transaction (of the default server) or null if there is
* no current transaction in scope.
* <p>
* This is the same as <code>Ebean.currentTransaction()</code>
* </p>
* <p>
* This returns the current transaction for the 'default server'. If you are using
* multiple EbeanServer's then use {@link EbeanServer#currentTransaction()}.
* </p>
* @see Ebean#currentTransaction()
* @see EbeanServer#currentTransaction()
*/
static Transaction current() {
return Ebean.currentTransaction();
}
/**
* Read Committed transaction isolation. Same as
* java.sql.Connection.TRANSACTION_READ_COMMITTED.
@@ -41,6 +59,14 @@ public interface Transaction extends AutoCloseable {
*/
void register(TransactionCallback callback);
/**
* Set a label on the transaction.
* <p>
* This label is used to group transaction execution times for performance metrics reporting.
* </p>
*/
void setLabel(String label);
/**
* Return true if this transaction is read only.
*/
-47
View File
@@ -1,47 +0,0 @@
package io.ebean;
/**
* Execute a TxCallable in a Transaction scope.
* <p>
* Use this with the {@link Ebean#execute(TxCallable)} method.
* </p>
* <p>
* Note that this is basically the same as TxRunnable except that it returns an
* Object (and you specify the return type via generics).
* </p>
* <p>
* See also {@link TxRunnable}.
* </p>
* <p>
* <pre>{@code
*
* Ebean.execute(new TxCallable<String>() {
* public String call() {
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
* u1.setName("u1 mod");
* u2.setName("u2 mod");
*
* Ebean.save(u1);
* Ebean.save(u2);
*
* return u1.getEmail();
* }
* });
*
* }</pre>
*
* @see TxRunnable
*/
public interface TxCallable<T> {
/**
* Execute the method within a transaction scope returning the result.
* <p>
* If you do not want to return a result you should look to use TxRunnable
* instead.
* </p>
*/
T call();
}
-40
View File
@@ -1,40 +0,0 @@
package io.ebean;
/**
* Execute a TxRunnable in a Transaction scope.
* <p>
* Use this with the {@link Ebean#execute(TxRunnable)} method.
* </p>
* <p>
* See also {@link TxCallable}.
* </p>
* <p>
* <pre>{@code
*
* // this run method runs in a transaction scope
* // which by default is TxScope.REQUIRED
*
* Ebean.execute(new TxRunnable() {
* public void run() {
* User u1 = Ebean.find(User.class, 1);
* User u2 = Ebean.find(User.class, 2);
*
* u1.setName("u1 mod");
* u2.setName("u2 mod");
*
* Ebean.save(u1);
* Ebean.save(u2);
* }
* });
*
* }</pre>
*
* @see TxCallable
*/
public interface TxRunnable {
/**
* Run the method in a transaction sope.
*/
void run();
}
+115 -17
View File
@@ -1,46 +1,62 @@
package io.ebean;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxIsolation;
import io.ebean.annotation.TxType;
import java.util.ArrayList;
import java.util.concurrent.Callable;
/**
* Holds the definition of how a transactional method should run.
* <p>
* This information matches the features of the Transactional annotation. You
* can use it directly with TxRunnable or TxCallable via
* {@link Ebean#execute(TxScope, TxCallable)} or
* {@link Ebean#execute(TxScope, TxRunnable)}.
* can use it directly with Runnable or Callable via
* {@link Ebean#execute(TxScope, Runnable)} or
* {@link Ebean#executeCall(TxScope, Callable)}.
* </p>
* <p>
* This object is used internally with the enhancement of a method with
* Transactional annotation.
* </p>
*
* @see TxCallable
* @see TxRunnable
* @see Ebean#execute(TxScope, TxCallable)
* @see Ebean#execute(TxScope, TxRunnable)
* @see Ebean#execute(TxScope, Runnable)
* @see Ebean#executeCall(TxScope, Callable)
*/
public final class TxScope {
TxType type;
private int profileId;
String serverName;
private TxType type;
TxIsolation isolation;
private String serverName;
PersistBatch batch;
private TxIsolation isolation;
PersistBatch batchOnCascade;
private PersistBatch batch;
int batchSize;
private PersistBatch batchOnCascade;
boolean skipGeneratedKeys;
private int batchSize;
boolean readOnly;
private boolean skipGeneratedKeys;
ArrayList<Class<? extends Throwable>> rollbackFor;
private boolean readOnly;
ArrayList<Class<? extends Throwable>> noRollbackFor;
/**
* Set this to false if the JDBC batch should not be automatically be flushed when a query is executed.
*/
private boolean flushOnQuery = true;
private boolean skipCache;
private String label;
private ArrayList<Class<? extends Throwable>> rollbackFor;
private ArrayList<Class<? extends Throwable>> noRollbackFor;
private ProfileLocation profileLocation;
/**
* Helper method to create a TxScope with REQUIRES.
@@ -160,6 +176,66 @@ public final class TxScope {
return this;
}
/**
* Return the transaction profile id.
*/
public int getProfileId() {
return profileId;
}
/**
* Set the transaction profile id.
*/
public TxScope setProfileId(int profileId) {
this.profileId = profileId;
return this;
}
/**
* Return the profile location.
*/
public ProfileLocation getProfileLocation() {
return profileLocation;
}
/**
* Set the profile location.
*/
public TxScope setProfileLocation(ProfileLocation profileLocation) {
this.profileLocation = profileLocation;
return this;
}
/**
* Return true if the L2 cache should be skipped for this transaction.
*/
public boolean isSkipCache() {
return skipCache;
}
/**
* Set to true if the transaction should skip L2 cache access.
*/
public TxScope setSkipCache(boolean skipCache) {
this.skipCache = skipCache;
return this;
}
/**
* Return the label for the transaction.
*/
public String getLabel() {
return label;
}
/**
* Set a label for the transaction.
*/
public TxScope setLabel(String label) {
this.label = label;
return this;
}
/**
* Return the batch mode.
*/
@@ -235,6 +311,28 @@ public final class TxScope {
return this;
}
/**
* Return false if the JDBC batch buffer should not be flushed automatically when a query is executed.
*/
public boolean isFlushOnQuery() {
return flushOnQuery;
}
/**
* Set flushOnQuery to be false to stop automatically flushing the JDBC batch buffer when a query is executed.
*/
public TxScope setFlushOnQuery(boolean flushOnQuery) {
this.flushOnQuery = flushOnQuery;
return this;
}
/**
* Return the isolation level.
*/
public int getIsolationLevel() {
return isolation != null ? isolation.getLevel() : -1;
}
/**
* Return the Isolation level this transaction should run with.
*/
+6
View File
@@ -148,6 +148,12 @@ public interface UpdateQuery<T> {
*/
UpdateQuery<T> setRaw(String propertyExpression, Object... values);
/**
* Set the profile location of this update query. This is used to relate query execution metrics
* back to a location like a specific line of code.
*/
UpdateQuery<T> setProfileLocation(ProfileLocation profileLocation);
/**
* Return the query expression list to add predicates to.
*/
@@ -0,0 +1,50 @@
package io.ebean;
import io.ebean.service.SpiProfileLocationFactory;
import io.ebean.service.SpiRawSqlService;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
* Lookup internal services.
*/
class XServiceProvider {
private static SpiRawSqlService rawSqlService = initRawSql();
private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation();
private static SpiRawSqlService initRawSql() {
Iterator<SpiRawSqlService> loader = ServiceLoader.load(SpiRawSqlService.class).iterator();
if (loader.hasNext()) {
return loader.next();
}
throw new IllegalStateException("No service implementation found for SpiRawSqlService?");
}
private static SpiProfileLocationFactory initProfileLocation() {
Iterator<SpiProfileLocationFactory> loader = ServiceLoader.load(SpiProfileLocationFactory.class).iterator();
if (loader.hasNext()) {
return loader.next();
}
throw new IllegalStateException("No service implementation found for SpiProfileLocationFactory?");
}
/**
* Return the RawSqlService implementation.
*/
static SpiRawSqlService rawSql() {
return rawSqlService;
}
/**
* Return the RawSqlService implementation.
*/
static SpiProfileLocationFactory profileLocationFactory() {
return profileLocationFactory;
}
}
@@ -226,4 +226,9 @@ public interface BeanCollection<E> extends Serializable {
* additions and removals have been processed.
*/
void modifyReset();
/**
* Return a shallow copy of this collection that is modifiable.
*/
BeanCollection<E> getShallowCopy();
}
@@ -0,0 +1,22 @@
package io.ebean.bean;
/**
* Visitor for collecting new/old values for a bean update.
*/
public interface BeanDiffVisitor {
/**
* Collect a new/old value pair.
*/
void visit(int position, Object newVal, Object oldVal);
/**
* Start processing an associated bean.
*/
void visitPush(int position);
/**
* Stop processing an associated bean.
*/
void visitPop();
}
+8 -2
View File
@@ -26,14 +26,16 @@ public final class CallStack implements Serializable {
private final StackTraceElement[] callStack;
private final int hc;
public CallStack(StackTraceElement[] callStack, int zeroHash, int pathHash) {
this.callStack = callStack;
this.zeroHash = enc(zeroHash);
this.pathHash = enc(pathHash);
this.hc = computeHashCode();
}
@Override
public int hashCode() {
private int computeHashCode() {
int hc = 0;
for (StackTraceElement aCallStack : callStack) {
hc = 92821 * hc + aCallStack.hashCode();
@@ -41,6 +43,10 @@ public final class CallStack implements Serializable {
return hc;
}
public int hashCode() {
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
+6 -11
View File
@@ -1,6 +1,5 @@
package io.ebean.bean;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
/**
@@ -14,8 +13,14 @@ import java.io.Serializable;
*/
public interface EntityBean extends Serializable {
/**
* Return all the property names in defined order.
*/
String[] _ebean_getPropertyNames();
/**
* Return the property name at the given position.
*/
String _ebean_getPropertyName(int pos);
/**
@@ -32,16 +37,6 @@ public interface EntityBean extends Serializable {
*/
Object _ebean_newInstance();
/**
* Add a PropertyChangeListener to this bean.
*/
void addPropertyChangeListener(PropertyChangeListener listener);
/**
* Remove a PropertyChangeListener from this bean.
*/
void removePropertyChangeListener(PropertyChangeListener listener);
/**
* Generated method that sets the loaded state on all the embedded beans on
* this entity bean by using EntityBeanIntercept.setEmbeddedLoaded(Object o);
@@ -3,15 +3,12 @@ package io.ebean.bean;
import io.ebean.Ebean;
import io.ebean.ValuePair;
import java.util.Arrays;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.math.BigDecimal;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
@@ -34,8 +31,6 @@ public final class EntityBeanIntercept implements Serializable {
private transient NodeUsageCollector nodeUsageCollector;
private transient PropertyChangeSupport pcs;
private transient PersistenceContext persistenceContext;
private transient BeanLoader beanLoader;
@@ -128,46 +123,6 @@ public final class EntityBeanIntercept implements Serializable {
this.persistenceContext = persistenceContext;
}
/**
* Add a property change listener for this entity bean.
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
if (pcs == null) {
pcs = new PropertyChangeSupport(owner);
}
pcs.addPropertyChangeListener(listener);
}
/**
* Add a property change listener for this entity bean for a specific
* property.
*/
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
if (pcs == null) {
pcs = new PropertyChangeSupport(owner);
}
pcs.addPropertyChangeListener(propertyName, listener);
}
/**
* Remove a property change listener for this entity bean.
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
if (pcs != null) {
pcs.removePropertyChangeListener(listener);
}
}
/**
* Remove a property change listener for this entity bean for a specific
* property.
*/
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
if (pcs != null) {
pcs.removePropertyChangeListener(propertyName, listener);
}
}
/**
* Turn on profile collection.
*/
@@ -507,6 +462,15 @@ public final class EntityBeanIntercept implements Serializable {
loadedProps[propertyIndex] = true;
}
/**
* Set all properties to be loaded (post insert).
*/
public void setLoadedPropertyAll() {
for (int i = 0; i < loadedProps.length; i++) {
loadedProps[i] = true;
}
}
/**
* Return true if the property is loaded.
*/
@@ -694,6 +658,28 @@ public final class EntityBeanIntercept implements Serializable {
}
}
/**
* Recursively add dirty properties.
*/
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
// the property has been changed on this bean
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
visitor.visit(i, newVal, oldVal);
} else if (embeddedDirty != null && embeddedDirty[i]) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
visitor.visitPush(i);
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(visitor);
visitor.visitPop();
}
}
}
/**
* Return a dirty property hash taking into account embedded beans.
*/
@@ -860,9 +846,9 @@ public final class EntityBeanIntercept implements Serializable {
loadedProps[propertyIndex] = true;
}
private void preGetterCallback() {
private void preGetterCallback(int propertyIndex) {
if (preGetterCallback != null) {
preGetterCallback.preGetterTrigger();
preGetterCallback.preGetterTrigger(propertyIndex);
}
}
@@ -870,14 +856,14 @@ public final class EntityBeanIntercept implements Serializable {
* Called prior to Id property getter.
*/
public void preGetId() {
preGetterCallback();
preGetterCallback(-1);
}
/**
* Method that is called prior to a getter method on the actual entity.
*/
public void preGetter(int propertyIndex) {
preGetterCallback();
preGetterCallback(propertyIndex);
if (state == STATE_NEW || disableLazyLoad) {
return;
}
@@ -889,49 +875,17 @@ public final class EntityBeanIntercept implements Serializable {
}
}
/**
* Called for "enhancement" postSetter processing. This is around a PUTFIELD
* so no need to check the newValue afterwards.
*/
public void postSetter(PropertyChangeEvent event) {
if (pcs != null && event != null) {
pcs.firePropertyChange(event);
}
}
/**
* Called for "subclassed" postSetter processing. Here the newValue has to be
* re-fetched (and passed into this method) in case there is code inside the
* setter that further mutates the value.
*/
public void postSetter(PropertyChangeEvent event, Object newValue) {
if (pcs != null && event != null) {
if (newValue != null && newValue.equals(event.getNewValue())) {
pcs.firePropertyChange(event);
} else {
pcs.firePropertyChange(event.getPropertyName(), event.getOldValue(), newValue);
}
}
}
/**
* OneToMany and ManyToMany don't have any interception so just check for
* PropertyChangeSupport.
*/
public PropertyChangeEvent preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setLoadedProperty(propertyIndex);
// Bean itself not considered dirty when many changed
if (pcs != null) {
return new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
} else {
return null;
}
}
private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
@@ -960,168 +914,140 @@ public final class EntityBeanIntercept implements Serializable {
* Check to see if the values are not equal. If they are not equal then create
* the old values for use with ConcurrencyMode.ALL.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!areEqual(oldValue, newValue)) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* Check for primitive boolean.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* Check for primitive int.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* long.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* double.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (Double.compare(oldValue, newValue) != 0) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* float.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (Float.compare(oldValue, newValue) != 0) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* short.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* char.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* byte.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* char[].
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!Arrays.equals(oldValue, newValue)) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* byte[].
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!Arrays.equals(oldValue, newValue)) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* Explicitly set an old value.
*/
public void setOldValue(int propertyIndex, Object oldValue) {
setChangedPropertyValue(propertyIndex, true, oldValue);
}
}
@@ -7,7 +7,9 @@ package io.ebean.bean;
public interface PreGetterCallback {
/**
* Trigger the callback.
* Trigger the callback based on a getter on a property.
*
* @param propertyIndex The index of the property
*/
void preGetterTrigger();
void preGetterTrigger(int propertyIndex);
}
+32 -2
View File
@@ -1,5 +1,9 @@
package io.ebean.cache;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* Represents part of the "L2" server side cache.
* <p>
@@ -13,20 +17,46 @@ package io.ebean.cache;
*/
public interface ServerCache {
default Map<Object, Object> getAll(Set<Object> keys) {
Map<Object, Object> map = new LinkedHashMap<>();
for (Object key : keys) {
Object value = get(key);
if (value != null) {
map.put(key, value);
}
}
return map;
}
/**
* Return the value given the key.
*/
Object get(Object id);
/**
* Put all the values in the cache.
*/
default void putAll(Map<Object, Object> keyValues) {
keyValues.forEach(this::put);
}
/**
* Put the value in the cache with a given id.
*/
Object put(Object id, Object value);
void put(Object id, Object value);
/**
* Remove the entries from the cache given the id values.
*/
default void removeAll(Set<Object> keys) {
keys.forEach(this::remove);
}
/**
* Remove a entry from the cache given its id.
*/
Object remove(Object id);
void remove(Object id);
/**
* Clear all entries from the cache.
+15
View File
@@ -44,4 +44,19 @@ public interface ServerCacheManager {
*/
void clearAll();
/**
* Clear all the local caches.
*
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
*/
void clearAllLocal();
/**
* Clear the local caches for this bean type.
*
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
*/
void clearLocal(Class<?> beanType);
}
@@ -213,4 +213,16 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
boolean holdsModifications() {
return modifyHolder != null && modifyHolder.hasModifications();
}
/**
* Copies all relevant properties for a clone. See {@link #getShallowCopy()}
* @param other
*/
protected void setFromOriginal(AbstractBeanCollection<E> other) {
this.disableLazyLoad = other.disableLazyLoad;
this.ebeanServerName = other.ebeanServerName;
this.loader = other.loader;
this.ownerBean = other.ownerBean;
this.propertyName = other.propertyName;
}
}
@@ -541,4 +541,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
}
}
@Override
public BeanCollection<E> getShallowCopy() {
BeanList<E> copy = new BeanList<>(new CopyOnFirstWriteList<>(list));
copy.setFromOriginal(this);
return copy;
}
}
@@ -340,4 +340,10 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
return map.values();
}
@Override
public BeanCollection<E> getShallowCopy() {
BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
copy.setFromOriginal(this);
return copy;
}
}
+7 -1
View File
@@ -377,5 +377,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
throw new IllegalStateException("This collection is in ReadOnly mode");
}
}
@Override
public BeanCollection<E> getShallowCopy() {
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
copy.setFromOriginal(this);
return copy;
}
}
@@ -0,0 +1,180 @@
package io.ebean.common;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
/**
* List that copies itself on first write access. Needed to keep memory footprint low and the ability
* to modify lists from cache.
*
* @author Roland Praml, FOCONIS AG
*/
public final class CopyOnFirstWriteList<E> extends AbstractList<E> implements List<E>, Serializable {
private static final long serialVersionUID = 1L;
/**
* The underlying List implementation.
*/
private List<E> list;
public CopyOnFirstWriteList(List<E> list) {
super();
this.list = list;
}
private volatile boolean copied = false;
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(o);
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public boolean add(E e) {
checkCopyOnWrite();
return list.add(e);
}
@Override
public boolean remove(Object o) {
checkCopyOnWrite();
return list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
checkCopyOnWrite();
return list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
checkCopyOnWrite();
return list.addAll(index, c);
}
@Override
public boolean removeAll(Collection<?> c) {
checkCopyOnWrite();
return list.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
checkCopyOnWrite();
return list.retainAll(c);
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
checkCopyOnWrite();
list.replaceAll(operator);
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
checkCopyOnWrite();
return list.removeIf(filter);
}
@Override
public void sort(Comparator<? super E> c) {
checkCopyOnWrite();
list.sort(c);
}
@Override
public void clear() {
if (!copied) {
list = new ArrayList<>();
copied = true;
}
}
@Override
public boolean equals(Object o) {
return list.equals(o);
}
@Override
public int hashCode() {
return list.hashCode();
}
@Override
public E get(int index) {
return list.get(index);
}
@Override
public E set(int index, E element) {
checkCopyOnWrite();
return list.set(index, element);
}
@Override
public void add(int index, E element) {
checkCopyOnWrite();
list.add(index, element);
}
@Override
public E remove(int index) {
checkCopyOnWrite();
return list.remove(index);
}
@Override
public int indexOf(Object o) {
return list.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}
private void checkCopyOnWrite() {
if (!copied) {
synchronized (this) {
if (!copied) {
list = new ArrayList<>(list);
copied = true;
}
}
}
}
}
@@ -1,13 +1,13 @@
package io.ebean.config;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.util.AnnotationUtil;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Inheritance;
import javax.persistence.Table;
import java.lang.annotation.Annotation;
/**
* Provides some base implementation for NamingConventions.
@@ -219,15 +219,10 @@ public abstract class AbstractNamingConvention implements NamingConvention {
* Return true if this class is part of entity inheritance.
*/
protected boolean hasInheritance(Class<?> supCls) {
return hasAnnotation(supCls, Inheritance.class) || hasAnnotation(supCls, DiscriminatorValue.class);
return AnnotationUtil.findAnnotationRecursive(supCls, Inheritance.class) != null
|| AnnotationUtil.findAnnotation(supCls, DiscriminatorValue.class) != null;
}
/**
* Return true if the class has the given annotation.
*/
protected boolean hasAnnotation(Class<?> supCls, Class<? extends Annotation> annotation) {
return supCls.getAnnotation(annotation) != null;
}
@Override
public TableName getM2MJoinTableName(TableName lhsTable, TableName rhsTable) {
@@ -258,7 +253,7 @@ public abstract class AbstractNamingConvention implements NamingConvention {
*/
protected TableName getTableNameFromAnnotation(Class<?> beanClass) {
final Table t = findTableAnnotation(beanClass);
final Table t = AnnotationUtil.findAnnotationRecursive(beanClass, Table.class);
// Take the annotation if defined
if (t != null && !isEmpty(t.name())) {
@@ -271,22 +266,6 @@ public abstract class AbstractNamingConvention implements NamingConvention {
return null;
}
/**
* Search recursively for an @Table in the class hierarchy.
*/
protected Table findTableAnnotation(Class<?> cls) {
while (true) {
if (cls.equals(Object.class)) {
return null;
}
Table table = cls.getAnnotation(Table.class);
if (table != null) {
return table;
}
cls = cls.getSuperclass();
}
}
/**
* Replace back ticks (if they are used) with database platform specific
* quoted identifiers.
@@ -32,6 +32,15 @@ public class AutoTuneConfig {
public AutoTuneConfig() {
}
/**
* Return true if we are profiling or query tuning.
*
* If we are not doing either then we don't need a CallStack.
*/
public boolean isActive() {
return profiling || queryTuning;
}
/**
* Return the name of the file that holds the query tuning information.
*/
@@ -54,6 +54,22 @@ public class ClassLoadConfig {
return isPresent("javax.validation.constraints.NotNull");
}
/**
* Return true if javax PostConstruct annotation is present (maybe not in java9).
* If not we don't support PostConstruct lifecycle events.
*/
public boolean isJavaxPostConstructPresent() {
return isPresent("javax.annotation.PostConstruct");
}
/**
* Return true if javax JAXB is present (maybe not in java9).
* If not we don't try to parse or support 'extra ddl'.
*/
public boolean isJavaxJAXBPresent() {
return isPresent("javax.xml.bind.JAXBException");
}
/**
* Return true if Jackson annotations like JsonIgnore are present.
*/
@@ -10,22 +10,82 @@ import java.util.Properties;
*/
public class ContainerConfig {
protected boolean clusterActive;
private boolean active;
private String serviceName;
private String namespace;
private String podName;
private int port;
protected Properties properties;
private Properties properties;
/**
* Return the service name.
*/
public String getServiceName() {
return serviceName;
}
/**
* Set the service name.
*/
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
/**
* Return the namespace.
*/
public String getNamespace() {
return namespace;
}
/**
* Set the namespace.
*/
public void setNamespace(String namespace) {
this.namespace = namespace;
}
/**
* Return the pod name.
*/
public String getPodName() {
return podName;
}
/**
* Set the pod name.
*/
public void setPodName(String podName) {
this.podName = podName;
}
/**
* Return the port to use.
*/
public int getPort() {
return port;
}
/**
* Set the port to use.
*/
public void setPort(int port) {
this.port = port;
}
/**
* Return true if clustering is active.
*/
public boolean isClusterActive() {
return clusterActive;
public boolean isActive() {
return active;
}
/**
* Set to true for clustering to be active.
*/
public void setClusterActive(boolean clusterActive) {
this.clusterActive = clusterActive;
public void setActive(boolean active) {
this.active = active;
}
/**
@@ -47,7 +107,14 @@ public class ContainerConfig {
*/
public void loadFromProperties(Properties properties) {
this.properties = properties;
this.clusterActive = getProperty(properties, "ebean.cluster.active", clusterActive);
this.active = getProperty(properties, "ebean.cluster.active", active);
this.serviceName = properties.getProperty("ebean.cluster.serviceName", serviceName);
this.namespace = properties.getProperty("ebean.cluster.namespace", namespace);
this.podName = properties.getProperty("ebean.cluster.podName", podName);
String portParam = properties.getProperty("ebean.cluster.port");
if (portParam != null) {
this.port = Integer.parseInt(portParam);
}
}
/**
@@ -1,7 +1,7 @@
package io.ebean.config;
import io.ebean.config.dbplatform.DbType;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
/**
* Custom mappings for DB types that override the default.
@@ -1,8 +1,8 @@
package io.ebean.config;
import io.ebean.Platform;
import io.ebean.dbmigration.MigrationConfig;
import io.ebean.dbmigration.MigrationRunner;
import io.ebean.annotation.Platform;
import io.ebean.migration.MigrationConfig;
import io.ebean.migration.MigrationRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,6 +16,8 @@ public class DbMigrationConfig {
protected static final Logger logger = LoggerFactory.getLogger(DbMigrationConfig.class);
protected MigrationConfig runnerConfig = new MigrationConfig();
/**
* The database platform to generate migration DDL for.
*/
@@ -105,6 +107,15 @@ public class DbMigrationConfig {
*/
protected String dbPassword;
protected String patchInsertOn;
protected String patchResetChecksumOn;
/**
* Mode used to check non-null columns added via migration have a default value specified etc.
*/
protected boolean strictMode = true;
/**
* Return the DB platform to generate migration DDL for.
* <p>
@@ -352,6 +363,65 @@ public class DbMigrationConfig {
this.dbPassword = dbPassword;
}
/**
* Return migration versions that should be added to history without running.
*/
public String getPatchInsertOn() {
return patchInsertOn;
}
/**
* Set migration versions that should be added to history without running.
* <p>
* Value can be a string containing comma delimited list of version numbers.
* </p>
*/
public void setPatchInsertOn(String patchInsertOn) {
this.patchInsertOn = patchInsertOn;
}
/**
* Return migration versions that should have their checksum reset and not run.
*/
public String getPatchResetChecksumOn() {
return patchResetChecksumOn;
}
/**
* Set migration versions that should have their checksum reset and not run.
* <p>
* Value can be a string containing comma delimited list of version numbers.
* </p>
*/
public void setPatchResetChecksumOn(String patchResetChecksumOn) {
this.patchResetChecksumOn = patchResetChecksumOn;
}
/**
* Return true if strict mode is used which includes a check that non-null columns have a default value.
*/
public boolean isStrictMode() {
String envValue = readEnvironment("ddl.migration.strictMode");
if (!isEmpty(envValue)) {
return Boolean.parseBoolean(envValue.trim());
}
return strictMode;
}
/**
* Set to false to turn off strict mode allowing non-null columns to not have a default value.
*/
public void setStrictMode(boolean strictMode) {
this.strictMode = strictMode;
}
/**
* Return the underlying migration runner configuration allowing for more advanced settings.
*/
public MigrationConfig getRunnerConfig() {
return runnerConfig;
}
/**
* Load the settings from the PropertiesWrapper.
*/
@@ -370,6 +440,8 @@ public class DbMigrationConfig {
generate = properties.getBoolean("migration.generate", generate);
version = properties.get("migration.version", version);
name = properties.get("migration.name", name);
patchInsertOn = properties.get("migration.patchInsertOn", patchInsertOn);
patchResetChecksumOn = properties.get("migration.patchResetChecksumOn", patchResetChecksumOn);
runMigration = properties.getBoolean("migration.run", runMigration);
metaTable = properties.get("migration.metaTable", metaTable);
@@ -446,7 +518,6 @@ public class DbMigrationConfig {
*/
public MigrationRunner createRunner(ClassLoader classLoader, Properties properties) {
MigrationConfig runnerConfig = new MigrationConfig();
runnerConfig.setMetaTable(metaTable);
runnerConfig.setApplySuffix(applySuffix);
runnerConfig.setMigrationPath(migrationPath);
@@ -455,6 +526,12 @@ public class DbMigrationConfig {
runnerConfig.setDbUsername(getDbUsername());
runnerConfig.setDbPassword(getDbPassword());
runnerConfig.setClassLoader(classLoader);
if (patchInsertOn != null) {
runnerConfig.setPatchInsertOn(patchInsertOn);
}
if (patchResetChecksumOn != null) {
runnerConfig.setPatchResetChecksumOn(patchResetChecksumOn);
}
if (properties != null) {
runnerConfig.load(properties);
}
@@ -2,7 +2,7 @@ package io.ebean.config;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import java.util.ArrayList;
import java.util.List;
@@ -12,7 +12,7 @@ public interface EncryptKeyManager {
* This gives the EncryptKeyManager the opportunity to get keys etc.
* </p>
*/
default void initialise() {};
default void initialise() {}
/**
* Return the key used to encrypt and decrypt a property mapping to the given
@@ -0,0 +1,148 @@
package io.ebean.config;
/**
* Configuration for transaction profiling.
*/
public class ProfilingConfig {
/**
* When true transaction profiling is enabled.
*/
private boolean enabled;
/**
* Set true for verbose mode.
*/
private boolean verbose;
/**
* The minimum transaction execution time to be included in profiling.
*/
private long minimumMicros;
/**
* A specific set of profileIds to include in profiling.
*/
private int[] includeProfileIds = {};
/**
* The number of profiles to write per file.
*/
private long profilesPerFile = 1000;
private String directory = "profiling";
/**
* Return true if transaction profiling is enabled.
*/
public boolean isEnabled() {
return enabled;
}
/**
* Set to true to enable transaction profiling.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Return true if verbose mode is used.
*/
public boolean isVerbose() {
return verbose;
}
/**
* Set to true to use verbose mode.
*/
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
/**
* Return the minimum transaction execution to be included in profiling.
*/
public long getMinimumMicros() {
return minimumMicros;
}
/**
* Set the minimum transaction execution to be included in profiling.
*/
public void setMinimumMicros(long minimumMicros) {
this.minimumMicros = minimumMicros;
}
/**
* Return the specific set of profileIds to include in profiling.
* When not set all transactions with profileIds are included.
*/
public int[] getIncludeProfileIds() {
return includeProfileIds;
}
/**
* Set a specific set of profileIds to include in profiling.
* When not set all transactions with profileIds are included.
*/
public void setIncludeProfileIds(int[] includeProfileIds) {
this.includeProfileIds = includeProfileIds;
}
/**
* Return the number of profiles to write to a single file.
*/
public long getProfilesPerFile() {
return profilesPerFile;
}
/**
* Set the number of profiles to write to a single file.
*/
public void setProfilesPerFile(long profilesPerFile) {
this.profilesPerFile = profilesPerFile;
}
/**
* Return the directory profiling files are put into.
*/
public String getDirectory() {
return directory;
}
/**
* Set the directory profiling files are put into.
*/
public void setDirectory(String directory) {
this.directory = directory;
}
/**
* Load setting from properties.
*/
public void loadSettings(PropertiesWrapper p, String name) {
enabled = p.getBoolean("profiling", enabled);
verbose = p.getBoolean("profiling.verbose", verbose);
directory = p.get("profiling.directory", directory);
profilesPerFile = p.getLong("profiling.profilesPerFile", profilesPerFile);
minimumMicros = p.getLong("profiling.minimumMicros", minimumMicros);
String includeIds = p.get("profiling.includeProfileIds");
if (includeIds != null) {
includeProfileIds = parseIds(includeIds);
}
}
private int[] parseIds(String includeIds) {
String[] ids = includeIds.split(",");
int[] vals = new int[ids.length];
for (int i = 0; i < ids.length; i++) {
vals[i] = Integer.parseInt(ids[i]);
}
return vals;
}
}
+186 -28
View File
@@ -2,18 +2,16 @@ package io.ebean.config;
import com.fasterxml.jackson.core.JsonFactory;
import io.ebean.EbeanServerFactory;
import io.ebean.PersistBatch;
import io.ebean.PersistenceContextScope;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.annotation.Encrypted;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.cache.ServerCachePlugin;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbEncrypt;
import io.ebean.config.dbplatform.DbType;
import io.ebean.dbmigration.DbOffline;
import io.ebean.dbmigration.MigrationRunner;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanPersistController;
import io.ebean.event.BeanPersistListener;
@@ -28,11 +26,13 @@ import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import java.util.regex.Pattern;
import io.ebean.migration.MigrationRunner;
import io.ebean.util.StringHelper;
import org.avaje.datasource.DataSourceConfig;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -77,8 +77,6 @@ import java.util.ServiceLoader;
*/
public class ServerConfig {
private static final Pattern NAMES_SPLIT = Pattern.compile("[ ,;]");
/**
* The EbeanServer name.
*/
@@ -275,11 +273,29 @@ public class ServerConfig {
*/
private DataSource dataSource;
/**
* The read only data source (can be null).
*/
private DataSource readOnlyDataSource;
/**
* The data source config.
*/
private DataSourceConfig dataSourceConfig = new DataSourceConfig();
/**
* When true create a read only DataSource using readOnlyDataSourceConfig defaulting values from dataSourceConfig.
* I believe this will default to true in some future release (as it has a nice performance benefit).
*
* autoReadOnlyDataSource is an unfortunate name for this config option but I haven't come up with a better one.
*/
private boolean autoReadOnlyDataSource;
/**
* Optional configuration for a read only data source.
*/
private DataSourceConfig readOnlyDataSourceConfig = new DataSourceConfig();
/**
* The db migration config (migration resource path etc).
*/
@@ -370,6 +386,8 @@ public class ServerConfig {
private ChangeLogRegister changeLogRegister;
private boolean changeLogAsync = true;
private ReadAuditLogger readAuditLogger;
private ReadAuditPrepare readAuditPrepare;
@@ -435,11 +453,29 @@ public class ServerConfig {
private String jodaLocalTimeMode;
/**
* Time to live for query plans - defaults to 5 minutes.
*/
private int queryPlanTTLSeconds = 60 * 5;
/**
* Set to true to globally disable L2 caching (typically for performance testing).
*/
private boolean disableL2Cache;
/**
* The time in millis used to determine when a query is alerted for being slow.
*/
private long slowQueryMillis;
/**
* The listener for processing slow query events.
*/
private SlowQueryListener slowQueryListener;
private ProfilingConfig profilingConfig = new ProfilingConfig();
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -447,6 +483,34 @@ public class ServerConfig {
}
/**
* Return the slow query time in millis.
*/
public long getSlowQueryMillis() {
return slowQueryMillis;
}
/**
* Set the slow query time in millis.
*/
public void setSlowQueryMillis(long slowQueryMillis) {
this.slowQueryMillis = slowQueryMillis;
}
/**
* Return the slow query event listener.
*/
public SlowQueryListener getSlowQueryListener() {
return slowQueryListener;
}
/**
* Set the slow query event listener.
*/
public void setSlowQueryListener(SlowQueryListener slowQueryListener) {
this.slowQueryListener = slowQueryListener;
}
/**
* Put a service object into configuration such that it can be passed to a plugin.
* <p>
@@ -934,6 +998,20 @@ public class ServerConfig {
this.changeLogIncludeInserts = changeLogIncludeInserts;
}
/**
* Return true (default) if the changelog should be written async.
*/
public boolean isChangeLogAsync() {
return changeLogAsync;
}
/**
* Sets if the changelog should be written async (default = true).
*/
public void setChangeLogAsync(boolean changeLogAsync) {
this.changeLogAsync = changeLogAsync;
}
/**
* Return the ReadAuditLogger to use.
*/
@@ -969,6 +1047,20 @@ public class ServerConfig {
this.readAuditPrepare = readAuditPrepare;
}
/**
* Return the configuration for profiling.
*/
public ProfilingConfig getProfilingConfig() {
return profilingConfig;
}
/**
* Set the configuration for profiling.
*/
public void setProfilingConfig(ProfilingConfig profilingConfig) {
this.profilingConfig = profilingConfig;
}
/**
* Return the DB migration configuration.
*/
@@ -1368,6 +1460,28 @@ public class ServerConfig {
this.dataSource = dataSource;
}
/**
* Return the read only DataSource.
*/
public DataSource getReadOnlyDataSource() {
return readOnlyDataSource;
}
/**
* Set the read only DataSource.
* <p>
* Note that the DataSource is expected to use AutoCommit true mode avoiding the need
* for explicit commit (or rollback).
* </p>
* <p>
* This read only DataSource will be used for implicit query only transactions. It is not
* used if the transaction is created explicitly or if the query is an update or delete query.
* </p>
*/
public void setReadOnlyDataSource(DataSource readOnlyDataSource) {
this.readOnlyDataSource = readOnlyDataSource;
}
/**
* Return the configuration to build a DataSource using Ebean's own DataSource
* implementation.
@@ -1384,6 +1498,42 @@ public class ServerConfig {
this.dataSourceConfig = dataSourceConfig;
}
/**
* Return true if Ebean should create a DataSource for use with implicit read only transactions.
*/
public boolean isAutoReadOnlyDataSource() {
return autoReadOnlyDataSource;
}
/**
* Set to true if Ebean should create a DataSource for use with implicit read only transactions.
*/
public void setAutoReadOnlyDataSource(boolean autoReadOnlyDataSource) {
this.autoReadOnlyDataSource = autoReadOnlyDataSource;
}
/**
* Return the configuration for the read only DataSource.
* <p>
* This is only used if autoReadOnlyDataSource is true.
* </p>
* <p>
* The driver, url, username and password default to the configuration for the main DataSource if they are not
* set on this configuration. This means there is actually no need to set any configuration here and we only
* set configuration for url, username and password etc if it is different from the main DataSource.
* </p>
*/
public DataSourceConfig getReadOnlyDataSourceConfig() {
return readOnlyDataSourceConfig;
}
/**
* Set the configuration for the read only DataSource.
*/
public void setReadOnlyDataSourceConfig(DataSourceConfig readOnlyDataSourceConfig) {
this.readOnlyDataSourceConfig = readOnlyDataSourceConfig;
}
/**
* Return the JNDI name of the DataSource to use.
*/
@@ -1633,16 +1783,6 @@ public class ServerConfig {
this.encryptor = encryptor;
}
/**
* Return true if the instance is coming up in offline mode.
* <p>
* Offline mode is mostly used when generating DB migration.
* </p>
*/
public boolean isOfflineMode() {
return dbOffline || DbOffline.isSet();
}
/**
* Return true if the EbeanServer instance should be created in offline mode.
*/
@@ -2504,6 +2644,7 @@ public class ServerConfig {
*/
protected void loadSettings(PropertiesWrapper p) {
profilingConfig.loadSettings(p, name);
migrationConfig.loadSettings(p, name);
boolean quotedIdentifiers = p.getBoolean("allQuotedIdentifiers", allQuotedIdentifiers);
@@ -2535,11 +2676,14 @@ public class ServerConfig {
dbTypeConfig.setGeometrySRID(srid);
}
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
@@ -2580,6 +2724,7 @@ public class ServerConfig {
persistenceContextScope = PersistenceContextScope.valueOf(p.get("persistenceContextScope", "TRANSACTION"));
changeLogAsync = p.getBoolean("changeLogAsync", changeLogAsync);
changeLogIncludeInserts = p.getBoolean("changeLogIncludeInserts", changeLogIncludeInserts);
expressionEqualsWithNullAsNoop = p.getBoolean("expressionEqualsWithNullAsNoop", expressionEqualsWithNullAsNoop);
expressionNativeIlike = p.getBoolean("expressionNativeIlike", expressionNativeIlike);
@@ -2647,10 +2792,9 @@ public class ServerConfig {
List<Class<?>> classes = new ArrayList<>();
String[] split = NAMES_SPLIT.split(classNames);
for (String aSplit : split) {
String cn = aSplit.trim();
if (!cn.isEmpty() && !"class".equalsIgnoreCase(cn)) {
String[] split = StringHelper.splitNames(classNames);
for (String cn : split) {
if (!"class".equalsIgnoreCase(cn)) {
try {
classes.add(Class.forName(cn));
} catch (ClassNotFoundException e) {
@@ -2664,16 +2808,16 @@ public class ServerConfig {
private List<String> getSearchJarsPackages(String searchPackages) {
List<String> hitList = new ArrayList<>();
if (searchPackages != null) {
String[] entries = StringHelper.splitNames(searchPackages);
String[] entries = NAMES_SPLIT.split(searchPackages);
for (String entry : entries) {
hitList.add(entry.trim());
}
List<String> hitList = new ArrayList<>(entries.length);
Collections.addAll(hitList, entries);
return hitList;
} else {
return new ArrayList<>();
}
return hitList;
}
/**
@@ -2756,6 +2900,20 @@ public class ServerConfig {
this.disableL2Cache = disableL2Cache;
}
/**
* Return the query plan time to live.
*/
public int getQueryPlanTTLSeconds() {
return queryPlanTTLSeconds;
}
/**
* Set the query plan time to live.
*/
public void setQueryPlanTTLSeconds(int queryPlanTTLSeconds) {
this.queryPlanTTLSeconds = queryPlanTTLSeconds;
}
/**
* Run the DB migration against the DataSource.
*/
@@ -0,0 +1,59 @@
package io.ebean.config;
import io.ebean.bean.ObjectGraphNode;
/**
* Slow query event.
*/
public class SlowQueryEvent {
private final String sql;
private final long timeMillis;
private final int rowCount;
private final ObjectGraphNode originNode;
/**
* Construct with the SQL and execution time in millis.
*/
public SlowQueryEvent(String sql, long timeMillis, int rowCount, ObjectGraphNode originNode) {
this.sql = sql;
this.timeMillis = timeMillis;
this.rowCount = rowCount;
this.originNode = originNode;
}
/**
* Return the SQL for the slow query.
*/
public String getSql() {
return sql;
}
/**
* Return the execution time in millis.
*/
public long getTimeMillis() {
return timeMillis;
}
/**
* Return the total row count associated with the query.
*/
public int getRowCount() {
return rowCount;
}
/**
* Return the origin point for the root query.
* <p>
* Typically the <code>originNode.getOriginQueryPoint().getFirstStackElement()</code> provides the stack line that
* shows the code that invoked the query.
* </p>
*/
public ObjectGraphNode getOriginNode() {
return originNode;
}
}
@@ -0,0 +1,13 @@
package io.ebean.config;
/**
* Listener for slow query events.
*/
@FunctionalInterface
public interface SlowQueryListener {
/**
* Process a slow query event.
*/
void process(SlowQueryEvent event);
}
@@ -16,5 +16,5 @@ public interface TenantDataSourceProvider {
/**
* Shutdown all the DataSources.
*/
default void shutdown(boolean deregisterDriver) {};
default void shutdown(boolean deregisterDriver) {}
}
@@ -2,13 +2,10 @@ package io.ebean.config.dbplatform;
import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.CustomDbTypeMapping;
import io.ebean.config.DbTypeConfig;
import io.ebean.PersistBatch;
import io.ebean.Platform;
import io.ebean.config.ServerConfig;
import io.ebean.dbmigration.ddlgeneration.DdlHandler;
import io.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,6 +61,8 @@ public class DatabasePlatform {
*/
protected String closeQuote = "\"";
protected String concatOperator = "||";
/**
* When set to true all db column names and table names use quoted identifiers.
*/
@@ -141,9 +140,25 @@ public class DatabasePlatform {
private static final char BACK_TICK = '`';
/**
* The like clause. Can be overridden to disable default escape character.
* The non-escaped like clause (to stop slash being escaped on some platforms).
* Used for the 'raw like' expression but not for startsWith, endsWith and contains expressions.
*/
protected String likeClause = "like ?";
protected String likeClauseRaw = "like ? escape''";
/**
* Escaped like clause for startsWith, endsWith and contains.
*/
protected String likeClauseEscaped = "like ? escape'|'";
/**
* Escape character used for startsWith, endsWith and contains.
*/
protected char likeEscapeChar = '|';
/**
* Characters escaped for startsWith, endsWith and contains.
*/
protected char[] likeSpecialCharacters = { '%', '_', '|' };
protected DbEncrypt dbEncrypt;
@@ -162,8 +177,6 @@ public class DatabasePlatform {
*/
protected PersistBatch persistBatchOnCascade = PersistBatch.ALL;
protected PlatformDdl platformDdl;
/**
* The maximum length of table names - used specifically when derived
* default table names for intersection tables.
@@ -180,8 +193,6 @@ public class DatabasePlatform {
protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator();
protected char[] specialLikeCharacters = { '%', '_' };
/**
* Instantiates a new database platform.
*/
@@ -232,7 +243,7 @@ public class DatabasePlatform {
* Return true if this matches the given platform.
*/
public boolean isPlatform(Platform platform) {
return this.platform.equals(platform);
return this.platform == platform;
}
/**
@@ -276,20 +287,6 @@ public class DatabasePlatform {
return maxConstraintNameLength;
}
/**
* Return the platform specific DDL.
*/
public PlatformDdl getPlatformDdl() {
return platformDdl;
}
/**
* Create and return a DDL handler for generating DDL scripts.
*/
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
return platformDdl.createDdlHandler(serverConfig);
}
/**
* Return true if the JDBC driver does not allow additional queries to execute
* when a resultSet is being 'streamed' as is the case with findEach() etc.
@@ -409,8 +406,6 @@ public class DatabasePlatform {
/**
* Return the close quote for quoted identifiers.
*
* @return the close quote
*/
public String getCloseQuote() {
return closeQuote;
@@ -418,17 +413,20 @@ public class DatabasePlatform {
/**
* Return the open quote for quoted identifiers.
*
* @return the open quote
*/
public String getOpenQuote() {
return openQuote;
}
/**
* Return the DB concat operator.
*/
public String getConcatOperator() {
return concatOperator;
}
/**
* Return the JDBC type used to store booleans.
*
* @return the boolean db type
*/
public int getBooleanDbType() {
return booleanDbType;
@@ -599,8 +597,8 @@ public class DatabasePlatform {
* <p>
* This may include an escape clause to disable a default escape character.
*/
public String getLikeClause() {
return likeClause;
public String getLikeClause(boolean rawLikeExpression) {
return rawLikeExpression ? likeClauseRaw : likeClauseEscaped;
}
/**
@@ -643,7 +641,7 @@ public class DatabasePlatform {
for (int i = 0; i < value.length(); i++) {
char ch = value.charAt(i);
boolean escaped = false;
for (char escapeChar: specialLikeCharacters) {
for (char escapeChar: likeSpecialCharacters) {
if (ch == escapeChar) {
if (sb == null) {
sb = new StringBuilder(value.substring(0, i));
@@ -665,6 +663,6 @@ public class DatabasePlatform {
}
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('\\').append(ch);
sb.append(likeEscapeChar).append(ch);
}
}
@@ -1,8 +1,13 @@
package io.ebean.config.dbplatform;
import java.sql.Types;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import io.ebean.annotation.DbDefault;
/**
* DB Column default values mapping to database platform specific literals.
*/
@@ -22,6 +27,13 @@ public class DbDefaultValue {
* The key for the NOW / current timestamp.
*/
public static final String NOW = "now";
/**
* The 'null' literal.
*/
public static final String NULL = "null";
protected Map<String, String> map = new LinkedHashMap<>();
@@ -63,8 +75,128 @@ public class DbDefaultValue {
if (dbDefaultLiteral == null) {
return null;
}
if (dbDefaultLiteral.startsWith("$RAW:")) {
return dbDefaultLiteral.substring(5);
}
String val = map.get(dbDefaultLiteral);
return val != null ? val : dbDefaultLiteral;
}
/**
* This method checks & convert the {@link DbDefault#value()} to a valid SQL literal.
*
* This is mainly to quote string literals and verify integer/dates for correctness.
* <p>
* Note: There are some special cases:
* </p>
* <ul>
* <li>Normal Quoting: <code>@DbDefault("User's default")</code> on a String propery
* returns: <code>default 'User''s default'</code><br/>
* (the same on an integer property will throw a NumberFormatException)</li>
* <li>Special case null: <code>@DbDefault("null")</code> will return this: <code>default null</code><br/>
* If you need really the String "null", you have to specify <code>@DbDefault("'null'")</code>
* which gives you the <code>default 'null'</code> statement.</li>
* <li>Any statement, that begins and ends with single quote will not be checked or get quoted again.</li>
* <li>A statement that begins with "$RAW:", e.g <code>@DbDefault("$RAW:N'SANDNES'")</code> will lead to
* a <code>default N'SANDNES'</code> in DDL. Note that this is platform specific!</li>
* </ul>
*/
public static String toSqlLiteral(String defaultValue, Class<?> propertyType, int sqlType) {
if (propertyType == null
|| defaultValue == null
|| NULL.equals(defaultValue)
|| (defaultValue.startsWith("'") && defaultValue.endsWith("'"))
|| (defaultValue.startsWith("$RAW:"))) {
return defaultValue;
}
if (Boolean.class.isAssignableFrom(propertyType) || Boolean.TYPE.isAssignableFrom(propertyType)) {
return toBooleanLiteral(defaultValue);
}
if (Number.class.isAssignableFrom(propertyType)
|| Byte.TYPE.equals(propertyType)
|| Short.TYPE.equals(propertyType)
|| Integer.TYPE.equals(propertyType)
|| Long.TYPE.equals(propertyType)
|| Float.TYPE.equals(propertyType)
|| Double.TYPE.equals(propertyType)
|| (propertyType.isEnum() && sqlType == Types.INTEGER)) {
Double.valueOf(defaultValue); // verify if it is a number
return defaultValue;
}
// check if it is a date/time - in all other cases return quoted defaultValue
switch (sqlType) {
// date
case Types.DATE:
return toDateLiteral(defaultValue);
// time
case Types.TIME:
case Types.TIME_WITH_TIMEZONE:
return toTimeLiteral(defaultValue);
// timestamp
case Types.TIMESTAMP:
case Types.TIMESTAMP_WITH_TIMEZONE:
return toDateTimeLiteral(defaultValue);
default:
return toTextLiteral(defaultValue); // do not check other datatypes
}
}
/**
* Checks if specified value is either 'true' or 'false'. The literal is translated later.
*/
private static String toBooleanLiteral(String value) {
if (DbDefaultValue.FALSE.equals(value) || DbDefaultValue.TRUE.equals(value)) {
return value;
}
throw new IllegalArgumentException("'" + value + "' is not a valid value for boolean");
}
/**
* This adds single qoutes around the <code>value</code> and doubles single quotes.
* "User's home" will return "'User''s home'"
*/
private static String toTextLiteral(String value) {
StringBuilder sb = new StringBuilder(value.length()+10);
sb.append('\'');
for (int i = 0; i < value.length(); i++) {
char ch = value.charAt(i);
if (ch == '\'') {
sb.append("''");
} else {
sb.append(ch);
}
}
sb.append('\'');
return sb.toString();
}
private static String toDateLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseDate(value); // verify
return toTextLiteral(value);
}
private static String toTimeLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseTime(value); // verify
return toTextLiteral(value);
}
private static String toDateTimeLiteral(String value) {
if (NOW.equals(value)) {
return value; // this will get translated later
}
DatatypeConverter.parseDateTime(value); // verify
return toTextLiteral(value);
}
}
@@ -1,6 +1,5 @@
package io.ebean.config.dbplatform;
import io.ebean.dbmigration.migration.IdentityType;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -121,28 +120,4 @@ public class DbIdentity {
this.idType = idType;
}
/**
* Determine the id type to use based on requested identityType and
* the support for that in the database platform.
*/
public IdType useIdentityType(IdentityType identityType) {
if (identityType == null) {
// use the default
return idType;
}
switch (identityType) {
case GENERATOR:
return IdType.GENERATOR;
case EXTERNAL:
return IdType.EXTERNAL;
case SEQUENCE:
return supportsSequence ? IdType.SEQUENCE : idType;
case IDENTITY:
return supportsIdentity ? IdType.IDENTITY : idType;
default:
return idType;
}
}
}
@@ -34,6 +34,10 @@ public class SqlCodeTranslator implements SqlExceptionTranslator {
public PersistenceException translate(String message, SQLException e) {
DataErrorType errorType = map.get(e.getSQLState());
if (errorType == null) {
// fall back to error code
errorType = map.get(String.valueOf(e.getErrorCode()));
}
if (errorType != null) {
switch (errorType) {
case AcquireLock:
@@ -1,14 +1,13 @@
package io.ebean.config.dbplatform.db2;
import io.ebean.BackgroundExecutor;
import io.ebean.PersistBatch;
import io.ebean.Platform;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.platform.DB2Ddl;
import javax.sql.DataSource;
import java.sql.Types;
@@ -24,7 +23,6 @@ public class DB2Platform extends DatabasePlatform {
this.maxTableNameLength = 18;
this.maxConstraintNameLength = 18;
this.sqlLimiter = new Db2SqlLimiter();
this.platformDdl = new DB2Ddl(this);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
@@ -44,10 +42,14 @@ public class DB2Platform extends DatabasePlatform {
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("bigint", false));
dbTypeMap.put(DbType.REAL, new DbPlatformType("real"));
dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("decimal", 15));
persistBatchOnCascade = PersistBatch.NONE;
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
}
/**
* Return a DB2 specific sequence IdGenerator that supports batch fetching
* sequence values.
@@ -1,15 +1,14 @@
package io.ebean.config.dbplatform.h2;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.platform.H2Ddl;
import javax.sql.DataSource;
@@ -22,7 +21,6 @@ public class H2Platform extends DatabasePlatform {
super();
this.platform = Platform.H2;
this.dbEncrypt = new H2DbEncrypt();
this.platformDdl = new H2Ddl(this);
this.historySupport = new H2HistorySupport();
this.nativeUuidType = true;
this.dbDefaultValue.setNow("now()");
@@ -1,7 +1,7 @@
package io.ebean.config.dbplatform.hsqldb;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
@@ -9,7 +9,6 @@ import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.h2.H2DbEncrypt;
import io.ebean.config.dbplatform.h2.H2SequenceIdGenerator;
import io.ebean.dbmigration.ddlgeneration.platform.HsqldbDdl;
import javax.sql.DataSource;
@@ -22,7 +21,6 @@ public class HsqldbPlatform extends DatabasePlatform {
super();
this.platform = Platform.HSQLDB;
this.dbEncrypt = new H2DbEncrypt();
this.platformDdl = new HsqldbDdl(this);
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
@@ -1,15 +1,14 @@
package io.ebean.config.dbplatform.mysql;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.platform.MySqlDdl;
import javax.sql.DataSource;
import java.sql.Types;
@@ -32,7 +31,6 @@ public class MySqlPlatform extends DatabasePlatform {
this.useExtraTransactionOnIterateSecondaryQueries = true;
this.selectCountWithAlias = true;
this.dbEncrypt = new MySqlDbEncrypt();
this.platformDdl = new MySqlDdl(this);
this.historySupport = new MySqlHistorySupport();
this.columnAliasPrefix = null;
@@ -44,12 +42,15 @@ public class MySqlPlatform extends DatabasePlatform {
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("1205")
.addDuplicateKey("1062")
.addDataIntegrity("630","839","840","893","1169","1215","1216","1217","1364","1451","1452","1557","23000")
.addDuplicateKey("1062", "1169")
.addDataIntegrity("630", "839", "840", "893", "1215", "1216", "1217", "1364", "1451", "1452", "1557")
.build();
this.openQuote = "`";
this.closeQuote = "`";
// use pipe for escaping as it depends if mysql runs in no_backslash_escapes or not.
this.likeClauseRaw = "like binary ? escape ''";
this.likeClauseEscaped = "like binary ? escape '|'";
this.forwardOnlyHintOnFindIterate = true;
this.booleanDbType = Types.BIT;
@@ -78,4 +79,9 @@ public class MySqlPlatform extends DatabasePlatform {
// NOWAIT and SKIP LOCKED currently not supported with MySQL
return sql + " for update";
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
}
}
@@ -1,8 +1,8 @@
package io.ebean.config.dbplatform.oracle;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.BasicSqlAnsiLimiter;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
@@ -10,13 +10,13 @@ import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.RownumSqlLimiter;
import io.ebean.dbmigration.ddlgeneration.platform.Oracle10Ddl;
import io.ebean.config.dbplatform.SqlErrorCodes;
import javax.sql.DataSource;
import java.sql.Types;
/**
* Oracle10 and greater specific platform.
* Oracle specific platform.
*/
public class OraclePlatform extends DatabasePlatform {
@@ -28,16 +28,22 @@ public class OraclePlatform extends DatabasePlatform {
this.dbEncrypt = new OracleDbEncrypt();
this.sqlLimiter = new RownumSqlLimiter();
this.basicSqlLimiter = new BasicSqlAnsiLimiter();
this.platformDdl = new Oracle10Ddl(this);
this.historySupport = new OracleDbHistorySupport();
// Not using getGeneratedKeys as instead we will
// batch load sequences which enables JDBC batch execution
dbIdentity.setSupportsGetGeneratedKeys(false);
dbIdentity.setIdType(IdType.SEQUENCE);
dbIdentity.setSupportsSequence(true);
dbIdentity.setSupportsIdentity(true);
dbIdentity.setSupportsGetGeneratedKeys(true);
this.treatEmptyStringsAsNull = true;
this.likeClauseRaw = "like ?";
this.exceptionTranslator =
new SqlErrorCodes()
//.addAcquireLock("")
.addDuplicateKey("1")
.addDataIntegrity("2291")
.build();
this.openQuote = "\"";
this.closeQuote = "\"";
@@ -80,4 +86,9 @@ public class OraclePlatform extends DatabasePlatform {
return sql + " for update";
}
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
}
}
@@ -1,17 +1,14 @@
package io.ebean.config.dbplatform.postgres;
import io.ebean.BackgroundExecutor;
import io.ebean.Platform;
import io.ebean.Query;
import io.ebean.config.ServerConfig;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.DdlHandler;
import io.ebean.dbmigration.ddlgeneration.platform.PostgresDdl;
import javax.sql.DataSource;
import java.sql.Types;
@@ -36,7 +33,6 @@ public class PostgresPlatform extends DatabasePlatform {
this.dbEncrypt = new PostgresDbEncrypt();
this.historySupport = new PostgresHistorySupport();
this.platformDdl = new PostgresDdl(this);
// Use Identity and getGeneratedKeys
this.dbIdentity.setIdType(IdType.IDENTITY);
@@ -90,14 +86,6 @@ public class PostgresPlatform extends DatabasePlatform {
return new DbPlatformType("geometry(" + type + "," + srid + ")");
}
/**
* Return a DdlHandler instance for generating DDL for the specific platform.
*/
@Override
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
return this.platformDdl.createDdlHandler(serverConfig);
}
/**
* Create a Postgres specific sequence IdGenerator.
*/
@@ -1,6 +1,6 @@
package io.ebean.config.dbplatform.sqlanywhere;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
@@ -1,11 +1,10 @@
package io.ebean.config.dbplatform.sqlite;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.dbmigration.ddlgeneration.platform.SQLiteDdl;
import java.sql.Types;
@@ -14,20 +13,23 @@ public class SQLitePlatform extends DatabasePlatform {
public SQLitePlatform() {
super();
this.platform = Platform.SQLITE;
this.platformDdl = new SQLiteDdl(this);
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(false);
this.dbIdentity.setSupportsSequence(false);
this.dbIdentity.setSelectLastInsertedIdTemplate("select last_insert_rowid()");
this.booleanDbType = Types.INTEGER;
this.likeClauseRaw = "like ?";
this.likeClauseEscaped = "like ?";
dbTypeMap.put(DbType.BIT, new DbPlatformType("int default 0"));
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("int default 0"));
dbTypeMap.put(DbType.BIGINT, new DbPlatformType("integer"));
dbTypeMap.put(DbType.SMALLINT, new DbPlatformType("integer"));
}
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append(ch);
}
}
@@ -1,13 +1,13 @@
package io.ebean.config.dbplatform.sqlserver;
import io.ebean.PersistBatch;
import io.ebean.Platform;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.SqlErrorCodes;
import io.ebean.dbmigration.ddlgeneration.platform.SqlServerDdl;
import java.sql.Types;
/**
@@ -25,7 +25,6 @@ public class SqlServerPlatform extends DatabasePlatform {
this.selectCountWithAlias = true;
this.sqlLimiter = new SqlServerSqlLimiter();
this.basicSqlLimiter = new SqlServerBasicSqlLimiter();
this.platformDdl = new SqlServerDdl(this);
this.historySupport = new SqlServerHistorySupport();
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
@@ -40,8 +39,9 @@ public class SqlServerPlatform extends DatabasePlatform {
this.openQuote = "[";
this.closeQuote = "]";
this.specialLikeCharacters = new char[]{'%', '_', '['};
this.likeClause = "like ? COLLATE Latin1_General_BIN";
this.likeSpecialCharacters = new char[]{'%', '_', '['};
this.likeClauseRaw = "like ? collate Latin1_General_BIN";
this.likeClauseEscaped = "like ? collate Latin1_General_BIN";
booleanDbType = Types.INTEGER;
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("bit default 0"));
@@ -1,103 +1,52 @@
package io.ebean.dbmigration;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.config.DbConstraintNaming;
import io.ebean.config.DbMigrationConfig;
import io.ebean.Platform;
import io.ebean.annotation.Platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
import io.ebean.dbmigration.ddlgeneration.DdlWrite;
import io.ebean.dbmigration.migration.Migration;
import io.ebean.dbmigration.migrationreader.MigrationXmlWriter;
import io.ebean.dbmigration.model.CurrentModel;
import io.ebean.dbmigration.model.MConfiguration;
import io.ebean.dbmigration.model.MigrationModel;
import io.ebean.dbmigration.model.MigrationVersion;
import io.ebean.dbmigration.model.ModelContainer;
import io.ebean.dbmigration.model.ModelDiff;
import io.ebean.dbmigration.model.PlatformDdlWriter;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.extraddl.model.DdlScript;
import io.ebeaninternal.extraddl.model.ExtraDdl;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
* Generates DB Migration xml and sql scripts.
* Generates DDL migration scripts based on changes to the current model.
*
* <p>
* Reads the prior migrations and compares with the current model of the EbeanServer
* and generates a migration 'diff' in the form of xml document with the logical schema
* changes and a series of sql scripts to apply, rollback the applied changes if necessary
* and drop objects (drop tables, drop columns).
* </p>
* <p>
* This does not run the migration or ddl scripts but just generates them.
* Typically this is run as a main method in src/test once a developer is happy
* with the next set of changes to the model.
* </p>
*
* <h3>Example: Run for a single specific platform</h3>
*
* <pre>{@code
*
* DbMigration migration = new DbMigration();
* migration.setPathToResources("src/main/resources");
* migration.setPlatform(DbPlatformName.ORACLE);
* // optionally specify the version and name
* //System.setProperty("ddl.migration.version", "1.1");
* //System.setProperty("ddl.migration.name", "add bars");
*
* migration.generateMigration();
* // generate a migration using drops from a prior version
* //System.setProperty("ddl.migration.pendingDropsFor", "1.2");
*
* DbMigration migration = DbMigration.create();
*
* migration.setPlatform(Platform.POSTGRES);
* migration.generateMigration();
*
* }</pre>
*/
public class DbMigration {
protected static final Logger logger = LoggerFactory.getLogger(DbMigration.class);
private static final String initialVersion = "1.0";
private static final String GENERATED_COMMENT = "THIS IS A GENERATED FILE - DO NOT MODIFY";
public interface DbMigration {
/**
* Set to true if DbMigration run with online EbeanServer instance.
* Create a DbMigration implementation to use.
*/
protected final boolean online;
static DbMigration create() {
protected SpiEbeanServer server;
protected DbMigrationConfig migrationConfig;
protected String pathToResources = "src/main/resources";
protected DatabasePlatform databasePlatform;
protected List<Pair> platforms = new ArrayList<>();
protected ServerConfig serverConfig;
protected DbConstraintNaming constraintNaming;
/**
* Create for offline migration generation.
*/
public DbMigration() {
this.online = false;
}
/**
* Create using online EbeanServer.
*/
public DbMigration(EbeanServer server) {
this.online = true;
setServer(server);
Iterator<DbMigration> loader = ServiceLoader.load(DbMigration.class).iterator();
if (loader.hasNext()) {
return loader.next();
}
throw new IllegalStateException("No service implementation found for DbMigration?");
}
/**
@@ -105,33 +54,18 @@ public class DbMigration {
* <p>
* This defaults to maven style 'src/main/resources'.
*/
public void setPathToResources(String pathToResources) {
this.pathToResources = pathToResources;
}
void setPathToResources(String pathToResources);
/**
* Set the server to use to determine the current model.
* Typically this is not called explicitly.
*/
public void setServer(EbeanServer ebeanServer) {
this.server = (SpiEbeanServer) ebeanServer;
setServerConfig(server.getServerConfig());
}
void setServer(EbeanServer ebeanServer);
/**
* Set the serverConfig to use. Typically this is not called explicitly.
*/
public void setServerConfig(ServerConfig config) {
if (this.serverConfig == null) {
this.serverConfig = config;
}
if (migrationConfig == null) {
this.migrationConfig = serverConfig.getMigrationConfig();
}
if (constraintNaming == null) {
this.constraintNaming = serverConfig.getConstraintNaming();
}
}
void setServerConfig(ServerConfig config);
/**
* Set the specific platform to generate DDL for.
@@ -139,9 +73,7 @@ public class DbMigration {
* If not set this defaults to the platform of the default server.
* </p>
*/
public void setPlatform(Platform platform) {
setPlatform(getPlatform(platform));
}
void setPlatform(Platform platform);
/**
* Set the specific platform to generate DDL for.
@@ -149,12 +81,17 @@ public class DbMigration {
* If not set this defaults to the platform of the default server.
* </p>
*/
public void setPlatform(DatabasePlatform databasePlatform) {
this.databasePlatform = databasePlatform;
if (!online) {
DbOffline.setPlatform(databasePlatform.getPlatform());
}
}
void setPlatform(DatabasePlatform databasePlatform);
/**
* Set to false to turn off strict mode.
* <p>
* Strict mode checks that a column changed to non-null on an existing table via DB migration has a default
* value specified. Set this to false if that isn't the case but it is known that all the existing rows have
* a value specified (there are no existing null values for the column).
* </p>
*/
void setStrictMode(boolean strictMode);
/**
* Add an additional platform to write the migration DDL.
@@ -163,9 +100,7 @@ public class DbMigration {
* from the migration (e.g. generate migration sql for MySql, Postgres and Oracle).
* </p>
*/
public void addPlatform(Platform platform, String prefix) {
platforms.add(new Pair(getPlatform(platform), prefix));
}
void addPlatform(Platform platform, String prefix);
/**
* Generate the next migration xml file and associated apply and rollback sql scripts.
@@ -175,384 +110,29 @@ public class DbMigration {
* <h3>Example: Run for a single specific platform</h3>
* <pre>{@code
*
* DbMigration migration = new DbMigration();
* migration.setPathToResources("src/main/resources");
* migration.setPlatform(DbPlatformName.ORACLE);
* DbMigration migration = DbMigration.create();
* migration.setPlatform(Platform.POSTGRES);
*
* migration.generateMigration();
* migration.generateMigration();
*
* }</pre>
* <p>
* <h3>Example: Run migration generating DDL for multiple platforms</h3>
* <pre>{@code
*
* DbMigration migration = new DbMigration();
* migration.setPathToResources("src/main/resources");
* DbMigration migration = DbMigration.create();
*
* migration.addPlatform(DbPlatformName.POSTGRES, "pg");
* migration.addPlatform(DbPlatformName.MYSQL, "mysql");
* migration.addPlatform(DbPlatformName.ORACLE, "mysql");
* migration.setPathToResources("src/main/resources");
*
* migration.generateMigration();
* migration.addPlatform(Platform.POSTGRES, "pg");
* migration.addPlatform(Platform.MYSQL, "mysql");
* migration.addPlatform(Platform.ORACLE, "oracle");
*
* migration.generateMigration();
*
* }</pre>
*
* @return the version of the generated migration or null
*/
public void generateMigration() throws IOException {
// use this flag to stop other plugins like full DDL generation
if (!online) {
DbOffline.setGenerateMigration();
if (databasePlatform == null && !platforms.isEmpty()) {
// for multiple platform generation set the general platform
// to H2 so that it runs offline without DB connection
setPlatform(platforms.get(0).platform);
}
}
setDefaults();
try {
Request request = createRequest();
if (platforms.isEmpty()) {
generateExtraDdl(request.migrationDir, databasePlatform);
}
String pendingVersion = generatePendingDrop();
if (pendingVersion != null) {
generatePendingDrop(request, pendingVersion);
} else {
generateDiff(request);
}
} finally {
if (!online) {
DbOffline.reset();
}
}
}
/**
* Generate "repeatable" migration scripts.
* <p>
* These take scrips from extra-dll.xml (typically views) and outputs "repeatable"
* migration scripts (starting with "R__") to be run by FlywayDb or Ebean's own
* migration runner.
* </p>
*/
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform) throws IOException {
if (dbPlatform != null) {
ExtraDdl extraDdl = ExtraDdlXmlReader.read("/extra-ddl.xml");
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
writeExtraDdl(migrationDir, script);
}
}
}
}
}
/**
* Write (or override) the "repeatable" migration script.
*/
private void writeExtraDdl(File migrationDir, DdlScript script) throws IOException {
String fullName = repeatableMigrationName(script.getName());
logger.info("writing repeatable script {}", fullName);
File file = new File(migrationDir, fullName);
try (FileWriter writer = new FileWriter(file)) {
writer.write(script.getValue());
writer.flush();
}
}
private String repeatableMigrationName(String scriptName) {
return "R__" + scriptName.replace(' ', '_') + migrationConfig.getApplySuffix();
}
/**
* Generate the diff migration.
*/
private void generateDiff(Request request) throws IOException {
List<String> pendingDrops = request.getPendingDrops();
if (!pendingDrops.isEmpty()) {
logger.info("Pending un-applied drops in versions {}", pendingDrops);
}
Migration migration = request.createDiffMigration();
if (migration == null) {
logger.info("no changes detected - no migration written");
} else {
// there were actually changes to write
generateMigration(request, migration, null);
}
}
/**
* Generate the migration based on the pendingDrops from a prior version.
*/
private void generatePendingDrop(Request request, String pendingVersion) throws IOException {
Migration migration = request.migrationForPendingDrop(pendingVersion);
generateMigration(request, migration, pendingVersion);
List<String> pendingDrops = request.getPendingDrops();
if (!pendingDrops.isEmpty()) {
logger.info("... remaining pending un-applied drops in versions {}", pendingDrops);
}
}
private Request createRequest() {
return new Request();
}
private class Request {
final File migrationDir;
final File modelDir;
final MigrationModel migrationModel;
final CurrentModel currentModel;
final ModelContainer migrated;
final ModelContainer current;
private Request() {
this.migrationDir = getMigrationDirectory();
this.modelDir = getModelDirectory(migrationDir);
this.migrationModel = new MigrationModel(modelDir, migrationConfig.getModelSuffix());
this.migrated = migrationModel.read();
this.currentModel = new CurrentModel(server, constraintNaming);
this.current = currentModel.read();
}
/**
* Return the migration for the pending drops for a given version.
*/
public Migration migrationForPendingDrop(String pendingVersion) {
Migration migration = migrated.migrationForPendingDrop(pendingVersion);
// register any remaining pending drops
migrated.registerPendingHistoryDropColumns(current);
return migration;
}
/**
* Return the list of versions that have pending un-applied drops.
*/
public List<String> getPendingDrops() {
return migrated.getPendingDrops();
}
/**
* Create and return the diff of the current model to the migration model.
*/
public Migration createDiffMigration() {
ModelDiff diff = new ModelDiff(migrated);
diff.compareTo(current);
return diff.isEmpty() ? null : diff.getMigration();
}
}
private void generateMigration(Request request, Migration dbMigration, String dropsFor) throws IOException {
String fullVersion = getFullVersion(request.migrationModel, dropsFor);
logger.info("generating migration:{}", fullVersion);
if (!writeMigrationXml(dbMigration, request.modelDir, fullVersion)) {
logger.warn("migration already exists, not generating DDL");
} else {
if (!platforms.isEmpty()) {
writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir);
} else if (databasePlatform != null) {
// writer needs the current model to provide table/column details for
// history ddl generation (triggers, history tables etc)
DdlWrite write = new DdlWrite(new MConfiguration(), request.current);
PlatformDdlWriter writer = createDdlWriter(databasePlatform);
writer.processMigration(dbMigration, write, request.migrationDir, fullVersion);
}
}
}
/**
* Return true if the next pending drop changeSet should be generated as the next migration.
*/
private String generatePendingDrop() {
String nextDrop = System.getProperty("ddl.migration.pendingDropsFor");
if (nextDrop != null) {
return nextDrop;
}
return migrationConfig.getGeneratePendingDrop();
}
/**
* Return the full version for the migration being generated.
* <p>
* The full version can contain a comment suffix after a "__" double underscore.
*/
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
String version = migrationConfig.getVersion();
if (version == null) {
version = migrationModel.getNextVersion(initialVersion);
}
String fullVersion = migrationConfig.getApplyPrefix() + version;
if (migrationConfig.getName() != null) {
fullVersion += "__" + toUnderScore(migrationConfig.getName());
} else if (dropsFor != null) {
fullVersion += "__" + toUnderScore("dropsFor_" + MigrationVersion.trim(dropsFor));
} else if (version.equals(initialVersion)) {
fullVersion += "__initial";
}
return fullVersion;
}
/**
* Replace spaces with underscores.
*/
private String toUnderScore(String name) {
return name.replace(' ', '_');
}
/**
* Write any extra platform ddl.
*/
protected void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
for (Pair pair : platforms) {
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read());
PlatformDdlWriter platformWriter = createDdlWriter(pair.platform);
File subPath = platformWriter.subPath(writePath, pair.prefix);
platformWriter.processMigration(dbMigration, platformBuffer, subPath, fullVersion);
generateExtraDdl(subPath, pair.platform);
}
}
private PlatformDdlWriter createDdlWriter(DatabasePlatform platform) {
return new PlatformDdlWriter(platform, serverConfig, migrationConfig);
}
/**
* Write the migration xml.
*/
protected boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) {
String modelFile = fullVersion + migrationConfig.getModelSuffix();
File file = new File(resourcePath, modelFile);
if (file.exists()) {
return false;
}
String comment = migrationConfig.isIncludeGeneratedFileComment() ? GENERATED_COMMENT : null;
MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment);
xmlWriter.write(dbMigration, file);
return true;
}
/**
* Set default server and platform if necessary.
*/
protected void setDefaults() {
if (server == null) {
setServer(Ebean.getDefaultServer());
}
if (databasePlatform == null && platforms.isEmpty()) {
// not explicitly set not set a list of platforms so
// default to the platform of the default server
databasePlatform = server.getDatabasePlatform();
logger.debug("set platform to {}", databasePlatform.getName());
}
}
/**
* Return the file path to write the xml and sql to.
*/
protected File getMigrationDirectory() {
// path to src/main/resources in typical maven project
File resourceRootDir = new File(pathToResources);
String resourcePath = migrationConfig.getMigrationPath();
// expect to be a path to something like - src/main/resources/dbmigration/model
File path = new File(resourceRootDir, resourcePath);
if (!path.exists()) {
if (!path.mkdirs()) {
logger.debug("Unable to ensure migration directory exists at {}", path.getAbsolutePath());
}
}
return path;
}
/**
* Return the model directory (relative to the migration directory).
*/
protected File getModelDirectory(File migrationDirectory) {
String modelPath = migrationConfig.getModelPath();
if (modelPath == null || modelPath.isEmpty()) {
return migrationDirectory;
}
File modelDir = new File(migrationDirectory, migrationConfig.getModelPath());
if (!modelDir.exists() && !modelDir.mkdirs()) {
logger.debug("Unable to ensure migration model directory exists at {}", modelDir.getAbsolutePath());
}
return modelDir;
}
/**
* Return the DatabasePlatform given the platform key.
*/
protected DatabasePlatform getPlatform(Platform platform) {
switch (platform) {
case H2:
return new H2Platform();
case POSTGRES:
return new PostgresPlatform();
case MYSQL:
return new MySqlPlatform();
case ORACLE:
return new OraclePlatform();
case SQLSERVER:
return new SqlServerPlatform();
case DB2:
return new DB2Platform();
case SQLITE:
return new SQLitePlatform();
default:
throw new IllegalArgumentException("Platform missing? " + platform);
}
}
/**
* Holds a platform and prefix. Used to generate multiple platform specific DDL
* for a single migration.
*/
public static class Pair {
/**
* The platform to generate the DDL for.
*/
public final DatabasePlatform platform;
/**
* A prefix included into the file/resource names indicating the platform.
*/
public final String prefix;
public Pair(DatabasePlatform platform, String prefix) {
this.platform = platform;
this.prefix = prefix;
}
}
String generateMigration() throws IOException;
}
@@ -1,8 +1,4 @@
/**
* 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>
* Generates DDL migration scripts based on changes to the model.
*/
package io.ebean.dbmigration;
@@ -24,4 +24,13 @@ public interface BeanQueryRequest<T> {
*/
Query<T> getQuery();
/**
* Return true if multi-value binding using Array or Table Values is supported.
*/
boolean isMultiValueIdSupported();
/**
* Return true if multi-value binding is supported for this value type.
*/
boolean isMultiValueSupported(Class<?> valueType);
}
@@ -1,9 +1,5 @@
package io.ebean.event.changelog;
import io.ebean.ValuePair;
import java.util.Map;
/**
* A bean insert, update or delete change sent as part of a ChangeSet.
*/
@@ -12,68 +8,74 @@ public class BeanChange {
/**
* The underling base table name.
*/
String table;
private String type;
/**
* The tenantId value.
*/
Object tenantId;
private Object tenantId;
/**
* The id value.
*/
Object id;
private Object id;
/**
* The INSERT, UPDATE or DELETE change type.
*/
ChangeType type;
private ChangeType event;
/**
* The time the bean change was created.
*/
long eventTime;
private long eventTime;
/**
* The values for insert or update. Note that null values are not included for insert.
* The change in JSON form.
*/
Map<String, ValuePair> values;
private String data;
/**
* Construct with all the details.
* The change in JSON form.
*/
public BeanChange(String table, Object tenantId, Object id, ChangeType type, Map<String, ValuePair> values) {
this.table = table;
this.tenantId = tenantId;
this.id = id;
this.type = type;
this.eventTime = System.currentTimeMillis();
this.values = values;
}
private String oldData;
/**
* Default constructor for JSON tools.
* Constructor for JSON tools.
*/
public BeanChange() {
}
/**
* Construct with change as JSON.
*/
public BeanChange(String type, Object tenantId, Object id, ChangeType event, String data, String oldData) {
this.type = type;
this.tenantId = tenantId;
this.id = id;
this.event = event;
this.eventTime = System.currentTimeMillis();
this.data = data;
this.oldData = oldData;
}
/**
* Construct with change as JSON.
*/
public BeanChange(String table, Object tenantId, Object id, ChangeType event, String data) {
this(table, tenantId , id , event , data , null);
}
@Override
public String toString() {
return "table:" + table + " tenantId: " + tenantId + " id:" + id + " values:" + values;
return "type:" + type + " tenantId: " + tenantId + " id:" + id + " data:" + data;
}
/**
* Return the object type (typically table name).
*/
public String getTable() {
return table;
}
/**
* Set the object type (for JSON tools).
*/
public void setTable(String table) {
this.table = table;
public String getType() {
return type;
}
/**
@@ -83,13 +85,6 @@ public class BeanChange {
return tenantId;
}
/**
* Set the bean id (for JSON tools).
*/
public void setTenantId(Object tenantId) {
this.tenantId = tenantId;
}
/**
* Return the object id.
*/
@@ -97,25 +92,11 @@ public class BeanChange {
return id;
}
/**
* Set the bean id (for JSON tools).
*/
public void setId(Object id) {
this.id = id;
}
/**
* Return the change type (INSERT, UPDATE or DELETE).
*/
public ChangeType getType() {
return type;
}
/**
* Set the type (for JSON tools).
*/
public void setType(ChangeType type) {
this.type = type;
public ChangeType getEvent() {
return event;
}
/**
@@ -126,23 +107,16 @@ public class BeanChange {
}
/**
* Set the event time in epoch millis.
* Return the change data in JSON form.
*/
public void setEventTime(long eventTime) {
this.eventTime = eventTime;
public String getData() {
return data;
}
/**
* Return the value pairs. For inserts the ValuePair oldValue is always null.
* Return the old data in JSON form.
*/
public Map<String, ValuePair> getValues() {
return values;
}
/**
* Set the value pairs (for JSON tools).
*/
public void setValues(Map<String, ValuePair> values) {
this.values = values;
public String getOldData() {
return oldData;
}
}
@@ -1,17 +0,0 @@
package io.ebean.meta;
import java.util.List;
public interface MetaBeanInfo {
/**
* Collect the current query plan statistics return the non-empty statistics.
*/
List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
/**
* Collect the current query plan statistics return all the statistics (include query plans that haven't had query executions).
*/
List<MetaQueryPlanStatistic> collectAllQueryPlanStatistics(boolean reset);
}
@@ -8,17 +8,12 @@ import java.util.List;
public interface MetaInfoManager {
/**
* Return the MetaBeanInfo for a bean type.
* Collect and return the transaction execution metrics.
*/
MetaBeanInfo getMetaBeanInfo(Class<?> beanClass);
List<MetaTimedMetric> collectTransactionStatistics(boolean reset);
/**
* Return all the MetaBeanInfo.
*/
List<MetaBeanInfo> getMetaBeanInfoList();
/**
* Collect and return the query plan statistics for all the beans.
* Collect and return the non-empty query plan statistics for all the beans.
* <p>
* Note that this excludes the query plan statistics where there has been no
* executions (since the last collection with reset).
@@ -1,5 +1,7 @@
package io.ebean.meta;
import io.ebean.ProfileLocation;
import java.util.List;
/**
@@ -14,6 +16,11 @@ public interface MetaQueryPlanStatistic {
*/
Class<?> getBeanType();
/**
* Return the profile location.
*/
ProfileLocation getProfileLocation();
/**
* Return true if this query plan was tuned by AutoTune.
*/
@@ -0,0 +1,43 @@
package io.ebean.meta;
/**
* Timed execution statistics.
*/
public interface MetaTimedMetric {
/**
* Return the metric name.
*/
String getName();
/**
* Return the metric location if defined.
*/
String getLocation();
/**
* Return the time the counters started from.
*/
long getStartTime();
/**
* Return the total count.
*/
long getCount();
/**
* Return the total execution time.
*/
long getTotal();
/**
* Return the max execution time.
*/
long getMax();
/**
* Return the mean execution time.
*/
long getMean();
}
+1 -1
View File
@@ -35,7 +35,7 @@ Customer customer = Ebean.find(Customer.class)
.fetch("billingAddress");
.fetch("shippingAddress");
.setId(7)
.findUnique();
.findOne();
Address billAddr = customer.getBillingAddress();
+1 -1
View File
@@ -28,7 +28,7 @@ Customer customer =
.setId(7)
.fetch("billingAddress")
.fetch("shippingAddress")
.findUnique();
.findOne();
Address billAddr = customer.getBillingAddress();
Address shipAddr = customer.getShippingAddress();
+7 -2
View File
@@ -1,11 +1,11 @@
package io.ebean.plugin;
import io.ebean.Query;
import io.ebean.config.dbplatform.IdType;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanPersistController;
import io.ebean.event.BeanPersistListener;
import io.ebean.event.BeanQueryAdapter;
import io.ebeaninternal.api.SpiQuery;
import io.ebeanservice.docstore.api.mapping.DocumentMapping;
import java.util.Collection;
@@ -20,6 +20,11 @@ public interface BeanType<T> {
*/
String getName();
/**
* Return the profileId of the bean type.
*/
short getProfileId();
/**
* Return the full name of the bean type.
*/
@@ -180,7 +185,7 @@ public interface BeanType<T> {
/**
* Add the discriminator value to the query if needed.
*/
void addInheritanceWhere(SpiQuery<?> query);
void addInheritanceWhere(Query<?> query);
/**
* Return the root bean type for an inheritance hierarchy.
@@ -47,4 +47,9 @@ public interface SpiServer extends EbeanServer {
*/
DataSource getDataSource();
/**
* Return the associated read only DataSource for this EbeanServer instance (can be null).
*/
DataSource getReadOnlyDataSource();
}
@@ -1,4 +1,4 @@
package io.ebean.common;
package io.ebean.service;
import io.ebean.EbeanServer;
import io.ebean.config.ServerConfig;
@@ -0,0 +1,14 @@
package io.ebean.service;
import io.ebean.config.ContainerConfig;
/**
* Provides shutdown of the entire container.
*/
public interface SpiContainerFactory {
/**
* Create the Container that builds EbeanServer instances.
*/
SpiContainer create(ContainerConfig containerConfig);
}
@@ -0,0 +1,125 @@
package io.ebean.service;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* JSON service that Ebean is expected to provide.
*
* Supports converting between JSON content and simple java Maps/Lists.
*/
public interface SpiJsonService {
/**
* Write the nested Map/List as json.
*/
String write(Object object) throws IOException;
/**
* Write the nested Map/List as json to the writer.
*/
void write(Object object, Writer writer) throws IOException;
/**
* Write the nested Map/List as json to the jsonGenerator.
*/
void write(Object object, JsonGenerator jsonGenerator) throws IOException;
/**
* Write the collection as json array to the jsonGenerator.
*/
void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException;
/**
* Parse the json and return as a Map additionally specifying if the returned map should
* be modify aware meaning that it can detect when it has been modified.
*/
Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException;
/**
* Parse the json and return as a Map.
*/
Map<String, Object> parseObject(String json) throws IOException;
/**
* Parse the json and return as a Map taking a reader.
*/
Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException;
/**
* Parse the json and return as a Map taking a reader.
*/
Map<String, Object> parseObject(Reader reader) throws IOException;
/**
* Parse the json and return as a Map taking a JsonParser.
*/
Map<String, Object> parseObject(JsonParser parser) throws IOException;
/**
* Parse the json and return as a Map taking a JsonParser and a starting token.
* <p>
* Used when the first token is checked to see if the value is null prior to calling this.
* </p>
*/
Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException;
/**
* Parse the json and return as a modify aware List.
*/
<T> List<T> parseList(String json, boolean modifyAware) throws IOException;
/**
* Parse the json and return as a List.
*/
List<Object> parseList(String json) throws IOException;
/**
* Parse the json and return as a List taking a Reader.
*/
List<Object> parseList(Reader reader) throws IOException;
/**
* Parse the json and return as a List taking a JsonParser.
*/
List<Object> parseList(JsonParser parser) throws IOException;
/**
* Parse the json returning as a List taking into account the current token.
*/
<T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException;
/**
* Parse the json and return as a List or Map.
*/
Object parse(String json) throws IOException;
/**
* Parse the json and return as a List or Map.
*/
Object parse(Reader reader) throws IOException;
/**
* Parse the json and return as a List or Map.
*/
Object parse(JsonParser parser) throws IOException;
/**
* Parse the json returning a Set that might be modify aware.
*/
<T> Set<T> parseSet(String json, boolean modifyAware) throws IOException;
/**
* Parse the json returning as a Set taking into account the current token.
*/
<T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException;
}
@@ -0,0 +1,24 @@
package io.ebean.service;
import io.ebean.ProfileLocation;
/**
* Factory for creating profile locations.
*/
public interface SpiProfileLocationFactory {
/**
* Create a profile location.
*/
ProfileLocation create();
/**
* Create a profile location with a line number.
*/
ProfileLocation create(int lineNumber, String label);
/**
* Create a known location.
*/
ProfileLocation createAt(String location);
}
@@ -0,0 +1,27 @@
package io.ebean.service;
import io.ebean.RawSql;
import io.ebean.RawSqlBuilder;
import java.sql.ResultSet;
/**
* Service provided by Ebean for parsing and column mapping raw SQL queries.
*/
public interface SpiRawSqlService {
/**
* Create based on a JDBC ResultSet.
*/
RawSql resultSet(ResultSet resultSet, String... propertyNames);
/**
* Parse the SQL determining column mapping.
*/
RawSqlBuilder parsed(String sql);
/**
* Unparsed SQL so explicit column mapping expected.
*/
RawSqlBuilder unparsed(String sql);
}
@@ -2,7 +2,7 @@ package io.ebean.text;
import io.ebean.FetchPath;
import io.ebean.Query;
import io.ebeaninternal.server.query.SplitName;
import io.ebean.util.SplitName;
import java.util.Collection;
import java.util.Iterator;
+38 -35
View File
@@ -1,17 +1,18 @@
package io.ebean.text.json;
import io.ebeaninternal.server.type.ModifyAwareList;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.service.SpiJsonService;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
/**
@@ -19,32 +20,43 @@ import java.util.Set;
*/
public class EJson {
private static SpiJsonService plugin = init();
private static SpiJsonService init() {
Iterator<SpiJsonService> loader = ServiceLoader.load(SpiJsonService.class).iterator();
if (loader.hasNext()) {
return loader.next();
}
throw new IllegalStateException("No service implementation found for SpiJsonService?");
}
/**
* Write the nested Map/List as json.
*/
public static String write(Object object) throws IOException {
return EJsonWriter.write(object);
return plugin.write(object);
}
/**
* Write the nested Map/List as json to the writer.
*/
public static void write(Object object, Writer writer) throws IOException {
EJsonWriter.write(object, writer);
plugin.write(object, writer);
}
/**
* Write the nested Map/List as json to the jsonGenerator.
*/
public static void write(Object object, JsonGenerator jsonGenerator) throws IOException {
EJsonWriter.write(object, jsonGenerator);
plugin.write(object, jsonGenerator);
}
/**
* Write the collection as json array to the jsonGenerator.
*/
public static void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
EJsonWriter.writeCollection(collection, jsonGenerator);
plugin.writeCollection(collection, jsonGenerator);
}
/**
@@ -52,35 +64,35 @@ public class EJson {
* be modify aware meaning that it can detect when it has been modified.
*/
public static Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException {
return EJsonReader.parseObject(json, modifyAware);
return plugin.parseObject(json, modifyAware);
}
/**
* Parse the json and return as a Map.
*/
public static Map<String, Object> parseObject(String json) throws IOException {
return EJsonReader.parseObject(json);
return plugin.parseObject(json);
}
/**
* Parse the json and return as a Map taking a reader.
*/
public static Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
return EJsonReader.parseObject(reader, modifyAware);
return plugin.parseObject(reader, modifyAware);
}
/**
* Parse the json and return as a Map taking a reader.
*/
public static Map<String, Object> parseObject(Reader reader) throws IOException {
return EJsonReader.parseObject(reader);
return plugin.parseObject(reader);
}
/**
* Parse the json and return as a Map taking a JsonParser.
*/
public static Map<String, Object> parseObject(JsonParser parser) throws IOException {
return EJsonReader.parseObject(parser);
return plugin.parseObject(parser);
}
/**
@@ -90,86 +102,77 @@ public class EJson {
* </p>
*/
public static Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
return EJsonReader.parseObject(parser, token);
return plugin.parseObject(parser, token);
}
/**
* Parse the json and return as a modify aware List.
*/
public static List<Object> parseList(String json, boolean modifyAware) throws IOException {
return EJsonReader.parseList(json, modifyAware);
public static <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
return plugin.parseList(json, modifyAware);
}
/**
* Parse the json and return as a List.
*/
public static List<Object> parseList(String json) throws IOException {
return EJsonReader.parseList(json);
return plugin.parseList(json);
}
/**
* Parse the json and return as a List taking a Reader.
*/
public static List<Object> parseList(Reader reader) throws IOException {
return EJsonReader.parseList(reader);
return plugin.parseList(reader);
}
/**
* Parse the json and return as a List taking a JsonParser.
*/
public static List<Object> parseList(JsonParser parser) throws IOException {
return EJsonReader.parseList(parser, false);
return plugin.parseList(parser);
}
/**
* Parse the json returning as a List taking into account the current token.
*/
@SuppressWarnings("unchecked")
public static List<Object> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
return (List<Object>) EJsonReader.parse(parser, currentToken, false);
public static <T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
return plugin.parseList(parser, currentToken);
}
/**
* Parse the json and return as a List or Map.
*/
public static Object parse(String json) throws IOException {
return EJsonReader.parse(json);
return plugin.parse(json);
}
/**
* Parse the json and return as a List or Map.
*/
public static Object parse(Reader reader) throws IOException {
return EJsonReader.parse(reader);
return plugin.parse(reader);
}
/**
* Parse the json and return as a List or Map.
*/
public static Object parse(JsonParser parser) throws IOException {
return EJsonReader.parse(parser);
return plugin.parse(parser);
}
/**
* Parse the json returning a Set that might be modify aware.
*/
public static Set parseSet(String json, boolean modifyAware) throws IOException {
List<Object> list = parseList(json, modifyAware);
if (list == null) {
return null;
}
if (modifyAware) {
return ((ModifyAwareList) list).asSet();
} else {
return new LinkedHashSet<>(list);
}
public static <T> Set<T> parseSet(String json, boolean modifyAware) throws IOException {
return plugin.parseSet(json, modifyAware);
}
/**
* Parse the json returning as a Set taking into account the current token.
*/
public static Set<Object> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
return new LinkedHashSet<>(parseList(parser, currentToken));
public static <T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
return plugin.parseSet(parser, currentToken);
}
}
@@ -0,0 +1,288 @@
package io.ebean.util;
import io.ebean.annotation.Formula;
import io.ebean.annotation.Platform;
import io.ebean.annotation.Where;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Annotation utility methods to find annotations recursively - taken from the spring framework.
*/
public class AnnotationUtil {
/**
* Determine if the supplied {@link Annotation} is defined in the core JDK {@code java.lang.annotation} package.
*/
public static boolean isInJavaLangAnnotationPackage(Annotation annotation) {
return annotation.annotationType().getName().startsWith("java.lang.annotation");
}
/**
* Find a single {@link Annotation} of {@code annotationType} on the supplied {@link AnnotatedElement}.
* <p>
* Meta-annotations will be searched if the annotation is not <em>directly present</em> on the supplied element.
* <p>
* <strong>Warning</strong>: this method operates generically on annotated elements. In other words, this method
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
* It also does not filter out platform dependent annotations!
*/
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
if (annotationType == null) {
return null;
}
// check if directly present, if not, start search for meta-annotations.
Annotation[] anns = annotatedElement.getAnnotations();
if (anns.length == 0) {
return null; // no annotations present, so searching for meta annotations not required
}
// As we need the anns array anyway, we iterate over this instead
// of using annotatedElement.getAnnotation(...) which is synchronized internally
for (Annotation ann : anns) {
if (ann.annotationType() == annotationType) {
return (A) ann;
}
}
return findAnnotation(anns, annotationType, new HashSet<>());
}
/**
* Find a single {@link Annotation} of {@code annotationType} on the supplied class.
* <p>Meta-annotations will be searched if the annotation is not directly present on
* the supplied element.
* <p><strong>Note</strong>: this method searches for annotations at class & superClass(es)!
*/
public static <A extends Annotation> A findAnnotationRecursive(Class<?> clazz, Class<A> annotationType) {
if (annotationType == null) {
return null;
}
while (clazz != null && clazz != Object.class) {
// check if directly present, if not, start search for meta-annotations.
Annotation[] anns = clazz.getAnnotations();
if (anns.length != 0) {
for (Annotation ann : anns) {
if (ann.annotationType() == annotationType) {
return (A) ann;
}
}
A ann = findAnnotation(anns, annotationType, new HashSet<>());
if (ann != null) {
return ann;
}
}
// no meta-annotation present at this class - traverse to superclass
clazz = clazz.getSuperclass();
}
return null;
}
/**
* Finds the first annotation of a type for this platform. (if annotation is platform specific, otherwise first
* found annotation is returned)
*/
public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType, Platform platform) {
if (annotationType == null) {
return null;
}
Set<A> anns = findAnnotations(annotatedElement, annotationType);
return getPlatformMatchingAnnotation(anns, platform);
}
/**
* Finds all annotations recusively for a class and its superclasses.
*/
public static <A extends Annotation> Set<A> findAnnotationsRecursive(Class<?> clazz, Class<A> annotationType) {
if (annotationType == null) {
return null;
}
Set<A> ret = new LinkedHashSet<>();
Set<Annotation> visited = new HashSet<>();
while (clazz != null && clazz != Object.class) {
findMetaAnnotations(clazz, annotationType, ret, visited);
clazz = clazz.getSuperclass();
}
return ret;
}
/**
* Perform the search algorithm avoiding endless recursion by tracking which
* annotations have already been visited.
*/
@SuppressWarnings("unchecked")
private static <A extends Annotation> A findAnnotation(Annotation[] anns, Class<A> annotationType, Set<Annotation> visited) {
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
Annotation[] metaAnns = ann.annotationType().getAnnotations();
for (Annotation metaAnn : metaAnns) {
if (metaAnn.annotationType() == annotationType) {
return (A) metaAnn;
}
}
if (metaAnns.length > 0) {
A annotation = findAnnotation(metaAnns, annotationType, visited);
if (annotation != null) {
return annotation;
}
}
}
}
return null;
}
/**
* Find all {@link Annotation}s of {@code annotationType} on the supplied {@link AnnotatedElement}.
* <p>
* Meta-annotations will be searched if the annotation is not <em>directly present</em> on the supplied element.
* <p>
* <strong>Warning</strong>: this method operates generically on annotated elements. In other words, this method
* does not execute specialized search algorithms for classes or methods. It only traverses through Annotations!
*/
public static <A extends Annotation> Set<A> findAnnotations(AnnotatedElement annotatedElement, Class<A> annotationType) {
if (annotationType == null) {
return null;
}
Set<A> ret = new LinkedHashSet<>();
findMetaAnnotations(annotatedElement, annotationType, ret, new HashSet<>());
return ret;
}
/**
* Perform the search algorithm avoiding endless recursion by tracking which
* annotations have already been visited.
*/
@SuppressWarnings("unchecked")
private static <A extends Annotation> void findMetaAnnotations(AnnotatedElement annotatedElement, Class<A> annotationType, Set<A> ret, Set<Annotation> visited) {
Annotation[] anns = annotatedElement.getAnnotations();
for (Annotation ann : anns) {
if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
if (ann.annotationType() == annotationType) {
ret.add((A) ann);
} else {
Method repeatableValueMethod = getRepeatableValueMethod(ann, annotationType);
if (repeatableValueMethod != null) {
try {
A[] repeatedAnns = (A[]) repeatableValueMethod.invoke(ann);
for (Annotation repeatedAnn : repeatedAnns) {
ret.add((A) repeatedAnn);
findMetaAnnotations(repeatedAnn.annotationType(), annotationType, ret, visited);
}
} catch (Exception e) { // catch all exceptions (thrown by invoke)
throw new RuntimeException(e);
}
} else {
findMetaAnnotations(ann.annotationType(), annotationType, ret, visited);
}
}
}
}
}
// caches for getRepeatableValueMethod
private static Method getNullMethod() {
try {
return AnnotationUtil.class.getDeclaredMethod("getNullMethod");
} catch (NoSuchMethodException e) {
return null;
}
}
private static final ConcurrentMap<Annotation, Method> valueMethods = new ConcurrentHashMap<>();
// only a non-null-marker the valueMethods - Cache
private static final Method nullMethod = getNullMethod();
/**
* Returns the <code>value()</code> method for a possible containerAnnotation.
* Method is retuned only, if its signature is <code>array of containingType</code>.
*/
private static <A extends Annotation> Method getRepeatableValueMethod(
Annotation containerAnnotation, Class<A> containingType) {
Method method = valueMethods.get(containerAnnotation);
if (method == null) {
try {
method = containerAnnotation.annotationType().getMethod("value");
} catch (NoSuchMethodException e) {
method = nullMethod;
} catch (Exception e) {
throw new RuntimeException(e);
}
Method prev = valueMethods.putIfAbsent(containerAnnotation, method);
method = prev == null ? method : prev;
}
if (method != nullMethod) {
Class<?> retType = method.getReturnType();
if (retType.isArray() && retType.getComponentType() == containingType) {
return method;
}
}
return null;
}
/**
* Finds a suitable annotation from <code>Set<T> anns</code> for this platform.
* To distinguish between platforms, annotation type <code>T</code> must define
* a method withthis signature:
* <p>
* <code>Class<? extends DatabasePlatform>[] platforms() default {};</code>
* </p>
* The finding rules are:
* <ol>
* <li>Check if T has method "platforms" if not, return <code>ann[0]</code></code>
* <li>find the annotation that is defined for <code>databasePlatform</code></li>
* <li>otherwise return the annotation for default platform (platforms = {})</li>
* <li>return null
* </ol>
* (This mechanism is currently used by {@link Where} and {@link Formula})
*/
public static <T extends Annotation> T getPlatformMatchingAnnotation(Set<T> anns, Platform matchPlatform) {
if (anns.isEmpty()) {
return null;
}
Method getPlatformsMethod = null;
T fallback = null;
for (T ann : anns) {
try {
if (getPlatformsMethod == null) {
getPlatformsMethod = ann.getClass().getMethod("platforms");
}
if (!Platform[].class.isAssignableFrom(getPlatformsMethod.getReturnType())) {
return ann;
}
Platform[] platforms = (Platform[]) getPlatformsMethod.invoke(ann);
if (platforms.length == 0) {
fallback = ann;
} else {
for (Platform platform : platforms) {
if (matchPlatform == platform) {
return ann;
}
}
}
} catch (NoSuchMethodException e) {
return ann; // not platform specific - return first one
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return fallback;
}
}
@@ -1,47 +0,0 @@
package io.ebean.util;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
/**
* Helper to find classes taking into account the context class loader.
*/
public class ClassUtil {
/**
* Returns the raw type for the 2nd generic parameter for a subclass.
*/
public static Class<?> getSecondArgumentType(Class<?> subclass) {
Type[] typeArguments = getSuperclassTypeParameter(subclass);
if (typeArguments.length != 2) {
throw new IllegalArgumentException("Expected type with 2 generic argument types but got "
+ typeArguments.length + " - " + Arrays.toString(typeArguments));
}
return getRawType(typeArguments[1]);
}
static Type[] getSuperclassTypeParameter(Class<?> subclass) {
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing generics type parameters on subclass " + subclass);
}
return ((ParameterizedType) superclass).getActualTypeArguments();
}
private static Class<?> getRawType(Type type) {
if (type instanceof Class<?>) {
return (Class<?>) type;
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type rawType = parameterizedType.getRawType();
if (rawType instanceof Class<?>) {
return (Class<?>) rawType;
}
}
throw new RuntimeException("Unable to obtain raw class type from " + type);
}
}
@@ -1,4 +1,4 @@
package io.ebeaninternal.server.query;
package io.ebean.util;
/**
* Helper for dot notation property paths.
+22 -98
View File
@@ -3,6 +3,7 @@ package io.ebean.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Utility String class that supports String manipulation functions.
@@ -13,6 +14,10 @@ public class StringHelper {
private static final char DOUBLE_QUOTE = '"';
private static final Pattern SPLIT_NAMES = Pattern.compile("[\\s,;]+");
private static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* parses a String of the form name1='value1' name2='value2'. Note that you
* can use either single or double quotes for any particular name value pair
@@ -265,66 +270,6 @@ public class StringHelper {
}
}
/**
* This returns the FIRST string in str that is bounded on the left by
* leftBound, and bounded on the right by rightBound. This will return null if
* the leftBound is not found within str.
* <p>
* If leftBound can't be found this returns null.
* <p>
* This rightBound can't be found then this throws a
* StringIndexOutOfBoundsException.
*
* @param str the base string that we will search for the bounded string.
* @param leftBound the left bound of the string.
* @param rightBound the right bound of the string.
*/
public static String getBoundedString(String str, String leftBound, String rightBound)
throws RuntimeException {
if (str == null) {
throw new RuntimeException("string to parse is null?");
}
int startPos = str.indexOf(leftBound);
if (startPos > -1) {
startPos += leftBound.length();
int endPos = str.indexOf(rightBound, startPos);
// dp(str+" start:"+startPos+" end:"+endPos);
if (endPos == -1) {
throw new RuntimeException("Can't find rightBound: " + rightBound);
}
return str.substring(startPos, endPos);
} else {
// if no leftBound can be found.. return null... could be in a
// search n parse type loop?
// this keeps "no tag"==null different from "tag not formed
// properly"==StringParsingException
return null;
}
}
/**
* Takes the String bounded by leftBound & rightBound, and replaces it with
* replaceString. Actually removes the left and right bound strings aswell.
*/
public static String setBoundedString(String str, String leftBound, String rightBound,
String replaceString) {
int startPos = str.indexOf(leftBound);
if (startPos > -1) {
// startPos = startPos;
int endPos = str.indexOf(rightBound, startPos + leftBound.length());
if (endPos > -1) {
String toReplace = str.substring(startPos, endPos + 1);
return replaceString(str, toReplace, replaceString);
} else {
return str;
}
} else {
return str;
}
}
/**
* This method takes a String and will replace all occurrences of the match
* String with that of the replace String.
@@ -508,48 +453,27 @@ public class StringHelper {
}
/**
* This method takes a String as an argument and removes all occurrences of
* the supplied Char. It returns the resulting String.
* Splits at any whitespace "," or ";" and trims the result.
* It does not return empty entries.
*/
public static String removeChar(String s, char chr) {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c != chr) {
sb.append(c);
}
public static String[] splitNames(String names) {
if (names == null || names.isEmpty()) {
return EMPTY_STRING_ARRAY;
}
return sb.toString();
}
/**
* This method takes a String as an argument and removes all occurrences of
* the supplied Chars. It returns the resulting String.
*/
public static String removeChars(String s, char[] chr) {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (!charMatch(c, chr)) {
sb.append(c);
}
String[] result = SPLIT_NAMES.split(names);
if (result.length == 0) {
return EMPTY_STRING_ARRAY; // don't know if this ever can happen
}
return sb.toString();
}
private static boolean charMatch(int iChr, char[] chr) {
for (char aChr : chr) {
if (iChr == aChr) {
return true;
if ("".equals(result[0])) { // = input string starts with whitespace
if (result.length == 1) { // = input string contains only whitespace
return EMPTY_STRING_ARRAY;
} else {
String ret[] = new String[result.length-1]; // remove first entry
System.arraycopy(result, 1, ret, 0, ret.length);
return ret;
}
} else {
return result;
}
return false;
}
}
@@ -0,0 +1,54 @@
package io.ebeaninternal.api;
import java.util.ArrayList;
import java.util.List;
/**
* The results of bean cache hit.
*/
public class BeanCacheResult<T> {
private List<Entry<T>> list = new ArrayList<>();
/**
* Add an entry.
*/
public void add(T bean, Object key) {
list.add(new Entry<>(bean, key));
}
/**
* Return the hits.
*/
public List<Entry<T>> hits() {
return list;
}
/**
* Bean and cache key pair.
*/
static class Entry<T> {
private final T bean;
private final Object key;
public Entry(T bean, Object key) {
this.bean = bean;
this.key = key;
}
/**
* Return the natural key or id value.
*/
public Object getKey() {
return key;
}
/**
* Return the bean.
*/
public T getBean() {
return bean;
}
}
}
@@ -35,7 +35,7 @@ public class BindParams implements Serializable {
* Bind hash and count used to detect when the bind values have changed such
* that the generated SQL (with named parameters) needs to be recalculated.
*/
private int[] bindHash;
private String bindHash;
public BindParams() {
}
@@ -56,32 +56,29 @@ public class BindParams implements Serializable {
* taken into account when calculating the query hash.
* </p>
*/
public void buildQueryPlanHash(HashQueryPlanBuilder builder) {
int[] vals = calcQueryPlanHash();
builder.add(vals[0]).bind(vals[1]);
public String calcQueryPlanHash() {
StringBuilder builder = new StringBuilder();
buildQueryPlanHash(builder);
return builder.toString();
}
/**
* Calculate and return a query plan bind hash with total bind count.
*/
public int[] calcQueryPlanHash() {
public void buildQueryPlanHash(StringBuilder builder) {
int tempBindCount;
int bc = 0;
int hc = 92821;
for (Param param : positionedParameters) {
tempBindCount = param.queryBindCount();
bc += tempBindCount;
hc = hc * 92821 + tempBindCount;
builder.append("p").append(bc).append(" ?:").append(tempBindCount).append(",");
}
for (Map.Entry<String, Param> entry : namedParameters.entrySet()) {
tempBindCount = entry.getValue().queryBindCount();
bc += tempBindCount;
hc = hc * 92821 + entry.getKey().hashCode();
hc = hc * 92821 + tempBindCount;
builder.append("n").append(bc).append(" k:").append(entry.getKey()).append(" ?:").append(tempBindCount).append(",");
}
return new int[]{hc, bc};
}
/**
@@ -277,9 +274,9 @@ public class BindParams implements Serializable {
bindHash = calcQueryPlanHash();
return false;
}
int[] oldPlan = bindHash;
String oldPlan = bindHash;
bindHash = calcQueryPlanHash();
return bindHash[0] == oldPlan[0] && bindHash[1] == oldPlan[1];
return bindHash.equals(oldPlan);
}
/**
@@ -1,4 +1,4 @@
package io.ebean.plugin;
package io.ebeaninternal.api;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.server.type.ScalarType;
@@ -1,57 +0,0 @@
package io.ebeaninternal.api;
/**
* A hash for a query plan.
*/
public class HashQueryPlan {
private final String rawSql;
private final int planHash;
private final int bindCount;
public HashQueryPlan(String rawSql, int planHash, int bindCount) {
this.rawSql = rawSql;
this.planHash = planHash;
this.bindCount = bindCount;
}
@Override
public String toString() {
return planHash + ":" + bindCount + (rawSql != null ? ":r" : "");
}
/**
* Return as a partial key. For rawSql hash the sql is part of the key and as such
* needs to be included in order to have a complete key. Typically the MD5 of the sql
* can be used as a shot form proxy for the actual sql.
*/
public String getPartialKey() {
return planHash + "_" + bindCount;
}
@Override
public int hashCode() {
int hc = planHash;
hc = hc * 92821 + bindCount;
hc = hc * 92821 + (rawSql == null ? 0 : rawSql.hashCode());
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof HashQueryPlan)) {
return false;
}
HashQueryPlan e = (HashQueryPlan) obj;
//noinspection StringEquality
return e.planHash == planHash
&& e.bindCount == bindCount
&& ((e.rawSql == rawSql) || (e.rawSql != null && e.rawSql.equals(rawSql)));
}
}
@@ -1,97 +0,0 @@
package io.ebeaninternal.api;
import java.util.Set;
/**
* Used to build HashQueryPlan instances.
*/
public class HashQueryPlanBuilder {
private int planHash;
private int bindCount;
public HashQueryPlanBuilder() {
this.planHash = 92821;
}
@Override
public String toString() {
return planHash + ":" + bindCount;
}
/**
* Add a class to the hash calculation.
*/
public HashQueryPlanBuilder add(Class<?> cls) {
planHash = planHash * 92821 + cls.getName().hashCode();
return this;
}
/**
* Add an object to the hash calculation.
*/
public HashQueryPlanBuilder add(Object object) {
planHash = planHash * 92821 + (object == null ? 0 : object.hashCode());
return this;
}
/**
* Add the set with order being important.
*/
public HashQueryPlanBuilder addOrdered(Set<?> set) {
if (set == null) {
add(false);
} else {
add(true);
for (Object o : set) {
add(o);
}
}
return this;
}
/**
* Add an integer to the hash calculation.
*/
public HashQueryPlanBuilder add(int hashValue) {
planHash = planHash * 92821 + (hashValue);
return this;
}
/**
* Add a boolean to the hash calculation.
*/
public HashQueryPlanBuilder add(boolean booleanValue) {
planHash = planHash * 92821 + (booleanValue ? 92821 : 0);
return this;
}
/**
* Add a number to the bind count for the hash.
*/
public void bind(int extraBindCount) {
bindCount += extraBindCount;
}
public void bindIfNotNull(Object someValue) {
if (someValue != null) {
bindCount++;
}
}
/**
* Build and return the calculated HashQueryPlan.
*/
public String build() {
return planHash + "_" + bindCount;
}
public int getPlanHash() {
return planHash;
}
public int getBindCount() {
return bindCount;
}
}
@@ -1,7 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.TxScope;
/**
@@ -10,28 +9,20 @@ import io.ebean.TxScope;
public class HelpScopeTrans {
/**
* Create a ScopeTrans for a given methods TxScope.
* Entering an enhanced transactional method.
*/
public static ScopeTrans createScopeTrans(TxScope txScope) {
EbeanServer server = Ebean.getServer(txScope.getServerName());
SpiEbeanServer iserver = (SpiEbeanServer) server;
return iserver.createScopeTrans(txScope);
public static void enter(TxScope txScope) {
server().scopedTransactionEnter(txScope);
}
/**
* Exiting the method in an expected fashion.
* <p>
* That is returning successfully or via a caught exception.
* Unexpected exceptions are caught via the Thread uncaughtExceptionHandler.
* </p>
*
* @param returnOrThrowable the return or throwable object
* @param opCode the opcode for ATHROW or ARETURN etc
* @param scopeTrans the scoped transaction the method was run with.
* Exiting an enhanced transactional method.
*/
public static void onExitScopeTrans(Object returnOrThrowable, int opCode, ScopeTrans scopeTrans) {
public static void exit(Object returnOrThrowable, int opCode) {
server().scopedTransactionExit(returnOrThrowable, opCode);
}
scopeTrans.onExit(returnOrThrowable, opCode);
private static SpiEbeanServer server() {
return (SpiEbeanServer) Ebean.getDefaultServer();
}
}
@@ -103,7 +103,8 @@ public class LoadBeanRequest extends LoadRequest {
idList.add(desc.getId(bean));
}
if (!idList.isEmpty()) {
if (!desc.isMultiValueIdSupported() && !idList.isEmpty()) {
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
// for performance make up the Id's to the batch size
@@ -156,9 +157,9 @@ public class LoadBeanRequest extends LoadRequest {
for (Object aList : list) {
EntityBean loadedBean = (EntityBean) aList;
loadedIds.add(desc.getId(loadedBean));
if (isLoadCache()) {
desc.cacheBeanPut(loadedBean);
}
}
if (isLoadCache()) {
desc.cacheBeanPutAll(list);
}
if (lazyLoadProperty != null) {
@@ -107,11 +107,13 @@ public class LoadManyRequest extends LoadRequest {
for (BeanCollection<?> bc : batch) {
idList.add(many.getParentId(bc.getOwnerBean()));
}
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
idList.add(firstId);
if (!many.getTargetDescriptor().isMultiValueIdSupported()) {
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
idList.add(firstId);
}
}
}
@@ -26,16 +26,6 @@ public abstract class LoadRequest {
*/
public abstract Class<?> getBeanType();
/**
* Log the just executed secondary query with the 'root' query if 'logSecondaryQuery' is set to
* true. This is for testing purposes to confirm the secondary query executes etc.
*/
public void logSecondaryQuery(SpiQuery<?> query) {
if (parentRequest != null && parentRequest.isLogSecondaryQuery()) {
parentRequest.getQuery().logSecondaryQuery(query);
}
}
/**
* Return true if this is a lazy load and false if it is a secondary query.
*/
@@ -3,7 +3,7 @@ package io.ebeaninternal.api;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.query.SplitName;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.query.SqlJoinType;
import java.io.Serializable;
@@ -0,0 +1,86 @@
package io.ebeaninternal.api;
import io.ebean.Pairs;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Natural key entry with name value pairs for each of the properties making up the key.
*/
public class NaturalKeyEntry {
private Map<String,Object> map = new HashMap<>();
private Object key;
private Object inValue;
/**
* Used when query query just has a series of EQ expressions (no IN clause).
*/
public NaturalKeyEntry(String[] naturalKey, List<NaturalKeyEq> eqList) {
load(eqList);
this.key = calculateKey(naturalKey);
}
/**
* Create when query uses an IN clause.
*/
public NaturalKeyEntry(String[] naturalKey, List<NaturalKeyEq> eqList, String inProperty, Object inValue) {
load(eqList);
if (inProperty != null) {
map.put(inProperty, inValue);
this.inValue = inValue;
}
this.key = calculateKey(naturalKey);
}
/**
* Create when query uses an IN PAIRS clause.
*/
public NaturalKeyEntry(String[] naturalKey, List<NaturalKeyEq> eqList,
String inMapProperty0, String inMapProperty1, Pairs.Entry pair) {
load(eqList);
map.put(inMapProperty0, pair.getA());
map.put(inMapProperty1, pair.getB());
this.inValue = pair;
this.key = calculateKey(naturalKey);
}
private void load(List<NaturalKeyEq> eqList) {
if (eqList != null) {
for (NaturalKeyEq eq : eqList) {
map.put(eq.property, eq.value);
}
}
}
private Object calculateKey(String[] naturalKey) {
if (naturalKey.length == 1) {
return map.get(naturalKey[0]);
}
StringBuilder sb = new StringBuilder();
for (String key : naturalKey) {
sb.append(map.get(key)).append(";");
}
return sb.toString();
}
/**
* Return the natural cache key (String concatenation of values).
*/
public Object key() {
return key;
}
/**
* Return the inValue (used to remove from IN clause of original query).
*/
public Object getInValue() {
return inValue;
}
}
@@ -0,0 +1,15 @@
package io.ebeaninternal.api;
/**
* A property value pair in a natural key lookup.
*/
public class NaturalKeyEq {
final String property;
final Object value;
public NaturalKeyEq(String property, Object value) {
this.property = property;
this.value = value;
}
}

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