Compare commits

..
Author SHA1 Message Date
rbygrave 9eb54da2dc [maven-release-plugin] prepare release ebean-parent-12.8.3 2021-04-19 21:40:07 +12:00
rbygrave bb6ff866c7 #2220 - DDL generation of nullable columns expected with nullable Embedded containing non null properties 2021-04-19 21:37:05 +12:00
Robin Bygrave 2d719c5b37 #2218 - @Encrypted columns on Oracle. The identifier alias starts with an underscore (ORA-00911)
Change to use zx__ for the encryption column alias prefix rather than _e_
2021-04-19 20:08:38 +12:00
Robin Bygrave 9d54b264ad #2219 - Compile error with ZonedDateTime and query beans - type argument java.time.ZonedDateTime is not within bounds of type-variable D 2021-04-19 17:04:57 +12:00
rbygrave 65b282a056 [maven-release-plugin] prepare for next development iteration 2021-04-09 17:49:16 +12:00
rbygrave fc174c8988 [maven-release-plugin] prepare release ebean-parent-12.8.2 2021-04-09 17:49:07 +12:00
rbygrave 06802896ef #2213 Bump agent for @Transactional(autoPersistUpdates = TxOption.ON) 2021-04-09 16:17:58 +12:00
Robin Bygrave 7f13ac9e5e #2213 - Support autoPersistUpdates via TxScope 2021-04-08 23:15:49 +12:00
Rob BygraveandGitHub 4a1374d22d Merge pull request #2213 from ebean-orm/feature/transparentPersistence
Initial experimental support for autoPersistUpdates / "transparent persistence"
2021-04-08 23:03:05 +12:00
rbygrave 4ee8d647b7 No effective change - tidy test TestCustomerFinder 2021-04-07 23:28:53 +12:00
rbygrave 59793f3c01 Improve javadoc for Transaction#setGetGeneratedKeys
More clearly document the limitation that we can't update beans that don't have id values
2021-04-07 15:50:04 +12:00
rbygrave f7f383d8f3 No effective change - tidy tests TestRawSqlPositionedParams, TestRawSqlUnparsedQuery 2021-04-07 13:38:34 +12:00
rbygrave 562a3959b6 Merge branch 'master' of github.com:ebean-orm/ebean 2021-04-06 21:14:21 +12:00
rbygrave bb1275f4f9 #2217 - Inserting an entity bean is skipped with a String @Id property and no other properties set 2021-04-06 21:13:59 +12:00
Rob BygraveandGitHub 7fde6b9331 Merge pull request #2216 from ebean-orm/feature/2177-take2
#2177 - Fix where schema not provided by JDBC driver
2021-04-06 20:13:24 +12:00
rbygrave 3e97a4005a Change TestCustomerFinder drop query plan threshold for tests only 2021-04-06 20:09:09 +12:00
Rob BygraveandGitHub c271760230 Merge pull request #2215 from PromanSEW/patch-1
Fix copy/paste typo in javadoc for endOr()
2021-04-06 20:00:02 +12:00
rbygrave f7c1845f0c #2177 - Fix where schema not provided by JDBC driver
For @Table with schema, using findNative with a JDBC driver that does not provide the schema this fix matches by just using the table name.
2021-04-06 19:58:02 +12:00
Roman ParshikovandGitHub 9b04f20ed3 Fix copy/paste typo 2021-04-06 13:32:57 +08:00
Rob BygraveandGitHub 62b6b6293b Merge pull request #2214 from cit-consulting/fix_with_lock
Explicit locking method withLock() is unaccessible on query beans
2021-04-06 08:27:38 +12:00
Vladimir Konkov 6d8a1c9398 Explicit locking method withLock() is unaccessible on query beans 2021-04-05 21:45:48 +03:00
Robin Bygrave 303039735f Refactor rename transparentPersistence to autoPersistUpdates 2021-04-01 23:04:22 +13:00
Robin Bygrave 6bcf5c6881 Add DatabaseConfig.autoPersistUpdates for experimental "transparent persistence" 2021-04-01 22:57:34 +13:00
Robin Bygrave 0527f45f5a For experimental "transparent persistence" - Inserts added to PC
When inserting bean with transparent persistence the beans are registered with the persistence context. This is done to support the cases where the beans are inserted, a flush() occurs, the inserted bean is mutated and now dirty - we want that to be detected and get an update
2021-04-01 16:59:03 +13:00
Robin Bygrave ba397bbb0c For experimental "transparent persistence" - Delete removes from PC early
When delete bean executed with transparent persistence, this marks the bean as removed from the persistence context early.

This avoids a "dirty" deleted bean from being seen as a "dirty" bean in the persistence context at flush() time.
2021-04-01 16:04:18 +13:00
Robin Bygrave a617d58902 Initial experimental support for "transparent persistence"
In short, when this is turned on at flush() get any dirty beans held by the PersistenceContext and persist them (always update for these beans)
2021-04-01 14:47:54 +13:00
Robin Bygrave 7fc4a229ae Merge branch 'master' of https://github.com/ebean-orm/ebean 2021-04-01 12:23:00 +13:00
Robin Bygrave f53d31aa2e No functional change - final field for BatchControl 2021-04-01 12:22:45 +13:00
Rob BygraveandGitHub b889b6a6b0 Merge pull request #2212 from tobias-/bitwise_querybean_operators
Add bitwise operators to query beans
2021-04-01 09:38:00 +13:00
Tobias f9b55a06b8 Add bitwise operators to query beans 2021-03-31 12:53:06 +02:00
rob bygrave 09d772a5e5 [maven-release-plugin] prepare for next development iteration 2021-03-31 00:06:13 +13:00
rob bygrave f7035eb228 [maven-release-plugin] prepare release ebean-parent-12.8.1 2021-03-31 00:06:04 +13:00
Robin Bygrave 1bf09ddd38 #2211 - Improve javadoc for queryPlanTTLSeconds
Hopefully people understand it is unrelated. Unfortunately we now have some properties with similar names so maybe we need a better name for this sometime in the future.
2021-03-30 23:49:44 +13:00
Robin Bygrave a8bf27da55 #2211 - Improve query plan capture - Align config property names
collectQueryPlans -> queryPlanEnable
collectQueryPlanThresholdMicros -> queryPlanThresholdMicros

properties keys:
ebean.collectQueryPlans -> ebean.queryPlan.enable
ebean.collectQueryPlanThresholdMicros -> ebean.queryPlan.thresholdMicros

This then aligns all the queryPlan related properties together which is important in order to avoid confusion.
2021-03-30 23:44:30 +13:00
Robin Bygrave ee8bb5ef75 #2211 - Improve query plan capture - Add default mechanism to log captured query plans 2021-03-30 23:19:21 +13:00
Robin Bygrave bcfd9cab1d #2210 - Improve query plan capture - bump threshold multiplier to 1.5 2021-03-30 16:05:42 +13:00
Robin Bygrave 2a5fd7f3a9 #2210 - Improve query plan capture - ability to change default threshold micros 2021-03-30 16:02:27 +13:00
Robin Bygrave f8c37772d4 Tidy internals of DefaultServer with @Nonnull, <>, Collections.singleton(), remove unused queryBatchSize 2021-03-30 10:23:28 +13:00
Robin Bygrave d988dbe69d #2209 - Registering [default] as the default server but [default] is already registered as the default exception with hot-reload 2021-03-30 09:56:48 +13:00
rob bygrave 5cb9b9b237 #2208 - When using fetchCache() with a single id / findOne ... doesn't hit L2 cache
Plus #2207 ENH: Query beans add support for nested FetchGroup
2021-03-29 23:21:07 +13:00
rob bygrave 819d391495 Bump agent maven plugin to 12.8.1 2021-03-26 20:31:57 +13:00
rob bygrave 1f0ed629a7 Bump java8-oss to 3.1 with removal of --illegal-access=permit argLine 2021-03-26 20:31:03 +13:00
rob bygrave 91044d0a75 javadoc only - improve javadoc for DatabaseConfig.loadFromProperties() 2021-03-25 23:13:51 +13:00
rob bygrave 57736185d0 #2206 - Postgres DDL generation - for create index use "if not exists" clause 2021-03-25 23:13:15 +13:00
Robin Bygrave 8992980689 #2206 - Postgres DDL generation - for create index use "if not exists" clause 2021-03-25 16:45:44 +13:00
rob bygrave f341ff0167 #2205 - [oracle] Incorrect DDL generated for alter table add column 2021-03-23 23:58:28 +13:00
rob bygrave abfa841ed7 #2205 - [oracle] Incorrect DDL generated for alter table add column 2021-03-23 23:51:07 +13:00
Rob BygraveandGitHub e68adf53bc Merge pull request #2204 from ebean-orm/feature/Honor-setLazyLoadBatchSize
Honor setLazyLoadBatchSize() when using FetchConfig.ofLazy() of unspecified size
2021-03-23 23:25:08 +13:00
Robin Bygrave 562d175de1 With FetchConfig.ofLazy() honor query.setLazyLoadBatchSize()
Use 0 for default lazy batch such that it honors a value set via query.setLazyLoadBatchSize()
2021-03-23 23:17:07 +13:00
Robin Bygrave b5cef354ad Refactor simplify LoadContext.getSecondaryQueriesMinBatchSize()
No functional change here. Just move the constant into DLoadContext.
2021-03-23 14:53:30 +13:00
Rob Bygrave 09a887aeb7 #2203 - Support use of Java Record type with @Entity, @Embeddable @IdClass
Add tests for IdClass use of record type
2021-03-23 00:19:52 +13:00
Rob Bygrave 53c903fa48 #2203 - Support use of Java Record type with @Entity, @Embeddable @IdClass
Add tests for EmbeddedId use of record type
2021-03-23 00:10:28 +13:00
Rob Bygrave 548454c90d #2203 - Support use of Java Record type with @Entity, @Embeddable @IdClass
Add tests for embedded use of record type
2021-03-22 23:57:14 +13:00
Rob Bygrave 94bab5f83b #2203 - Support use of Java Record type with @Entity, @Embeddable @IdClass
Add tests for entity use of record type
2021-03-22 23:44:10 +13:00
Rob Bygrave 78965c1ac4 #2203 - Support use of Java Record type with @Entity, @Embeddable @IdClass
Allow java.lang.Record to be a root level parent for entity beans etc
2021-03-22 23:43:35 +13:00
Rob Bygrave 00b0fcaeb3 #2202 - kotlin-maven-plugin issue with JDK 16
Temporary modify tests to not invoke the kotlin-maven-plugin while adding JDK 16 specific tests.

Hitting error InaccessibleObjectException: Unable to make protected void java.util.ResourceBundle.setParent(java.util.ResourceBundle) accessible: module java.base does not "opens java.util" to unnamed module @62732be7
2021-03-22 23:24:16 +13:00
rob bygrave f7905c8e94 [maven-release-plugin] prepare for next development iteration 2021-03-18 22:30:17 +13:00
rob bygrave 7ab7b612f6 [maven-release-plugin] prepare release ebean-parent-12.8.0 2021-03-18 22:23:34 +13:00
rob bygrave ce1d2803ce Bump test wait time for redis 2021-03-18 22:18:34 +13:00
rob bygrave 9b066e4eac Bump version to 12.8.0-SNAPSHOT 2021-03-18 21:26:50 +13:00
Robin Bygrave e1f9d106f0 Additional tests for #2191 query findEach batch consumer 2021-03-18 17:12:21 +13:00
Robin Bygrave e40a539406 #2200 - ENH: Add DtoQuery findEach with batch consumer - findEach(int batchSize, Consumer<List<T>> consumer) 2021-03-18 17:02:41 +13:00
Robin Bygrave bb73c30bb7 #2199 - ScalarTypeWrapper doesn't handle "nullValue" correctly
Fix for - standard JPA AttributeConverter which doesn't have the getNullValue method, he/she can't convert null to a custom null object

AttributeConverterAdapter probes the AttributeConverter for the nullValue rather than just assuming it is null.
2021-03-18 16:14:46 +13:00
Robin Bygrave 2e16f05a70 #2199 - ScalarTypeWrapper doesn't handle "nullValue" correctly
Handles the case for ScalarTypeConverter with custom null value and binding the custom null value (via query parameter, CallableSql parameter etc)
2021-03-18 10:03:35 +13:00
Robin Bygrave 1837c3443d #2186 - Invalid generated table and column names of @ManyToMany relation with allQuotedIdentifiers=true 2021-03-17 21:02:59 +13:00
Rob BygraveandGitHub bd1060ff28 Merge pull request #2198 from ebean-orm/sebastian-mrozek-feature/json-assert-compare-arrays
Feature/json assert compare arrays
2021-03-16 22:06:28 +13:00
Robin Bygrave c9968a9662 Change JsonAssertContains array matching to match on size 2021-03-16 22:03:12 +13:00
Robin Bygrave e3001e8bdd Fix JsonAssertContains testContainsFails with missing parts of JSON
Just add missing parts of test/resources/original.json and original-subset-modified.json
2021-03-16 09:30:54 +13:00
Robin Bygrave 82bb2b4567 Merge branch 'feature/json-assert-compare-arrays' of https://github.com/sebastian-mrozek/ebean into sebastian-mrozek-feature/json-assert-compare-arrays 2021-03-16 09:14:06 +13:00
Robin Bygrave f483002e39 #2197 - [ebean-redis] - Bump jedis dependency to 3.5.2 (from 3.4.0 2021-03-16 09:13:01 +13:00
Robin Bygrave c2a87a0c88 #2196 - SQLException:Column "T0.ID" must be in the GROUP BY list - when findCount with having clause 2021-03-15 22:43:25 +13:00
Robin Bygrave 733886c70b #2195 - @Aggregation on OneToMany property not mapping to column properly 2021-03-15 22:03:14 +13:00
sebastian-mrozek 899fdca995 Improve assertions in case json compare does not fail as expected
Remove sys out print.
2021-03-15 13:04:05 +13:00
sebastian-mrozek 4731ee034e Handle searching for matching elements in an array
Add unit tests.
Refactor internals to allow reusing assertion method for finding matches in an array.
2021-03-15 12:51:36 +13:00
Robin Bygrave e6faf9efd1 #2193 - Prevent registration of 2 default servers (2 Database that have defaultServer=true) 2021-03-11 23:21:44 +13:00
Rob BygraveandGitHub d75d1fce43 Change ModuleInfoLoader API and querybean-generator for named default database (#2190)
Currently we can not use @DbName with the entities of the default database. With this change the generated code that registers entity classes will support using @DbName with the default database.
2021-03-11 20:42:25 +13:00
sebastian-mrozek ef3980a3ee Add test resources 2021-03-11 13:47:46 +13:00
sebastian-mrozek 2a1c170aa4 Add test for shuffled array comparison 2021-03-11 13:44:59 +13:00
Rob BygraveandGitHub d65e769dd7 Merge pull request #2191 from ebean-orm/feature/findEachBatch
ENH: Add Query findEach() with batch consumer
2021-03-11 13:34:58 +13:00
Rob Bygrave 2bb5a85bd6 ENH: Add Query findEach() with batch consumer
This is a variation of findEach() that makes it easy to have a batch consumer processing a large result in batches.  For example, process in batches of 50 beans.

Note that the last batch consumed/processed will often have less than the batch size.
2021-03-10 00:18:35 +13:00
rob bygrave 98aee9ca62 Merge branch 'master' of github.com:ebean-orm/ebean 2021-03-09 20:13:47 +13:00
Robin Bygrave c86f965d36 #2187 - Improve error message thrown when @JoinTable @JoinColumn not satisfied.
Added " or a @JoinColumn needs an explicit referencedColumnName specified?";
2021-03-04 08:55:11 +13:00
Robin Bygrave 81e445e386 #2184 - Fix for findNative() with fetch() - convert fetch to fetchQuery with nativeSql
When using nativeSql (think of it as the "root query") we can't use "fetch joins" (FetchConfig.ofDefault()) as that means to prefer to use a SQL JOIN. In this nativeSql case we need to effectively automatically convert fetch() to fetchQuery()
2021-03-02 15:47:07 +13:00
Robin Bygrave 2cbbbc4419 Merge branch 'master' of https://github.com/ebean-orm/ebean 2021-03-02 15:32:40 +13:00
Rob BygraveandGitHub 632f43016e Merge pull request #2183 from bkempe/findNative_fetch_inconsistent
added tests for findNative query with fetch() and fetchQuery()
2021-03-02 15:31:38 +13:00
Robin Bygrave fe62ad2ea2 #2184 - Initial refactor of DefaultOrmQuery to support 2184
- Add fetchInternal() and point fetchQuery() fetchLazy() and fetchCache() to it
- Move the fetch() methods so they are located together
2021-03-02 15:28:17 +13:00
Robin Bygrave f416309863 No effective change - change DefaultOrmQuery methods to return Query<T> rather than DefaultOrmQuery<T>
This is no effective functional change but we do need to update the test OrmQueryPlanKeyTest to cast now.
2021-03-02 14:44:54 +13:00
Rob BygraveandGitHub dcf5e7a15c Merge pull request #2185 from bkempe/querybeans_is_in_subquery
ENH: add in(subQuery) support for query beans
2021-03-02 14:10:53 +13:00
Ben Kempe af4f70e3a7 add in(subQuery) support for query beans 2021-03-01 13:21:05 +01:00
Ben Kempe 79dde23ec6 added tests for findNative fetches 2021-03-01 12:16:22 +01:00
robin e4d0341e67 [maven-release-plugin] prepare for next development iteration 2021-03-01 23:45:15 +13:00
robin 907b182931 [maven-release-plugin] prepare release ebean-parent-12.7.2 2021-03-01 23:45:05 +13:00
robin 1a26cd717f #2182 - Tidy javadoc - this may mean JDK 15 is required to generate javadoc 2021-03-01 23:40:06 +13:00
Robin Bygrave 7bfacb095b #2181 - Oracle 9,10,11 rownum based limiting for SqlQuery and DtoQuery (rather than Oracle 12g row limiting) 2021-03-01 22:47:34 +13:00
Robin Bygrave 6315edfc21 #2177 - findNative not mapping bean when schema specified in entity mapping 2021-03-01 22:08:17 +13:00
Robin Bygrave 464f48d717 Bump test kotlin-querybean-generator version 2021-03-01 21:34:17 +13:00
Robin Bygrave eacfaf3403 No effective change - refactor tidy SqlTreeNodeBean 2021-03-01 21:19:25 +13:00
Rob BygraveandGitHub 36d8028045 Merge pull request #2180 from ebean-orm/feature/refactor-SqlTreeNodeBean
Improved fix for #2179 - Fetch on nested one-to-many collectio…
2021-03-01 20:32:08 +13:00
Robin Bygrave 297c1d7abb #2179 - Improved fix for (2179) Fetch on nested one-to-many collections results in duplicates
The improvement here is that SqlTreeNodeManyRoot uses load and isContextBean() so that it only checks if the collection already contains the detailBean if the detailBean was already in the persistence context (aka not newly loaded and added).  The vast majority case is that detailBean is loaded fresh so for the common case we can skip the extra collection contains() check.
2021-03-01 20:27:50 +13:00
Robin Bygrave 5a73c85e6e Refactor internals - SqlTreeNodeBean add derived readIdNormal + private methods
This is no effective change
2021-03-01 18:36:49 +13:00
Robin Bygrave 68138bc009 Refactor internals no effective change - reorder methods of SqlTreeNodeManyRoot 2021-03-01 17:45:52 +13:00
Robin Bygrave 8c7df3212a Refactor internals add SqlTreeRoot as follow up to #2179
Adds SqlTreeRoot interface to make it more explicit that SqlTreeNodeRoot is the only valid top level node in the sql tree (and as such has the root level load() method)
2021-03-01 17:44:13 +13:00
Robin Bygrave 5ebad53072 #2179 - Improve internals for outer join resulting in null many bean
Refactor improving the BeanList.internalAddWithCheck() for the null bean case
2021-03-01 14:00:40 +13:00
Robin Bygrave 640824ae31 Merge branch 'bkempe-nested_fetch_duplicates' 2021-03-01 13:39:16 +13:00
Robin Bygrave 6acf93c978 #2178 - Fix for duplicate instances added to collection for nested one-to-many fetch
This is an initial fix for this case.
2021-03-01 13:38:40 +13:00
Robin Bygrave 6897e06dcd Merge branch 'nested_fetch_duplicates' of https://github.com/bkempe/ebean into bkempe-nested_fetch_duplicates 2021-03-01 10:23:49 +13:00
Ben Kempe d036af8d1a Add more assertions for nested one-to-many fetch 2021-02-28 19:06:51 +01:00
robin b77c62e0db [maven-release-plugin] prepare for next development iteration 2021-02-19 22:51:03 +13:00
robin bbf46bb0ec [maven-release-plugin] prepare release ebean-parent-12.7.1 2021-02-19 22:50:53 +13:00
Rob BygraveandGitHub 6aa4337b27 #2174 - Entity beans with @Cache + @DbArray of enums (or UUID or Integer) not converted to enums (come back as String) (#2175) 2021-02-19 22:42:48 +13:00
Robin Bygrave 99d7a4e4b4 Tidy up pom jackson dependency versions (all optional dependencies) and bump to 2.12.1 2021-02-19 09:43:56 +13:00
Robin Bygrave b7da2cbd97 #2173 - ebean-test: Add assertContains - DbJson.of(entityBean).assertContains(...) 2021-02-19 09:29:06 +13:00
Rob BygraveandGitHub b4ae721998 #2169 Refactor after #2170 tidy javax/jakarta validation annotation reading (#2171)
Introduces ReadValidationAnnotations with javax and jakarta implementations and moves the validation annotation reading there
2021-02-17 22:23:07 +13:00
André CamiloandGitHub 3bb4126e26 #2169 Support for jakarta.validation.constraints.NotNull and (#2170)
jakarta.validation.constraints.Size
2021-02-17 18:04:43 +13:00
robin de439e7695 Improve Refactor internals OrmQueryProperties invert isEmpty()
Rename to hasProperties() and improve javadoc
2021-02-16 23:20:44 +13:00
robin e5ec72df5c Refactor internals OrmQueryProperties invert isEmpty() -> nonEmpty() 2021-02-16 20:37:58 +13:00
Rob BygraveandGitHub 98a89156bf Refactor query internals to reduce property parsing for partial select and fetch (#2167)
* Update test only - update TestBasicClobNoVer to use DB and AssertJ

* Refactor OrmQueryProperties internals to reduce raw property parsing

- Remove String properties
- Reduces parsing of properties via  fetchProperties() selectProperties() methods

* No effective change - tidy pom.xml for ebean-querybean

* Refactor query internals adding fetchProperties() selectProperties()

- Adds SpiQueryFetch
- Used by query beans with Set<String> properties passed (effectively skipping any parsing)
2021-02-16 17:51:47 +13:00
robin dc65475dbb #2168 - Using ebean-test with redis container - hanging testing redis connectivity 2021-02-16 17:47:56 +13:00
rob bygrave bab637667d Update test only - update TestBasicClobNoVer to use DB and AssertJ 2021-02-14 11:50:54 +13:00
rob bygrave cc64ff5f10 Update javadoc with use of FetchConfig.ofQuery() etc 2021-02-14 11:37:19 +13:00
rob bygrave ab87174e5b #2163 - Fix OrmQueryProperties when FetchConfig null 2021-02-14 11:30:24 +13:00
Rob BygraveandGitHub a6fdc42934 Remove support for "query hints" like +query(50) (#2163)
* Remove support for "query hints" like +query(50)

* Remove unused readOnly from OrmQueryProperties

* OrmQueryProperties cache as final field
2021-02-14 11:21:10 +13:00
rob bygrave 4c16b85e85 #2166 - Remove deprecated forUpdate(lockType) methods - migrate to withLock(lockType, waitType) 2021-02-14 10:52:18 +13:00
rob bygrave 9e3d6c6a08 #2165 - Joda LocalDate West of UTC converts incorrectly (Fix: change from epoc millis conversion to y/m/d conversion) 2021-02-14 10:28:55 +13:00
Rob BygraveandGitHub d567872813 Deprecate the mutating methods of FetchConfig to migrate to the static factory methods (#2164)
e.g. migrate from            ->   to use
new FetchConfig().query()    ->   FetchConfig.ofQuery()
new FetchConfig().query(50)  ->   FetchConfig.ofQuery(50)
2021-02-13 14:50:02 +13:00
rob bygrave 7069d3f600 Update tests to use fetchQuery and fetchLazy rather than FetchConfig 2021-02-13 12:31:00 +13:00
rob bygrave b2f8b7c711 Bump pom version to 12.7.1-SNAPSHOT 2021-02-13 12:29:17 +13:00
rob bygrave 4092ea307e Bump pom version to 12.7.1-SNAPSHOT 2021-02-13 12:29:09 +13:00
rob bygrave 01f64ef9f9 git push origin masterMerge branch 'feature/refactor-FetchConfig' 2021-02-13 12:10:39 +13:00
rob bygrave f1a48b182e Refactor FetchConfig 2021-02-13 12:10:21 +13:00
Rob BygraveandGitHub 8ac5467856 Improve internal calculation of orm query plan hash key (Require use of inline hints like +query(50) to be comma delimited with properties) (#2159)
* WIP Tidy DefaultOrmQuery planDescription()

* WIP Tidy DefaultOrmQuery planDescription()

* Refactor OrmQueryPropertiesParser with hints requiring comma delimiting (previously didn't)

* Refactor OrmQueryPropertiesParser (reuse inputProperties) and DRawSqlColumnsParser (to return Set)

* Refactor OrmQueryPropertiesParser - remove JunkMain

* Refactor OrmQueryDetail, OrmQueryProperties - properties
2021-02-13 11:58:00 +13:00
SnōwballandGitHub 758f84f573 Ignore special kotlin collection types (#2130)
Kotlin prefers(emits compiler warnings if not) the built-in types
e.g. Set and MutableSet instead of the java.util.* variety. This
patch removes the imports for those, so the built-in varieties are
used.
2021-02-13 11:56:04 +13:00
rob bygrave 2073fefc92 #2162 - Change ebean artifact such that ebean-ddl-generator is a dependency of ebean-test and ebean-autotune optional 2021-02-12 23:12:32 +13:00
rob bygrave 5b98d8d9c2 [maven-release-plugin] prepare for next development iteration 2021-02-12 22:27:45 +13:00
rob bygrave e0c3a92b60 Refactor FetchConfig 2021-02-12 21:01:57 +13:00
305 changed files with 6395 additions and 2330 deletions
+1 -1
View File
@@ -293,6 +293,6 @@ ebean.tenant.schemaProvider
ebean.updateAllPropertiesInBatch
ebean.updateChangesOnly
ebean.updatesDeleteMissingChildren
ebean.useJavaxValidationNotNull
ebean.useValidationNotNull
ebean.useJtaTransactionManager
+4 -17
View File
@@ -4,18 +4,13 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.6.7</version>
<version>12.8.3</version>
</parent>
<name>ebean api</name>
<description>ebean api</description>
<artifactId>ebean-api</artifactId>
<properties>
<jackson-core.version>2.11.3</jackson-core.version>
<jackson-databind.version>2.11.3</jackson-databind.version>
</properties>
<dependencies>
<!--
@@ -55,7 +50,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>6.15</version>
<version>7.0</version>
</dependency>
<dependency>
@@ -74,7 +69,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-core.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
@@ -82,7 +77,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
@@ -102,14 +97,6 @@
<optional>true</optional>
</dependency>
<!-- provided scope to read validation annotations Size etc -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
@@ -14,7 +14,7 @@ import java.util.Optional;
*
* public class CustomerFinder extends BeanFinder<Long,Customer> {
*
* Inject
* @Inject
* public CustomerFinder(Database database) {
* super(Customer.class, database);
* }
@@ -9,10 +9,10 @@ import java.util.Collection;
* <p>
* <pre>{@code
*
* Repository
* @Repository
* public class CustomerRepository extends BeanRepository<Long,Customer> {
*
* Inject
* @Inject
* public CustomerRepository(Database server) {
* super(Customer.class, server);
* }
@@ -897,7 +897,7 @@ public interface Database {
* <pre>{@code
* public class Order { ...
*
* OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* List<OrderDetail> details;
* ...
* }
@@ -33,6 +33,7 @@ public class DatabaseFactory {
private static final ReentrantLock lock = new ReentrantLock();
private static SpiContainer container;
private static String defaultServerName;
static {
EbeanVersion.getVersion();
@@ -76,6 +77,12 @@ public class DatabaseFactory {
}
Database server = createInternal(config);
if (config.isRegister()) {
if (config.isDefaultServer()) {
if (defaultServerName != null && !defaultServerName.equals(config.getName())) {
throw new IllegalStateException("Registering [" + config.getName() + "] as the default server but [" + defaultServerName + "] is already registered as the default");
}
defaultServerName = config.getName();
}
DbPrimary.setSkip(true);
DbContext.getInstance().register(server, config.isDefaultServer());
}
@@ -149,7 +149,7 @@ public interface DocumentStore {
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEachWhile(new Predicate<Order>() {
* Override
* @Override
* public void accept(Order bean) {
* // process the bean
*
@@ -53,6 +53,17 @@ public interface DtoQuery<T> {
*/
void findEach(Consumer<T> consumer);
/**
* Execute the query iterating the results and batching them for the consumer.
* <p>
* This runs like findEach streaming results from the database but just collects the results
* into batches to pass to the consumer.
*
* @param batch The number of dto beans to collect before given them to the consumer
* @param consumer The consumer to process the batch of DTO beans
*/
void findEach(int batch, Consumer<List<T>> consumer);
/**
* Execute the query iterating a row at a time with the ability to stop consuming part way through.
* <p>
+1 -1
View File
@@ -346,7 +346,7 @@ public final class Ebean {
* <pre>{@code
* public class Order { ...
*
* OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* List<OrderDetail> details;
* ...
* }
@@ -200,7 +200,7 @@ public interface ExpressionFactory {
Expression gtOrNull(String propertyName, Object value);
/**
* Greater than or Equal to OR Null <code> >= or null </code>
* Greater than or Equal to OR Null ({@code >= or null })
* <p>
* A convenient expression combining GE and Is Null. Most often useful for range
* expressions where the top range value is nullable.
@@ -227,7 +227,7 @@ public interface ExpressionFactory {
Expression ltOrNull(String propertyName, Object value);
/**
* Less Than or Equal to OR Null <code> <= or null </code>
* Less Than or Equal to OR Null ({@code <= or null })
* <p>
* A convenient expression combining LE and Is Null. Most often useful for range
* expressions where the bottom range value is nullable.
@@ -196,13 +196,6 @@ public interface ExpressionList<T> {
*/
Query<T> forUpdate();
/**
* Deprecated - migrate to withLock().
* Execute using "for update" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdate(Query.LockType lockType);
/**
* Execute using "for update" clause with No Wait option.
* <p>
@@ -211,13 +204,6 @@ public interface ExpressionList<T> {
*/
Query<T> forUpdateNoWait();
/**
* Deprecated - migrate to withLock().
* Execute using "for update nowait" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdateNoWait(Query.LockType lockType);
/**
* Execute using "for update" clause with Skip Locked option.
* <p>
@@ -226,13 +212,6 @@ public interface ExpressionList<T> {
*/
Query<T> forUpdateSkipLocked();
/**
* Deprecated - migrate to withLock().
* Execute using "for update skip locked" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdateSkipLocked(Query.LockType lockType);
/**
* Execute the query including soft deleted rows.
*/
@@ -328,6 +307,13 @@ public interface ExpressionList<T> {
*/
void findEach(Consumer<T> consumer);
/**
* Execute findEach with a batch consumer.
*
* @see Query#findEach(int, Consumer)
*/
void findEach(int batch, Consumer<List<T>> consumer);
/**
* Execute the query processing the beans one at a time with the ability to
* stop processing before reading all the beans.
@@ -930,7 +916,7 @@ public interface ExpressionList<T> {
ExpressionList<T> gtOrNull(String propertyName, Object value);
/**
* Greater Than or Equal to OR Null - <code> >= or null </code>.
* Greater Than or Equal to OR Null - ({@code >= or null }).
*/
ExpressionList<T> geOrNull(String propertyName, Object value);
@@ -951,7 +937,7 @@ public interface ExpressionList<T> {
ExpressionList<T> ltOrNull(String propertyName, Object value);
/**
* Less Than or Equal to OR Null - <code> <= or null </code>.
* Less Than or Equal to OR Null - ({@code <= or null }).
*/
ExpressionList<T> leOrNull(String propertyName, Object value);
@@ -1685,7 +1671,7 @@ public interface ExpressionList<T> {
ExpressionList<T> endAnd();
/**
* End a AND junction - synonym for endJunction().
* End a OR junction - synonym for endJunction().
*/
ExpressionList<T> endOr();
@@ -159,6 +159,13 @@ public interface ExtendedServer {
*/
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
/**
* Execute findEach with batch consumer.
*
* @see Query#findEach(int, Consumer)
*/
<T> void findEach(Query<T> query, int batch, Consumer<List<T>> consumer, Transaction t);
/**
* Execute the query visiting the each bean one at a time.
* <p>
+138 -185
View File
@@ -3,30 +3,13 @@ package io.ebean;
import java.io.Serializable;
/**
* Defines the configuration options for a "query fetch" or a
* "lazy loading fetch". This gives you the ability to use multiple smaller
* queries to populate an object graph as opposed to a single large query.
* <p>
* The primary goal is to provide efficient ways of loading complex object
* graphs avoiding SQL Cartesian product and issues around populating object
* graphs that have multiple *ToMany relationships.
* </p>
* <p>
* It also provides the ability to control the lazy loading queries (batch size,
* selected properties and fetches) to avoid N+1 queries etc.
* <p>
* There can also be cases loading across a single OneToMany where 2 SQL queries
* using Ebean FetchConfig.query() can be more efficient than one SQL query.
* When the "One" side is wide (lots of columns) and the cardinality difference
* is high (a lot of "Many" beans per "One" bean) then this can be more
* efficient loaded as 2 SQL queries.
* </p>
* Defines how a relationship is fetched via either normal SQL join,
* a eager secondary query, via lazy loading or via eagerly hitting L2 cache.
* <p>
* <pre>{@code
* // Normal fetch join results in a single SQL query
* List<Order> list = DB.find(Order.class).fetch("details").findList();
*
* // Find Orders join details using a single SQL query
* }</pre>
* <p>
* Example: Using a "query join" instead of a "fetch join" we instead use 2 SQL queries
@@ -37,103 +20,13 @@ import java.io.Serializable;
* // This will use 2 SQL queries to build this object graph
* List<Order> list =
* DB.find(Order.class)
* .fetch("details", new FetchConfig().query())
* .fetch("details", FetchConfig.ofQuery())
* .findList();
*
* // query 1) find order
* // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
*
* }</pre>
* <p>
* Example: Using 2 "query joins"
* </p>
* <p>
* <pre>{@code
*
* // This will use 3 SQL queries to build this object graph
* List<Order> list =
* DB.find(Order.class)
* .fetch("details", new FetchConfig().query())
* .fetch("customer", new FetchConfig().queryFirst(5))
* .findList();
*
* // query 1) find order
* // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
* // query 3) find customer where id in (?,?,?,?,?) // first 5 customers
*
* }</pre>
* <p>
* Example: Using "query joins" and partial objects
* </p>
* <p>
*
* <pre>{@code
* // This will use 3 SQL queries to build this object graph
* List<Order> list =
* DB.find(Order.class)
* .select("status, shipDate")
* .fetch("details", "quantity, price", new FetchConfig().query())
* .fetch("details.product", "sku, name")
* .fetch("customer", "name", new FetchConfig().queryFirst(5))
* .fetch("customer.contacts")
* .fetch("customer.shippingAddress")
* .findList();
*
* // query 1) find order (status, shipDate)
* // query 2) find orderDetail (quantity, price) fetch product (sku, name) where
* // order.id in (?,? ...)
* // query 3) find customer (name) fetch contacts (*) fetch shippingAddress (*)
* // where id in (?,?,?,?,?)
*
* // Note: the fetch of "details.product" is automatically included into the
* // fetch of "details"
* //
* // Note: the fetch of "customer.contacts" and "customer.shippingAddress"
* // are automatically included in the fetch of "customer"
* }</pre>
* <p>
* You can use query() and lazy together on a single join. The query is executed
* immediately and the lazy defines the batch size to use for further lazy
* loading (if lazy loading is invoked).
* </p>
* <p>
* <pre>{@code
*
* List<Order> list =
* DB.find(Order.class)
* .fetch("customer", new FetchConfig().query(10).lazy(5))
* .findList();
*
* // query 1) find order
* // query 2) find customer where id in (?,?,?,?,?,?,?,?,?,?) // first 10 customers
* // .. then if lazy loading of customers is invoked
* // .. use a batch size of 5 to load the customers
*
* }</pre>
* <p>
* <p>
* Example of controlling the lazy loading query:
* </p>
* <p>
* This gives us the ability to optimise the lazy loading query for a given use
* case.
* </p>
* <p>
* <pre>{@code
*
* List<Order> list = DB.find(Order.class)
* .fetch("customer","name", new FetchConfig().lazy(5))
* .fetch("customer.contacts","contactName, phone, email")
* .fetch("customer.shippingAddress")
* .where().eq("status",Order.Status.NEW)
* .findList();
*
* // query 1) find order where status = Order.Status.NEW
* //
* // .. if lazy loading of customers is invoked
* // .. use a batch size of 5 to load the customers
*
* }</pre>
*
* @author mario
* @author rbygrave
@@ -142,149 +35,209 @@ public class FetchConfig implements Serializable {
private static final long serialVersionUID = 1L;
private int lazyBatchSize = -1;
private static final int JOIN_MODE = 0;
private static final int QUERY_MODE = 1;
private static final int LAZY_MODE = 2;
private static final int CACHE_MODE = 3;
private int queryBatchSize = -1;
private boolean queryAll;
private boolean cache;
private int mode;
private int batchSize;
private int hashCode;
/**
* Construct the fetch configuration object.
* Deprecated - migrate to one of the static factory methods like {@link FetchConfig#ofQuery()}
* <p>
* Construct using default JOIN mode.
*/
@Deprecated
public FetchConfig() {
//this.mode = JOIN_MODE;
this.batchSize = 100;
this.hashCode = 1000;
}
private FetchConfig(int mode, int batchSize) {
this.mode = mode;
this.batchSize = batchSize;
this.hashCode = mode + 10 * batchSize;
}
/**
* Specify that this path should be lazy loaded using the default batch load
* size.
* Return FetchConfig to eagerly fetch the relationship using L2 cache.
* <p>
* Any cache misses will be loaded by secondary query to the database.
*/
public static FetchConfig ofCache() {
return new FetchConfig(CACHE_MODE, 100);
}
/**
* Return FetchConfig to eagerly fetch the relationship using a secondary query.
*/
public static FetchConfig ofQuery() {
return new FetchConfig(QUERY_MODE, 100);
}
/**
* Return FetchConfig to eagerly fetch the relationship using a secondary with a given batch size.
*/
public static FetchConfig ofQuery(int batchSize) {
return new FetchConfig(QUERY_MODE, batchSize);
}
/**
* Return FetchConfig to lazily load the relationship.
*/
public static FetchConfig ofLazy() {
return new FetchConfig(LAZY_MODE, 0);
}
/**
* Return FetchConfig to lazily load the relationship specifying the batch size.
*/
public static FetchConfig ofLazy(int batchSize) {
return new FetchConfig(LAZY_MODE, batchSize);
}
/**
* Return FetchConfig to fetch the relationship using SQL join.
*/
public static FetchConfig ofDefault() {
return new FetchConfig(JOIN_MODE, 100);
}
/**
* We want to migrate away from mutating FetchConfig to a fully immutable FetchConfig.
*/
private FetchConfig mutate(int mode, int batchSize) {
if (batchSize < 0) {
throw new IllegalArgumentException("batch size " + batchSize + " must be > 0");
}
this.mode = mode;
this.batchSize = batchSize;
this.hashCode = mode + 10 * batchSize;
return this;
}
/**
* Deprecated - migrate to FetchConfig.ofLazy().
*/
@Deprecated
public FetchConfig lazy() {
this.lazyBatchSize = 0;
this.queryAll = false;
return this;
return mutate(LAZY_MODE, 0);
}
/**
* Specify that this path should be lazy loaded with a specified batch size.
*
* @param lazyBatchSize the batch size for lazy loading
* Deprecated - migrate to FetchConfig.ofLazy(batchSize).
*/
public FetchConfig lazy(int lazyBatchSize) {
this.lazyBatchSize = lazyBatchSize;
this.queryAll = false;
return this;
@Deprecated
public FetchConfig lazy(int batchSize) {
return mutate(LAZY_MODE, batchSize);
}
/**
* Deprecated - migrate to FetchConfig.ofQuery().
* <p>
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
* <p>
* This will use the default batch size for separate query which is 100.
* </p>
*/
@Deprecated
public FetchConfig query() {
this.queryBatchSize = 0;
this.queryAll = true;
return this;
}
/**
* Eagerly fetch the beans fetching the beans from the L2 bean cache
* and using the DB for beans not in the cache.
*/
public FetchConfig cache() {
this.cache = true;
this.queryBatchSize = 0;
this.queryAll = true;
return this;
return mutate(QUERY_MODE, 100);
}
/**
* Deprecated - migrate to FetchConfig.ofQuery(batchSize).
* <p>
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
* <p>
* The queryBatchSize is the number of parent id's that this separate query
* will load per batch.
* </p>
* <p>
* This will load all beans on this path eagerly unless a {@link #lazy(int)}
* is also used.
* </p>
*
* @param queryBatchSize the batch size used to load beans on this path
* @param batchSize the batch size used to load beans on this path
*/
public FetchConfig query(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
// queryAll true as long as a lazy batch size has not already been set
this.queryAll = (lazyBatchSize == -1);
return this;
@Deprecated
public FetchConfig query(int batchSize) {
return mutate(QUERY_MODE, batchSize);
}
/**
* Deprecated - migrate to FetchConfig.ofQuery(batchSize).
* <p>
* Eagerly fetch the first batch of beans on this path.
* This is similar to {@link #query(int)} but only fetches the first batch.
* <p>
* If there are more parent beans than the batch size then they will not be
* loaded eagerly but instead use lazy loading.
* </p>
*
* @param queryBatchSize the number of parent beans this path is populated for
* @param batchSize the number of parent beans this path is populated for
*/
public FetchConfig queryFirst(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
this.queryAll = false;
return this;
@Deprecated
public FetchConfig queryFirst(int batchSize) {
return query(batchSize);
}
/**
* Return the batch size for lazy loading.
* Deprecated - migrate to FetchConfig.ofCache().
* <p>
* Eagerly fetch the beans fetching the beans from the L2 bean cache
* and using the DB for beans not in the cache.
*/
public int getLazyBatchSize() {
return lazyBatchSize;
@Deprecated
public FetchConfig cache() {
return mutate(CACHE_MODE, 100);
}
/**
* Return the batch size for separate query load.
* Return the batch size for fetching.
*/
public int getQueryBatchSize() {
return queryBatchSize;
public int getBatchSize() {
return batchSize;
}
/**
* Return true if the query fetch should fetch 'all' rather than just the
* 'first' batch.
*/
public boolean isQueryAll() {
return queryAll;
}
/**
* Return true if this uses L2 bean cache.
* Return true if the fetch should use the L2 cache.
*/
public boolean isCache() {
return cache;
return mode == CACHE_MODE;
}
/**
* Return true if the fetch should be a eager secondary query.
*/
public boolean isQuery() {
return mode == QUERY_MODE;
}
/**
* Return true if the fetch should be a lazy query.
*/
public boolean isLazy() {
return mode == LAZY_MODE;
}
/**
* Return true if the fetch should try to use SQL join.
*/
public boolean isJoin() {
return mode == JOIN_MODE;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FetchConfig that = (FetchConfig) o;
if (lazyBatchSize != that.lazyBatchSize) return false;
if (queryBatchSize != that.queryBatchSize) return false;
if (cache != that.cache) return false;
return queryAll == that.queryAll;
return (hashCode == ((FetchConfig) o).hashCode);
}
@Override
public int hashCode() {
int result = lazyBatchSize;
result = 92821 * result + queryBatchSize;
result = 92821 * result + (queryAll ? 1 : 0);
result = 92821 * result + (cache ? 1 : 0);
return result;
return hashCode;
}
}
+2 -2
View File
@@ -37,7 +37,7 @@ import java.util.List;
* }
* }
*
* Entity
* @Entity
* public class Customer extends BaseModel {
*
* public static final CustomerFinder find = new CustomerFinder();
@@ -80,7 +80,7 @@ public class Finder<I, T> {
* // ... add extra customer specific finder methods
* }
*
* Entity
* @Entity
* public class Customer extends BaseModel {
*
* public static final CustomerFinder find = new CustomerFinder();
+6 -6
View File
@@ -32,16 +32,16 @@ import io.ebean.bean.EntityBean;
* // Typically there is a common base model that has some
* // common properties like the ones below
*
* MappedSuperclass
* @MappedSuperclass
* public class BaseModel extends Model {
*
* Id Long id;
* @Id Long id;
*
* Version Long version;
* @Version Long version;
*
* WhenCreated Timestamp whenCreated;
* @WhenCreated Timestamp whenCreated;
*
* WhenUpdated Timestamp whenUpdated;
* @WhenUpdated Timestamp whenUpdated;
*
* ...
* }
@@ -52,7 +52,7 @@ import io.ebean.bean.EntityBean;
*
* // Extend the mappedSuperclass
*
* Entity Table(name="o_account")
* @Entity @Table(name="o_account")
* public class Customer extends BaseModel {
*
* String name;
+1 -1
View File
@@ -18,7 +18,7 @@ import java.util.List;
*
* // where a bean is annotated with a complex
* // natural key made of several properties
* Cache(naturalKey = {"store","code","sku"})
* @Cache(naturalKey = {"store","code","sku"})
*
*
* Pairs pairs = new Pairs("sku", "code");
+24 -27
View File
@@ -514,7 +514,7 @@ public interface Query<T> {
* </p>
* <pre>{@code
*
* fetch(path, fetchProperties, new FetchConfig().query())
* fetch(path, fetchProperties, FetchConfig.ofQuery())
*
* }</pre>
* <p>
@@ -548,7 +548,7 @@ public interface Query<T> {
* </p>
* <pre>{@code
*
* fetch(path, fetchProperties, new FetchConfig().lazy())
* fetch(path, fetchProperties, FetchConfig.ofLazy())
*
* }</pre>
* <p>
@@ -573,7 +573,7 @@ public interface Query<T> {
* // fetch customers (their id, name and status)
* List<Customer> customers = DB.find(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName,lastName,email", new FetchConfig().lazy(10))
* .fetch("contacts", "firstName,lastName,email", FetchConfig.ofLazy(10))
* .findList();
*
* }</pre>
@@ -610,7 +610,7 @@ public interface Query<T> {
* </p>
* <pre>{@code
*
* fetch(path, new FetchConfig().query())
* fetch(path, FetchConfig.ofQuery())
*
* }</pre>
* <p>
@@ -639,7 +639,7 @@ public interface Query<T> {
* </p>
* <pre>{@code
*
* fetch(path, new FetchConfig().lazy())
* fetch(path, FetchConfig.ofLazy())
*
* }</pre>
* <p>
@@ -662,7 +662,7 @@ public interface Query<T> {
* // fetch customers (their id, name and status)
* List<Customer> customers = DB.find(Customer.class)
* // lazy fetch contacts with a batch size of 100
* .fetch("contacts", new FetchConfig().lazy(100))
* .fetch("contacts", FetchConfig.ofLazy(100))
* .findList();
*
* }</pre>
@@ -810,7 +810,7 @@ public interface Query<T> {
* </p>
* <p>
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the Consumer interface which is better suited to use with Java8 closures.
* iterator uses the Consumer interface which is better suited to use with closures.
* </p>
* <pre>{@code
*
@@ -829,6 +829,21 @@ public interface Query<T> {
*/
void findEach(Consumer<T> consumer);
/**
* Execute findEach streaming query batching the results for consuming.
* <p>
* This query execution will stream the results and is suited to consuming
* large numbers of results from the database.
* <p>
* Typically we use this batch consumer when we want to do further processing on
* the beans and want to do that processing in batch form, for example - 100 at
* a time.
*
* @param batch The number of beans processed in the batch
* @param consumer Process the batch of beans
*/
void findEach(int batch, Consumer<List<T>> consumer);
/**
* Execute the query using callbacks to a visitor to process the resulting
* beans one at a time.
@@ -839,12 +854,12 @@ public interface Query<T> {
* </p>
* <p>
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the Predicate (SAM) interface which is better suited to use with Java8 closures.
* iterator uses the Predicate interface which is better suited to use with closures.
* </p>
* <pre>{@code
*
* DB.find(Customer.class)
* .fetch("contacts", new FetchConfig().query(2))
* .fetchQuery("contacts")
* .where().eq("status", Status.NEW)
* .order().asc("id")
* .setMaxRows(2000)
@@ -1673,12 +1688,6 @@ public interface Query<T> {
*/
Query<T> forUpdate();
/**
* Execute using "for update" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdate(LockType lockType);
/**
* Execute using "for update" clause with "no wait" option.
* <p>
@@ -1688,12 +1697,6 @@ public interface Query<T> {
*/
Query<T> forUpdateNoWait();
/**
* Execute using "for update nowait" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdateNoWait(LockType lockType);
/**
* Execute using "for update" clause with "skip locked" option.
* <p>
@@ -1703,12 +1706,6 @@ public interface Query<T> {
*/
Query<T> forUpdateSkipLocked();
/**
* Execute using "for update skip locked" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdateSkipLocked(LockType lockType);
/**
* Return true if this query has forUpdate set.
*/
+5 -5
View File
@@ -41,14 +41,14 @@ package io.ebean;
* <h3>Example OrderAggregate</h3>
* <pre>{@code
* ...
* // Sql indicates to that this bean
* // @Sql indicates to that this bean
* // is based on RawSql rather than a table
*
* Entity
* Sql
* @Entity
* @Sql
* public class OrderAggregate {
*
* OneToOne
* @OneToOne
* Order order;
*
* Double totalAmount;
@@ -107,7 +107,7 @@ package io.ebean;
*
* List<OrderAggregate> orders = DB.find(OrderAggregate.class)
* .setRawSql(rawSql)
* .fetch("order", "status,orderDate", new FetchConfig().query())
* .fetch("order", "status,orderDate", FetchConfig.ofQuery())
* .fetch("order.customer", "name")
* .where().gt("order.id", 0)
* .having().gt("totalAmount", 20)
@@ -22,7 +22,7 @@ import java.sql.SQLException;
* //
* class CustomerMapper implements RowMapper<CustomerDto> {
*
* Override
* @Override
* public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
*
* long id = rset.getLong(1);
@@ -59,6 +59,14 @@ public interface Transaction extends AutoCloseable {
*/
void register(TransactionCallback callback);
/**
* EXPERIMENTAL - turn on automatic persistence of dirty beans and batchMode true.
* <p>
* With this turned on beans that are dirty in the persistence context
* are automatically persisted on flush() and commit().
*/
void setAutoPersistUpdates(boolean autoPersistUpdates);
/**
* Set a label on the transaction.
* <p>
@@ -339,15 +347,12 @@ public interface Transaction extends AutoCloseable {
* The batch is automatically flushed when it hits the batch size and also when we
* execute queries or when we mix UpdateSql and CallableSql with save and delete of
* beans.
* </p>
* <p>
* We use {@link #flush()} to explicitly flush the batch and we can use
* {@link #setFlushOnQuery(boolean)} and {@link #setFlushOnMixed(boolean)}
* to control the automatic flushing behaviour.
* </p>
* <p>
* Example: batch processing of CallableSql executing every 10 rows
* </p>
*
* <pre>{@code
*
@@ -392,14 +397,11 @@ public interface Transaction extends AutoCloseable {
* <p>
* This only takes effect when batch mode on the transaction has not already meant that
* JDBC batch mode is being used.
* </p>
* <p>
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
* </p>
* <p>
* This is effectively already turned on for all platforms apart from older Sql Server.
* </p>
*
* @param batchMode the batch mode to use per save(), insert(), update() or delete()
* @see io.ebean.config.DatabaseConfig#setPersistBatchOnCascade(PersistBatch)
@@ -422,15 +424,19 @@ public interface Transaction extends AutoCloseable {
int getBatchSize();
/**
* Specify if you want batched inserts to use getGeneratedKeys.
* Specify if we want batched inserts to use getGeneratedKeys.
* <p>
* By default batched inserts will try to use getGeneratedKeys if it is
* supported by the underlying jdbc driver and database.
* </p>
* <p>
* You may want to turn getGeneratedKeys off when you are inserting a large
* number of objects and you don't care about getting back the ids.
* </p>
* We want to turn off getGeneratedKeys when we are inserting a large
* number of objects and we don't care about getting back the ids. In this
* way we avoid the extra cost of getting back the generated id values
* from the database.
* <p>
* Note that when we do turn off getGeneratedKeys then we have the limitation
* that after a bean has been inserted we are unable to then mutate the bean
* and update it in the same transaction as we have not obtained it's id value.
*/
void setGetGeneratedKeys(boolean getGeneratedKeys);
@@ -449,13 +455,11 @@ public interface Transaction extends AutoCloseable {
* <p>
* If you want to execute both WITHOUT having the batch automatically flush
* you need to call this with batchFlushOnMixed = false.
* </p>
* <p>
* Note that UpdateSql and CallableSql are ALWAYS executed first (before the
* beans are executed). This is because the UpdateSql and CallableSql have
* already been bound to their PreparedStatements. The beans on the other hand
* have a 2 step process (delayed binding).
* </p>
*/
void setFlushOnMixed(boolean batchFlushOnMixed);
@@ -473,7 +477,6 @@ public interface Transaction extends AutoCloseable {
* <p>
* Calling this method with batchFlushOnQuery = false means that you can
* execute a query and the batch will not be automatically flushed.
* </p>
*/
void setFlushOnQuery(boolean batchFlushOnQuery);
@@ -490,7 +493,6 @@ public interface Transaction extends AutoCloseable {
* should be flushed prior to executing a query.
* <p>
* The default is for this to be true.
* </p>
*/
boolean isFlushOnQuery();
@@ -507,7 +509,6 @@ public interface Transaction extends AutoCloseable {
* flush the batch if you like.
* <p>
* Flushing occurs automatically when:
* </p>
* <ul>
* <li>the batch size is reached</li>
* <li>A query is executed on the same transaction</li>
@@ -533,11 +534,9 @@ public interface Transaction extends AutoCloseable {
* commit() rollback() and end() methods on the Transaction should still be
* used. Calling these methods on the Connection would be a big no no unless
* you know what you are doing.
* </p>
* <p>
* Examples of when a developer may wish to use the connection directly are:
* Savepoints, advanced CLOB BLOB use and advanced stored procedure calls.
* </p>
*/
Connection getConnection();
@@ -545,17 +544,14 @@ public interface Transaction extends AutoCloseable {
* Add table modification information to the TransactionEvent.
* <p>
* Use this in conjunction with getConnection() and raw JDBC.
* </p>
* <p>
* This effectively informs Ebean of the data that has been changed by the
* transaction and this information is normally automatically handled by Ebean
* when you save entity beans or use UpdateSql etc.
* </p>
* <p>
* If you use raw JDBC then you can use this method to inform Ebean for the
* tables that have been modified. Ebean uses this information to keep its
* caches in synch and maintain text indexes.
* </p>
*/
void addModification(String tableName, boolean inserts, boolean updates, boolean deletes);
@@ -2,6 +2,7 @@ package io.ebean;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxIsolation;
import io.ebean.annotation.TxOption;
import io.ebean.annotation.TxType;
import java.util.ArrayList;
@@ -33,6 +34,8 @@ public final class TxScope {
private TxIsolation isolation;
private TxOption autoPersistUpdates;
private PersistBatch batch;
private PersistBatch batchOnCascade;
@@ -123,6 +126,13 @@ public final class TxScope {
+ "] serverName[" + serverName + "] rollbackFor[" + rollbackFor + "] noRollbackFor[" + noRollbackFor + "]";
}
/**
* Return the AutoPersistUpdates mode as a nullable Boolean.
*/
public Boolean getAutoPersistUpdates() {
return autoPersistUpdates == null ? null : autoPersistUpdates.asBoolean();
}
/**
* Return true if PersistBatch has been set.
*/
@@ -176,6 +186,14 @@ public final class TxScope {
return this;
}
/**
* Set the autoPersistUpdates mode.
*/
public TxScope setAutoPersistUpdates(TxOption autoPersistUpdates) {
this.autoPersistUpdates = autoPersistUpdates;
return this;
}
/**
* Return the transaction profile id.
*/
+6 -6
View File
@@ -13,23 +13,23 @@ package io.ebean;
* </p>
* <pre>{@code
* ...
* NamedUpdates(value = {
* NamedUpdate(
* @NamedUpdates(value = {
* @NamedUpdate(
* name = "setTitle",
* notifyCache = false,
* update = "update topic set title = :title, postCount = :count where id = :id"),
* NamedUpdate(
* @NamedUpdate(
* name = "setPostCount",
* notifyCache = false,
* update = "update f_topic set post_count = :postCount where id = :id"),
* NamedUpdate(
* @NamedUpdate(
* name = "incrementPostCount",
* notifyCache = false,
* update = "update Topic set postCount = postCount + 1 where id = :id")
* //update = "update f_topic set post_count = post_count + 1 where id = :id")
* })
* Entity
* Table(name = "f_topic")
* @Entity
* @Table(name = "f_topic")
* public class Topic {
* ...
* }
@@ -1,5 +1,7 @@
package io.ebean.bean;
import java.util.List;
/**
* Holds entity beans by there type and id.
* <p>
@@ -77,6 +79,11 @@ public interface PersistenceContext {
*/
boolean resetLimit();
/**
* Return the list of dirty beans held by this persistence context.
*/
List<Object> dirtyBeans();
/**
* Wrapper on a bean to also indicate if a bean has been deleted.
* <p>
@@ -87,7 +87,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public void internalAddWithCheck(Object bean) {
if (list == null || !containsInstance(bean)) {
if (list == null || bean == null || !containsInstance(bean)) {
internalAdd(bean);
}
}
@@ -77,7 +77,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
}
public void internalPutWithCheck(Object key, Object bean) {
if (map == null || !map.containsKey(key)) {
if (map == null || key == null || !map.containsKey(key)) {
internalPut(key, bean);
}
}
@@ -70,9 +70,8 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public void internalAddWithCheck(Object bean) {
if (set == null || !set.contains(bean)) {
internalAdd(bean);
}
// set add() already de-dups so just add it
internalAdd(bean);
}
@Override
@@ -7,6 +7,8 @@ import javax.persistence.DiscriminatorValue;
import javax.persistence.Inheritance;
import javax.persistence.Table;
import static io.ebean.util.StringHelper.isNull;
/**
* Provides some base implementation for NamingConventions.
*
@@ -78,10 +80,16 @@ public abstract class AbstractNamingConvention implements NamingConvention {
@Override
public String getSequenceName(String rawTableName, String pkColumn) {
final String tableNameUnquoted = databasePlatform.unQuote(rawTableName);
TableName tableName = new TableName(rawTableName);
String seqName = seqName(pkColumn, tableName.getName());
return tableName.withCatalogAndSchema(seqName);
}
private String seqName(String pkColumn, String tableName) {
final String tableNameUnquoted = unQuote(tableName);
String seqName = sequenceFormat.replace("{table}", tableNameUnquoted);
pkColumn = (pkColumn == null) ? "" : databasePlatform.unQuote(pkColumn);
return seqName.replace("{column}", pkColumn);
pkColumn = (pkColumn == null) ? "" : unQuote(pkColumn);
return quoteIdentifiers(seqName.replace("{column}", pkColumn));
}
/**
@@ -216,15 +224,13 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|| AnnotationUtil.has(supCls, DiscriminatorValue.class);
}
@Override
public TableName getM2MJoinTableName(TableName lhsTable, TableName rhsTable) {
StringBuilder buffer = new StringBuilder();
buffer.append(lhsTable.getName());
buffer.append(unQuote(lhsTable.getName()));
buffer.append("_");
String rhsTableName = rhsTable.getName();
String rhsTableName = unQuote(rhsTable.getName());
if (rhsTableName.indexOf('_') < rhsPrefixLength) {
// trim off a xx_ prefix if there is one
rhsTableName = rhsTableName.substring(rhsTableName.indexOf('_') + 1);
@@ -238,7 +244,13 @@ public abstract class AbstractNamingConvention implements NamingConvention {
buffer.setLength(maxTableNameLength);
}
return new TableName(lhsTable.getCatalog(), lhsTable.getSchema(), buffer.toString());
String tableName = quoteIdentifiers(buffer.toString());
return new TableName(lhsTable.getCatalog(), lhsTable.getSchema(), tableName);
}
@Override
public String deriveM2MColumn(String tableName, String dbColumn) {
return quoteIdentifiers(unQuote(tableName) +"_" + unQuote(dbColumn));
}
/**
@@ -255,14 +267,29 @@ public abstract class AbstractNamingConvention implements NamingConvention {
return null;
}
@Override
public String getTableName(String catalog, String schema, String name) {
StringBuilder sb = new StringBuilder();
if (!isNull(catalog)) {
sb.append(quoteIdentifiers(catalog)).append(".");
}
if (!isNull(schema)) {
sb.append(quoteIdentifiers(schema)).append(".");
}
return sb.append(quoteIdentifiers(name)).toString();
}
/**
* Replace back ticks (if they are used) with database platform specific
* quoted identifiers.
* Replace back ticks (if they are used) with database platform specific quoted identifiers.
*/
protected String quoteIdentifiers(String s) {
return databasePlatform.convertQuotedIdentifiers(s);
}
private String unQuote(String val) {
return databasePlatform.unQuote(val);
}
/**
* Checks string is null or empty .
*/
@@ -40,6 +40,13 @@ public class ClassLoadConfig {
return isPresent("javax.validation.constraints.NotNull");
}
/**
* Return true if jakarta validation annotations like Size and NotNull are present.
*/
public boolean isJakartaValidationAnnotationsPresent() {
return isPresent("jakarta.validation.constraints.NotNull");
}
/**
* Return true if javax PostConstruct annotation is present (maybe not in java9).
* If not we don't support PostConstruct lifecycle events.
@@ -232,6 +232,12 @@ public class DatabaseConfig {
*/
private String historyTableSuffix = "_history";
/**
* When true explicit transactions beans that have been made dirty will be
* automatically persisted via update on flush.
*/
private boolean autoPersistUpdates;
/**
* Use for transaction scoped batch mode.
*/
@@ -489,7 +495,7 @@ public class DatabaseConfig {
* Should the javax.validation.constraints.NotNull enforce a notNull column in DB.
* If set to false, use io.ebean.annotation.NotNull or Column(nullable=true).
*/
private boolean useJavaxValidationNotNull = true;
private boolean useValidationNotNull = true;
/**
* Generally we want to perform L2 cache notification in the background and not impact
@@ -498,14 +504,23 @@ public class DatabaseConfig {
private boolean notifyL2CacheInForeground;
/**
* Set to true to support query plan capture.
* Set to true to enable bind capture required for query plan capture.
*/
private boolean collectQueryPlans;
private boolean queryPlanEnable;
/**
* The default threshold in micros for collecting query plans.
*/
private long collectQueryPlanThresholdMicros = Long.MAX_VALUE;
private long queryPlanThresholdMicros = Long.MAX_VALUE;
/**
* Set to true to enable automatic periodic query plan capture.
*/
private boolean queryPlanCapture;
private long queryPlanCapturePeriodSecs = 60 * 10; // 10 minutes
private long queryPlanCaptureMaxTimeMillis = 10_000; // 10 seconds
private int queryPlanCaptureMaxCount = 10;
private QueryPlanListener queryPlanListener;
/**
* The time in millis used to determine when a query is alerted for being slow.
@@ -896,6 +911,20 @@ public class DatabaseConfig {
this.tenantCatalogProvider = tenantCatalogProvider;
}
/**
* Return true if dirty beans are automatically persisted.
*/
public boolean isAutoPersistUpdates() {
return autoPersistUpdates;
}
/**
* Set to true if dirty beans are automatically persisted.
*/
public void setAutoPersistUpdates(boolean autoPersistUpdates) {
this.autoPersistUpdates = autoPersistUpdates;
}
/**
* Return the PersistBatch mode to use by default at the transaction level.
* <p>
@@ -1045,7 +1074,6 @@ public class DatabaseConfig {
* This is a performance optimisation to reduce the number times Ebean
* requests a sequence to be used as an Id for a bean (aka reduce network
* chatter).
*/
public void setDatabaseSequenceBatchSize(int databaseSequenceBatchSize) {
platformConfig.setDatabaseSequenceBatchSize(databaseSequenceBatchSize);
@@ -2693,7 +2721,10 @@ public class DatabaseConfig {
}
/**
* Load settings from ebean.properties.
* Load settings from application.properties, application.yaml and other sources.
* <p>
* Uses <code>avaje-config</code> to load configuration properties. Goto https://avaje.io/config
* for detail on how and where properties are loaded from.
*/
public void loadFromProperties() {
this.properties = Config.asProperties();
@@ -2795,21 +2826,27 @@ public class DatabaseConfig {
}
loadDocStoreSettings(p);
defaultServer = p.getBoolean("defaultServer", defaultServer);
autoPersistUpdates = p.getBoolean("autoPersistUpdates", autoPersistUpdates);
loadModuleInfo = p.getBoolean("loadModuleInfo", loadModuleInfo);
maxCallStack = p.getInt("maxCallStack", maxCallStack);
dumpMetricsOnShutdown = p.getBoolean("dumpMetricsOnShutdown", dumpMetricsOnShutdown);
dumpMetricsOptions = p.get("dumpMetricsOptions", dumpMetricsOptions);
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
collectQueryPlans = p.getBoolean("collectQueryPlans", collectQueryPlans);
collectQueryPlanThresholdMicros = p.getLong("collectQueryPlanThresholdMicros", collectQueryPlanThresholdMicros);
queryPlanEnable = p.getBoolean("queryPlan.enable", queryPlanEnable);
queryPlanThresholdMicros = p.getLong("queryPlan.thresholdMicros", queryPlanThresholdMicros);
queryPlanCapture = p.getBoolean("queryPlan.capture", queryPlanCapture);
queryPlanCapturePeriodSecs = p.getLong("queryPlan.capturePeriodSecs", queryPlanCapturePeriodSecs);
queryPlanCaptureMaxTimeMillis = p.getLong("queryPlan.captureMaxTimeMillis", queryPlanCaptureMaxTimeMillis);
queryPlanCaptureMaxCount = p.getInt("queryPlan.captureMaxCount", queryPlanCaptureMaxCount);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
localOnlyL2Cache = p.getBoolean("localOnlyL2Cache", localOnlyL2Cache);
enabledL2Regions = p.get("enabledL2Regions", enabledL2Regions);
notifyL2CacheInForeground = p.getBoolean("notifyL2CacheInForeground", notifyL2CacheInForeground);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
useValidationNotNull = p.getBoolean("useValidationNotNull", useValidationNotNull);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
@@ -3056,20 +3093,21 @@ public class DatabaseConfig {
/**
* Returns if we use javax.validation.constraints.NotNull
*/
public boolean isUseJavaxValidationNotNull() {
return useJavaxValidationNotNull;
public boolean isUseValidationNotNull() {
return useValidationNotNull;
}
/**
* Controls if Ebean should ignore <code>&x64;javax.validation.contstraints.NotNull</code>
* Controls if Ebean should ignore <code>&x64;javax.validation.contstraints.NotNull</code> or
* <code>&x64;jakarta.validation.contstraints.NotNull</code>
* with respect to generating a <code>NOT NULL</code> column.
* <p>
* Normally when Ebean sees javax NotNull annotation it means that column is defined as NOT NULL.
* Set this to <code>false</code> and the javax NotNull annotation is effectively ignored (and
* we instead use Ebean's own NotNull annotation or JPA Column(nullable=false) annotation.
*/
public void setUseJavaxValidationNotNull(boolean useJavaxValidationNotNull) {
this.useJavaxValidationNotNull = useJavaxValidationNotNull;
public void setUseValidationNotNull(boolean useValidationNotNull) {
this.useValidationNotNull = useValidationNotNull;
}
/**
@@ -3091,14 +3129,17 @@ public class DatabaseConfig {
}
/**
* Return the query plan time to live.
* Return the time to live for ebean's internal query plan.
*/
public int getQueryPlanTTLSeconds() {
return queryPlanTTLSeconds;
}
/**
* Set the query plan time to live.
* Set the time to live for ebean's internal query plan.
* <p>
* This is the plan that knows how to execute the query, read the result
* and collects execution metrics. By default this is set to 5 mins.
*/
public void setQueryPlanTTLSeconds(int queryPlanTTLSeconds) {
this.queryPlanTTLSeconds = queryPlanTTLSeconds;
@@ -3171,29 +3212,110 @@ public class DatabaseConfig {
/**
* Return true if query plan capture is enabled.
*/
public boolean isCollectQueryPlans() {
return collectQueryPlans;
public boolean isQueryPlanEnable() {
return queryPlanEnable;
}
/**
* Set to true to enable query plan capture.
*/
public void setCollectQueryPlans(boolean collectQueryPlans) {
this.collectQueryPlans = collectQueryPlans;
public void setQueryPlanEnable(boolean queryPlanEnable) {
this.queryPlanEnable = queryPlanEnable;
}
/**
* Return the query plan collection threshold in microseconds.
*/
public long getCollectQueryPlanThresholdMicros() {
return collectQueryPlanThresholdMicros;
public long getQueryPlanThresholdMicros() {
return queryPlanThresholdMicros;
}
/**
* Set the query plan collection threshold in microseconds.
* <p>
* Queries executing slower than this will have bind values captured such that later
* the query plan can be captured and reported.
*/
public void setCollectQueryPlanThresholdMicros(long collectQueryPlanThresholdMicros) {
this.collectQueryPlanThresholdMicros = collectQueryPlanThresholdMicros;
public void setQueryPlanThresholdMicros(long queryPlanThresholdMicros) {
this.queryPlanThresholdMicros = queryPlanThresholdMicros;
}
/**
* Return true if periodic capture of query plans is enabled.
*/
public boolean isQueryPlanCapture() {
return queryPlanCapture;
}
/**
* Set to true to turn on periodic capture of query plans.
*/
public void setQueryPlanCapture(boolean queryPlanCapture) {
this.queryPlanCapture = queryPlanCapture;
}
/**
* Return the frequency to capture query plans.
*/
public long getQueryPlanCapturePeriodSecs() {
return queryPlanCapturePeriodSecs;
}
/**
* Set the frequency in seconds to capture query plans.
*/
public void setQueryPlanCapturePeriodSecs(long queryPlanCapturePeriodSecs) {
this.queryPlanCapturePeriodSecs = queryPlanCapturePeriodSecs;
}
/**
* Return the time after which a capture query plans request will
* stop capturing more query plans.
* <p>
* Effectively this controls the amount of load/time we want to
* allow for query plan capture.
*/
public long getQueryPlanCaptureMaxTimeMillis() {
return queryPlanCaptureMaxTimeMillis;
}
/**
* Set the time after which a capture query plans request will
* stop capturing more query plans.
* <p>
* Effectively this controls the amount of load/time we want to
* allow for query plan capture.
*/
public void setQueryPlanCaptureMaxTimeMillis(long queryPlanCaptureMaxTimeMillis) {
this.queryPlanCaptureMaxTimeMillis = queryPlanCaptureMaxTimeMillis;
}
/**
* Return the max number of query plans captured per request.
*/
public int getQueryPlanCaptureMaxCount() {
return queryPlanCaptureMaxCount;
}
/**
* Set the max number of query plans captured per request.
*/
public void setQueryPlanCaptureMaxCount(int queryPlanCaptureMaxCount) {
this.queryPlanCaptureMaxCount = queryPlanCaptureMaxCount;
}
/**
* Return the listener used to process captured query plans.
*/
public QueryPlanListener getQueryPlanListener() {
return queryPlanListener;
}
/**
* Set the listener used to process captured query plans.
*/
public void setQueryPlanListener(QueryPlanListener queryPlanListener) {
this.queryPlanListener = queryPlanListener;
}
/**
@@ -3247,7 +3369,7 @@ public class DatabaseConfig {
this.loadModuleInfo = loadModuleInfo;
}
public enum UuidVersion {
public enum UuidVersion {
VERSION4,
VERSION1,
VERSION1RND
@@ -15,7 +15,7 @@ package io.ebean.config;
*
* public class EbeanConfigProvider implements DatabaseConfigProvider {
*
* Override
* @Override
* public void apply(DatabaseConfig config) {
*
* // register the entity bean classes explicitly
@@ -39,11 +39,6 @@ public class MatchingNamingConvention extends AbstractNamingConvention {
return new TableName(quoteIdentifiers(getCatalog()), quoteIdentifiers(getSchema()), quoteIdentifiers(beanClass.getSimpleName()));
}
@Override
public String getPropertyFromColumn(Class<?> beanClass, String dbColumnName) {
return dbColumnName;
}
@Override
public String getForeignKey(String prefix, String fkProperty) {
prefix = databasePlatform.unQuote(prefix);
@@ -7,13 +7,8 @@ import java.util.List;
*/
public interface ModuleInfoLoader {
/**
* Return the entity classes to register with the default DB.
*/
List<Class<?>> entityClasses();
/**
* Return entity classes to register for a named DB (not default DB).
*/
List<Class<?>> entityClassesFor(String dbName);
List<Class<?>> classesFor(String dbName, boolean defaultServer);
}
@@ -53,6 +53,16 @@ public interface NamingConvention {
*/
TableName getM2MJoinTableName(TableName lhsTable, TableName rhsTable);
/**
* Derive a DB Column from a FK table and column.
*/
String deriveM2MColumn(String tableName, String dbColumn);
/**
* Return the full table name taking into account quoted identifiers.
*/
String getTableName(String catalog, String schema, String name);
/**
* Return the column name given the property name.
*
@@ -60,18 +70,6 @@ public interface NamingConvention {
*/
String getColumnFromProperty(Class<?> beanClass, String propertyName);
/**
* Return the property name from the column name.
* <p>
* This is used to help mapping of raw SQL queries onto bean properties.
* </p>
*
* @param beanClass the bean class
* @param dbColumnName the db column name
* @return the property name from the column name
*/
String getPropertyFromColumn(Class<?> beanClass, String dbColumnName);
/**
* Return the sequence name given the table name (for DB's that use sequences).
* <p>
@@ -0,0 +1,34 @@
package io.ebean.config;
import io.ebean.Database;
import io.ebean.meta.MetaQueryPlan;
import java.util.List;
/**
* The captured query plans.
*/
public class QueryPlanCapture {
private final Database database;
private final List<MetaQueryPlan> plans;
public QueryPlanCapture(Database database, List<MetaQueryPlan> plans) {
this.database = database;
this.plans = plans;
}
/**
* Return the database the plans were captured for.
*/
public Database getDatabase() {
return database;
}
/**
* Return the captured query plans.
*/
public List<MetaQueryPlan> getPlans() {
return plans;
}
}
@@ -0,0 +1,13 @@
package io.ebean.config;
/**
* EXPERIMENTAL: Listener for captured query plans.
*/
@FunctionalInterface
public interface QueryPlanListener {
/**
* Process the captured query plans.
*/
void process(QueryPlanCapture capture);
}
@@ -16,7 +16,7 @@ package io.ebean.config;
*
* public class EbeanConfigProvider implements ServerConfigProvider {
*
* Override
* @Override
* public void apply(ServerConfig config) {
*
* // register the entity bean classes explicitly
@@ -20,7 +20,7 @@ public final class TableName {
/**
* The name.
*/
private String name;
private final String name;
/**
* Construct with the given catalog schema and table name.
@@ -29,7 +29,6 @@ public final class TableName {
* </p>
*/
public TableName(String catalog, String schema, String name) {
super();
this.catalog = catalog != null ? catalog.trim() : null;
this.schema = schema != null ? schema.trim() : null;
this.name = name != null ? name.trim() : null;
@@ -110,14 +109,11 @@ public final class TableName {
* @return the qualified name
*/
public String getQualifiedName() {
StringBuilder buffer = new StringBuilder();
// Add catalog
if (catalog != null) {
buffer.append(catalog);
}
// Add schema
if (schema != null) {
if (buffer.length() > 0) {
@@ -125,31 +121,27 @@ public final class TableName {
}
buffer.append(schema);
}
if (buffer.length() > 0) {
buffer.append(".");
}
buffer.append(name);
return buffer.toString();
return buffer.append(name).toString();
}
/**
* Append a catalog and schema prefix if they exist to the string builder.
*/
public void appendCatalogAndSchema(StringBuilder buffer) {
if (catalog != null) {
buffer.append(catalog).append(".");
}
public String withCatalogAndSchema(String name) {
if (schema != null) {
buffer.append(schema).append(".");
name = schema + "." + name;
}
if (catalog != null) {
name = catalog + "." + name;
}
return name;
}
/**
* Checks if is table name is valid i.e. it has at least a name.
*
* @return true, if is valid
*/
public boolean isValid() {
return name != null && !name.isEmpty();
@@ -60,18 +60,6 @@ public class UnderscoreNamingConvention extends AbstractNamingConvention {
return toUnderscoreFromCamel(propertyName);
}
/**
* Converts underscore based column name to Camel case property name.
*
* @param beanClass the bean class
* @param dbColumnName the db column name
* @return the property from column
*/
@Override
public String getPropertyFromColumn(Class<?> beanClass, String dbColumnName) {
return toCamelFromUnderscore(dbColumnName);
}
/**
* Return true if the result will be upper case.
* <p>
@@ -79,7 +79,7 @@ public class DbDefaultValue {
}
/**
* This method checks & convert the {@link DbDefault#value()} to a valid SQL literal.
* This method checks and converts the {@link DbDefault#value()} to a valid SQL literal.
*
* This is mainly to quote string literals and verify integer/dates for correctness.
* <p>
@@ -13,6 +13,7 @@ public class Oracle11Platform extends OraclePlatform {
this.platform = Platform.ORACLE11;
this.columnAliasPrefix = "c";
this.sqlLimiter = new OracleRownumSqlLimiter();
this.basicSqlLimiter = new OracleRownumBasicLimiter();
dbIdentity.setIdType(IdType.SEQUENCE);
}
}
@@ -0,0 +1,35 @@
package io.ebean.config.dbplatform.oracle;
import io.ebean.config.dbplatform.BasicSqlLimiter;
/**
* Row limiter for Oracle 9,10,11 using rownum.
*/
public class OracleRownumBasicLimiter implements BasicSqlLimiter {
@Override
public String limit(String dbSql, int firstRow, int maxRows) {
if (firstRow < 1 && maxRows < 1) {
return dbSql;
}
StringBuilder sb = new StringBuilder(60 + dbSql.length());
int lastRow = maxRows;
if (lastRow > 0) {
lastRow += firstRow;
}
sb.append("select * from (select ");
if (maxRows > 0) {
sb.append("/*+ FIRST_ROWS(").append(maxRows).append(") */ ");
}
sb.append("a.*, rownum rn_ from (");
sb.append(dbSql).append(") a ");
if (lastRow > 0) {
sb.append(" where rownum <= ").append(lastRow);
}
sb.append(") ");
if (firstRow > 0) {
sb.append(" where rn_ > ").append(firstRow);
}
return sb.toString();
}
}
@@ -41,6 +41,9 @@ public class QueryPlanRequest {
/**
* Set the maximum number of plans to capture.
* <p>
* Use this to limit how much query plan capturing is done as query
* plan capture is actual database load.
*/
public void setMaxCount(int maxCount) {
this.maxCount = maxCount;
@@ -58,7 +61,9 @@ public class QueryPlanRequest {
/**
* Set the maximum amount of time we want to use to capture plans.
* <p>
* Query plan collection will stop once this time is exceeded.
* Query plan collection will stop once this time is exceeded. We use
* this to ensure the query plan capture does not use excessive amount
* of time - put too much load on the database.
*/
public void setMaxTimeMillis(long maxTimeMillis) {
this.maxTimeMillis = maxTimeMillis;
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.6.7</version>
<version>12.8.3</version>
</parent>
<!-- <parent>-->
<!-- <groupId>org.avaje</groupId>-->
@@ -14,7 +14,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.6.7</tag>
<tag>ebean-parent-12.8.3</tag>
</scm>
<name>ebean autotune</name>
@@ -26,7 +26,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
<scope>provided</scope>
</dependency>
@@ -11,12 +11,9 @@ import org.tests.model.basic.Order;
import static org.assertj.core.api.Assertions.assertThat;
//import org.tests.model.basic.ResetBasicData;
public class ProfileOriginTest extends BaseTestCase {
private BeanDescriptor<Order> desc = getBeanDescriptor(Order.class);
private final BeanDescriptor<Order> desc = getBeanDescriptor(Order.class);
@Test
public void buildDetail() {
@@ -30,7 +27,7 @@ public class ProfileOriginTest extends BaseTestCase {
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString().trim()).isEqualTo("fetch customer (name)");
assertThat(detail.asStringDebug().trim()).isEqualTo("fetch customer (name)");
}
@Test
@@ -49,7 +46,7 @@ public class ProfileOriginTest extends BaseTestCase {
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (name)");
assertThat(detail.asStringDebug()).isEqualTo("select (orderDate) fetch customer (name)");
}
@Test
@@ -67,7 +64,7 @@ public class ProfileOriginTest extends BaseTestCase {
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString().trim()).isEqualTo("select (orderDate,customer)");
assertThat(detail.asStringDebug().trim()).isEqualTo("select (orderDate,customer)");
}
@Test
@@ -90,7 +87,7 @@ public class ProfileOriginTest extends BaseTestCase {
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (billingAddress)");
assertThat(detail.asStringDebug()).isEqualTo("select (orderDate) fetch customer (billingAddress)");
}
@@ -119,7 +116,7 @@ public class ProfileOriginTest extends BaseTestCase {
po.collectUsageInfo(c);
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString()).isEqualTo("fetch customer (name,note) fetch customer.billingAddress (line1)");
assertThat(detail.asStringDebug()).isEqualTo("fetch customer (name,note) fetch customer.billingAddress (line1)");
}
private NodeUsageCollector node(String path) {
+17 -17
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.6.7</version>
<version>12.8.3</version>
</parent>
<name>ebean bom</name>
@@ -18,8 +18,8 @@
<ebean-migration.version>12.4.0</ebean-migration.version>
<ebean-test-docker.version>4.1</ebean-test-docker.version>
<ebean-datasource.version>7.0</ebean-datasource.version>
<ebean-agent.version>12.6.6</ebean-agent.version>
<ebean-maven-plugin.version>12.6.6</ebean-maven-plugin.version>
<ebean-agent.version>12.8.2</ebean-agent.version>
<ebean-maven-plugin.version>12.8.2</ebean-maven-plugin.version>
</properties>
<dependencyManagement>
@@ -81,88 +81,88 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-xml</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-autotune</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
</dependencies>
+3 -8
View File
@@ -4,30 +4,25 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.6.7</version>
<version>12.8.3</version>
</parent>
<artifactId>ebean-core-type</artifactId>
<name>ebean core type</name>
<description>ebean scalar types api</description>
<properties>
<jackson-core.version>2.11.3</jackson-core.version>
<jackson-databind.version>2.11.3</jackson-databind.version>
</properties>
<dependencies>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-core.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
+15 -14
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.6.7</version>
<version>12.8.3</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -15,14 +15,9 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.6.7</tag>
<tag>ebean-parent-12.8.3</tag>
</scm>
<properties>
<jackson-core.version>2.11.3</jackson-core.version>
<jackson-databind.version>2.11.3</jackson-databind.version>
</properties>
<profiles>
<profile>
<!-- Note: to use this profile, you need to download manually the db2jcc4 driver.
@@ -77,7 +72,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.6.1</version>
<version>12.8.4a</version>
<scope>test</scope>
</dependency>
@@ -92,19 +87,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.6.7</version>
<version>12.8.3</version>
</dependency>
<dependency>
@@ -138,6 +133,12 @@
<version>1.1.0.Final</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>joda-time</groupId>
@@ -157,14 +158,14 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-core.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
@@ -301,7 +302,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>12.6.6</version>
<version>12.8.2</version>
<executions>
<execution>
<id>test</id>
@@ -1,52 +1,25 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.expression.IdInExpression;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Used for bean cache lookup with where ids in expression.
* Process Cache lookup by Id(s).
*/
public class CacheIdLookup<T> {
private final IdInExpression idInExpression;
private int remaining;
public CacheIdLookup(IdInExpression idInExpression) {
this.idInExpression = idInExpression;
}
public interface CacheIdLookup<T> {
/**
* Return the Id values for the in expression.
* Return the Id values to lookup against the L2 cache.
*/
public Collection<?> idValues() {
return idInExpression.idValues();
}
Collection<?> idValues();
/**
* Process the hits returning the beans fetched from cache and
* adjusting the in expression (to not fetch the hits).
* Remove the hits returning the beans fetched from L2 cache.
*/
public List<T> removeHits(BeanCacheResult<T> cacheResult) {
List<T> removeHits(BeanCacheResult<T> cacheResult);
Set<Object> hitIds = new HashSet<>();
List<T> beans = new ArrayList<>(hitIds.size());
for (BeanCacheResult.Entry<T> hit : cacheResult.hits()) {
hitIds.add(hit.getKey());
beans.add(hit.getBean());
}
this.remaining = idInExpression.removeIds(hitIds);
return beans;
}
public boolean allHits() {
return remaining == 0;
}
/**
* Return true if all beans where found in L2 cache.
*/
boolean allHits();
}
@@ -0,0 +1,55 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.expression.IdInExpression;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Used for bean cache lookup with where ids in expression.
*/
public class CacheIdLookupMany<T> implements CacheIdLookup<T> {
private final IdInExpression idInExpression;
private int remaining;
public CacheIdLookupMany(IdInExpression idInExpression) {
this.idInExpression = idInExpression;
}
/**
* Return the Id values for the in expression.
*/
@Override
public Collection<?> idValues() {
return idInExpression.idValues();
}
/**
* Process the hits returning the beans fetched from cache and
* adjusting the in expression (to not fetch the hits).
*/
@Override
public List<T> removeHits(BeanCacheResult<T> cacheResult) {
Set<Object> hitIds = new HashSet<>();
List<T> beans = new ArrayList<>(hitIds.size());
for (BeanCacheResult.Entry<T> hit : cacheResult.hits()) {
hitIds.add(hit.getKey());
beans.add(hit.getBean());
}
this.remaining = idInExpression.removeIds(hitIds);
return beans;
}
@Override
public boolean allHits() {
return remaining == 0;
}
}
@@ -0,0 +1,38 @@
package io.ebeaninternal.api;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Used for bean cache lookup with a single id value.
*/
public class CacheIdLookupSingle<T> implements CacheIdLookup<T> {
private final Object idValue;
private boolean found;
public CacheIdLookupSingle(Object idValue) {
this.idValue = idValue;
}
@Override
public Collection<?> idValues() {
return Collections.singleton(idValue);
}
@Override
public List<T> removeHits(BeanCacheResult<T> cacheResult) {
final List<BeanCacheResult.Entry<T>> hits = cacheResult.hits();
if (hits.size() == 1) {
found = true;
return Collections.singletonList(hits.get(0).getBean());
}
return Collections.emptyList();
}
@Override
public boolean allHits() {
return found;
}
}
@@ -16,7 +16,7 @@ public interface LoadContext {
/**
* Return the minimum batch size when using QueryIterator with query joins.
*/
int getSecondaryQueriesMinBatchSize(int defaultQueryBatch);
int getSecondaryQueriesMinBatchSize();
/**
* Execute any secondary (+query) queries if there are any defined.
@@ -8,6 +8,11 @@ import java.util.List;
class NoopQueryPlanManager implements QueryPlanManager {
@Override
public void setDefaultThreshold(long thresholdMicros) {
// do nothing
}
@Override
public SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan) {
return SpiQueryBindCapture.NOOP;
@@ -12,6 +12,11 @@ public interface QueryPlanManager {
QueryPlanManager NOOP = new NoopQueryPlanManager();
/**
* Update the global default threshold used when new query plans are created.
*/
void setDefaultThreshold(long thresholdMicros);
/**
* Create the bind capture for the given query plan.
*/
@@ -73,6 +73,10 @@ public class ScopeTrans {
restoreBatchGeneratedKeys = transaction.getBatchGetGeneratedKeys();
restoreBatchFlushOnQuery = transaction.isFlushOnQuery();
}
Boolean autoPersistUpdates = txScope.getAutoPersistUpdates();
if (autoPersistUpdates != null) {
transaction.setAutoPersistUpdates(autoPersistUpdates);
}
if (txScope.isBatchSet()) {
transaction.setBatchMode(txScope.isBatchMode());
}
@@ -264,6 +264,11 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollect
*/
<T> void findDtoEach(SpiDtoQuery<T> query, Consumer<T> consumer);
/**
* DTO findEach batch query.
*/
<T> void findDtoEach(SpiDtoQuery<T> query, int batch, Consumer<List<T>> consumer);
/**
* DTO findEachWhile query.
*/
@@ -20,6 +20,7 @@ import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
import io.ebeaninternal.server.querydefn.OrmUpdateProperties;
import io.ebeaninternal.server.rawsql.SpiRawSql;
@@ -30,7 +31,7 @@ import java.util.Set;
/**
* Object Relational query - Internal extension to Query object.
*/
public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCodes {
enum Mode {
NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true);
@@ -289,6 +290,16 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
boolean selectAllForLazyLoadProperty();
/**
* Set the select properties.
*/
void selectProperties(OrmQueryProperties other);
/**
* Set the fetch properties for the given path.
*/
void fetchProperties(String path, OrmQueryProperties other);
/**
* Set the on a secondary query given the label, relativePath and profile location of the parent query.
*/
@@ -0,0 +1,27 @@
package io.ebeaninternal.api;
import io.ebean.FetchConfig;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import java.util.Set;
/**
* Query select and fetch properties (that avoids parsing).
*/
public interface SpiQueryFetch {
/**
* Specify the select properties.
*/
void selectProperties(Set<String> properties);
/**
* Specify the fetch properties for the given path.
*/
void fetchProperties(String name, Set<String> properties, FetchConfig config);
/**
* Add a nested fetch graph.
*/
void addNested(String name, OrmQueryDetail nestedDetail, FetchConfig config);
}
@@ -147,6 +147,11 @@ public interface SpiTransaction extends Transaction {
*/
int depth();
/**
* Return true if dirty beans are automatically persisted.
*/
boolean isAutoPersistUpdates();
/**
* Return true if this transaction was created explicitly via
* <code>Ebean.beginTransaction()</code>.
@@ -43,6 +43,16 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.getLabel();
}
@Override
public void setAutoPersistUpdates(boolean autoPersistUpdates) {
transaction.setAutoPersistUpdates(autoPersistUpdates);
}
@Override
public boolean isAutoPersistUpdates() {
return transaction.isAutoPersistUpdates();
}
@Override
public void commitAndContinue() {
transaction.commitAndContinue();
@@ -135,16 +135,11 @@ public class DefaultContainer implements SpiContainer {
configProvider.apply((ServerConfig)config);
}
}
if (config.isAutoLoadModuleInfo()) {
// auto register entity classes (default db)
for (ModuleInfoLoader loader : ServiceLoader.load(ModuleInfoLoader.class)) {
config.addAll(loader.entityClasses());
}
}
} else if (config.isAutoLoadModuleInfo()) {
// auto register entity classes (other named db)
}
if (config.isAutoLoadModuleInfo()) {
// auto register entity classes
for (ModuleInfoLoader loader : ServiceLoader.load(ModuleInfoLoader.class)) {
config.addAll(loader.entityClassesFor(config.getName()));
config.addAll(loader.classesFor(config.getName(), config.isDefaultServer()));
}
}
}
@@ -0,0 +1,25 @@
package io.ebeaninternal.server.core;
import io.ebean.config.QueryPlanCapture;
import io.ebean.config.QueryPlanListener;
import io.ebean.meta.MetaQueryPlan;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class DefaultQueryPlanListener implements QueryPlanListener {
static final QueryPlanListener INSTANT = new DefaultQueryPlanListener();
private static final Logger log = LoggerFactory.getLogger("io.ebean.QUERYPLAN");
@Override
public void process(QueryPlanCapture capture) {
// better to log this in JSON form?
String dbName = capture.getDatabase().getName();
for (MetaQueryPlan plan : capture.getPlans()) {
log.info("queryPlan db:{} label:{} queryTimeMicros:{} loc:{} sql:{} bind:{} plan:{}",
dbName, plan.getLabel(), plan.getQueryTimeMicros(), plan.getProfileLocation(),
plan.getSql(), plan.getBind(), plan.getPlan());
}
}
}
@@ -45,12 +45,7 @@ import io.ebean.bean.PersistenceContext.WithOption;
import io.ebean.bean.SingleBeanLoader;
import io.ebean.cache.ServerCacheManager;
import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.EncryptKeyManager;
import io.ebean.config.SlowQueryEvent;
import io.ebean.config.SlowQueryListener;
import io.ebean.config.TenantMode;
import io.ebean.config.*;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.event.BeanPersistController;
import io.ebean.event.ShutdownManager;
@@ -125,6 +120,7 @@ import io.ebeanservice.docstore.api.DocStoreIntegration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
@@ -137,7 +133,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -145,6 +140,7 @@ import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -196,7 +192,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final SpiLogManager logManager;
private final PersistenceContextScope defaultPersistenceContextScope;
private final int lazyLoadBatchSize;
private final int queryBatchSize;
private final boolean updateAllPropertiesInBatch;
private final long slowQueryMicros;
private final SlowQueryListener slowQueryListener;
@@ -217,7 +212,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.extraMetrics = config.getExtraMetrics();
this.serverName = this.config.getName();
this.lazyLoadBatchSize = this.config.getLazyLoadBatchSize();
this.queryBatchSize = this.config.getQueryBatchSize();
this.cqueryEngine = config.getCQueryEngine();
this.expressionFactory = config.getExpressionFactory();
this.encryptKeyManager = this.config.getEncryptKeyManager();
@@ -242,7 +236,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.clockService = config.getClockService();
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
this.transactionManager = config.createTransactionManager(this, docStoreComponents.updateProcessor());
this.documentStore = docStoreComponents.documentStore();
this.queryPlanManager = config.initQueryPlanManager(transactionManager);
this.metaInfoManager = new DefaultMetaInfoManager(this);
@@ -305,10 +299,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return lazyLoadBatchSize;
}
public int getQueryBatchSize() {
return queryBatchSize;
}
@Override
public Object currentTenantId() {
return currentTenantProvider == null ? null : currentTenantProvider.currentId();
@@ -410,6 +400,31 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
migrationRunner.loadProperties(config.getProperties());
migrationRunner.run(config.getDataSource());
}
startQueryPlanCapture();
}
private void startQueryPlanCapture() {
if (config.isQueryPlanCapture()) {
long secs = config.getQueryPlanCapturePeriodSecs();
if (secs > 10) {
logger.info("capture query plan enabled, every {}secs", secs);
backgroundExecutor.scheduleWithFixedDelay(this::collectQueryPlans, secs, secs, TimeUnit.SECONDS);
}
}
}
private void collectQueryPlans() {
QueryPlanRequest request = new QueryPlanRequest();
request.setMaxCount(config.getQueryPlanCaptureMaxCount());
request.setMaxTimeMillis(config.getQueryPlanCaptureMaxTimeMillis());
// obtains query explain plans ...
List<MetaQueryPlan> plans = metaInfoManager.queryPlanCollectNow(request);
QueryPlanListener listener = config.getQueryPlanListener();
if (listener == null) {
listener = DefaultQueryPlanListener.INSTANT;
}
listener.process(new QueryPlanCapture(this, plans));
}
@Override
@@ -1150,6 +1165,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public <T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction) {
return Optional.ofNullable(findOne(query, transaction));
@@ -1180,6 +1196,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> Set<T> findSet(Query<T> query, Transaction t) {
@@ -1196,6 +1213,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <K, T> Map<K, T> findMap(Query<T> query, Transaction t) {
@@ -1217,6 +1235,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
@SuppressWarnings("unchecked")
public <A, T> List<A> findSingleAttributeList(Query<T> query, Transaction t) {
@@ -1227,7 +1246,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
try {
request.initTransIfRequired();
return (List<A>) request.findSingleAttributeList();
return request.findSingleAttributeList();
} finally {
request.endTransIfRequired();
}
@@ -1277,6 +1296,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public <A, T> List<A> findIds(Query<T> query, Transaction t) {
return findIdsWithCopy(((SpiQuery<T>) query).copy(), t);
@@ -1337,26 +1357,29 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public <T> FutureRowCount<T> findFutureCount(Query<T> q, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) q).copy();
copy.setFutureFetch(true);
Transaction newTxn = createTransaction();
QueryFutureRowCount<T> queryFuture = new QueryFutureRowCount<>(new CallableQueryCount<T>(this, copy, newTxn));
QueryFutureRowCount<T> queryFuture = new QueryFutureRowCount<>(new CallableQueryCount<>(this, copy, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
@Nonnull
@Override
public <T> FutureIds<T> findFutureIds(Query<T> query, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) query).copy();
copy.setFutureFetch(true);
Transaction newTxn = createTransaction();
QueryFutureIds<T> queryFuture = new QueryFutureIds<>(new CallableQueryIds<T>(this, copy, newTxn));
QueryFutureIds<T> queryFuture = new QueryFutureIds<>(new CallableQueryIds<>(this, copy, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
@Nonnull
@Override
public <T> FutureList<T> findFutureList(Query<T> query, Transaction t) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
@@ -1369,11 +1392,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
// Create a new transaction solely to execute the findList() at some future time
Transaction newTxn = createTransaction();
QueryFutureList<T> queryFuture = new QueryFutureList<>(new CallableQueryList<T>(this, spiQuery, newTxn));
QueryFutureList<T> queryFuture = new QueryFutureList<>(new CallableQueryList<>(this, spiQuery, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
@Nonnull
@Override
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
@@ -1387,6 +1411,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new LimitOffsetPagedList<>(this, spiQuery);
}
@Nonnull
@Override
public <T> QueryIterator<T> findIterate(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
@@ -1399,11 +1424,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public <T> Stream<T> findLargeStream(Query<T> query, Transaction transaction) {
return findStream(query, transaction);
}
@Nonnull
@Override
public <T> Stream<T> findStream(Query<T> query, Transaction transaction) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, transaction);
@@ -1446,6 +1473,18 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
// no try finally - findEach guarantee's cleanup of the transaction if required
}
@Override
public <T> void findEach(Query<T> query, int batch, Consumer<List<T>> consumer, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
// if (request.isUseDocStore()) {
// docStore().findEach(request, consumer);
// return;
// }
request.initTransIfRequired();
request.findEach(batch, consumer);
// no try finally - findEach guarantee's cleanup of the transaction if required
}
@Override
public <T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
@@ -1458,6 +1497,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
// no try finally - findEachWhile guarantee's cleanup of the transaction if required
}
@Nonnull
@Override
public <T> List<Version<T>> findVersions(Query<T> query, Transaction transaction) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.LIST, query, transaction);
@@ -1469,6 +1509,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public <T> List<T> findList(Query<T> query, Transaction t) {
return findList(query, t, false);
@@ -1528,6 +1569,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Nonnull
@Override
public List<SqlRow> findList(SqlQuery query, Transaction t) {
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, t);
@@ -1585,6 +1627,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public <T> void findDtoEach(SpiDtoQuery<T> query, int batch, Consumer<List<T>> consumer) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
try {
request.initTransIfRequired();
request.findEach(batch, consumer);
} finally {
request.endTransIfRequired();
}
}
@Override
public <T> void findDtoEachWhile(SpiDtoQuery<T> query, Predicate<T> consumer) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
@@ -2218,6 +2271,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return checkUniqueness(bean, null);
}
@Nonnull
@Override
public Set<Property> checkUniqueness(Object bean, Transaction transaction) {
EntityBean entityBean = checkEntityBean(bean);
@@ -2229,14 +2283,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
Object id = idProperty.getVal(entityBean);
if (entityBean._ebean_getIntercept().isNew() && id != null) {
// Primary Key is changeable only on new models - so skip check if we are not
// new.
// Primary Key is changeable only on new models - so skip check if we are not new
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
query.setId(id);
if (findCount(query, transaction) > 0) {
Set<Property> ret = new HashSet<>();
ret.add(idProperty);
return ret;
return Collections.singleton(idProperty);
}
}
for (BeanProperty[] props : beanDesc.getUniqueProps()) {
@@ -2299,6 +2350,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
List<MetaQueryPlan> queryPlanInit(QueryPlanInit initRequest) {
if (initRequest.isAll()) {
queryPlanManager.setDefaultThreshold(initRequest.getThresholdMicros());
}
return beanDescriptorManager.queryPlanInit(initRequest);
}
@@ -23,6 +23,9 @@ import java.util.function.Predicate;
*/
public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
private static final String ENC_PREFIX = EncryptAlias.PREFIX;
private static final String ENC_PREFIX_UPPER = EncryptAlias.PREFIX.toUpperCase();
private final SpiDtoQuery<T> query;
private final DtoQueryEngine queryEngine;
@@ -92,6 +95,11 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
queryEngine.findEach(this, consumer);
}
public void findEach(int batch, Consumer<List<T>> consumer) {
flushJdbcBatchOnQuery();
queryEngine.findEach(this, batch, consumer);
}
public void findEachWhile(Predicate<T> consumer) {
flushJdbcBatchOnQuery();
queryEngine.findEachWhile(this, consumer);
@@ -128,9 +136,9 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
}
static String parseColumn(String columnLabel) {
if (columnLabel.startsWith("_e_") || columnLabel.startsWith("_E_")) {
if (columnLabel.startsWith(ENC_PREFIX) || columnLabel.startsWith(ENC_PREFIX_UPPER)) {
// encrypted column alias in the form _e_<tableAlias>_<column>
final int pos = columnLabel.indexOf("_", 3);
final int pos = columnLabel.indexOf("_", 4);
if (pos > -1) {
return columnLabel.substring(pos + 1);
}
@@ -0,0 +1,8 @@
package io.ebeaninternal.server.core;
/**
* Used to create column alias for encrypted columns.
*/
public interface EncryptAlias {
String PREFIX = "zx__";
}
@@ -423,13 +423,13 @@ public class InternalConfiguration {
/**
* Create the TransactionManager taking into account autoCommit mode.
*/
TransactionManager createTransactionManager(DocStoreUpdateProcessor indexUpdateProcessor) {
TransactionManager createTransactionManager(SpiServer server, DocStoreUpdateProcessor indexUpdateProcessor) {
TransactionScopeManager scopeManager = createTransactionScopeManager();
boolean notifyL2CacheInForeground = cacheManager.isLocalL2Caching() || config.isNotifyL2CacheInForeground();
TransactionManagerOptions options =
new TransactionManagerOptions(notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
new TransactionManagerOptions(server, notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
tableModState, cacheNotify, clockService);
@@ -617,10 +617,10 @@ public class InternalConfiguration {
}
public QueryPlanManager initQueryPlanManager(TransactionManager transactionManager) {
if (!config.isCollectQueryPlans()) {
if (!config.isQueryPlanEnable()) {
return QueryPlanManager.NOOP;
}
long threshold = config.getCollectQueryPlanThresholdMicros();
long threshold = config.getQueryPlanThresholdMicros();
return new CQueryPlanManager(transactionManager, threshold, queryPlanLogger(databasePlatform.getPlatform()), extraMetrics);
}
@@ -173,8 +173,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
* iteration is fine.
* </p>
*/
public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) {
return loadContext.getSecondaryQueriesMinBatchSize(defaultQueryBatch);
public int getSecondaryQueriesMinBatchSize() {
return loadContext.getSecondaryQueriesMinBatchSize();
}
/**
@@ -430,6 +430,24 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
}
@Override
public void findEach(int batch, Consumer<List<T>> batchConsumer) {
final List<T> buffer = new ArrayList<>(batch);
try (QueryIterator<T> it = queryEngine.findIterate(this)) {
while (it.hasNext()) {
buffer.add(it.next());
if (buffer.size() >= batch) {
batchConsumer.accept(buffer);
buffer.clear();
}
}
if (!buffer.isEmpty()) {
// consume the remainder
batchConsumer.accept(buffer);
}
}
}
@Override
public void findEachWhile(Predicate<T> consumer) {
try (QueryIterator<T> it = queryEngine.findIterate(this)) {
@@ -887,6 +887,14 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* Remove deleted beans from the persistence context early.
*/
public void removeFromPersistenceContext() {
idValue = beanDescriptor.getId(entityBean);
beanDescriptor.contextDeleted(transaction.getPersistenceContext(), idValue);
}
/**
* Aggressive L1 and L2 cache cleanup for deletes.
*/
@@ -1030,6 +1038,17 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
if (!publish) {
beanDescriptor.setDraft(entityBean);
}
if (transaction.isAutoPersistUpdates() && idValue != null) {
// with getGeneratedKeys off we will not have a idValue
beanDescriptor.contextPut(transaction.getPersistenceContext(), idValue, entityBean);
}
}
/**
* Return if persist can be skipped on the reference only bean.
*/
public boolean isSkipReference() {
return intercept.isReference() || (Flags.isRecurse(flags) && beanDescriptor.referenceIdPropertyOnly(intercept));
}
public boolean isReference() {
@@ -80,10 +80,15 @@ public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T>, DocQueryRequ
<A> List<A> findIds();
/**
* Execute the find returning a QueryIterator and visitor pattern.
* Execute findEach iterating results one bean at a time.
*/
void findEach(Consumer<T> consumer);
/**
* Execute findEach with a batch consumer.
*/
void findEach(int batch, Consumer<List<T>> batchConsumer);
/**
* Execute the find returning a QueryIterator and visitor pattern.
*/
@@ -727,7 +727,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
String[] cols = indexDef.getColumns();
BeanProperty[] props = new BeanProperty[cols.length];
for (int i = 0; i < cols.length; i++) {
String propName = findBeanPath("", cols[i]);
String propName = findBeanPath("", "", cols[i]);
if (propName == null) {
return;
}
@@ -2472,13 +2472,16 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Return the property path given the db table and column.
*/
public String findBeanPath(String tableName, String columnName) {
if (tableName.isEmpty() || tableName.equalsIgnoreCase(baseTable)) {
public String findBeanPath(String schemaName, String tableName, String columnName) {
if (matchBaseTable(tableName)) {
return columnPath.get(columnName);
}
BeanPropertyAssoc<?> assocProperty = tablePath.get(tableName);
if (assocProperty == null) {
assocProperty = tablePath.get(schemaName + "." + tableName);
}
if (assocProperty != null) {
String relativePath = assocProperty.getTargetDescriptor().findBeanPath(tableName, columnName);
String relativePath = assocProperty.getTargetDescriptor().findBeanPath(schemaName, tableName, columnName);
if (relativePath != null) {
return SplitName.add(assocProperty.getName(), relativePath);
}
@@ -2486,6 +2489,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return null;
}
boolean matchBaseTable(String tableName) {
return tableName.isEmpty()
|| baseTable.equalsIgnoreCase(tableName)
|| baseTable.endsWith("." + tableName);
}
/**
* Return a 'dynamic property' used to read a formula.
*/
@@ -3125,7 +3134,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return ebi.isReference() || referenceIdPropertyOnly(ebi);
}
boolean referenceIdPropertyOnly(EntityBeanIntercept ebi) {
public boolean referenceIdPropertyOnly(EntityBeanIntercept ebi) {
return idOnlyReference && ebi.hasIdOnly(idPropertyIndex);
}
@@ -93,6 +93,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptorManager.class);
private static final BeanDescComparator beanDescComparator = new BeanDescComparator();
public static final String JAVA_LANG_RECORD = "java.lang.Record";
private final ReadAnnotations readAnnotations;
private final TransientProperties transientProperties;
@@ -1418,14 +1419,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
private void checkInheritedClasses(Class<?> beanClass) {
Class<?> superclass = beanClass.getSuperclass();
if (Object.class.equals(superclass)) {
if (Object.class.equals(superclass) || Model.class.equals(superclass) || JAVA_LANG_RECORD.equals(superclass.getName())) {
// we got to the top of the inheritance
return;
}
if (Model.class.equals(superclass)) {
// top of the inheritance. Not enhancing Model at this stage
return;
}
if (!EntityBean.class.isAssignableFrom(superclass)) {
if (isMappedSuperWithNoProperties(superclass)) {
// ok to stop and treat just the same as Object.class
@@ -18,6 +18,7 @@ import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.core.EncryptAlias;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedWhenCreated;
@@ -65,6 +66,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class);
private static final String ENC_PREFIX = " " + EncryptAlias.PREFIX;
/**
* Flag to mark this is the id property.
*/
@@ -324,9 +327,9 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
this.generatedProperty = deploy.getGeneratedProperty();
this.getter = deploy.getGetter();
this.setter = deploy.getSetter();
this.aggregation = deploy.parseAggregation();
this.dbColumn = tableAliasIntern(descriptor, deploy.getDbColumn(), false, null);
this.dbComment = deploy.getDbComment();
this.aggregation = deploy.parseAggregation();
this.sqlFormulaJoin = InternString.intern(deploy.getSqlFormulaJoin());
this.sqlFormulaSelect = InternString.intern(deploy.getSqlFormulaSelect());
this.formula = sqlFormulaSelect != null;
@@ -531,7 +534,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
* Return the SQL for the column including decryption function and column alias.
*/
private String getDecryptSqlWithColumnAlias(String tableAlias) {
return dbEncryptFunction.getDecryptSql(tableAlias + "." + this.getDbColumn()) + " _e_" + tableAlias + "_" + this.getDbColumn();
return dbEncryptFunction.getDecryptSql(tableAlias + "." + this.getDbColumn()) + ENC_PREFIX + tableAlias + "_" + this.getDbColumn();
}
@Override
@@ -609,7 +609,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
String msg = "Error with the Join on [" + getFullBeanName()
+ "]. Could not find the matching foreign key for [" + matchColumn + "] in table[" + searchTable + "]?"
+ " Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped?";
+ " Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped? "
+ " or a @JoinColumn needs an explicit referencedColumnName specified?";
throw new PersistenceException(msg);
}
}
@@ -81,18 +81,18 @@ class BeanPropertyAssocManySqlHelp<T> {
@Override
public void visitOneImported(BeanPropertyAssocOne<?> p) {
// do nothing
}
@Override
public void visitScalar(BeanProperty p) {
public void visitScalar(BeanProperty p, boolean allowNonNull) {
sb.append(",").append(p.getDbColumn());
colCount++;
}
@Override
public void visitEnd() {
// do nothing
}
}
@@ -16,6 +16,7 @@ import io.ebean.event.BeanPostLoad;
import io.ebean.event.BeanQueryAdapter;
import io.ebean.event.changelog.ChangeLogFilter;
import io.ebean.text.PathProperties;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.server.core.CacheOptions;
import io.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
@@ -1149,8 +1150,21 @@ public class DeployBeanDescriptor<T> {
return null;
}
// use 'current' table alias - refer BeanProperty appendSelect() for aggregation
DeployBeanProperty property = propMap.get(expression);
return (property == null) ? null : "${ta}." + property.getDbColumn();
String[] split = SplitName.split(expression);
if (split[0] == null) {
DeployBeanProperty property = propMap.get(expression);
return (property == null) ? null : "${ta}." + property.getDbColumn();
} else {
DeployBeanProperty property = propMap.get(split[0]);
if (property instanceof DeployBeanPropertyAssoc) {
DeployBeanPropertyAssoc<?> prop = (DeployBeanPropertyAssoc<?>) property;
DeployBeanProperty beanProperty = prop.getTargetDeploy().getBeanProperty(split[1]);
if (beanProperty != null) {
return "u1." + beanProperty.getDbColumn();
}
}
return null;
}
}
/**
@@ -34,7 +34,6 @@ import javax.persistence.EmbeddedId;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.Version;
import javax.validation.constraints.Size;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
@@ -683,7 +682,6 @@ public class DeployBeanProperty {
*/
public void setAggregationPrefix(String prefix) {
this.aggregationPrefix = prefix;
this.aggregation = (prefix == null) ? aggregation : aggregation.replace(aggregationPrefix, "u1");
}
public String getElPrefix() {
@@ -716,7 +714,7 @@ public class DeployBeanProperty {
return sqlFormulaSelect;
}
if (aggregation != null) {
return aggregation;
return aggregationParsed == null ? dbColumn : aggregationParsed;
}
return dbColumn;
}
@@ -1140,15 +1138,6 @@ public class DeployBeanProperty {
return result;
}
public List<Size> getMetaAnnotationSize() {
final List<Size> size = getMetaAnnotations(Size.class);
final List<Size.List> lists = getMetaAnnotations(Size.List.class);
for (Size.List list : lists) {
Collections.addAll(size, list.value());
}
return size;
}
public Formula getMetaAnnotationFormula(Platform platform) {
Formula fallback = null;
for (Annotation ann : metaAnnotations) {
@@ -38,8 +38,6 @@ import javax.persistence.OrderBy;
import javax.persistence.OrderColumn;
import java.util.Set;
import static io.ebean.util.StringHelper.isNull;
/**
* Read the deployment annotation for Assoc Many beans.
*/
@@ -346,18 +344,6 @@ class AnnotationAssocManys extends AnnotationAssoc {
return append(joinTable.catalog(), joinTable.schema(), joinTable.name());
}
private String append(String catalog, String schema, String name) {
StringBuilder sb = new StringBuilder();
if (!isNull(catalog)) {
sb.append(catalog).append(".");
}
if (!isNull(schema)) {
sb.append(schema).append(".");
}
sb.append(name);
return sb.toString();
}
/**
* Return the full table name
*/
@@ -368,6 +354,13 @@ class AnnotationAssocManys extends AnnotationAssoc {
return append(collectionTable.catalog(), collectionTable.schema(), collectionTable.name());
}
/**
* Return the full table name taking into account quoted identifiers.
*/
private String append(String catalog, String schema, String name) {
return namingConvention.getTableName(catalog, schema, name);
}
/**
* Define intersection table and foreign key columns for ManyToMany.
* <p>
@@ -414,8 +407,8 @@ class AnnotationAssocManys extends AnnotationAssoc {
BeanProperty localId = localTable.getIdProperty();
if (localId != null) {
// add the source to intersection join columns
String fkCol = localTableName + "_" + localId.getDbColumn();
intJoin.addJoinColumn(new DeployTableJoinColumn(localId.getDbColumn(), namingConvention.getColumnFromProperty(null, fkCol)));
String fkCol = namingConvention.deriveM2MColumn(localTableName, localId.getDbColumn());
intJoin.addJoinColumn(new DeployTableJoinColumn(localId.getDbColumn(), fkCol));
}
}
@@ -424,8 +417,8 @@ class AnnotationAssocManys extends AnnotationAssoc {
BeanProperty otherId = otherTable.getIdProperty();
if (otherId != null) {
// set the intersection to dest table join columns
final String fkCol = otherTableName + "_" + otherId.getDbColumn();
destJoin.addJoinColumn(new DeployTableJoinColumn(namingConvention.getColumnFromProperty(null, fkCol), otherId.getDbColumn()));
String fkCol = namingConvention.deriveM2MColumn(otherTableName, otherId.getDbColumn());
destJoin.addJoinColumn(new DeployTableJoinColumn(fkCol, otherId.getDbColumn()));
}
}
@@ -4,13 +4,11 @@ import io.ebean.annotation.DbForeignKey;
import io.ebean.annotation.FetchPreference;
import io.ebean.annotation.TenantId;
import io.ebean.annotation.Where;
import io.ebean.config.BeanNotRegisteredException;
import io.ebean.config.NamingConvention;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
import io.ebeaninternal.server.query.SqlJoinType;
@@ -28,7 +26,6 @@ import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.validation.constraints.NotNull;
/**
* Read the deployment annotations for Associated One beans.
@@ -117,13 +114,10 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
if (nonNull != null) {
prop.setNullable(false);
}
if (validationAnnotations) {
NotNull notNull = get(prop, NotNull.class);
if (notNull != null && isEbeanValidationGroups(notNull.groups())) {
prop.setNullable(false);
// overrides optional attribute of ManyToOne etc
prop.getTableJoin().setType(SqlJoinType.INNER);
}
if (readConfig.isValidationNotNull(prop)) {
// overrides optional attribute of ManyToOne etc
prop.setNullable(false);
prop.getTableJoin().setType(SqlJoinType.INNER);
}
// check for manually defined joins
@@ -69,8 +69,6 @@ import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.sql.Types;
import java.util.Set;
import java.util.UUID;
@@ -258,29 +256,19 @@ public class AnnotationFields extends AnnotationParser {
}
private void initValidation(DeployBeanProperty prop) {
NotNull notNull = get(prop, NotNull.class);
if (notNull != null && isEbeanValidationGroups(notNull.groups())) {
// Not null on all validation groups so enable
// DDL generation of Not Null Constraint
if (readConfig.isValidationNotNull(prop)) {
prop.setNullable(false);
}
if (!prop.isLob()) {
// take the max size of all @Size annotations
int maxSize = -1;
for (Size size : prop.getMetaAnnotationSize()) {
if (size.max() < Integer.MAX_VALUE) {
maxSize = Math.max(maxSize, size.max());
}
}
if (maxSize != -1) {
int maxSize = readConfig.maxValidationSize(prop);
if (maxSize > 0) {
prop.setDbLength(maxSize);
}
}
}
private void initTenantId(DeployBeanProperty prop) {
if (validationAnnotations) {
if (readConfig.checkValidationAnnotations()) {
initValidation(prop);
}
if (has(prop, TenantId.class)) {
@@ -9,7 +9,6 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import javax.persistence.AttributeOverride;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.validation.groups.Default;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -26,14 +25,11 @@ public abstract class AnnotationParser extends AnnotationBase {
final Class<?> beanType;
final boolean validationAnnotations;
final ReadAnnotationConfig readConfig;
AnnotationParser(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig) {
super(info.getUtil());
this.readConfig = readConfig;
this.validationAnnotations = readConfig.isJavaxValidationAnnotations();
this.info = info;
this.beanType = info.getDescriptor().getBeanType();
this.descriptor = info.getDescriptor();
@@ -127,17 +123,6 @@ public abstract class AnnotationParser extends AnnotationBase {
}
}
/**
* Return true if the validation groups are {@link Default} (respectively empty)
* can be applied to DDL generation.
*/
boolean isEbeanValidationGroups(Class<?>[] groups) {
if (!util.isUseJavaxValidationNotNull()) {
return false;
}
return groups.length == 0 || groups.length == 1 && Default.class.isAssignableFrom(groups[0]);
}
String[] convertColumnNames(String[] columnNames) {
for (int i = 0; i < columnNames.length; i++) {
columnNames[i] = databasePlatform.convertQuotedIdentifiers(columnNames[i]);
@@ -57,7 +57,7 @@ public class DeployUtil {
private final Encryptor bytesEncryptor;
private final boolean useJavaxValidationNotNull;
private final boolean useValidationNotNull;
public DeployUtil(TypeManager typeMgr, DatabaseConfig config) {
this.typeManager = typeMgr;
@@ -67,7 +67,7 @@ public class DeployUtil {
this.encryptKeyManager = config.getEncryptKeyManager();
Encryptor be = config.getEncryptor();
this.bytesEncryptor = be != null ? be : new SimpleAesEncryptor();
this.useJavaxValidationNotNull = config.isUseJavaxValidationNotNull();
this.useValidationNotNull = config.isUseValidationNotNull();
}
public TypeManager getTypeManager() {
@@ -286,8 +286,8 @@ public class DeployUtil {
return type.equals(String.class);
}
boolean isUseJavaxValidationNotNull() {
return useJavaxValidationNotNull;
boolean isUseValidationNotNull() {
return useValidationNotNull;
}
/**
@@ -1,11 +0,0 @@
package io.ebeaninternal.server.deploy.parse;
import javax.validation.constraints.Size;
class InitMetaValidationAnnotation {
static void init(ReadAnnotationConfig readConfig) {
readConfig.addMetaAnnotation(Size.class);
readConfig.addMetaAnnotation(Size.List.class);
}
}
@@ -3,8 +3,10 @@ package io.ebeaninternal.server.deploy.parse;
import io.ebean.annotation.Aggregation;
import io.ebean.annotation.Formula;
import io.ebean.annotation.Where;
import io.ebean.config.ClassLoadConfig;
import io.ebean.config.DatabaseConfig;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.Column;
import java.util.HashSet;
@@ -21,9 +23,12 @@ class ReadAnnotationConfig {
private final boolean disableL2Cache;
private final boolean eagerFetchLobs;
private final boolean javaxValidationAnnotations;
private final boolean jakartaValidationAnnotations;
private final boolean jacksonAnnotations;
private final boolean idGeneratorAutomatic;
private final boolean useValidationNotNull;
private final ReadValidationAnnotations javaxValidation;
private final ReadValidationAnnotations jakartaValidation;
private final Set<Class<?>> metaAnnotations = new HashSet<>();
ReadAnnotationConfig(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, DatabaseConfig config) {
@@ -33,20 +38,32 @@ class ReadAnnotationConfig {
this.disableL2Cache = config.isDisableL2Cache();
this.eagerFetchLobs = config.isEagerFetchLobs();
this.idGeneratorAutomatic = config.isIdGeneratorAutomatic();
this.javaxValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJavaxValidationAnnotationsPresent();
this.jacksonAnnotations = generatedPropFactory.getClassLoadConfig().isJacksonAnnotationsPresent();
this.useValidationNotNull = config.isUseValidationNotNull();
ClassLoadConfig classLoadConfig = generatedPropFactory.getClassLoadConfig();
this.javaxValidationAnnotations = classLoadConfig.isJavaxValidationAnnotationsPresent();
this.jakartaValidationAnnotations = classLoadConfig.isJakartaValidationAnnotationsPresent();
this.jacksonAnnotations = classLoadConfig.isJacksonAnnotationsPresent();
this.metaAnnotations.add(Column.class);
this.metaAnnotations.add(Formula.class);
this.metaAnnotations.add(Formula.List.class);
this.metaAnnotations.add(Where.class);
this.metaAnnotations.add(Where.List.class);
this.metaAnnotations.add(Aggregation.class);
this.javaxValidation = javaxValidationAnnotations ? new ReadValidationAnnotationsJavax(this) : null;
this.jakartaValidation = jakartaValidationAnnotations ? new ReadValidationAnnotationsJakarta(this) : null;
if (jacksonAnnotations) {
InitMetaJacksonAnnotation.init(this);
}
}
public void addMetaAnnotation(Class<?> annotation) {
void addMetaAnnotation(Class<?> annotation) {
metaAnnotations.add(annotation);
}
boolean checkValidationAnnotations() {
return javaxValidationAnnotations || jakartaValidationAnnotations;
}
GeneratedPropertyFactory getGeneratedPropFactory() {
return generatedPropFactory;
}
@@ -71,10 +88,6 @@ class ReadAnnotationConfig {
return idGeneratorAutomatic;
}
boolean isJavaxValidationAnnotations() {
return javaxValidationAnnotations;
}
boolean isJacksonAnnotations() {
return jacksonAnnotations;
}
@@ -82,4 +95,35 @@ class ReadAnnotationConfig {
public Set<Class<?>> getMetaAnnotations() {
return metaAnnotations;
}
/**
* Return true if a NotNull validation annotation is on the property.
*/
boolean isValidationNotNull(DeployBeanProperty property) {
if (!useValidationNotNull) {
return false;
}
if (javaxValidation != null && javaxValidation.isValidationNotNull(property)) {
return true;
}
if (jakartaValidation != null && jakartaValidation.isValidationNotNull(property)) {
return true;
}
return false;
}
/**
* Return the max size of all validation @Size annotations.
*/
int maxValidationSize(DeployBeanProperty prop) {
int maxSize = 0;
if (javaxValidation != null) {
maxSize = Math.max(maxSize, javaxValidation.maxSize(prop));
}
if (jakartaValidation != null) {
maxSize = Math.max(maxSize, jakartaValidation.maxSize(prop));
}
return maxSize;
}
}
@@ -14,12 +14,6 @@ public class ReadAnnotations {
public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, DatabaseConfig config) {
this.readConfig = new ReadAnnotationConfig(generatedPropFactory, asOfViewSuffix, versionsBetweenSuffix, config);
if (readConfig.isJavaxValidationAnnotations()) {
InitMetaValidationAnnotation.init(readConfig);
}
if (readConfig.isJacksonAnnotations()) {
InitMetaJacksonAnnotation.init(readConfig);
}
}
/**
@@ -0,0 +1,19 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
* Reads validation NotNull and Size annotations for mapping.
*/
interface ReadValidationAnnotations {
/**
* Return true if the property has a NotNull validation annotation.
*/
boolean isValidationNotNull(DeployBeanProperty property);
/**
* Return the max value of the Size validation annotations on the property.
*/
int maxSize(DeployBeanProperty property);
}
@@ -0,0 +1,51 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.validation.groups.Default;
import java.util.Collections;
import java.util.List;
/**
* Jakarta validation annotations reader.
*/
class ReadValidationAnnotationsJakarta implements ReadValidationAnnotations {
ReadValidationAnnotationsJakarta(ReadAnnotationConfig readConfig) {
readConfig.addMetaAnnotation(Size.class);
readConfig.addMetaAnnotation(Size.List.class);
}
@Override
public boolean isValidationNotNull(DeployBeanProperty property) {
NotNull notNull = AnnotationUtil.get(property.getField(), NotNull.class);
return (notNull != null && isEbeanValidationGroups(notNull.groups()));
}
private boolean isEbeanValidationGroups(Class<?>[] groups) {
return groups.length == 0 || groups.length == 1 && Default.class.isAssignableFrom(groups[0]);
}
@Override
public int maxSize(DeployBeanProperty property) {
int maxSize = 0;
for (Size size : getMetaAnnotationJavaxSize(property)) {
if (size.max() < Integer.MAX_VALUE) {
maxSize = Math.max(maxSize, size.max());
}
}
return maxSize;
}
private List<Size> getMetaAnnotationJavaxSize(DeployBeanProperty prop) {
final List<Size> size = prop.getMetaAnnotations(Size.class);
final List<Size.List> lists = prop.getMetaAnnotations(Size.List.class);
for (Size.List list : lists) {
Collections.addAll(size, list.value());
}
return size;
}
}
@@ -0,0 +1,52 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.groups.Default;
import java.util.Collections;
import java.util.List;
/**
* Javax validation annotations reader.
*/
class ReadValidationAnnotationsJavax implements ReadValidationAnnotations {
ReadValidationAnnotationsJavax(ReadAnnotationConfig readConfig) {
readConfig.addMetaAnnotation(Size.class);
readConfig.addMetaAnnotation(Size.List.class);
}
@Override
public boolean isValidationNotNull(DeployBeanProperty property) {
NotNull notNull = AnnotationUtil.get(property.getField(), NotNull.class);
return (notNull != null && isEbeanValidationGroups(notNull.groups()));
}
private boolean isEbeanValidationGroups(Class<?>[] groups) {
return groups.length == 0 || groups.length == 1 && Default.class.isAssignableFrom(groups[0]);
}
@Override
public int maxSize(DeployBeanProperty prop) {
int maxSize = 0;
for (Size size : getMetaAnnotationJavaxSize(prop)) {
if (size.max() < Integer.MAX_VALUE) {
maxSize = Math.max(maxSize, size.max());
}
}
return maxSize;
}
private List<Size> getMetaAnnotationJavaxSize(DeployBeanProperty prop) {
final List<Size> size = prop.getMetaAnnotations(Size.class);
final List<Size.List> lists = prop.getMetaAnnotations(Size.List.class);
for (Size.List list : lists) {
Collections.addAll(size, list.value());
}
return size;
}
}
@@ -44,9 +44,11 @@ public abstract class BaseTablePropertyVisitor implements BeanPropertyVisitor {
public abstract void visitOneImported(BeanPropertyAssocOne<?> p);
/**
* Override this method for normal scalar property.
* Override this method for normal scalar property. With allowNonNull
* used with properties of Embedded beans that can only be Nonnull when
* the Embedded bean is Nonnull.
*/
@Override
public abstract void visitScalar(BeanProperty p);
public abstract void visitScalar(BeanProperty p, boolean allowNonNull);
}
@@ -40,8 +40,8 @@ public interface BeanPropertyVisitor {
void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded);
/**
* Visit a scalar property.
* Visit a scalar property specify allowing non-null (e.g. embedded bean scalar properties).
*/
void visitScalar(BeanProperty p);
void visitScalar(BeanProperty p, boolean allowNonNull);
}
@@ -72,7 +72,7 @@ public class VisitProperties {
} else {
// simple scalar type
pv.visitScalar(p);
pv.visitScalar(p, true);
}
}
@@ -436,6 +436,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
query.findEach(consumer);
}
@Override
public void findEach(int batch, Consumer<List<T>> consumer) {
query.findEach(batch, consumer);
}
@Override
public void findEachWhile(Predicate<T> consumer) {
query.findEachWhile(consumer);
@@ -501,31 +506,16 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.forUpdate();
}
@Override
public Query<T> forUpdate(Query.LockType lockType) {
return query.forUpdate(lockType);
}
@Override
public Query<T> forUpdateNoWait() {
return query.forUpdateNoWait();
}
@Override
public Query<T> forUpdateNoWait(Query.LockType lockType) {
return query.forUpdateNoWait(lockType);
}
@Override
public Query<T> forUpdateSkipLocked() {
return query.forUpdateSkipLocked();
}
@Override
public Query<T> forUpdateSkipLocked(Query.LockType lockType) {
return query.forUpdateSkipLocked(lockType);
}
@Override
public Query<T> select(String fetchProperties) {
return query.select(fetchProperties);
@@ -446,6 +446,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
exprList.findEach(consumer);
}
@Override
public void findEach(int batch, Consumer<List<T>> consumer) {
exprList.findEach(batch, consumer);
}
@Override
public void findEachWhile(Predicate<T> consumer) {
exprList.findEachWhile(consumer);
@@ -506,31 +511,16 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.forUpdate();
}
@Override
public Query<T> forUpdate(Query.LockType lockType) {
return exprList.forUpdate(lockType);
}
@Override
public Query<T> forUpdateNoWait() {
return exprList.forUpdateNoWait();
}
@Override
public Query<T> forUpdateNoWait(Query.LockType lockType) {
return exprList.forUpdateNoWait(lockType);
}
@Override
public Query<T> forUpdateSkipLocked() {
return exprList.forUpdateSkipLocked();
}
@Override
public Query<T> forUpdateSkipLocked(Query.LockType lockType) {
return exprList.forUpdateSkipLocked(lockType);
}
/**
* Path exists - for the given path in a JSON document.
*/
@@ -17,11 +17,11 @@ class ParseFetchConfig {
if (path.startsWith("lazy")) {
if (path.length() == 4) {
return new FetchConfig().lazy();
return FetchConfig.ofLazy();
} else if (path.charAt(4) == '(') {
path = path.substring(5);
int batchSize = parseBatchSize(path);
return new FetchConfig().lazy(batchSize);
return FetchConfig.ofLazy(batchSize);
} else {
return null;
}
@@ -29,11 +29,11 @@ class ParseFetchConfig {
if (path.startsWith("query")) {
if (path.length() == 5) {
return new FetchConfig().query();
return FetchConfig.ofQuery();
} else if (path.charAt(5) == '(') {
path = path.substring(6);
int batchSize = parseBatchSize(path);
return new FetchConfig().query(batchSize);
return FetchConfig.ofQuery(batchSize);
} else {
return null;
}
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.loadcontext;
import io.ebean.FetchConfig;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.api.SpiQuery;
@@ -28,15 +27,13 @@ abstract class DLoadBaseContext {
final boolean hitCache;
final int firstBatchSize;
final int secondaryBatchSize;
final int batchSize;
final ObjectGraphNode objectGraphNode;
final boolean queryFetch;
DLoadBaseContext(DLoadContext parent, BeanDescriptor<?> desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) {
DLoadBaseContext(DLoadContext parent, BeanDescriptor<?> desc, String path, OrmQueryProperties queryProps) {
this.parent = parent;
this.serverName = parent.getEbeanServer().getName();
this.desc = desc;
@@ -45,38 +42,7 @@ abstract class DLoadBaseContext {
this.hitCache = parent.isBeanCacheGet() && desc.isBeanCaching();
this.objectGraphNode = parent.getObjectGraphNode(path);
this.queryFetch = queryProps != null && queryProps.isQueryFetch();
this.firstBatchSize = initFirstBatchSize(defaultBatchSize, queryProps);
this.secondaryBatchSize = initSecondaryBatchSize(defaultBatchSize, firstBatchSize, queryProps);
}
private int initFirstBatchSize(int batchSize, OrmQueryProperties queryProps) {
if (queryProps == null) {
return batchSize;
}
int queryBatchSize = queryProps.getQueryFetchBatch();
if (queryBatchSize == -1) {
return batchSize;
} else if (queryBatchSize == 0) {
return 100;
} else {
return queryBatchSize;
}
}
private int initSecondaryBatchSize(int defaultBatchSize, int firstBatchSize, OrmQueryProperties queryProps) {
if (queryProps == null) {
return defaultBatchSize;
}
FetchConfig fetchConfig = queryProps.getFetchConfig();
if (fetchConfig.isQueryAll()) {
return firstBatchSize;
}
int lazyBatchSize = fetchConfig.getLazyBatchSize();
return (lazyBatchSize > 1) ? lazyBatchSize : defaultBatchSize;
this.batchSize = parent.batchSize(queryProps);
}
/**
@@ -84,7 +50,6 @@ abstract class DLoadBaseContext {
* set onto the secondary query.
*/
void setLabel(SpiQuery<?> query) {
String label = parent.getPlanLabel();
if (label != null) {
query.setProfilePath(label, fullPath, parent.getProfileLocation());
@@ -31,11 +31,11 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
private LoadBuffer currentBuffer;
DLoadBeanContext(DLoadContext parent, BeanDescriptor<?> desc, String path, int defaultBatchSize, OrmQueryProperties queryProps) {
super(parent, desc, path, defaultBatchSize, queryProps);
DLoadBeanContext(DLoadContext parent, BeanDescriptor<?> desc, String path, OrmQueryProperties queryProps) {
super(parent, desc, path, queryProps);
// bufferList only required when using query joins (queryFetch)
this.bufferList = (!queryFetch) ? null : new ArrayList<>();
this.currentBuffer = createBuffer(firstBatchSize);
this.currentBuffer = createBuffer(batchSize);
this.cache = (queryProps != null) && queryProps.isCache();
}
@@ -52,7 +52,7 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
if (bufferList != null) {
bufferList.clear();
}
currentBuffer = createBuffer(secondaryBatchSize);
currentBuffer = createBuffer(batchSize);
}
private void configureQuery(SpiQuery<?> query, String lazyLoadProperty) {
@@ -70,7 +70,7 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
protected void register(EntityBeanIntercept ebi) {
if (currentBuffer.isFull()) {
currentBuffer = createBuffer(secondaryBatchSize);
currentBuffer = createBuffer(batchSize);
}
ebi.setBeanLoader(currentBuffer, getPersistenceContext());
currentBuffer.add(ebi);
@@ -95,10 +95,6 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
for (LoadBuffer loadBuffer : bufferList) {
if (!loadBuffer.list.isEmpty()) {
parent.getEbeanServer().loadBean(new LoadBeanRequest(loadBuffer, parentRequest));
if (!queryProps.isQueryFetchAll()) {
// Stop - only fetch the first batch ... the rest will be lazy loaded
break;
}
}
if (forEach) {
clear();
@@ -90,7 +90,7 @@ public class DLoadContext implements LoadContext {
this.planLabel = null;
this.profileLocation = null;
this.profilingListener = null;
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, null);
}
private ObjectGraphOrigin initOrigin() {
@@ -128,7 +128,7 @@ public class DLoadContext implements LoadContext {
}
// initialise rootBeanContext after origin and relativePath have been set
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, null);
registerSecondaryQueries(secondaryQueries);
}
@@ -182,15 +182,15 @@ public class DLoadContext implements LoadContext {
* Return the minimum batch size when using QueryIterator with query joins.
*/
@Override
public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) {
public int getSecondaryQueriesMinBatchSize() {
if (secQuery == null) {
return -1;
}
int maxBatch = 0;
for (OrmQueryProperties aSecQuery : secQuery) {
int batchSize = aSecQuery.getQueryFetchBatch();
int batchSize = aSecQuery.getBatchSize();
if (batchSize == 0) {
batchSize = defaultQueryBatch;
batchSize = 100;
}
maxBatch = Math.max(maxBatch, batchSize);
}
@@ -287,54 +287,55 @@ public class DLoadContext implements LoadContext {
getManyContext(path, many).register(bc);
}
int batchSize(OrmQueryProperties props) {
if (props == null) {
return defaultBatchSize;
}
int batchSize = props.getBatchSize();
return batchSize == 0 ? defaultBatchSize : batchSize;
}
DLoadBeanContext getBeanContext(String path) {
if (path == null) {
return rootBeanContext;
}
return beanMap.computeIfAbsent(path, p -> createBeanContext(p, defaultBatchSize, null));
return beanMap.computeIfAbsent(path, p -> createBeanContext(p, null));
}
DLoadBeanContext getBeanContextWithInherit(String path, BeanPropertyAssocOne<?> property) {
String key = path + ":" + property.getTargetDescriptor().getName();
return beanMap.computeIfAbsent(key, p -> createBeanContext(property, path, defaultBatchSize, null));
return beanMap.computeIfAbsent(key, p -> createBeanContext(property, path, null));
}
private void registerSecondaryNode(boolean many, OrmQueryProperties props) {
int batchSize;
if (props.isQueryFetch()) {
batchSize = 100;
} else {
int lazyJoinBatch = props.getLazyFetchBatch();
batchSize = lazyJoinBatch > 0 ? lazyJoinBatch : defaultBatchSize;
}
String path = props.getPath();
if (many) {
manyMap.put(path, createManyContext(path, batchSize, props));
manyMap.put(path, createManyContext(path, props));
} else {
beanMap.put(path, createBeanContext(path, batchSize, props));
beanMap.put(path, createBeanContext(path, props));
}
}
DLoadManyContext getManyContext(String path, BeanPropertyAssocMany<?> many) {
return manyMap.computeIfAbsent(path, p -> createManyContext(p, many, defaultBatchSize));
return manyMap.computeIfAbsent(path, p -> createManyContext(p, many));
}
private DLoadManyContext createManyContext(String path, BeanPropertyAssocMany<?> many, int batchSize) {
return new DLoadManyContext(this, many, path, batchSize, null);
private DLoadManyContext createManyContext(String path, BeanPropertyAssocMany<?> many) {
return new DLoadManyContext(this, many, path, null);
}
private DLoadManyContext createManyContext(String path, int batchSize, OrmQueryProperties queryProps) {
private DLoadManyContext createManyContext(String path, OrmQueryProperties queryProps) {
BeanPropertyAssocMany<?> p = (BeanPropertyAssocMany<?>) getBeanProperty(rootDescriptor, path);
return new DLoadManyContext(this, p, path, batchSize, queryProps);
return new DLoadManyContext(this, p, path, queryProps);
}
private DLoadBeanContext createBeanContext(String path, int batchSize, OrmQueryProperties queryProps) {
private DLoadBeanContext createBeanContext(String path, OrmQueryProperties queryProps) {
BeanPropertyAssoc<?> p = (BeanPropertyAssoc<?>) getBeanProperty(rootDescriptor, path);
return new DLoadBeanContext(this, p.getTargetDescriptor(), path, batchSize, queryProps);
return new DLoadBeanContext(this, p.getTargetDescriptor(), path, queryProps);
}
private DLoadBeanContext createBeanContext(BeanPropertyAssoc<?> property, String path, int batchSize, OrmQueryProperties queryProps) {
return new DLoadBeanContext(this, property.getTargetDescriptor(), path, batchSize, queryProps);
private DLoadBeanContext createBeanContext(BeanPropertyAssoc<?> property, String path, OrmQueryProperties queryProps) {
return new DLoadBeanContext(this, property.getTargetDescriptor(), path, queryProps);
}
private BeanProperty getBeanProperty(BeanDescriptor<?> desc, String path) {
@@ -31,16 +31,13 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
private LoadBuffer currentBuffer;
DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany<?> property,
String path, int defaultBatchSize, OrmQueryProperties queryProps) {
super(parent, property.getBeanDescriptor(), path, defaultBatchSize, queryProps);
DLoadManyContext(DLoadContext parent, BeanPropertyAssocMany<?> property, String path, OrmQueryProperties queryProps) {
super(parent, property.getBeanDescriptor(), path, queryProps);
this.property = property;
this.docStoreMapped = property.isTargetDocStoreMapped();
// bufferList only required when using query joins (queryFetch)
this.bufferList = (!queryFetch) ? null : new ArrayList<>();
this.currentBuffer = createBuffer(firstBatchSize);
this.currentBuffer = createBuffer(batchSize);
}
private LoadBuffer createBuffer(int size) {
@@ -58,11 +55,10 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
if (bufferList != null) {
bufferList.clear();
}
currentBuffer = createBuffer(secondaryBatchSize);
currentBuffer = createBuffer(batchSize);
}
private void configureQuery(SpiQuery<?> query) {
setLabel(query);
parent.propagateQueryState(query, docStoreMapped);
query.setParentNode(objectGraphNode);
@@ -85,9 +81,8 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
}
public void register(BeanCollection<?> bc) {
if (currentBuffer.isFull()) {
currentBuffer = createBuffer(secondaryBatchSize);
currentBuffer = createBuffer(batchSize);
}
currentBuffer.add(bc);
bc.setLoader(currentBuffer);
@@ -105,13 +100,8 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
if (!loadBuffer.list.isEmpty()) {
LoadManyRequest req = new LoadManyRequest(loadBuffer, parentRequest);
parent.getEbeanServer().loadMany(req);
if (!queryProps.isQueryFetchAll()) {
// Stop - only fetch the first batch ... the rest will be lazy loaded
break;
}
}
}
if (forEach) {
clear();
} else {
@@ -81,7 +81,7 @@ public final class BatchControl {
*/
private int bufferMax;
private Queue[] queues = new Queue[3];
private final Queue[] queues = new Queue[3];
/**
* Create for a given transaction, PersistExecute, default size and getGeneratedKeys.
@@ -447,7 +447,7 @@ public final class DefaultPersister implements Persister {
public void insert(EntityBean bean, Transaction t) {
PersistRequestBean<?> req = createRequest(bean, t, PersistRequest.Type.INSERT);
if (req.isReference()) {
if (req.isSkipReference()) {
// skip insert on reference bean
return;
}
@@ -881,6 +881,7 @@ public final class DefaultPersister implements Persister {
}
int count = request.executeOrQueue();
request.removeFromPersistenceContext();
if (request.isPersistCascade()) {
deleteAssocOne(request);
@@ -142,7 +142,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Tree that knows how to build the master and detail beans from the
* resultSet.
*/
private final SqlTreeNode rootNode;
private final SqlTreeRoot rootNode;
/**
* For master detail query.
@@ -444,13 +444,13 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
if (manyProperty == null) {
// only single resultSet row required to build object so we are done
// read a single resultSet row into single bean
nextBean = rootNode.load(this, null, null);
nextBean = rootNode.load(this);
return true;
}
if (nextBean == null) {
// very first read
nextBean = rootNode.load(this, null, null);
nextBean = rootNode.load(this);
} else {
// nextBean set to previously read currentBean
nextBean = currentBean;
@@ -478,7 +478,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* is different to the nextBean (false if we need to read more rows).
*/
private boolean checkForDifferentBean() throws SQLException {
currentBean = rootNode.load(this, null, null);
currentBean = rootNode.load(this);
return currentBean != nextBean;
}
@@ -9,7 +9,7 @@ import java.util.concurrent.locks.ReentrantLock;
class CQueryBindCapture implements SpiQueryBindCapture {
private static final double multiplier = 1.3d;
private static final double multiplier = 1.5d;
private final ReentrantLock lock = new ReentrantLock();
private final CQueryPlanManager manager;

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