Compare commits

...
Author SHA1 Message Date
rbygrave 850f1c1469 [maven-release-plugin] prepare release ebean-parent-12.9.0 2021-05-23 23:08:38 +12:00
rbygrave 622a90c7d5 No effective change - tidy whitespace in TransactionManager 2021-05-23 23:00:34 +12:00
rbygrave 4c317f11ae Add additional test for PrimaryKeyJoinColumn 2021-05-23 22:21:05 +12:00
rbygrave 01bce5cfa6 #2226 - Automatically determining join columns and ignoring PrimaryKeyJoinColumn.name Id warning when I want to use Id as a foreign key
Improve the message and dropping to INFO level.
2021-05-23 22:20:40 +12:00
rbygrave 2158925d3c #2202 - kotlin-maven-plugin issue with JDK 16
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-05-23 21:28:11 +12:00
Rob BygraveandGitHub 2aff6403a2 Merge pull request #2235 from ebean-orm/feature/2223
#2223 #2224 Support BeanMap modification via entrySet() and keySet()
2021-05-23 17:20:55 +12:00
rbygrave 3c4d600187 #2223 #2224 Support BeanMap modification via entrySet() and keySet() 2021-05-23 17:17:34 +12:00
Rob BygraveandGitHub f60ccb9b90 Merge pull request #2222 from ebean-orm/feature/support-filterMany-inline
Change such that filterMany can be included in the main query.
2021-05-21 17:20:17 +12:00
rbygrave 2a6108087c Bump version to 12.9.0-SNAPSHOT 2021-05-21 17:18:30 +12:00
rbygrave 0da86a82ae #2233 Bump to ebean-agent 12.9.0 2021-05-21 17:14:49 +12:00
rbygrave 643b0499c4 #2233 Add ebean-version.mf so ebean-agent can sniff the internal version
This allows the ebean-agent to support the older behaviour. Devs using the latest IntelliJ ebean plugin will get the old behaviour unless they bump ebean-core to 12.9 or later.
2021-05-21 16:09:15 +12:00
rbygrave 676bac390e #2233 Followup change that unsets load buffer from BeanCollection
- Clears the buffer after it has been used to load BeanCollection's
- Sets the BeanCollection to use the server rather than the Load Buffer just in case there are subsequent calls for loading (which there should not be)
2021-05-21 09:01:45 +12:00
rbygrave 79740accfa #2233 Fix with setter on ToMany now not invoking lazy loading
- ebean-agent changed to not invoke preGetter on ToMany (just like Id)
- EntityBeanIntercept preSetterMany() changed to ensure loading flag set
2021-05-21 08:48:42 +12:00
rbygrave 77c6f1cb9f #2233 Tidy failing test Ebean -> DB and rename test 2021-05-21 08:44:04 +12:00
rbygrave 538361d5fd #2233 Failing test 2021-05-21 08:33:31 +12:00
rbygrave b17593ba3c No effective change - tidy BeanList, BeanMap, BeanSet internals 2021-05-20 21:44:07 +12:00
rbygrave eb00690b30 #2231 - Change OrderBy to not be final to support mocking via Mockito 2021-05-20 14:09:22 +12:00
Rob BygraveandGitHub a772e65d32 Merge pull request #2225 from ebean-orm/dependabot/maven/ebean-core/commons-io-commons-io-2.7
Bump commons-io from 2.5 to 2.7 in /ebean-core
2021-05-20 13:56:00 +12:00
Brian Payne 66f7f69ec7 Fixed bug with soft-delete on one-to-one exported 2021-05-12 09:24:33 -07:00
Brian Payne ad52711fe1 Add test demonstrating soft-delete bug with optional relationship 2021-05-11 10:42:04 -07:00
dependabot[bot]andGitHub d6fdb4ee34 Bump commons-io from 2.5 to 2.7 in /ebean-core
Bumps commons-io from 2.5 to 2.7.

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-26 20:37:55 +00:00
rbygrave 74c7268d2f Change such that filterMany can be included in the main query.
That is, currently a filterMany automatically marks that path as a fetchQuery path. This change turns that off and the filterMany path and predicates can then be included in the main query.

This is better for performance (to include the filterMany path in with the main query) when the query is a findEach and looking to return a large number of results.
2021-04-21 18:57:03 +12:00
rbygrave 42fc419759 #2221 - ebean-redis: bump jedis dependency to 3.6.0 2021-04-20 22:23:33 +12:00
rbygrave 4fb3b9cddc Tidy use of StringHelper replace() in BeanProperty, BaseTableDdl 2021-04-20 22:13:45 +12:00
Robin Bygrave c60077174e Tidy use of StringHelper replace() in BeanProperty, BaseTableDdl 2021-04-20 15:14:20 +12:00
rbygrave 5e893dd2ad [maven-release-plugin] prepare for next development iteration 2021-04-19 21:40:16 +12:00
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
rob bygrave bab637667d Update test only - update TestBasicClobNoVer to use DB and AssertJ 2021-02-14 11:50:54 +13:00
259 changed files with 5649 additions and 1265 deletions
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1</version>
<version>12.9.0</version>
</parent>
<name>ebean api</name>
@@ -50,7 +50,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>6.15</version>
<version>7.0</version>
</dependency>
<dependency>
@@ -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.
@@ -307,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.
@@ -909,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);
@@ -930,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);
@@ -1664,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>
@@ -46,7 +46,7 @@ public class FetchConfig implements Serializable {
/**
* Deprecated - migrate to one of the static factory methods like {@link FetchConfig#ofQuery()}
*
* <p>
* Construct using default JOIN mode.
*/
@Deprecated
@@ -89,7 +89,7 @@ public class FetchConfig implements Serializable {
* Return FetchConfig to lazily load the relationship.
*/
public static FetchConfig ofLazy() {
return new FetchConfig(LAZY_MODE, 10);
return new FetchConfig(LAZY_MODE, 0);
}
/**
@@ -110,8 +110,8 @@ public class FetchConfig implements Serializable {
* We want to migrate away from mutating FetchConfig to a fully immutable FetchConfig.
*/
private FetchConfig mutate(int mode, int batchSize) {
if (batchSize < 1) {
throw new IllegalArgumentException("batch size "+batchSize+" must be > 0");
if (batchSize < 0) {
throw new IllegalArgumentException("batch size " + batchSize + " must be > 0");
}
this.mode = mode;
this.batchSize = batchSize;
@@ -124,7 +124,7 @@ public class FetchConfig implements Serializable {
*/
@Deprecated
public FetchConfig lazy() {
return mutate(LAZY_MODE, 10);
return mutate(LAZY_MODE, 0);
}
/**
@@ -137,7 +137,7 @@ public class FetchConfig implements Serializable {
/**
* 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>
@@ -150,17 +150,15 @@ public class FetchConfig implements Serializable {
/**
* 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 batchSize the batch size used to load beans on this path
*/
@@ -171,13 +169,12 @@ public class FetchConfig implements Serializable {
/**
* 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 batchSize the number of parent beans this path is populated for
*/
@@ -188,7 +185,7 @@ public class FetchConfig implements Serializable {
/**
* 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.
*/
+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;
+4 -11
View File
@@ -16,7 +16,7 @@ import java.util.Objects;
* on the Query object.
* </p>
*/
public final class OrderBy<T> implements Serializable {
public class OrderBy<T> implements Serializable {
private static final long serialVersionUID = 9157089257745730539L;
@@ -69,7 +69,6 @@ public final class OrderBy<T> implements Serializable {
* Add a property with ascending order to this OrderBy.
*/
public Query<T> asc(String propertyName) {
list.add(new Property(propertyName, true));
return query;
}
@@ -98,7 +97,6 @@ public final class OrderBy<T> implements Serializable {
return query;
}
/**
* Return true if the property is known to be contained in the order by clause.
*/
@@ -207,7 +205,6 @@ public final class OrderBy<T> implements Serializable {
if (!(obj instanceof OrderBy<?>)) {
return false;
}
OrderBy<?> e = (OrderBy<?>) obj;
return e.list.equals(list);
}
@@ -249,7 +246,7 @@ public final class OrderBy<T> implements Serializable {
/**
* A property and its ascending descending order.
*/
public static final class Property implements Serializable {
public static class Property implements Serializable {
private static final long serialVersionUID = 1546009780322478077L;
@@ -415,13 +412,10 @@ public final class OrderBy<T> implements Serializable {
}
private void parse(String orderByClause) {
if (orderByClause == null) {
return;
}
String[] chunks = orderByClause.split(",");
for (String chunk : chunks) {
for (String chunk : orderByClause.split(",")) {
Property p = parseProperty(chunk);
if (p != null) {
list.add(p);
@@ -467,8 +461,7 @@ public final class OrderBy<T> implements Serializable {
if (s.startsWith("desc")) {
return false;
}
String m = "Expecting [" + s + "] to be asc or desc?";
throw new RuntimeException(m);
throw new RuntimeException("Expecting [" + s + "] to be asc or desc?");
}
private boolean isEmptyString(String s) {
+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");
+18 -3
View File
@@ -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", FetchConfig.ofQuery(2))
* .fetchQuery("contacts")
* .where().eq("status", Status.NEW)
* .order().asc("id")
* .setMaxRows(2000)
+4 -4
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;
@@ -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 {
* ...
* }
@@ -85,6 +85,8 @@ public final class EntityBeanIntercept implements Serializable {
*/
private static final byte FLAG_CHANGED_PROP = 2;
private static final byte FLAG_CHANGEDLOADED_PROP = 3;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distinguish
* between an embedded bean being completely overwritten and one of its
@@ -569,6 +571,10 @@ public final class EntityBeanIntercept implements Serializable {
flags[propertyIndex] |= FLAG_CHANGED_PROP;
}
private void setChangeLoaded(int propertyIndex) {
flags[propertyIndex] |= FLAG_CHANGEDLOADED_PROP;
}
/**
* Set that an embedded bean has had one of its properties changed.
*/
@@ -937,7 +943,7 @@ public final class EntityBeanIntercept implements Serializable {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setChangedProperty(propertyIndex);
setChangeLoaded(propertyIndex);
}
}
@@ -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);
}
}
@@ -198,10 +198,9 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
}
if (list == null) {
sb.append("deferred ");
} else {
sb.append("size[").append(list.size()).append("] ");
sb.append("list").append(list).append("");
sb.append("list").append(list);
}
return sb.toString();
}
@@ -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);
}
}
@@ -175,10 +175,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
/**
* Returns the map entrySet.
* <p>
* This is because the key values may need to be set against the details (so
* they don't need to be set twice).
* </p>
*/
@Override
public Collection<?> getActualEntries() {
@@ -194,7 +190,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
}
if (map == null) {
sb.append("deferred ");
} else {
sb.append("size[").append(map.size()).append("]");
sb.append(" map").append(map);
@@ -243,17 +238,12 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
}
@Override
@SuppressWarnings({"unchecked"})
public Set<Entry<K, E>> entrySet() {
init();
if (isReadOnly()) {
return Collections.unmodifiableSet(map.entrySet());
}
if (modifyListening) {
Set<Entry<K, E>> s = map.entrySet();
return new ModifySet(this, s);
}
return map.entrySet();
return modifyListening ? new ModifyEntrySet<>(this, map.entrySet()) : map.entrySet();
}
@Override
@@ -274,8 +264,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
if (isReadOnly()) {
return Collections.unmodifiableSet(map.keySet());
}
// we don't really care about modifications to the ketSet?
return map.keySet();
return modifyListening ? new ModifyKeySet<>(this, map.keySet()) : map.keySet();
}
@Override
@@ -346,11 +335,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
if (isReadOnly()) {
return Collections.unmodifiableCollection(map.values());
}
if (modifyListening) {
Collection<E> c = map.values();
return new ModifyCollection<>(this, c);
}
return map.values();
return modifyListening ? new ModifyCollection<>(this, map.values()) : map.values();
}
@Override
@@ -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
@@ -177,7 +176,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
}
if (set == null) {
sb.append("deferred ");
} else {
sb.append("size[").append(set.size()).append("]");
sb.append(" set").append(set);
@@ -25,7 +25,7 @@ class ModifyCollection<E> implements Collection<E> {
* The owner is notified of the additions and removals.
* </p>
*/
public ModifyCollection(BeanCollection<E> owner, Collection<E> c) {
ModifyCollection(BeanCollection<E> owner, Collection<E> c) {
this.owner = owner;
this.c = c;
}
@@ -0,0 +1,131 @@
package io.ebean.common;
import java.util.*;
/**
* Handles the Entry Set for BeanMap.
*/
class ModifyEntrySet<K, E> implements Set<Map.Entry<K, E>> {
private final BeanMap<K, E> owner;
private final Set<Map.Entry<K, E>> entrySet;
ModifyEntrySet(BeanMap<K, E> owner, Set<Map.Entry<K, E>> entrySet) {
this.owner = owner;
this.entrySet = entrySet;
}
@Override
public int size() {
return entrySet.size();
}
@Override
public boolean isEmpty() {
return entrySet.isEmpty();
}
@Override
public boolean contains(Object o) {
return entrySet.contains(o);
}
@Override
public Object[] toArray() {
return entrySet.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return entrySet.toArray(a);
}
@Override
public boolean containsAll(Collection<?> entries) {
return entrySet.containsAll(entries);
}
@Override
public void clear() {
owner.clear();
}
@Override
public boolean add(Map.Entry<K, E> entry) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends Map.Entry<K, E>> c) {
throw new UnsupportedOperationException();
}
@SuppressWarnings("rawtypes")
@Override
public boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry entry = (Map.Entry) o;
final E val = owner.get(entry.getKey());
if (Objects.equals(val, entry.getValue())) {
owner.remove(entry.getKey());
return true;
}
}
return false;
}
@Override
public boolean retainAll(Collection<?> entries) {
boolean modified = false;
final Iterator<Map.Entry<K, E>> it = iterator();
while (it.hasNext()) {
if (!entries.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
@Override
public boolean removeAll(Collection<?> entries) {
boolean modified = false;
for (Object entry : entries) {
modified |= remove(entry);
}
return modified;
}
@Override
public Iterator<Map.Entry<K, E>> iterator() {
return new EntrySetIterator(new ArrayList<>(entrySet).iterator());
}
class EntrySetIterator implements Iterator<Map.Entry<K, E>> {
private final Iterator<Map.Entry<K, E>> iterator;
private Map.Entry<K, E> entry;
EntrySetIterator(Iterator<Map.Entry<K, E>> iterator) {
this.iterator = iterator;
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Map.Entry<K, E> next() {
entry = iterator.next();
return entry;
}
@Override
public void remove() {
owner.remove(entry.getKey());
iterator.remove();
}
}
}
@@ -0,0 +1,126 @@
package io.ebean.common;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
/**
* Handle the Key Set for BeanMap.
*/
class ModifyKeySet<E> implements Set<E> {
private final Set<E> keySet;
private final BeanMap<E, ?> owner;
ModifyKeySet(BeanMap<E, ?> owner, Set<E> keySet) {
this.owner = owner;
this.keySet = keySet;
}
@Override
public int size() {
return keySet.size();
}
@Override
public boolean isEmpty() {
return keySet.isEmpty();
}
@Override
public boolean contains(Object o) {
return keySet.contains(o);
}
@Override
public Object[] toArray() {
return keySet.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return keySet.toArray(a);
}
@Override
public boolean add(E key) {
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection<? extends E> keys) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
return owner.remove(o) != null;
}
@Override
public boolean containsAll(Collection<?> keys) {
return keySet.containsAll(keys);
}
@Override
public void clear() {
owner.clear();
}
@Override
public Iterator<E> iterator() {
return new KeySetIterator<>(new ArrayList<>(keySet).iterator());
}
@Override
public boolean retainAll(Collection<?> keys) {
return keysMatch(keys, false);
}
@Override
public boolean removeAll(Collection<?> keys) {
return keysMatch(keys, true);
}
private boolean keysMatch(Collection<?> keys, boolean containsMatch) {
boolean changed = false;
final Iterator<E> iterator = iterator();
while (iterator.hasNext()) {
final E key = iterator.next();
if (keys.contains(key) == containsMatch) {
iterator.remove();
changed = true;
}
}
return changed;
}
class KeySetIterator<K> implements Iterator<K> {
private final Iterator<K> iterator;
private K key;
KeySetIterator(Iterator<K> iterator) {
this.iterator = iterator;
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public K next() {
key = iterator.next();
return key;
}
@Override
public void remove() {
owner.remove(key);
iterator.remove();
}
}
}
@@ -1,24 +0,0 @@
package io.ebean.common;
import io.ebean.bean.BeanCollection;
import java.util.Set;
/**
* Wraps a Set for the purposes of notifying removals and additions to the
* BeanCollection owner.
* <p>
* This is required for persisting ManyToMany objects. Additions and removals
* become inserts and deletes to the intersection table.
* </p>
*/
class ModifySet<E> extends ModifyCollection<E> implements Set<E> {
/**
* Create with an Owner that is notified of any additions or deletions.
*/
public ModifySet(BeanCollection<E> owner, Set<E> s) {
super(owner, s);
}
}
@@ -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 .
*/
@@ -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.
*/
@@ -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,14 +2826,20 @@ 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);
@@ -3092,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;
@@ -3172,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;
}
/**
@@ -3248,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.7.1</version>
<version>12.9.0</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.7.1</tag>
<tag>ebean-parent-12.9.0</tag>
</scm>
<name>ebean autotune</name>
@@ -26,7 +26,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
<scope>provided</scope>
</dependency>
+17 -17
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1</version>
<version>12.9.0</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.9.0</ebean-agent.version>
<ebean-maven-plugin.version>12.9.0</ebean-maven-plugin.version>
</properties>
<dependencyManagement>
@@ -81,88 +81,88 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-xml</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-autotune</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1</version>
<version>12.9.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
+8 -8
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1</version>
<version>12.9.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -15,7 +15,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.7.1</tag>
<tag>ebean-parent-12.9.0</tag>
</scm>
<profiles>
@@ -72,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>
@@ -87,19 +87,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.7.1</version>
<version>12.9.0</version>
</dependency>
<dependency>
@@ -282,7 +282,7 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
<version>2.7</version>
<scope>test</scope>
</dependency>
@@ -302,7 +302,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>12.6.6</version>
<version>12.9.0</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.
@@ -71,7 +71,6 @@ public class LoadManyRequest extends LoadRequest {
* This for use when lazy loading is invoked on methods such as clear() and removeAll() where it
* generally makes sense to only fetch the Id values as the other property information is not
* used.
* </p>
*/
private boolean isOnlyIds() {
return onlyIds;
@@ -91,18 +90,16 @@ public class LoadManyRequest extends LoadRequest {
return loadContext.getBatchSize();
}
private List<Object> getParentIdList() {
private List<Object> parentIdList(SpiEbeanServer server) {
List<Object> idList = new ArrayList<>();
BeanPropertyAssocMany<?> many = getMany();
for (BeanCollection<?> bc : batch) {
idList.add(many.getParentId(bc.getOwnerBean()));
bc.setLoader(server); // don't use the load buffer again
}
if (many.getTargetDescriptor().isPadInExpression()) {
BindPadding.padIds(idList);
}
return idList;
}
@@ -111,9 +108,7 @@ public class LoadManyRequest extends LoadRequest {
}
public SpiQuery<?> createQuery(SpiEbeanServer server) {
BeanPropertyAssocMany<?> many = getMany();
SpiQuery<?> query = many.newQuery(server);
String orderBy = many.getLazyFetchOrderBy();
if (orderBy != null) {
@@ -128,7 +123,7 @@ public class LoadManyRequest extends LoadRequest {
}
query.setLazyLoadForParents(many);
many.addWhereParentIdIn(query, getParentIdList(), loadContext.isUseDocStore());
many.addWhereParentIdIn(query, parentIdList(server), loadContext.isUseDocStore());
query.setPersistenceContext(loadContext.getPersistenceContext());
String mode = isLazy() ? "+lazy" : "+query";
@@ -146,7 +141,6 @@ public class LoadManyRequest extends LoadRequest {
// override to just select the Id values
query.select(many.getTargetIdProperty());
}
return query;
}
@@ -154,10 +148,8 @@ public class LoadManyRequest extends LoadRequest {
* After the query execution check for empty collections and load L2 cache if desired.
*/
public void postLoad() {
BeanDescriptor<?> desc = loadContext.getBeanDescriptor();
BeanPropertyAssocMany<?> many = getMany();
// check for BeanCollection's that where never processed
// in the +query or +lazy load due to no rows (predicates)
for (BeanCollection<?> bc : batch) {
@@ -172,6 +164,5 @@ public class LoadManyRequest extends LoadRequest {
desc.cacheManyPropPut(many, bc, parentId);
}
}
}
}
@@ -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.
*/
@@ -105,4 +105,9 @@ public interface SpiExpression extends Expression {
* Check for match to a natural key query returning false if it doesn't match.
*/
boolean naturalKey(NaturalKeyQueryData<?> data);
/**
* Apply property prefix when filterMany expressions included into main query.
*/
void prefixProperty(String path);
}
@@ -43,4 +43,9 @@ public interface SpiExpressionList<T> extends ExpressionList<T>, SpiExpression {
default void applyRowLimits(SpiQuery<?> query) {
// do nothing by default
}
/**
* Apply property prefix when filterMany expressions included in main query.
*/
void prefixProperty(String path);
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.api;
import io.ebean.FetchConfig;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import java.util.Set;
@@ -19,4 +20,8 @@ public interface SpiQueryFetch {
*/
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();
}
/**
@@ -219,10 +219,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
*/
@Override
public void prepareQuery() {
secondaryQueries = query.convertJoins();
beanDescriptor.prepareQuery(query);
adapterPreQuery();
this.secondaryQueries = query.convertJoins();
this.queryPlanKey = query.prepare(this);
queryPlanKey = query.prepare(this);
}
public boolean isNativeSql() {
@@ -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;
@@ -55,8 +56,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import static io.ebean.util.StringHelper.replace;
/**
* Description of a property of a bean. Includes its deployment information such
* as database column mapping information.
@@ -65,6 +64,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 +325,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;
@@ -353,13 +354,13 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
}
private String tableAliasIntern(BeanDescriptor<?> descriptor, String s, boolean dbEncrypted, String dbColumn) {
if (descriptor != null) {
s = replace(s, "${ta}.", "${}");
s = replace(s, "${ta}", "${}");
if (s != null && descriptor != null) {
s = s.replace("${ta}.", "${}");
s = s.replace("${ta}", "${}");
if (dbEncrypted) {
s = dbEncryptFunction.getDecryptSql(s);
String namedParam = ":encryptkey_" + descriptor.getBaseTable() + "___" + dbColumn;
s = replace(s, "?", namedParam);
s = s.replace("?", namedParam);
}
}
return InternString.intern(s);
@@ -531,7 +532,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;
}
}
/**
@@ -682,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() {
@@ -715,7 +714,7 @@ public class DeployBeanProperty {
return sqlFormulaSelect;
}
if (aggregation != null) {
return aggregation;
return aggregationParsed == null ? dbColumn : aggregationParsed;
}
return dbColumn;
}
@@ -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()));
}
}
@@ -54,7 +54,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
}
private void readAssocOne(DeployBeanPropertyAssocOne<?> prop) {
ManyToOne manyToOne = get(prop, ManyToOne.class);
if (manyToOne != null) {
readManyToOne(manyToOne, prop);
@@ -183,7 +182,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
}
private void readManyToOne(ManyToOne propAnn, DeployBeanPropertyAssocOne<?> beanProp) {
setCascadeTypes(propAnn.cascade(), beanProp.getCascadeInfo());
setTargetType(propAnn.targetEntity(), beanProp);
setBeanTable(beanProp);
@@ -194,7 +192,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
}
private void readOneToOne(OneToOne propAnn, DeployBeanPropertyAssocOne<?> prop) {
prop.setOneToOne();
prop.setDbInsertable(true);
prop.setDbUpdateable(true);
@@ -223,21 +220,19 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
}
private void readPrimaryKeyJoin(PrimaryKeyJoinColumn primaryKeyJoin, DeployBeanPropertyAssocOne<?> prop) {
if (!prop.isOneToOne()) {
throw new IllegalStateException("Expecting property " + prop.getFullBeanName() + " with PrimaryKeyJoinColumn to be a OneToOne?");
}
prop.setPrimaryKeyJoin(true);
if (!primaryKeyJoin.name().isEmpty()) {
log.warn("Automatically determining join columns and ignoring PrimaryKeyJoinColumn.name {} on {}", primaryKeyJoin.name(), prop.getFullBeanName());
log.info("Automatically determining join columns for @PrimaryKeyJoinColumn - ignoring PrimaryKeyJoinColumn.name attribute [{}] on {}", primaryKeyJoin.name(), prop.getFullBeanName());
}
if (!primaryKeyJoin.referencedColumnName().isEmpty()) {
log.warn("Automatically determining join columns and Ignoring PrimaryKeyJoinColumn.referencedColumnName {} on {}", primaryKeyJoin.referencedColumnName(), prop.getFullBeanName());
log.info("Automatically determining join columns for @PrimaryKeyJoinColumn - Ignoring PrimaryKeyJoinColumn.referencedColumnName attribute [{}] on {}", primaryKeyJoin.referencedColumnName(), prop.getFullBeanName());
}
BeanTable baseBeanTable = factory.getBeanTable(info.getDescriptor().getBeanType());
String localPrimaryKey = baseBeanTable.getIdColumn();
String foreignColumn = getBeanTable(prop).getIdColumn();
@@ -245,7 +240,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
}
private void readEmbedded(DeployBeanPropertyAssocOne<?> prop, Embedded embedded) {
if (descriptor.isDocStoreOnly() && prop.getDocStoreDoc() == null) {
prop.setDocStoreEmbedded("");
}
@@ -257,7 +251,6 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
} catch (NoSuchMethodError e) {
// using standard JPA API without prefix option, maybe in EE container
}
readEmbeddedAttributeOverrides(prop);
}
@@ -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);
}
}
@@ -16,12 +16,17 @@ import io.ebeaninternal.api.NaturalKeyQueryData;
*/
public abstract class AbstractExpression implements SpiExpression {
protected final String propName;
protected String propName;
protected AbstractExpression(String propName) {
this.propName = propName;
}
@Override
public void prefixProperty(String path) {
this.propName = path + "." + propName;
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// by default can't use naturalKey cache
@@ -17,8 +17,8 @@ class BetweenPropertyExpression extends NonPrepareExpression {
private static final String BETWEEN = " between ";
private final String lowProperty;
private final String highProperty;
private String lowProperty;
private String highProperty;
private final Object value;
BetweenPropertyExpression(String lowProperty, String highProperty, Object value) {
@@ -27,6 +27,12 @@ class BetweenPropertyExpression extends NonPrepareExpression {
this.value = value;
}
@Override
public void prefixProperty(String path) {
this.lowProperty = path + "." + lowProperty;
this.highProperty = path + "." + highProperty;
}
protected String name(String propName) {
return propName;
}
@@ -88,6 +88,13 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
}
}
@Override
public void prefixProperty(String path) {
for (SpiExpression exp : list) {
exp.prefixProperty(path);
}
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -126,6 +126,13 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
}
@Override
public void prefixProperty(String path) {
for (SpiExpression exp : list) {
exp.prefixProperty(path);
}
}
@Override
public Junction<T> toJunction() {
return new JunctionExpression<>(Junction.Type.FILTER, this);
@@ -436,6 +443,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);
@@ -36,6 +36,11 @@ class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpress
this.subQuery = null;
}
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -19,6 +19,11 @@ class IdExpression extends NonPrepareExpression implements SpiExpression {
this.value = value;
}
@Override
public void prefixProperty(String path) {
throw new IllegalStateException("Not allowed?");
}
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeId(value);
@@ -66,6 +66,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
this.exprList = exprList;
}
@Override
public void prefixProperty(String path) {
exprList.prefixProperty(path);
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -446,6 +451,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);
@@ -57,6 +57,12 @@ abstract class LogicExpression implements SpiExpression {
this.expTwo = (SpiExpression) expTwo;
}
@Override
public void prefixProperty(String path) {
expOne.prefixProperty(path);
expTwo.prefixProperty(path);
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -24,6 +24,11 @@ class NestedPathWrapperExpression implements SpiExpression {
this.delegate = delegate;
}
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -9,6 +9,11 @@ import io.ebeaninternal.api.NaturalKeyQueryData;
*/
abstract class NonPrepareExpression implements SpiExpression {
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -17,6 +17,11 @@ class NoopExpression implements SpiExpression {
protected static final NoopExpression INSTANCE = new NoopExpression();
@Override
public void prefixProperty(String path) {
// do nothing
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -22,6 +22,11 @@ final class NotExpression implements SpiExpression {
this.exp = (SpiExpression) exp;
}
@Override
public void prefixProperty(String path) {
exp.prefixProperty(path);
}
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache
@@ -33,7 +33,7 @@ abstract class DLoadBaseContext {
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;
@@ -42,11 +42,7 @@ abstract class DLoadBaseContext {
this.hitCache = parent.isBeanCacheGet() && desc.isBeanCaching();
this.objectGraphNode = parent.getObjectGraphNode(path);
this.queryFetch = queryProps != null && queryProps.isQueryFetch();
this.batchSize = initBatchSize(defaultBatchSize, queryProps);
}
private int initBatchSize(int batchSize, OrmQueryProperties queryProps) {
return queryProps == null ? batchSize : queryProps.getBatchSize();
this.batchSize = parent.batchSize(queryProps);
}
/**
@@ -31,8 +31,8 @@ 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(batchSize);
@@ -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,7 +182,7 @@ 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;
}
@@ -190,7 +190,7 @@ public class DLoadContext implements LoadContext {
for (OrmQueryProperties aSecQuery : secQuery) {
int batchSize = aSecQuery.getBatchSize();
if (batchSize == 0) {
batchSize = defaultQueryBatch;
batchSize = 100;
}
maxBatch = Math.max(maxBatch, batchSize);
}
@@ -287,51 +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 = props.getBatchSize();
if (batchSize == 0) {
batchSize = 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) {

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