Compare commits

...
Author SHA1 Message Date
rob bygrave 7ab7b612f6 [maven-release-plugin] prepare release ebean-parent-12.8.0 2021-03-18 22:23:34 +13:00
rob bygrave ce1d2803ce Bump test wait time for redis 2021-03-18 22:18:34 +13:00
rob bygrave 9b066e4eac Bump version to 12.8.0-SNAPSHOT 2021-03-18 21:26:50 +13:00
Robin Bygrave e1f9d106f0 Additional tests for #2191 query findEach batch consumer 2021-03-18 17:12:21 +13:00
Robin Bygrave e40a539406 #2200 - ENH: Add DtoQuery findEach with batch consumer - findEach(int batchSize, Consumer<List<T>> consumer) 2021-03-18 17:02:41 +13:00
Robin Bygrave bb73c30bb7 #2199 - ScalarTypeWrapper doesn't handle "nullValue" correctly
Fix for - standard JPA AttributeConverter which doesn't have the getNullValue method, he/she can't convert null to a custom null object

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

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

* Refactor OrmQueryProperties internals to reduce raw property parsing

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

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

* Refactor query internals adding fetchProperties() selectProperties()

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

* Remove unused readOnly from OrmQueryProperties

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

* WIP Tidy DefaultOrmQuery planDescription()

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

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

* Refactor OrmQueryPropertiesParser - remove JunkMain

* Refactor OrmQueryDetail, OrmQueryProperties - properties
2021-02-13 11:58:00 +13:00
SnōwballandGitHub 758f84f573 Ignore special kotlin collection types (#2130)
Kotlin prefers(emits compiler warnings if not) the built-in types
e.g. Set and MutableSet instead of the java.util.* variety. This
patch removes the imports for those, so the built-in varieties are
used.
2021-02-13 11:56:04 +13:00
rob bygrave 2073fefc92 #2162 - Change ebean artifact such that ebean-ddl-generator is a dependency of ebean-test and ebean-autotune optional 2021-02-12 23:12:32 +13:00
rob bygrave 5b98d8d9c2 [maven-release-plugin] prepare for next development iteration 2021-02-12 22:27:45 +13:00
rob bygrave 8e59e72983 [maven-release-plugin] prepare release ebean-parent-12.6.7 2021-02-12 22:27:34 +13:00
rob bygrave b56d68dac7 #2157 - ManyToMany mapping to itself results in broken generated SQL DDL when not defining @JoinTable. 2021-02-12 22:17:04 +13:00
rob bygrave e0c3a92b60 Refactor FetchConfig 2021-02-12 21:01:57 +13:00
Robin Bygrave 213562fcf1 No change - update ddl review for h2 2021-02-12 16:34:24 +13:00
Rob BygraveandGitHub d3a09b93c0 #2156 - Using @OneToOne(targetEntity...) with Interface field results in BeanNotRegisteredException (#2160) 2021-02-12 16:30:28 +13:00
Robin Bygrave 536ea57369 Merge branch 'master' of https://github.com/ebean-orm/ebean 2021-02-11 17:28:21 +13:00
Robin Bygrave ac1dd4cbab #2158 Refactor to use try with resources with Connection for platform selection via jdbc metaData 2021-02-11 17:28:05 +13:00
Robin Bygrave bc33e05906 #2158 - Oracle - automatic platform selection doesn't select older Oracle11Platform via jdbc metadata major version 2021-02-11 17:20:28 +13:00
rob bygrave 8ecc407cb0 #2154 - Fix tests for QueryBeans: TargetEntity is not evaluated during bean generation resulting in bad query beans 2021-02-10 19:25:42 +13:00
Robin Bygrave 2672dff512 #2155 - QueryBeans: Change to use own @Generated with retention RUNTIME 2021-02-10 15:28:55 +13:00
Robin Bygrave 74d907bbce Refactor kotlin querybean generator ProcessingContext extract asElement() method 2021-02-10 15:12:28 +13:00
Robin Bygrave 53aacabce7 #2154 - QueryBeans: TargetEntity is not evaluated during bean generation resulting in bad query beans 2021-02-10 15:10:03 +13:00
Robin Bygrave ab9cdd8f5b #2153 - QueryBeans: OneToMany Map property is handled as PScalar instead of TQAssocBean 2021-02-10 13:52:29 +13:00
Robin Bygrave b3d439ed11 Bump tile-maven-plugin to 2.19 2021-02-10 13:00:50 +13:00
Robin Bygrave d57657c209 #2127 - Fix for - Adding a new list of children to @OneToMany with orphanRemoval true does not delete existing orphans
Modifies EntityBeanIntercept preSetterMany to set the changed state on the many property and use this in SaveManyBeans to detect when the existing beans need orphan removal
2021-02-04 15:45:19 +13:00
Robin Bygrave dd080c5933 Test only change - refactor update TestBeanState 2021-02-04 15:19:34 +13:00
Rob BygraveandGitHub 0be98d7989 Fix to only cancel query once (#2152)
Change DefaultOrmQuery.cancel() to call underlying jdbc cancel once

- Refactor tidy already cancelled check (pre query execution)
- Remove unnecessary extra transaction.end() call on future query execution (as already handled by CallableQueryList etc)
2021-02-03 14:51:42 +13:00
rob bygrave fca54dbbe3 [maven-release-plugin] prepare for next development iteration 2021-01-24 17:42:50 +13:00
rob bygrave 7ec5fe6a85 [maven-release-plugin] prepare release ebean-parent-12.6.6 2021-01-24 17:42:38 +13:00
rob bygrave 9ea5505214 Bump to ebean agent 12.6.6 2021-01-24 17:40:13 +13:00
Robin Bygrave 75d3b566a7 Merge branch 'FOCONIS-prePersist-postPersist-fix-batch-flush-12' 2021-01-22 14:01:19 +13:00
Robin Bygrave e38059f52c #2149 - Fix for ADD: jdbc batch flush doesnt execute sql statements created in lifecycle methods (e.g. beans saved in PostInsert etc)
Note that the foconis fork has a onPersist extension which is removed from the test.
2021-01-22 14:00:48 +13:00
Robin Bygrave 72331aa945 Merge branch 'prePersist-postPersist-fix-batch-flush-12' of https://github.com/FOCONIS/ebean into FOCONIS-prePersist-postPersist-fix-batch-flush-12 2021-01-22 13:14:05 +13:00
Robin Bygrave e497c54f82 No effective change - modify test redis ClusterTest to have 20ms wait 2021-01-21 17:01:36 +13:00
Robin Bygrave b44091ff3a #2147 Fix for ADD: multiple to many outer joins cause wrong count in distinct count…
Copy of Rolands fix in FOCONIS branch. This also brings over some of the extra tests found there.

Note that the SQL is slightly different from the FOCONIS branch in that there is additional foreign key columns included in the sub-query select clause.
2021-01-21 16:56:42 +13:00
Robin Bygrave 2ab889f889 Merge branch 'FOCONIS-count-distinct-with-two-joins-12' 2021-01-21 14:50:32 +13:00
Robin Bygrave 23541e661e Merge branch 'count-distinct-with-two-joins-12' of https://github.com/FOCONIS/ebean into FOCONIS-count-distinct-with-two-joins-12 2021-01-21 14:45:56 +13:00
trojo 6778e37652 ADD: batch flash doesnt execute sql statements created in lifecycle methods 2021-01-20 17:43:14 +01:00
rob bygrave 5eac31283d #2148 - PersistenceException: No ScalarType registered for class java.util.LinkedHashMap 2021-01-20 23:31:45 +13:00
trojo 38016773d4 added comments 2021-01-20 10:47:53 +01:00
trojo ad80eb267f ADD: multiple to many outer joins cause wrong count in distinct count queries 2021-01-20 09:57:25 +01:00
rob bygrave 449e0832e0 Merge branch 'FOCONIS-cache-with-lazyload-wrong-12' 2021-01-20 21:37:57 +13:00
rob bygrave f28f5c9a01 #2146 - no change - Tidy test only TestWithCacheAndLazyLoad 2021-01-20 21:37:29 +13:00
rob bygrave 9dd28d8879 #2146 - Fix for Cache with lazy load doesnt work
When the list is empty() it has already been loaded and loading should skip the l2 cache
2021-01-20 21:10:49 +13:00
rob bygrave b47eec99a5 Merge branch 'cache-with-lazyload-wrong-12' of https://github.com/FOCONIS/ebean into FOCONIS-cache-with-lazyload-wrong-12 2021-01-20 20:36:18 +13:00
Robin Bygrave 99bb55efa0 No change - update ebean-test test configuration only for windows 2021-01-20 17:22:00 +13:00
Robin Bygrave 4408eb618a No change - update test only - TestHistoryOneToMany with wait for when run on windows 2021-01-20 17:11:07 +13:00
rob bygrave 9ccd1b1b27 No effective change - tidy whitespace in SqlTreeBuilder and SqlTreeNodeExtraJoin 2021-01-20 00:10:09 +13:00
rob bygrave 0c1657fef5 #2145 - SoftDelete predicate missing on join to ManyToOne supporting predicate expression 2021-01-20 00:00:27 +13:00
Noemi Szemenyei ee393c6485 ADD: testcase for wrong lazyload with cache 2021-01-19 11:41:19 +01:00
rob bygrave 7f68b9d846 No effective change - tidy whitespace in BeanDescriptorCacheHelp 2021-01-19 22:48:29 +13:00
rob bygrave 2035a3344e Merge branch 'FOCONIS-persistcontext-wrong' 2021-01-19 22:37:45 +13:00
rob bygrave ade36582a0 #2144 - Fix for NPE via Wrong beans in PersistContext avoid proper lazy load 2021-01-19 22:37:15 +13:00
Roland Praml a4b8be61e3 ADD: some suggestions/debug hints 2021-01-11 11:50:25 +01:00
Roland Praml b7feb62ecd ADD: testcase for broken lazyload 2021-01-11 11:50:05 +01:00
rob bygrave 7e48f7c8eb [maven-release-plugin] prepare for next development iteration 2021-01-05 18:02:21 +13:00
278 changed files with 5705 additions and 2622 deletions
+1 -1
View File
@@ -293,6 +293,6 @@ ebean.tenant.schemaProvider
ebean.updateAllPropertiesInBatch
ebean.updateChangesOnly
ebean.updatesDeleteMissingChildren
ebean.useJavaxValidationNotNull
ebean.useValidationNotNull
ebean.useJtaTransactionManager
+3 -16
View File
@@ -4,18 +4,13 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.6.5</version>
<version>12.8.0</version>
</parent>
<name>ebean api</name>
<description>ebean api</description>
<artifactId>ebean-api</artifactId>
<properties>
<jackson-core.version>2.11.3</jackson-core.version>
<jackson-databind.version>2.11.3</jackson-databind.version>
</properties>
<dependencies>
<!--
@@ -74,7 +69,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-core.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
@@ -82,7 +77,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
@@ -102,14 +97,6 @@
<optional>true</optional>
</dependency>
<!-- provided scope to read validation annotations Size etc -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
@@ -14,7 +14,7 @@ import java.util.Optional;
*
* public class CustomerFinder extends BeanFinder<Long,Customer> {
*
* Inject
* @Inject
* public CustomerFinder(Database database) {
* super(Customer.class, database);
* }
@@ -9,10 +9,10 @@ import java.util.Collection;
* <p>
* <pre>{@code
*
* Repository
* @Repository
* public class CustomerRepository extends BeanRepository<Long,Customer> {
*
* Inject
* @Inject
* public CustomerRepository(Database server) {
* super(Customer.class, server);
* }
@@ -897,7 +897,7 @@ public interface Database {
* <pre>{@code
* public class Order { ...
*
* OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* List<OrderDetail> details;
* ...
* }
@@ -33,6 +33,7 @@ public class DatabaseFactory {
private static final ReentrantLock lock = new ReentrantLock();
private static SpiContainer container;
private static String defaultServerName;
static {
EbeanVersion.getVersion();
@@ -76,6 +77,12 @@ public class DatabaseFactory {
}
Database server = createInternal(config);
if (config.isRegister()) {
if (config.isDefaultServer()) {
if (defaultServerName != null) {
throw new IllegalStateException("Registering [" + config.getName() + "] as the default server but [" + defaultServerName + "] is already registered as the default");
}
defaultServerName = config.getName();
}
DbPrimary.setSkip(true);
DbContext.getInstance().register(server, config.isDefaultServer());
}
@@ -149,7 +149,7 @@ public interface DocumentStore {
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEachWhile(new Predicate<Order>() {
* Override
* @Override
* public void accept(Order bean) {
* // process the bean
*
@@ -53,6 +53,17 @@ public interface DtoQuery<T> {
*/
void findEach(Consumer<T> consumer);
/**
* Execute the query iterating the results and batching them for the consumer.
* <p>
* This runs like findEach streaming results from the database but just collects the results
* into batches to pass to the consumer.
*
* @param batch The number of dto beans to collect before given them to the consumer
* @param consumer The consumer to process the batch of DTO beans
*/
void findEach(int batch, Consumer<List<T>> consumer);
/**
* Execute the query iterating a row at a time with the ability to stop consuming part way through.
* <p>
+1 -1
View File
@@ -346,7 +346,7 @@ public final class Ebean {
* <pre>{@code
* public class Order { ...
*
* OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* List<OrderDetail> details;
* ...
* }
@@ -200,7 +200,7 @@ public interface ExpressionFactory {
Expression gtOrNull(String propertyName, Object value);
/**
* Greater than or Equal to OR Null <code> >= or null </code>
* Greater than or Equal to OR Null ({@code >= or null })
* <p>
* A convenient expression combining GE and Is Null. Most often useful for range
* expressions where the top range value is nullable.
@@ -227,7 +227,7 @@ public interface ExpressionFactory {
Expression ltOrNull(String propertyName, Object value);
/**
* Less Than or Equal to OR Null <code> <= or null </code>
* Less Than or Equal to OR Null ({@code <= or null })
* <p>
* A convenient expression combining LE and Is Null. Most often useful for range
* expressions where the bottom range value is nullable.
@@ -196,13 +196,6 @@ public interface ExpressionList<T> {
*/
Query<T> forUpdate();
/**
* Deprecated - migrate to withLock().
* Execute using "for update" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdate(Query.LockType lockType);
/**
* Execute using "for update" clause with No Wait option.
* <p>
@@ -211,13 +204,6 @@ public interface ExpressionList<T> {
*/
Query<T> forUpdateNoWait();
/**
* Deprecated - migrate to withLock().
* Execute using "for update nowait" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdateNoWait(Query.LockType lockType);
/**
* Execute using "for update" clause with Skip Locked option.
* <p>
@@ -226,13 +212,6 @@ public interface ExpressionList<T> {
*/
Query<T> forUpdateSkipLocked();
/**
* Deprecated - migrate to withLock().
* Execute using "for update skip locked" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdateSkipLocked(Query.LockType lockType);
/**
* Execute the query including soft deleted rows.
*/
@@ -328,6 +307,13 @@ public interface ExpressionList<T> {
*/
void findEach(Consumer<T> consumer);
/**
* Execute findEach with a batch consumer.
*
* @see Query#findEach(int, Consumer)
*/
void findEach(int batch, Consumer<List<T>> consumer);
/**
* Execute the query processing the beans one at a time with the ability to
* stop processing before reading all the beans.
@@ -930,7 +916,7 @@ public interface ExpressionList<T> {
ExpressionList<T> gtOrNull(String propertyName, Object value);
/**
* Greater Than or Equal to OR Null - <code> >= or null </code>.
* Greater Than or Equal to OR Null - ({@code >= or null }).
*/
ExpressionList<T> geOrNull(String propertyName, Object value);
@@ -951,7 +937,7 @@ public interface ExpressionList<T> {
ExpressionList<T> ltOrNull(String propertyName, Object value);
/**
* Less Than or Equal to OR Null - <code> <= or null </code>.
* Less Than or Equal to OR Null - ({@code <= or null }).
*/
ExpressionList<T> leOrNull(String propertyName, Object value);
@@ -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>
+146 -190
View File
@@ -3,30 +3,13 @@ package io.ebean;
import java.io.Serializable;
/**
* Defines the configuration options for a "query fetch" or a
* "lazy loading fetch". This gives you the ability to use multiple smaller
* queries to populate an object graph as opposed to a single large query.
* <p>
* The primary goal is to provide efficient ways of loading complex object
* graphs avoiding SQL Cartesian product and issues around populating object
* graphs that have multiple *ToMany relationships.
* </p>
* <p>
* It also provides the ability to control the lazy loading queries (batch size,
* selected properties and fetches) to avoid N+1 queries etc.
* <p>
* There can also be cases loading across a single OneToMany where 2 SQL queries
* using Ebean FetchConfig.query() can be more efficient than one SQL query.
* When the "One" side is wide (lots of columns) and the cardinality difference
* is high (a lot of "Many" beans per "One" bean) then this can be more
* efficient loaded as 2 SQL queries.
* </p>
* Defines how a relationship is fetched via either normal SQL join,
* a eager secondary query, via lazy loading or via eagerly hitting L2 cache.
* <p>
* <pre>{@code
* // Normal fetch join results in a single SQL query
* List<Order> list = DB.find(Order.class).fetch("details").findList();
*
* // Find Orders join details using a single SQL query
* }</pre>
* <p>
* Example: Using a "query join" instead of a "fetch join" we instead use 2 SQL queries
@@ -37,103 +20,13 @@ import java.io.Serializable;
* // This will use 2 SQL queries to build this object graph
* List<Order> list =
* DB.find(Order.class)
* .fetch("details", new FetchConfig().query())
* .fetch("details", FetchConfig.ofQuery())
* .findList();
*
* // query 1) find order
* // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
*
* }</pre>
* <p>
* Example: Using 2 "query joins"
* </p>
* <p>
* <pre>{@code
*
* // This will use 3 SQL queries to build this object graph
* List<Order> list =
* DB.find(Order.class)
* .fetch("details", new FetchConfig().query())
* .fetch("customer", new FetchConfig().queryFirst(5))
* .findList();
*
* // query 1) find order
* // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
* // query 3) find customer where id in (?,?,?,?,?) // first 5 customers
*
* }</pre>
* <p>
* Example: Using "query joins" and partial objects
* </p>
* <p>
*
* <pre>{@code
* // This will use 3 SQL queries to build this object graph
* List<Order> list =
* DB.find(Order.class)
* .select("status, shipDate")
* .fetch("details", "quantity, price", new FetchConfig().query())
* .fetch("details.product", "sku, name")
* .fetch("customer", "name", new FetchConfig().queryFirst(5))
* .fetch("customer.contacts")
* .fetch("customer.shippingAddress")
* .findList();
*
* // query 1) find order (status, shipDate)
* // query 2) find orderDetail (quantity, price) fetch product (sku, name) where
* // order.id in (?,? ...)
* // query 3) find customer (name) fetch contacts (*) fetch shippingAddress (*)
* // where id in (?,?,?,?,?)
*
* // Note: the fetch of "details.product" is automatically included into the
* // fetch of "details"
* //
* // Note: the fetch of "customer.contacts" and "customer.shippingAddress"
* // are automatically included in the fetch of "customer"
* }</pre>
* <p>
* You can use query() and lazy together on a single join. The query is executed
* immediately and the lazy defines the batch size to use for further lazy
* loading (if lazy loading is invoked).
* </p>
* <p>
* <pre>{@code
*
* List<Order> list =
* DB.find(Order.class)
* .fetch("customer", new FetchConfig().query(10).lazy(5))
* .findList();
*
* // query 1) find order
* // query 2) find customer where id in (?,?,?,?,?,?,?,?,?,?) // first 10 customers
* // .. then if lazy loading of customers is invoked
* // .. use a batch size of 5 to load the customers
*
* }</pre>
* <p>
* <p>
* Example of controlling the lazy loading query:
* </p>
* <p>
* This gives us the ability to optimise the lazy loading query for a given use
* case.
* </p>
* <p>
* <pre>{@code
*
* List<Order> list = DB.find(Order.class)
* .fetch("customer","name", new FetchConfig().lazy(5))
* .fetch("customer.contacts","contactName, phone, email")
* .fetch("customer.shippingAddress")
* .where().eq("status",Order.Status.NEW)
* .findList();
*
* // query 1) find order where status = Order.Status.NEW
* //
* // .. if lazy loading of customers is invoked
* // .. use a batch size of 5 to load the customers
*
* }</pre>
*
* @author mario
* @author rbygrave
@@ -142,66 +35,122 @@ public class FetchConfig implements Serializable {
private static final long serialVersionUID = 1L;
private int lazyBatchSize = -1;
private static final int JOIN_MODE = 0;
private static final int QUERY_MODE = 1;
private static final int LAZY_MODE = 2;
private static final int CACHE_MODE = 3;
private int queryBatchSize = -1;
private boolean queryAll;
private boolean cache;
private int mode;
private int batchSize;
private int hashCode;
/**
* Construct the fetch configuration object.
*/
public FetchConfig() {
}
/**
* Specify that this path should be lazy loaded using the default batch load
* size.
*/
public FetchConfig lazy() {
this.lazyBatchSize = 0;
this.queryAll = false;
return this;
}
/**
* Specify that this path should be lazy loaded with a specified batch size.
* Deprecated - migrate to one of the static factory methods like {@link FetchConfig#ofQuery()}
*
* @param lazyBatchSize the batch size for lazy loading
* Construct using default JOIN mode.
*/
public FetchConfig lazy(int lazyBatchSize) {
this.lazyBatchSize = lazyBatchSize;
this.queryAll = false;
@Deprecated
public FetchConfig() {
//this.mode = JOIN_MODE;
this.batchSize = 100;
this.hashCode = 1000;
}
private FetchConfig(int mode, int batchSize) {
this.mode = mode;
this.batchSize = batchSize;
this.hashCode = mode + 10 * batchSize;
}
/**
* Return FetchConfig to eagerly fetch the relationship using L2 cache.
* <p>
* Any cache misses will be loaded by secondary query to the database.
*/
public static FetchConfig ofCache() {
return new FetchConfig(CACHE_MODE, 100);
}
/**
* Return FetchConfig to eagerly fetch the relationship using a secondary query.
*/
public static FetchConfig ofQuery() {
return new FetchConfig(QUERY_MODE, 100);
}
/**
* Return FetchConfig to eagerly fetch the relationship using a secondary with a given batch size.
*/
public static FetchConfig ofQuery(int batchSize) {
return new FetchConfig(QUERY_MODE, batchSize);
}
/**
* Return FetchConfig to lazily load the relationship.
*/
public static FetchConfig ofLazy() {
return new FetchConfig(LAZY_MODE, 10);
}
/**
* Return FetchConfig to lazily load the relationship specifying the batch size.
*/
public static FetchConfig ofLazy(int batchSize) {
return new FetchConfig(LAZY_MODE, batchSize);
}
/**
* Return FetchConfig to fetch the relationship using SQL join.
*/
public static FetchConfig ofDefault() {
return new FetchConfig(JOIN_MODE, 100);
}
/**
* We want to migrate away from mutating FetchConfig to a fully immutable FetchConfig.
*/
private FetchConfig mutate(int mode, int batchSize) {
if (batchSize < 1) {
throw new IllegalArgumentException("batch size "+batchSize+" must be > 0");
}
this.mode = mode;
this.batchSize = batchSize;
this.hashCode = mode + 10 * batchSize;
return this;
}
/**
* Deprecated - migrate to FetchConfig.ofLazy().
*/
@Deprecated
public FetchConfig lazy() {
return mutate(LAZY_MODE, 10);
}
/**
* Deprecated - migrate to FetchConfig.ofLazy(batchSize).
*/
@Deprecated
public FetchConfig lazy(int batchSize) {
return mutate(LAZY_MODE, batchSize);
}
/**
* Deprecated - migrate to FetchConfig.ofQuery().
*
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
* <p>
* This will use the default batch size for separate query which is 100.
* </p>
*/
@Deprecated
public FetchConfig query() {
this.queryBatchSize = 0;
this.queryAll = true;
return this;
}
/**
* Eagerly fetch the beans fetching the beans from the L2 bean cache
* and using the DB for beans not in the cache.
*/
public FetchConfig cache() {
this.cache = true;
this.queryBatchSize = 0;
this.queryAll = true;
return this;
return mutate(QUERY_MODE, 100);
}
/**
* Deprecated - migrate to FetchConfig.ofQuery(batchSize).
*
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
* <p>
@@ -213,16 +162,16 @@ public class FetchConfig implements Serializable {
* is also used.
* </p>
*
* @param queryBatchSize the batch size used to load beans on this path
* @param batchSize the batch size used to load beans on this path
*/
public FetchConfig query(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
// queryAll true as long as a lazy batch size has not already been set
this.queryAll = (lazyBatchSize == -1);
return this;
@Deprecated
public FetchConfig query(int batchSize) {
return mutate(QUERY_MODE, batchSize);
}
/**
* Deprecated - migrate to FetchConfig.ofQuery(batchSize).
*
* Eagerly fetch the first batch of beans on this path.
* This is similar to {@link #query(int)} but only fetches the first batch.
* <p>
@@ -230,61 +179,68 @@ public class FetchConfig implements Serializable {
* loaded eagerly but instead use lazy loading.
* </p>
*
* @param queryBatchSize the number of parent beans this path is populated for
* @param batchSize the number of parent beans this path is populated for
*/
public FetchConfig queryFirst(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
this.queryAll = false;
return this;
@Deprecated
public FetchConfig queryFirst(int batchSize) {
return query(batchSize);
}
/**
* Return the batch size for lazy loading.
* Deprecated - migrate to FetchConfig.ofCache().
*
* Eagerly fetch the beans fetching the beans from the L2 bean cache
* and using the DB for beans not in the cache.
*/
public int getLazyBatchSize() {
return lazyBatchSize;
@Deprecated
public FetchConfig cache() {
return mutate(CACHE_MODE, 100);
}
/**
* Return the batch size for separate query load.
* Return the batch size for fetching.
*/
public int getQueryBatchSize() {
return queryBatchSize;
public int getBatchSize() {
return batchSize;
}
/**
* Return true if the query fetch should fetch 'all' rather than just the
* 'first' batch.
*/
public boolean isQueryAll() {
return queryAll;
}
/**
* Return true if this uses L2 bean cache.
* Return true if the fetch should use the L2 cache.
*/
public boolean isCache() {
return cache;
return mode == CACHE_MODE;
}
/**
* Return true if the fetch should be a eager secondary query.
*/
public boolean isQuery() {
return mode == QUERY_MODE;
}
/**
* Return true if the fetch should be a lazy query.
*/
public boolean isLazy() {
return mode == LAZY_MODE;
}
/**
* Return true if the fetch should try to use SQL join.
*/
public boolean isJoin() {
return mode == JOIN_MODE;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FetchConfig that = (FetchConfig) o;
if (lazyBatchSize != that.lazyBatchSize) return false;
if (queryBatchSize != that.queryBatchSize) return false;
if (cache != that.cache) return false;
return queryAll == that.queryAll;
return (hashCode == ((FetchConfig) o).hashCode);
}
@Override
public int hashCode() {
int result = lazyBatchSize;
result = 92821 * result + queryBatchSize;
result = 92821 * result + (queryAll ? 1 : 0);
result = 92821 * result + (cache ? 1 : 0);
return result;
return hashCode;
}
}
+2 -2
View File
@@ -37,7 +37,7 @@ import java.util.List;
* }
* }
*
* Entity
* @Entity
* public class Customer extends BaseModel {
*
* public static final CustomerFinder find = new CustomerFinder();
@@ -80,7 +80,7 @@ public class Finder<I, T> {
* // ... add extra customer specific finder methods
* }
*
* Entity
* @Entity
* public class Customer extends BaseModel {
*
* public static final CustomerFinder find = new CustomerFinder();
+6 -6
View File
@@ -32,16 +32,16 @@ import io.ebean.bean.EntityBean;
* // Typically there is a common base model that has some
* // common properties like the ones below
*
* MappedSuperclass
* @MappedSuperclass
* public class BaseModel extends Model {
*
* Id Long id;
* @Id Long id;
*
* Version Long version;
* @Version Long version;
*
* WhenCreated Timestamp whenCreated;
* @WhenCreated Timestamp whenCreated;
*
* WhenUpdated Timestamp whenUpdated;
* @WhenUpdated Timestamp whenUpdated;
*
* ...
* }
@@ -52,7 +52,7 @@ import io.ebean.bean.EntityBean;
*
* // Extend the mappedSuperclass
*
* Entity Table(name="o_account")
* @Entity @Table(name="o_account")
* public class Customer extends BaseModel {
*
* String name;
+1 -1
View File
@@ -18,7 +18,7 @@ import java.util.List;
*
* // where a bean is annotated with a complex
* // natural key made of several properties
* Cache(naturalKey = {"store","code","sku"})
* @Cache(naturalKey = {"store","code","sku"})
*
*
* Pairs pairs = new Pairs("sku", "code");
+24 -27
View File
@@ -514,7 +514,7 @@ public interface Query<T> {
* </p>
* <pre>{@code
*
* fetch(path, fetchProperties, new FetchConfig().query())
* fetch(path, fetchProperties, FetchConfig.ofQuery())
*
* }</pre>
* <p>
@@ -548,7 +548,7 @@ public interface Query<T> {
* </p>
* <pre>{@code
*
* fetch(path, fetchProperties, new FetchConfig().lazy())
* fetch(path, fetchProperties, FetchConfig.ofLazy())
*
* }</pre>
* <p>
@@ -573,7 +573,7 @@ public interface Query<T> {
* // fetch customers (their id, name and status)
* List<Customer> customers = DB.find(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName,lastName,email", new FetchConfig().lazy(10))
* .fetch("contacts", "firstName,lastName,email", FetchConfig.ofLazy(10))
* .findList();
*
* }</pre>
@@ -610,7 +610,7 @@ public interface Query<T> {
* </p>
* <pre>{@code
*
* fetch(path, new FetchConfig().query())
* fetch(path, FetchConfig.ofQuery())
*
* }</pre>
* <p>
@@ -639,7 +639,7 @@ public interface Query<T> {
* </p>
* <pre>{@code
*
* fetch(path, new FetchConfig().lazy())
* fetch(path, FetchConfig.ofLazy())
*
* }</pre>
* <p>
@@ -662,7 +662,7 @@ public interface Query<T> {
* // fetch customers (their id, name and status)
* List<Customer> customers = DB.find(Customer.class)
* // lazy fetch contacts with a batch size of 100
* .fetch("contacts", new FetchConfig().lazy(100))
* .fetch("contacts", FetchConfig.ofLazy(100))
* .findList();
*
* }</pre>
@@ -810,7 +810,7 @@ public interface Query<T> {
* </p>
* <p>
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the Consumer interface which is better suited to use with Java8 closures.
* iterator uses the Consumer interface which is better suited to use with closures.
* </p>
* <pre>{@code
*
@@ -829,6 +829,21 @@ public interface Query<T> {
*/
void findEach(Consumer<T> consumer);
/**
* Execute findEach streaming query batching the results for consuming.
* <p>
* This query execution will stream the results and is suited to consuming
* large numbers of results from the database.
* <p>
* Typically we use this batch consumer when we want to do further processing on
* the beans and want to do that processing in batch form, for example - 100 at
* a time.
*
* @param batch The number of beans processed in the batch
* @param consumer Process the batch of beans
*/
void findEach(int batch, Consumer<List<T>> consumer);
/**
* Execute the query using callbacks to a visitor to process the resulting
* beans one at a time.
@@ -839,12 +854,12 @@ public interface Query<T> {
* </p>
* <p>
* This method is functionally equivalent to findIterate() but instead of using an
* iterator uses the Predicate (SAM) interface which is better suited to use with Java8 closures.
* iterator uses the Predicate interface which is better suited to use with closures.
* </p>
* <pre>{@code
*
* DB.find(Customer.class)
* .fetch("contacts", new FetchConfig().query(2))
* .fetchQuery("contacts")
* .where().eq("status", Status.NEW)
* .order().asc("id")
* .setMaxRows(2000)
@@ -1673,12 +1688,6 @@ public interface Query<T> {
*/
Query<T> forUpdate();
/**
* Execute using "for update" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdate(LockType lockType);
/**
* Execute using "for update" clause with "no wait" option.
* <p>
@@ -1688,12 +1697,6 @@ public interface Query<T> {
*/
Query<T> forUpdateNoWait();
/**
* Execute using "for update nowait" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdateNoWait(LockType lockType);
/**
* Execute using "for update" clause with "skip locked" option.
* <p>
@@ -1703,12 +1706,6 @@ public interface Query<T> {
*/
Query<T> forUpdateSkipLocked();
/**
* Execute using "for update skip locked" with given lock type (currently Postgres only).
*/
@Deprecated
Query<T> forUpdateSkipLocked(LockType lockType);
/**
* Return true if this query has forUpdate set.
*/
+5 -5
View File
@@ -41,14 +41,14 @@ package io.ebean;
* <h3>Example OrderAggregate</h3>
* <pre>{@code
* ...
* // Sql indicates to that this bean
* // @Sql indicates to that this bean
* // is based on RawSql rather than a table
*
* Entity
* Sql
* @Entity
* @Sql
* public class OrderAggregate {
*
* OneToOne
* @OneToOne
* Order order;
*
* Double totalAmount;
@@ -107,7 +107,7 @@ package io.ebean;
*
* List<OrderAggregate> orders = DB.find(OrderAggregate.class)
* .setRawSql(rawSql)
* .fetch("order", "status,orderDate", new FetchConfig().query())
* .fetch("order", "status,orderDate", FetchConfig.ofQuery())
* .fetch("order.customer", "name")
* .where().gt("order.id", 0)
* .having().gt("totalAmount", 20)
@@ -22,7 +22,7 @@ import java.sql.SQLException;
* //
* class CustomerMapper implements RowMapper<CustomerDto> {
*
* Override
* @Override
* public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
*
* long id = rset.getLong(1);
+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 {
* ...
* }
@@ -931,10 +931,14 @@ public final class EntityBeanIntercept implements Serializable {
* OneToMany and ManyToMany only set loaded state.
*/
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setChangedProperty(propertyIndex);
}
setLoadedProperty(propertyIndex);
}
private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
@@ -87,7 +87,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public void internalAddWithCheck(Object bean) {
if (list == null || !containsInstance(bean)) {
if (list == null || bean == null || !containsInstance(bean)) {
internalAdd(bean);
}
}
@@ -77,7 +77,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
}
public void internalPutWithCheck(Object key, Object bean) {
if (map == null || !map.containsKey(key)) {
if (map == null || key == null || !map.containsKey(key)) {
internalPut(key, bean);
}
}
@@ -70,9 +70,8 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public void internalAddWithCheck(Object bean) {
if (set == null || !set.contains(bean)) {
internalAdd(bean);
}
// set add() already de-dups so just add it
internalAdd(bean);
}
@Override
@@ -7,6 +7,8 @@ import javax.persistence.DiscriminatorValue;
import javax.persistence.Inheritance;
import javax.persistence.Table;
import static io.ebean.util.StringHelper.isNull;
/**
* Provides some base implementation for NamingConventions.
*
@@ -78,10 +80,16 @@ public abstract class AbstractNamingConvention implements NamingConvention {
@Override
public String getSequenceName(String rawTableName, String pkColumn) {
final String tableNameUnquoted = databasePlatform.unQuote(rawTableName);
TableName tableName = new TableName(rawTableName);
String seqName = seqName(pkColumn, tableName.getName());
return tableName.withCatalogAndSchema(seqName);
}
private String seqName(String pkColumn, String tableName) {
final String tableNameUnquoted = unQuote(tableName);
String seqName = sequenceFormat.replace("{table}", tableNameUnquoted);
pkColumn = (pkColumn == null) ? "" : databasePlatform.unQuote(pkColumn);
return seqName.replace("{column}", pkColumn);
pkColumn = (pkColumn == null) ? "" : unQuote(pkColumn);
return quoteIdentifiers(seqName.replace("{column}", pkColumn));
}
/**
@@ -216,15 +224,13 @@ public abstract class AbstractNamingConvention implements NamingConvention {
|| AnnotationUtil.has(supCls, DiscriminatorValue.class);
}
@Override
public TableName getM2MJoinTableName(TableName lhsTable, TableName rhsTable) {
StringBuilder buffer = new StringBuilder();
buffer.append(lhsTable.getName());
buffer.append(unQuote(lhsTable.getName()));
buffer.append("_");
String rhsTableName = rhsTable.getName();
String rhsTableName = unQuote(rhsTable.getName());
if (rhsTableName.indexOf('_') < rhsPrefixLength) {
// trim off a xx_ prefix if there is one
rhsTableName = rhsTableName.substring(rhsTableName.indexOf('_') + 1);
@@ -238,7 +244,13 @@ public abstract class AbstractNamingConvention implements NamingConvention {
buffer.setLength(maxTableNameLength);
}
return new TableName(lhsTable.getCatalog(), lhsTable.getSchema(), buffer.toString());
String tableName = quoteIdentifiers(buffer.toString());
return new TableName(lhsTable.getCatalog(), lhsTable.getSchema(), tableName);
}
@Override
public String deriveM2MColumn(String tableName, String dbColumn) {
return quoteIdentifiers(unQuote(tableName) +"_" + unQuote(dbColumn));
}
/**
@@ -255,14 +267,29 @@ public abstract class AbstractNamingConvention implements NamingConvention {
return null;
}
@Override
public String getTableName(String catalog, String schema, String name) {
StringBuilder sb = new StringBuilder();
if (!isNull(catalog)) {
sb.append(quoteIdentifiers(catalog)).append(".");
}
if (!isNull(schema)) {
sb.append(quoteIdentifiers(schema)).append(".");
}
return sb.append(quoteIdentifiers(name)).toString();
}
/**
* Replace back ticks (if they are used) with database platform specific
* quoted identifiers.
* Replace back ticks (if they are used) with database platform specific quoted identifiers.
*/
protected String quoteIdentifiers(String s) {
return databasePlatform.convertQuotedIdentifiers(s);
}
private String unQuote(String val) {
return databasePlatform.unQuote(val);
}
/**
* Checks string is null or empty .
*/
@@ -40,6 +40,13 @@ public class ClassLoadConfig {
return isPresent("javax.validation.constraints.NotNull");
}
/**
* Return true if jakarta validation annotations like Size and NotNull are present.
*/
public boolean isJakartaValidationAnnotationsPresent() {
return isPresent("jakarta.validation.constraints.NotNull");
}
/**
* Return true if javax PostConstruct annotation is present (maybe not in java9).
* If not we don't support PostConstruct lifecycle events.
@@ -489,7 +489,7 @@ public class DatabaseConfig {
* Should the javax.validation.constraints.NotNull enforce a notNull column in DB.
* If set to false, use io.ebean.annotation.NotNull or Column(nullable=true).
*/
private boolean useJavaxValidationNotNull = true;
private boolean useValidationNotNull = true;
/**
* Generally we want to perform L2 cache notification in the background and not impact
@@ -2795,6 +2795,7 @@ public class DatabaseConfig {
}
loadDocStoreSettings(p);
defaultServer = p.getBoolean("defaultServer", defaultServer);
loadModuleInfo = p.getBoolean("loadModuleInfo", loadModuleInfo);
maxCallStack = p.getInt("maxCallStack", maxCallStack);
dumpMetricsOnShutdown = p.getBoolean("dumpMetricsOnShutdown", dumpMetricsOnShutdown);
@@ -2809,7 +2810,7 @@ public class DatabaseConfig {
enabledL2Regions = p.get("enabledL2Regions", enabledL2Regions);
notifyL2CacheInForeground = p.getBoolean("notifyL2CacheInForeground", notifyL2CacheInForeground);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
useValidationNotNull = p.getBoolean("useValidationNotNull", useValidationNotNull);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
@@ -3056,20 +3057,21 @@ public class DatabaseConfig {
/**
* Returns if we use javax.validation.constraints.NotNull
*/
public boolean isUseJavaxValidationNotNull() {
return useJavaxValidationNotNull;
public boolean isUseValidationNotNull() {
return useValidationNotNull;
}
/**
* Controls if Ebean should ignore <code>&x64;javax.validation.contstraints.NotNull</code>
* Controls if Ebean should ignore <code>&x64;javax.validation.contstraints.NotNull</code> or
* <code>&x64;jakarta.validation.contstraints.NotNull</code>
* with respect to generating a <code>NOT NULL</code> column.
* <p>
* Normally when Ebean sees javax NotNull annotation it means that column is defined as NOT NULL.
* Set this to <code>false</code> and the javax NotNull annotation is effectively ignored (and
* we instead use Ebean's own NotNull annotation or JPA Column(nullable=false) annotation.
*/
public void setUseJavaxValidationNotNull(boolean useJavaxValidationNotNull) {
this.useJavaxValidationNotNull = useJavaxValidationNotNull;
public void setUseValidationNotNull(boolean useValidationNotNull) {
this.useValidationNotNull = useValidationNotNull;
}
/**
@@ -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>
@@ -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();
}
}
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.6.5</version>
<version>12.8.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.6.5</tag>
<tag>ebean-parent-12.8.0</tag>
</scm>
<name>ebean autotune</name>
@@ -26,7 +26,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
<scope>provided</scope>
</dependency>
@@ -59,7 +59,7 @@
<plugin>
<groupId>io.repaint.maven</groupId>
<artifactId>tiles-maven-plugin</artifactId>
<version>2.18</version>
<version>2.19</version>
<extensions>true</extensions>
<configuration>
<tiles>
@@ -11,12 +11,9 @@ import org.tests.model.basic.Order;
import static org.assertj.core.api.Assertions.assertThat;
//import org.tests.model.basic.ResetBasicData;
public class ProfileOriginTest extends BaseTestCase {
private BeanDescriptor<Order> desc = getBeanDescriptor(Order.class);
private final BeanDescriptor<Order> desc = getBeanDescriptor(Order.class);
@Test
public void buildDetail() {
@@ -30,7 +27,7 @@ public class ProfileOriginTest extends BaseTestCase {
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString().trim()).isEqualTo("fetch customer (name)");
assertThat(detail.asStringDebug().trim()).isEqualTo("fetch customer (name)");
}
@Test
@@ -49,7 +46,7 @@ public class ProfileOriginTest extends BaseTestCase {
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (name)");
assertThat(detail.asStringDebug()).isEqualTo("select (orderDate) fetch customer (name)");
}
@Test
@@ -67,7 +64,7 @@ public class ProfileOriginTest extends BaseTestCase {
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString().trim()).isEqualTo("select (orderDate,customer)");
assertThat(detail.asStringDebug().trim()).isEqualTo("select (orderDate,customer)");
}
@Test
@@ -90,7 +87,7 @@ public class ProfileOriginTest extends BaseTestCase {
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString()).isEqualTo("select (orderDate) fetch customer (billingAddress)");
assertThat(detail.asStringDebug()).isEqualTo("select (orderDate) fetch customer (billingAddress)");
}
@@ -119,7 +116,7 @@ public class ProfileOriginTest extends BaseTestCase {
po.collectUsageInfo(c);
OrmQueryDetail detail = po.buildDetail(desc);
assertThat(detail.asString()).isEqualTo("fetch customer (name,note) fetch customer.billingAddress (line1)");
assertThat(detail.asStringDebug()).isEqualTo("fetch customer (name,note) fetch customer.billingAddress (line1)");
}
private NodeUsageCollector node(String path) {
+17 -17
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.6.5</version>
<version>12.8.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.2</ebean-agent.version>
<ebean-maven-plugin.version>12.6.2</ebean-maven-plugin.version>
<ebean-agent.version>12.6.6</ebean-agent.version>
<ebean-maven-plugin.version>12.6.6</ebean-maven-plugin.version>
</properties>
<dependencyManagement>
@@ -81,88 +81,88 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-xml</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-autotune</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
</dependencies>
+3 -8
View File
@@ -4,30 +4,25 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.6.5</version>
<version>12.8.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
<name>ebean core type</name>
<description>ebean scalar types api</description>
<properties>
<jackson-core.version>2.11.3</jackson-core.version>
<jackson-databind.version>2.11.3</jackson-databind.version>
</properties>
<dependencies>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-core.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
+14 -13
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.6.5</version>
<version>12.8.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -15,14 +15,9 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.6.5</tag>
<tag>ebean-parent-12.8.0</tag>
</scm>
<properties>
<jackson-core.version>2.11.3</jackson-core.version>
<jackson-databind.version>2.11.3</jackson-databind.version>
</properties>
<profiles>
<profile>
<!-- Note: to use this profile, you need to download manually the db2jcc4 driver.
@@ -92,19 +87,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.6.5</version>
<version>12.8.0</version>
</dependency>
<dependency>
@@ -138,6 +133,12 @@
<version>1.1.0.Final</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>joda-time</groupId>
@@ -157,14 +158,14 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-core.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
@@ -301,7 +302,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>12.5.0</version>
<version>12.6.6</version>
<executions>
<execution>
<id>test</id>
@@ -264,6 +264,11 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanCollect
*/
<T> void findDtoEach(SpiDtoQuery<T> query, Consumer<T> consumer);
/**
* DTO findEach batch query.
*/
<T> void findDtoEach(SpiDtoQuery<T> query, int batch, Consumer<List<T>> consumer);
/**
* DTO findEachWhile query.
*/
@@ -20,6 +20,7 @@ import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
import io.ebeaninternal.server.querydefn.OrmUpdateProperties;
import io.ebeaninternal.server.rawsql.SpiRawSql;
@@ -30,7 +31,7 @@ import java.util.Set;
/**
* Object Relational query - Internal extension to Query object.
*/
public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCodes {
enum Mode {
NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true);
@@ -289,6 +290,16 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
boolean selectAllForLazyLoadProperty();
/**
* Set the select properties.
*/
void selectProperties(OrmQueryProperties other);
/**
* Set the fetch properties for the given path.
*/
void fetchProperties(String path, OrmQueryProperties other);
/**
* Set the on a secondary query given the label, relativePath and profile location of the parent query.
*/
@@ -0,0 +1,22 @@
package io.ebeaninternal.api;
import io.ebean.FetchConfig;
import java.util.Set;
/**
* Query select and fetch properties (that avoids parsing).
*/
public interface SpiQueryFetch {
/**
* Specify the select properties.
*/
void selectProperties(Set<String> properties);
/**
* Specify the fetch properties for the given path.
*/
void fetchProperties(String name, Set<String> properties, FetchConfig config);
}
@@ -131,19 +131,10 @@ public class DatabasePlatformFactory {
* Use JDBC DatabaseMetaData to determine the platform.
*/
private DatabasePlatform byDataSource(DataSource dataSource) {
Connection connection = null;
try {
connection = dataSource.getConnection();
DatabaseMetaData metaData = connection.getMetaData();
return byDatabaseMeta(metaData, connection);
try (Connection connection = dataSource.getConnection()) {
return byDatabaseMeta(connection.getMetaData(), connection);
} catch (SQLException ex) {
throw new PersistenceException(ex);
} finally {
JdbcClose.close(connection);
}
}
@@ -157,7 +148,7 @@ public class DatabasePlatformFactory {
final int minorVersion = metaData.getDatabaseMinorVersion();
if (dbProductName.contains("oracle")) {
return new OraclePlatform();
return oracleVersion(majorVersion);
} else if (dbProductName.contains("microsoft")) {
throw new IllegalArgumentException("For SqlServer please explicitly choose either sqlserver16 or sqlserver17 as the platform via DatabaseConfig.setDatabasePlatformName. Refer to issue #1340 for more details");
} else if (dbProductName.contains("h2")) {
@@ -188,6 +179,10 @@ public class DatabasePlatformFactory {
return new DatabasePlatform();
}
private DatabasePlatform oracleVersion(int majorVersion) {
return majorVersion < 12 ? new Oracle11Platform() : new OraclePlatform();
}
private DatabasePlatform mysqlVersion(int majorVersion, int minorVersion) {
if (majorVersion <= 5 && minorVersion <= 5) {
return new MySql55Platform();
@@ -215,7 +215,6 @@ class DefaultBeanLoader {
desc.contextPut(pc, id, bean);
ebi.setPersistenceContext(pc);
}
boolean draft = desc.isDraftInstance(bean);
if (embeddedOwnerIndex == -1) {
@@ -245,7 +244,6 @@ class DefaultBeanLoader {
// and put the data into the original bean
query.setUsageProfiling(false);
query.setPersistenceContext(pc);
query.setMode(mode);
query.setId(id);
@@ -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()));
}
}
}
@@ -1446,6 +1446,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);
@@ -1585,6 +1597,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);
@@ -92,6 +92,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);
@@ -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)) {
@@ -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;
}
@@ -1872,6 +1872,17 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return query.setId(id).findOne();
}
/**
* Create a reference with a check for the bean in the persistence context.
*/
public EntityBean createReference(Boolean readOnly, Object id, PersistenceContext pc) {
Object refBean = contextGet(pc, id);
if (refBean == null) {
refBean = createReference(readOnly, false, id, pc);
}
return (EntityBean)refBean;
}
/**
* Create a reference bean based on the id.
*/
@@ -2461,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(schemaName, 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);
}
@@ -2475,6 +2489,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return null;
}
private boolean matchBaseTable(String schemaName, String tableName) {
return tableName.isEmpty()
|| baseTable.equalsIgnoreCase(tableName)
|| baseTable.equalsIgnoreCase(schemaName + "." + tableName);
}
/**
* Return a 'dynamic property' used to read a formula.
*/
@@ -89,7 +89,6 @@ final class BeanDescriptorCacheHelp<T> {
BeanDescriptorCacheHelp(BeanDescriptor<T> desc, SpiCacheManager cacheManager, CacheOptions cacheOptions,
boolean cacheSharableBeans, BeanPropertyAssocOne<?>[] propertiesOneImported) {
this.desc = desc;
this.beanType = desc.rootBeanType;
this.cacheName = beanType.getSimpleName();
@@ -99,7 +98,6 @@ final class BeanDescriptorCacheHelp<T> {
this.cacheSharableBeans = cacheSharableBeans;
this.propertiesOneImported = propertiesOneImported;
this.naturalKey = cacheOptions.getNaturalKey();
if (!cacheOptions.isEnableQueryCache()) {
this.queryCache = null;
} else {
@@ -133,7 +131,6 @@ final class BeanDescriptorCacheHelp<T> {
void deriveNotifyFlags() {
cacheNotifyOnAll = (invalidateQueryCache || beanCache != null || queryCache != null);
cacheNotifyOnDelete = !cacheNotifyOnAll && isNotifyOnDeletes();
if (logger.isDebugEnabled()) {
if (cacheNotifyOnAll || cacheNotifyOnDelete) {
String notifyMode = cacheNotifyOnAll ? "All" : "Delete";
@@ -292,7 +289,6 @@ final class BeanDescriptorCacheHelp<T> {
* Try to load the bean collection from cache return true if successful.
*/
boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId, Boolean readOnly) {
if (many.isElementCollection()) {
// held as part of the bean cache so skip
return false;
@@ -307,14 +303,12 @@ final class BeanDescriptorCacheHelp<T> {
EntityBean ownerBean = bc.getOwnerBean();
EntityBeanIntercept ebi = ownerBean._ebean_getIntercept();
PersistenceContext persistenceContext = ebi.getPersistenceContext();
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
List<Object> idList = entry.getIdList();
bc.checkEmptyLazyLoad();
for (Object id : idList) {
Object refBean = targetDescriptor.createReference(readOnly, false, id, persistenceContext);
many.add(bc, (EntityBean) refBean);
many.add(bc, targetDescriptor.createReference(readOnly, id, persistenceContext));
}
return true;
}
@@ -323,7 +317,6 @@ final class BeanDescriptorCacheHelp<T> {
* Put the beanCollection into the cache.
*/
void manyPropPut(BeanPropertyAssocMany<?> many, Object details, Object parentId) {
if (many.isElementCollection()) {
CachedBeanData data = (CachedBeanData) beanCache.get(parentId);
if (data != null) {
@@ -351,7 +344,6 @@ final class BeanDescriptorCacheHelp<T> {
}
void cachePutManyIds(Object parentId, String manyName, CachedManyIds entry) {
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, manyName);
if (manyLog.isDebugEnabled()) {
manyLog.debug(" PUT {}({}).{} - ids:{}", cacheName, parentId, manyName, entry);
@@ -360,7 +352,6 @@ final class BeanDescriptorCacheHelp<T> {
}
private CachedManyIds createManyIds(BeanPropertyAssocMany<?> many, Object details) {
Collection<?> actualDetails = BeanCollectionUtil.getActualDetails(details);
if (actualDetails == null) {
return null;
@@ -378,7 +369,6 @@ final class BeanDescriptorCacheHelp<T> {
* Hit the bean cache with the given ids returning the hits.
*/
BeanCacheResult<T> cacheIdLookup(PersistenceContext context, Collection<?> ids) {
Set<Object> keys = new HashSet<>(ids.size());
for (Object id : ids) {
keys.add(desc.cacheKey(id));
@@ -395,7 +385,6 @@ final class BeanDescriptorCacheHelp<T> {
T bean = convertToBean(entry.getKey(), false, context, cachedBeanData);
result.add(bean, desc.getBeanId(bean));
}
return result;
}
@@ -403,14 +392,12 @@ final class BeanDescriptorCacheHelp<T> {
* Use natural keys to hit the bean cache and return resulting hits.
*/
BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, Set<Object> keys) {
if (context == null) {
context = new DefaultPersistenceContext();
}
// naturalKey -> Id map
Map<Object, Object> naturalKeyMap = naturalKeyCache.getAll(keys);
if (natLog.isTraceEnabled()) {
natLog.trace(" MLOOKUP {}({}) - hits:{}", cacheName, keys, naturalKeyMap);
}
@@ -433,15 +420,12 @@ final class BeanDescriptorCacheHelp<T> {
}
// process the hits into beans etc
for (Map.Entry<Object, Object> entry : beanDataMap.entrySet()) {
Object id = entry.getKey();
CachedBeanData cachedBeanData = (CachedBeanData) entry.getValue();
T bean = convertToBean(id, false, context, cachedBeanData);
Object naturalKey = reverseMap.get(id);
result.add(bean, naturalKey);
}
return result;
}
@@ -452,7 +436,6 @@ final class BeanDescriptorCacheHelp<T> {
if (context == null) {
context = new DefaultPersistenceContext();
}
// Not using a loadContext for beans coming out of L2 cache
// so that means no batch lazy loading for these beans
EntityBean entityBean = (EntityBean) bean;
@@ -522,7 +505,6 @@ final class BeanDescriptorCacheHelp<T> {
* Put a bean into the bean cache.
*/
void beanCachePut(EntityBean bean) {
if (desc.inheritInfo != null) {
desc.descOf(bean.getClass()).cacheBeanPutDirect(bean);
} else {
@@ -531,7 +513,6 @@ final class BeanDescriptorCacheHelp<T> {
}
void beanCachePutAllDirect(Collection<EntityBean> beans) {
Map<Object, Object> natKeys = null;
if (naturalKey != null) {
natKeys = new LinkedHashMap<>();
@@ -566,15 +547,12 @@ final class BeanDescriptorCacheHelp<T> {
* Put the bean into the bean cache.
*/
void beanCachePutDirect(EntityBean bean) {
CachedBeanData beanData = beanExtractData(desc, bean);
String key = desc.cacheKeyForBean(bean);
if (beanLog.isDebugEnabled()) {
beanLog.debug(" PUT {}({}) data:{}", cacheName, key, beanData);
}
getBeanCache().put(key, beanData);
if (naturalKey != null) {
String naturalKey = calculateNaturalKey(beanData);
if (naturalKey != null) {
@@ -618,7 +596,6 @@ final class BeanDescriptorCacheHelp<T> {
* Return a bean from the bean cache.
*/
private T beanCacheGetInternal(String key, Boolean readOnly, PersistenceContext context) {
CachedBeanData data = (CachedBeanData) getBeanCache().get(key);
if (data == null) {
if (beanLog.isTraceEnabled()) {
@@ -646,7 +623,6 @@ final class BeanDescriptorCacheHelp<T> {
return (T) bean;
}
}
return (T) loadBean(id, readOnly, data, context);
}
@@ -654,7 +630,6 @@ final class BeanDescriptorCacheHelp<T> {
* Load the entity bean taking into account inheritance.
*/
private EntityBean loadBean(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
String discValue = data.getDiscValue();
if (discValue == null) {
return loadBeanDirect(id, readOnly, data, context);
@@ -674,7 +649,6 @@ final class BeanDescriptorCacheHelp<T> {
* Load the entity bean from cache data given this is the root bean type.
*/
EntityBean loadBeanDirect(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
id = desc.convertId(id);
EntityBean bean = null;
if (context == null) {
@@ -682,7 +656,6 @@ final class BeanDescriptorCacheHelp<T> {
} else {
bean = (EntityBean) desc.contextGet(context, id);
}
if (bean == null) {
bean = desc.createEntityBean();
desc.setId(id, bean);
@@ -698,7 +671,6 @@ final class BeanDescriptorCacheHelp<T> {
}
CachedBeanDataToBean.load(desc, bean, data, context);
if (desc.isReadAuditing()) {
desc.readAuditBean("l2", "", bean);
}
@@ -709,7 +681,6 @@ final class BeanDescriptorCacheHelp<T> {
* Load the embedded bean checking for inheritance.
*/
EntityBean embeddedBeanLoad(CachedBeanData data, PersistenceContext context) {
String discValue = data.getDiscValue();
if (discValue == null) {
return embeddedBeanLoadDirect(data, context);
@@ -746,25 +717,20 @@ final class BeanDescriptorCacheHelp<T> {
* Load a batch of entities from L2 bean cache checking the lazy loaded property is loaded.
*/
Set<EntityBeanIntercept> beanCacheLoadAll(List<EntityBeanIntercept> list, PersistenceContext context, int lazyLoadProperty, String propertyName) {
Map<Object, EntityBeanIntercept> ebis = new HashMap<>();
for (EntityBeanIntercept ebi : list) {
ebis.put(desc.cacheKeyForBean(ebi.getOwner()), ebi);
}
Map<Object, Object> hits = getBeanCache().getAll(ebis.keySet());
if (beanLog.isTraceEnabled()) {
beanLog.trace(" MLOAD {}({}) - got hits ({})", cacheName, ebis.keySet(), hits.size());
}
Set<EntityBeanIntercept> loaded = new HashSet<>();
Iterator<Map.Entry<Object, Object>> iterator = hits.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Object, Object> hit = iterator.next();
Object key = hit.getKey();
EntityBeanIntercept ebi = ebis.remove(key);
CachedBeanData cacheData = (CachedBeanData) hit.getValue();
@@ -774,7 +740,6 @@ final class BeanDescriptorCacheHelp<T> {
beanLog.trace(" load {}({}) - cache miss on property({})", cacheName, key, propertyName);
}
iterator.remove();
} else {
CachedBeanDataToBean.load(desc, ebi.getOwner(), cacheData, context);
loaded.add(ebi);
@@ -783,11 +748,9 @@ final class BeanDescriptorCacheHelp<T> {
}
}
}
if (!ebis.isEmpty() && beanLog.isTraceEnabled()) {
beanLog.trace(" load {}({}) - cache miss", cacheName, ebis.keySet());
}
return loaded;
}
@@ -795,7 +758,6 @@ final class BeanDescriptorCacheHelp<T> {
* Returns true if it managed to populate/load the single bean from the cache.
*/
boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, String key, PersistenceContext context) {
CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(key);
if (cacheData == null) {
if (beanLog.isTraceEnabled()) {
@@ -810,7 +772,6 @@ final class BeanDescriptorCacheHelp<T> {
}
return false;
}
CachedBeanDataToBean.load(desc, bean, cacheData, context);
if (beanLog.isDebugEnabled()) {
beanLog.debug(" LOAD {}({}) - hit", cacheName, key);
@@ -932,7 +893,6 @@ final class BeanDescriptorCacheHelp<T> {
* Apply changes to the bean cache entry.
*/
void cacheBeanUpdate(String key, Map<String, Object> changes, boolean updateNaturalKey, long version) {
ServerCache cache = getBeanCache();
CachedBeanData existingData = (CachedBeanData) cache.get(key);
if (existingData != null) {
@@ -952,7 +912,6 @@ final class BeanDescriptorCacheHelp<T> {
}
cache.put(key, newData);
}
if (updateNaturalKey) {
Object oldKey = calculateNaturalKey(existingData);
if (oldKey != null) {
@@ -324,9 +324,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;
@@ -262,10 +262,16 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
/**
* Return true if the target side has soft delete.
*/
@Override
public boolean isTargetSoftDelete() {
return targetDescriptor.isSoftDelete();
}
@Override
public String getSoftDeletePredicate(String tableAlias) {
return targetDescriptor.getSoftDeletePredicate(tableAlias);
}
/**
* Return true if REFRESH should cascade.
*/
@@ -603,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);
}
}
@@ -16,6 +16,7 @@ import io.ebean.event.BeanPostLoad;
import io.ebean.event.BeanQueryAdapter;
import io.ebean.event.changelog.ChangeLogFilter;
import io.ebean.text.PathProperties;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.server.core.CacheOptions;
import io.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
@@ -1149,8 +1150,21 @@ public class DeployBeanDescriptor<T> {
return null;
}
// use 'current' table alias - refer BeanProperty appendSelect() for aggregation
DeployBeanProperty property = propMap.get(expression);
return (property == null) ? null : "${ta}." + property.getDbColumn();
String[] split = SplitName.split(expression);
if (split[0] == null) {
DeployBeanProperty property = propMap.get(expression);
return (property == null) ? null : "${ta}." + property.getDbColumn();
} else {
DeployBeanProperty property = propMap.get(split[0]);
if (property instanceof DeployBeanPropertyAssoc) {
DeployBeanPropertyAssoc<?> prop = (DeployBeanPropertyAssoc<?>) property;
DeployBeanProperty beanProperty = prop.getTargetDeploy().getBeanProperty(split[1]);
if (beanProperty != null) {
return "u1." + beanProperty.getDbColumn();
}
}
return null;
}
}
/**
@@ -34,7 +34,6 @@ import javax.persistence.EmbeddedId;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.Version;
import javax.validation.constraints.Size;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
@@ -683,7 +682,6 @@ public class DeployBeanProperty {
*/
public void setAggregationPrefix(String prefix) {
this.aggregationPrefix = prefix;
this.aggregation = (prefix == null) ? aggregation : aggregation.replace(aggregationPrefix, "u1");
}
public String getElPrefix() {
@@ -716,7 +714,7 @@ public class DeployBeanProperty {
return sqlFormulaSelect;
}
if (aggregation != null) {
return aggregation;
return aggregationParsed == null ? dbColumn : aggregationParsed;
}
return dbColumn;
}
@@ -1140,15 +1138,6 @@ public class DeployBeanProperty {
return result;
}
public List<Size> getMetaAnnotationSize() {
final List<Size> size = getMetaAnnotations(Size.class);
final List<Size.List> lists = getMetaAnnotations(Size.List.class);
for (Size.List list : lists) {
Collections.addAll(size, list.value());
}
return size;
}
public Formula getMetaAnnotationFormula(Platform platform) {
Formula fallback = null;
for (Annotation ann : metaAnnotations) {
@@ -176,4 +176,9 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
public void setFetchPreference(int fetchPreference) {
this.fetchPreference = fetchPreference;
}
@SuppressWarnings("unchecked")
public void setTargetType(Class<?> targetType) {
this.targetType = (Class<T>)targetType;
}
}
@@ -65,19 +65,6 @@ public class DeployBeanPropertyAssocMany<T> extends DeployBeanPropertyAssoc<T> {
this.manyType = manyType;
}
/**
* When generics is not used for manyType you can specify via annotations.
* <p>
* Really only expect this for Scala due to a Scala compiler bug at the moment.
* Otherwise I'd probably not bother support this.
* </p>
*/
@SuppressWarnings("unchecked")
public void setTargetType(Class<?> cls) {
this.targetType = (Class<T>) cls;
}
/**
* Return the many type.
*/
@@ -0,0 +1,39 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.config.BeanNotRegisteredException;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
abstract class AnnotationAssoc extends AnnotationParser {
final BeanDescriptorManager factory;
AnnotationAssoc(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig, BeanDescriptorManager factory) {
super(info, readConfig);
this.factory = factory;
}
void setTargetType(Class<?> targetType, DeployBeanPropertyAssoc<?> prop) {
if (!targetType.equals(void.class)) {
prop.setTargetType(targetType);
}
}
void setBeanTable(DeployBeanPropertyAssoc<?> prop) {
BeanTable assoc = getBeanTable(prop);
if (assoc == null) {
throw new BeanNotRegisteredException(errorMsgMissingBeanTable(prop.getTargetType(), prop.getFullBeanName()));
}
prop.setBeanTable(assoc);
}
BeanTable getBeanTable(DeployBeanPropertyAssoc<?> prop) {
return factory.getBeanTable(prop.getTargetType());
}
private String errorMsgMissingBeanTable(Class<?> type, String from) {
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? See https://ebean.io/docs/trouble-shooting#not-registered";
}
}
@@ -5,7 +5,6 @@ import io.ebean.annotation.FetchPreference;
import io.ebean.annotation.HistoryExclude;
import io.ebean.annotation.Where;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.config.BeanNotRegisteredException;
import io.ebean.config.NamingConvention;
import io.ebean.config.TableName;
import io.ebean.core.type.ScalarType;
@@ -39,21 +38,13 @@ 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.
*/
class AnnotationAssocManys extends AnnotationParser {
class AnnotationAssocManys extends AnnotationAssoc {
private final BeanDescriptorManager factory;
/**
* Create with the DeployInfo.
*/
AnnotationAssocManys(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig, BeanDescriptorManager factory) {
super(info, readConfig);
this.factory = factory;
super(info, readConfig, factory);
}
/**
@@ -146,15 +137,15 @@ class AnnotationAssocManys extends AnnotationParser {
JoinTable joinTable = get(prop, JoinTable.class);
if (joinTable != null) {
if (prop.isManyToMany()) {
// expected this
readJoinTable(joinTable, prop);
} else {
// OneToMany with @JoinTable
prop.setO2mJoinTable();
readJoinTable(joinTable, prop);
manyToManyDefaultJoins(prop);
}
} else if (prop.isManyToMany()) {
checkSelfManyToMany(prop);
}
if (prop.getMappedBy() != null) {
@@ -187,6 +178,12 @@ class AnnotationAssocManys extends AnnotationParser {
}
}
private void checkSelfManyToMany(DeployBeanPropertyAssocMany<?> prop) {
if (prop.getTargetType().equals(descriptor.getBeanType())) {
throw new IllegalStateException("@ManyToMany mapping for " + prop.getFullBeanName() + " requires explicit @JoinTable with joinColumns & inverseJoinColumns. Refer issue #2157");
}
}
@SuppressWarnings("unchecked")
private void readElementCollection(DeployBeanPropertyAssocMany<?> prop, ElementCollection elementCollection) {
@@ -347,18 +344,6 @@ class AnnotationAssocManys extends AnnotationParser {
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
*/
@@ -369,6 +354,13 @@ class AnnotationAssocManys extends AnnotationParser {
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>
@@ -415,8 +407,8 @@ class AnnotationAssocManys extends AnnotationParser {
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));
}
}
@@ -425,8 +417,8 @@ class AnnotationAssocManys extends AnnotationParser {
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()));
}
}
@@ -435,13 +427,7 @@ class AnnotationAssocManys extends AnnotationParser {
prop.setInverseJoin(inverseDest);
}
private String errorMsgMissingBeanTable(Class<?> type, String from) {
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? See https://ebean.io/docs/trouble-shooting#not-registered";
}
private void readToMany(ManyToMany propAnn, DeployBeanPropertyAssocMany<?> manyProp) {
manyProp.setMappedBy(propAnn.mappedBy());
manyProp.setFetchType(propAnn.fetch());
setCascadeTypes(propAnn.cascade(), manyProp.getCascadeInfo());
@@ -453,7 +439,6 @@ class AnnotationAssocManys extends AnnotationParser {
}
private void readToOne(OneToMany propAnn, DeployBeanPropertyAssocMany<?> manyProp) {
manyProp.setMappedBy(propAnn.mappedBy());
manyProp.setFetchType(propAnn.fetch());
setCascadeTypes(propAnn.cascade(), manyProp.getCascadeInfo());
@@ -462,20 +447,6 @@ class AnnotationAssocManys extends AnnotationParser {
manyProp.getTableJoin().setType(SqlJoinType.OUTER);
}
private void setTargetType(Class<?> targetType, DeployBeanPropertyAssocMany<?> prop) {
if (!targetType.equals(void.class)) {
prop.setTargetType(targetType);
}
}
private void setBeanTable(DeployBeanPropertyAssocMany<?> manyProp) {
BeanTable assoc = factory.getBeanTable(manyProp.getTargetType());
if (assoc == null) {
throw new BeanNotRegisteredException(errorMsgMissingBeanTable(manyProp.getTargetType(), manyProp.getFullBeanName()));
}
manyProp.setBeanTable(assoc);
}
private String getM2MJoinTableName(BeanTable lhsTable, BeanTable rhsTable) {
TableName lhs = new TableName(lhsTable.getBaseTable());
@@ -4,13 +4,11 @@ import io.ebean.annotation.DbForeignKey;
import io.ebean.annotation.FetchPreference;
import io.ebean.annotation.TenantId;
import io.ebean.annotation.Where;
import io.ebean.config.BeanNotRegisteredException;
import io.ebean.config.NamingConvention;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
import io.ebeaninternal.server.query.SqlJoinType;
@@ -28,23 +26,19 @@ import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.validation.constraints.NotNull;
/**
* Read the deployment annotations for Associated One beans.
*/
public class AnnotationAssocOnes extends AnnotationParser {
public class AnnotationAssocOnes extends AnnotationAssoc {
private static final Logger log = LoggerFactory.getLogger(AnnotationAssocOnes.class);
private final BeanDescriptorManager factory;
/**
* Create with the deploy Info.
*/
AnnotationAssocOnes(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig, BeanDescriptorManager factory) {
super(info, readConfig);
this.factory = factory;
super(info, readConfig, factory);
}
/**
@@ -120,13 +114,10 @@ public class AnnotationAssocOnes extends AnnotationParser {
if (nonNull != null) {
prop.setNullable(false);
}
if (validationAnnotations) {
NotNull notNull = get(prop, NotNull.class);
if (notNull != null && isEbeanValidationGroups(notNull.groups())) {
prop.setNullable(false);
// overrides optional attribute of ManyToOne etc
prop.getTableJoin().setType(SqlJoinType.INNER);
}
if (readConfig.isValidationNotNull(prop)) {
// overrides optional attribute of ManyToOne etc
prop.setNullable(false);
prop.getTableJoin().setType(SqlJoinType.INNER);
}
// check for manually defined joins
@@ -191,25 +182,11 @@ public class AnnotationAssocOnes extends AnnotationParser {
}
}
private String errorMsgMissingBeanTable(Class<?> type, String from) {
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? Does it have the @Entity annotation? See https://ebean.io/docs/trouble-shooting#not-registered";
}
private BeanTable beanTable(DeployBeanPropertyAssoc<?> prop) {
BeanTable assoc = factory.getBeanTable(prop.getPropertyType());
if (assoc == null) {
throw new BeanNotRegisteredException(errorMsgMissingBeanTable(prop.getPropertyType(), prop.getFullBeanName()));
}
return assoc;
}
private void readManyToOne(ManyToOne propAnn, DeployBeanProperty prop) {
DeployBeanPropertyAssocOne<?> beanProp = (DeployBeanPropertyAssocOne<?>) prop;
private void readManyToOne(ManyToOne propAnn, DeployBeanPropertyAssocOne<?> beanProp) {
setCascadeTypes(propAnn.cascade(), beanProp.getCascadeInfo());
beanProp.setBeanTable(beanTable(beanProp));
setTargetType(propAnn.targetEntity(), beanProp);
setBeanTable(beanProp);
beanProp.setDbInsertable(true);
beanProp.setDbUpdateable(true);
beanProp.setNullable(propAnn.optional());
@@ -232,7 +209,8 @@ public class AnnotationAssocOnes extends AnnotationParser {
}
setCascadeTypes(propAnn.cascade(), prop.getCascadeInfo());
prop.setBeanTable(beanTable(prop));
setTargetType(propAnn.targetEntity(), prop);
setBeanTable(prop);
}
private boolean readOrphanRemoval(OneToOne property) {
@@ -261,7 +239,7 @@ public class AnnotationAssocOnes extends AnnotationParser {
BeanTable baseBeanTable = factory.getBeanTable(info.getDescriptor().getBeanType());
String localPrimaryKey = baseBeanTable.getIdColumn();
String foreignColumn = beanTable(prop).getIdColumn();
String foreignColumn = getBeanTable(prop).getIdColumn();
prop.getTableJoin().addJoinColumn(new DeployTableJoinColumn(localPrimaryKey, foreignColumn, false, false));
}
@@ -69,8 +69,6 @@ import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.sql.Types;
import java.util.Set;
import java.util.UUID;
@@ -258,29 +256,19 @@ public class AnnotationFields extends AnnotationParser {
}
private void initValidation(DeployBeanProperty prop) {
NotNull notNull = get(prop, NotNull.class);
if (notNull != null && isEbeanValidationGroups(notNull.groups())) {
// Not null on all validation groups so enable
// DDL generation of Not Null Constraint
if (readConfig.isValidationNotNull(prop)) {
prop.setNullable(false);
}
if (!prop.isLob()) {
// take the max size of all @Size annotations
int maxSize = -1;
for (Size size : prop.getMetaAnnotationSize()) {
if (size.max() < Integer.MAX_VALUE) {
maxSize = Math.max(maxSize, size.max());
}
}
if (maxSize != -1) {
int maxSize = readConfig.maxValidationSize(prop);
if (maxSize > 0) {
prop.setDbLength(maxSize);
}
}
}
private void initTenantId(DeployBeanProperty prop) {
if (validationAnnotations) {
if (readConfig.checkValidationAnnotations()) {
initValidation(prop);
}
if (has(prop, TenantId.class)) {
@@ -9,7 +9,6 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import javax.persistence.AttributeOverride;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.validation.groups.Default;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -26,14 +25,11 @@ public abstract class AnnotationParser extends AnnotationBase {
final Class<?> beanType;
final boolean validationAnnotations;
final ReadAnnotationConfig readConfig;
AnnotationParser(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig) {
super(info.getUtil());
this.readConfig = readConfig;
this.validationAnnotations = readConfig.isJavaxValidationAnnotations();
this.info = info;
this.beanType = info.getDescriptor().getBeanType();
this.descriptor = info.getDescriptor();
@@ -127,17 +123,6 @@ public abstract class AnnotationParser extends AnnotationBase {
}
}
/**
* Return true if the validation groups are {@link Default} (respectively empty)
* can be applied to DDL generation.
*/
boolean isEbeanValidationGroups(Class<?>[] groups) {
if (!util.isUseJavaxValidationNotNull()) {
return false;
}
return groups.length == 0 || groups.length == 1 && Default.class.isAssignableFrom(groups[0]);
}
String[] convertColumnNames(String[] columnNames) {
for (int i = 0; i < columnNames.length; i++) {
columnNames[i] = databasePlatform.convertQuotedIdentifiers(columnNames[i]);
@@ -145,14 +145,6 @@ public class DeployCreateProperties {
private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field) {
Class<?> propertyType = field.getType();
ManyToOne manyToOne = AnnotationUtil.get(field, ManyToOne.class);
if (manyToOne != null) {
Class<?> tt = manyToOne.targetEntity();
if (!tt.equals(void.class)) {
propertyType = tt;
}
}
if (isSpecialScalarType(field)) {
return new DeployBeanProperty(desc, propertyType, field.getGenericType());
}
@@ -57,7 +57,7 @@ public class DeployUtil {
private final Encryptor bytesEncryptor;
private final boolean useJavaxValidationNotNull;
private final boolean useValidationNotNull;
public DeployUtil(TypeManager typeMgr, DatabaseConfig config) {
this.typeManager = typeMgr;
@@ -67,7 +67,7 @@ public class DeployUtil {
this.encryptKeyManager = config.getEncryptKeyManager();
Encryptor be = config.getEncryptor();
this.bytesEncryptor = be != null ? be : new SimpleAesEncryptor();
this.useJavaxValidationNotNull = config.isUseJavaxValidationNotNull();
this.useValidationNotNull = config.isUseValidationNotNull();
}
public TypeManager getTypeManager() {
@@ -286,8 +286,8 @@ public class DeployUtil {
return type.equals(String.class);
}
boolean isUseJavaxValidationNotNull() {
return useJavaxValidationNotNull;
boolean isUseValidationNotNull() {
return useValidationNotNull;
}
/**
@@ -1,11 +0,0 @@
package io.ebeaninternal.server.deploy.parse;
import javax.validation.constraints.Size;
class InitMetaValidationAnnotation {
static void init(ReadAnnotationConfig readConfig) {
readConfig.addMetaAnnotation(Size.class);
readConfig.addMetaAnnotation(Size.List.class);
}
}
@@ -3,8 +3,10 @@ package io.ebeaninternal.server.deploy.parse;
import io.ebean.annotation.Aggregation;
import io.ebean.annotation.Formula;
import io.ebean.annotation.Where;
import io.ebean.config.ClassLoadConfig;
import io.ebean.config.DatabaseConfig;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.Column;
import java.util.HashSet;
@@ -21,9 +23,12 @@ class ReadAnnotationConfig {
private final boolean disableL2Cache;
private final boolean eagerFetchLobs;
private final boolean javaxValidationAnnotations;
private final boolean jakartaValidationAnnotations;
private final boolean jacksonAnnotations;
private final boolean idGeneratorAutomatic;
private final boolean useValidationNotNull;
private final ReadValidationAnnotations javaxValidation;
private final ReadValidationAnnotations jakartaValidation;
private final Set<Class<?>> metaAnnotations = new HashSet<>();
ReadAnnotationConfig(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, DatabaseConfig config) {
@@ -33,20 +38,32 @@ class ReadAnnotationConfig {
this.disableL2Cache = config.isDisableL2Cache();
this.eagerFetchLobs = config.isEagerFetchLobs();
this.idGeneratorAutomatic = config.isIdGeneratorAutomatic();
this.javaxValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJavaxValidationAnnotationsPresent();
this.jacksonAnnotations = generatedPropFactory.getClassLoadConfig().isJacksonAnnotationsPresent();
this.useValidationNotNull = config.isUseValidationNotNull();
ClassLoadConfig classLoadConfig = generatedPropFactory.getClassLoadConfig();
this.javaxValidationAnnotations = classLoadConfig.isJavaxValidationAnnotationsPresent();
this.jakartaValidationAnnotations = classLoadConfig.isJakartaValidationAnnotationsPresent();
this.jacksonAnnotations = classLoadConfig.isJacksonAnnotationsPresent();
this.metaAnnotations.add(Column.class);
this.metaAnnotations.add(Formula.class);
this.metaAnnotations.add(Formula.List.class);
this.metaAnnotations.add(Where.class);
this.metaAnnotations.add(Where.List.class);
this.metaAnnotations.add(Aggregation.class);
this.javaxValidation = javaxValidationAnnotations ? new ReadValidationAnnotationsJavax(this) : null;
this.jakartaValidation = jakartaValidationAnnotations ? new ReadValidationAnnotationsJakarta(this) : null;
if (jacksonAnnotations) {
InitMetaJacksonAnnotation.init(this);
}
}
public void addMetaAnnotation(Class<?> annotation) {
void addMetaAnnotation(Class<?> annotation) {
metaAnnotations.add(annotation);
}
boolean checkValidationAnnotations() {
return javaxValidationAnnotations || jakartaValidationAnnotations;
}
GeneratedPropertyFactory getGeneratedPropFactory() {
return generatedPropFactory;
}
@@ -71,10 +88,6 @@ class ReadAnnotationConfig {
return idGeneratorAutomatic;
}
boolean isJavaxValidationAnnotations() {
return javaxValidationAnnotations;
}
boolean isJacksonAnnotations() {
return jacksonAnnotations;
}
@@ -82,4 +95,35 @@ class ReadAnnotationConfig {
public Set<Class<?>> getMetaAnnotations() {
return metaAnnotations;
}
/**
* Return true if a NotNull validation annotation is on the property.
*/
boolean isValidationNotNull(DeployBeanProperty property) {
if (!useValidationNotNull) {
return false;
}
if (javaxValidation != null && javaxValidation.isValidationNotNull(property)) {
return true;
}
if (jakartaValidation != null && jakartaValidation.isValidationNotNull(property)) {
return true;
}
return false;
}
/**
* Return the max size of all validation @Size annotations.
*/
int maxValidationSize(DeployBeanProperty prop) {
int maxSize = 0;
if (javaxValidation != null) {
maxSize = Math.max(maxSize, javaxValidation.maxSize(prop));
}
if (jakartaValidation != null) {
maxSize = Math.max(maxSize, jakartaValidation.maxSize(prop));
}
return maxSize;
}
}
@@ -14,12 +14,6 @@ public class ReadAnnotations {
public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, DatabaseConfig config) {
this.readConfig = new ReadAnnotationConfig(generatedPropFactory, asOfViewSuffix, versionsBetweenSuffix, config);
if (readConfig.isJavaxValidationAnnotations()) {
InitMetaValidationAnnotation.init(readConfig);
}
if (readConfig.isJacksonAnnotations()) {
InitMetaJacksonAnnotation.init(readConfig);
}
}
/**
@@ -0,0 +1,19 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
* Reads validation NotNull and Size annotations for mapping.
*/
interface ReadValidationAnnotations {
/**
* Return true if the property has a NotNull validation annotation.
*/
boolean isValidationNotNull(DeployBeanProperty property);
/**
* Return the max value of the Size validation annotations on the property.
*/
int maxSize(DeployBeanProperty property);
}
@@ -0,0 +1,51 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.validation.groups.Default;
import java.util.Collections;
import java.util.List;
/**
* Jakarta validation annotations reader.
*/
class ReadValidationAnnotationsJakarta implements ReadValidationAnnotations {
ReadValidationAnnotationsJakarta(ReadAnnotationConfig readConfig) {
readConfig.addMetaAnnotation(Size.class);
readConfig.addMetaAnnotation(Size.List.class);
}
@Override
public boolean isValidationNotNull(DeployBeanProperty property) {
NotNull notNull = AnnotationUtil.get(property.getField(), NotNull.class);
return (notNull != null && isEbeanValidationGroups(notNull.groups()));
}
private boolean isEbeanValidationGroups(Class<?>[] groups) {
return groups.length == 0 || groups.length == 1 && Default.class.isAssignableFrom(groups[0]);
}
@Override
public int maxSize(DeployBeanProperty property) {
int maxSize = 0;
for (Size size : getMetaAnnotationJavaxSize(property)) {
if (size.max() < Integer.MAX_VALUE) {
maxSize = Math.max(maxSize, size.max());
}
}
return maxSize;
}
private List<Size> getMetaAnnotationJavaxSize(DeployBeanProperty prop) {
final List<Size> size = prop.getMetaAnnotations(Size.class);
final List<Size.List> lists = prop.getMetaAnnotations(Size.List.class);
for (Size.List list : lists) {
Collections.addAll(size, list.value());
}
return size;
}
}
@@ -0,0 +1,52 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.groups.Default;
import java.util.Collections;
import java.util.List;
/**
* Javax validation annotations reader.
*/
class ReadValidationAnnotationsJavax implements ReadValidationAnnotations {
ReadValidationAnnotationsJavax(ReadAnnotationConfig readConfig) {
readConfig.addMetaAnnotation(Size.class);
readConfig.addMetaAnnotation(Size.List.class);
}
@Override
public boolean isValidationNotNull(DeployBeanProperty property) {
NotNull notNull = AnnotationUtil.get(property.getField(), NotNull.class);
return (notNull != null && isEbeanValidationGroups(notNull.groups()));
}
private boolean isEbeanValidationGroups(Class<?>[] groups) {
return groups.length == 0 || groups.length == 1 && Default.class.isAssignableFrom(groups[0]);
}
@Override
public int maxSize(DeployBeanProperty prop) {
int maxSize = 0;
for (Size size : getMetaAnnotationJavaxSize(prop)) {
if (size.max() < Integer.MAX_VALUE) {
maxSize = Math.max(maxSize, size.max());
}
}
return maxSize;
}
private List<Size> getMetaAnnotationJavaxSize(DeployBeanProperty prop) {
final List<Size> size = prop.getMetaAnnotations(Size.class);
final List<Size.List> lists = prop.getMetaAnnotations(Size.List.class);
for (Size.List list : lists) {
Collections.addAll(size, list.value());
}
return size;
}
}
@@ -436,6 +436,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
query.findEach(consumer);
}
@Override
public void findEach(int batch, Consumer<List<T>> consumer) {
query.findEach(batch, consumer);
}
@Override
public void findEachWhile(Predicate<T> consumer) {
query.findEachWhile(consumer);
@@ -501,31 +506,16 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.forUpdate();
}
@Override
public Query<T> forUpdate(Query.LockType lockType) {
return query.forUpdate(lockType);
}
@Override
public Query<T> forUpdateNoWait() {
return query.forUpdateNoWait();
}
@Override
public Query<T> forUpdateNoWait(Query.LockType lockType) {
return query.forUpdateNoWait(lockType);
}
@Override
public Query<T> forUpdateSkipLocked() {
return query.forUpdateSkipLocked();
}
@Override
public Query<T> forUpdateSkipLocked(Query.LockType lockType) {
return query.forUpdateSkipLocked(lockType);
}
@Override
public Query<T> select(String fetchProperties) {
return query.select(fetchProperties);
@@ -446,6 +446,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
exprList.findEach(consumer);
}
@Override
public void findEach(int batch, Consumer<List<T>> consumer) {
exprList.findEach(batch, consumer);
}
@Override
public void findEachWhile(Predicate<T> consumer) {
exprList.findEachWhile(consumer);
@@ -506,31 +511,16 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.forUpdate();
}
@Override
public Query<T> forUpdate(Query.LockType lockType) {
return exprList.forUpdate(lockType);
}
@Override
public Query<T> forUpdateNoWait() {
return exprList.forUpdateNoWait();
}
@Override
public Query<T> forUpdateNoWait(Query.LockType lockType) {
return exprList.forUpdateNoWait(lockType);
}
@Override
public Query<T> forUpdateSkipLocked() {
return exprList.forUpdateSkipLocked();
}
@Override
public Query<T> forUpdateSkipLocked(Query.LockType lockType) {
return exprList.forUpdateSkipLocked(lockType);
}
/**
* Path exists - for the given path in a JSON document.
*/
@@ -17,11 +17,11 @@ class ParseFetchConfig {
if (path.startsWith("lazy")) {
if (path.length() == 4) {
return new FetchConfig().lazy();
return FetchConfig.ofLazy();
} else if (path.charAt(4) == '(') {
path = path.substring(5);
int batchSize = parseBatchSize(path);
return new FetchConfig().lazy(batchSize);
return FetchConfig.ofLazy(batchSize);
} else {
return null;
}
@@ -29,11 +29,11 @@ class ParseFetchConfig {
if (path.startsWith("query")) {
if (path.length() == 5) {
return new FetchConfig().query();
return FetchConfig.ofQuery();
} else if (path.charAt(5) == '(') {
path = path.substring(6);
int batchSize = parseBatchSize(path);
return new FetchConfig().query(batchSize);
return FetchConfig.ofQuery(batchSize);
} else {
return null;
}
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.loadcontext;
import io.ebean.FetchConfig;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.api.SpiQuery;
@@ -28,9 +27,7 @@ abstract class DLoadBaseContext {
final boolean hitCache;
final int firstBatchSize;
final int secondaryBatchSize;
final int batchSize;
final ObjectGraphNode objectGraphNode;
@@ -45,38 +42,11 @@ abstract class DLoadBaseContext {
this.hitCache = parent.isBeanCacheGet() && desc.isBeanCaching();
this.objectGraphNode = parent.getObjectGraphNode(path);
this.queryFetch = queryProps != null && queryProps.isQueryFetch();
this.firstBatchSize = initFirstBatchSize(defaultBatchSize, queryProps);
this.secondaryBatchSize = initSecondaryBatchSize(defaultBatchSize, firstBatchSize, queryProps);
this.batchSize = initBatchSize(defaultBatchSize, queryProps);
}
private int initFirstBatchSize(int batchSize, OrmQueryProperties queryProps) {
if (queryProps == null) {
return batchSize;
}
int queryBatchSize = queryProps.getQueryFetchBatch();
if (queryBatchSize == -1) {
return batchSize;
} else if (queryBatchSize == 0) {
return 100;
} else {
return queryBatchSize;
}
}
private int initSecondaryBatchSize(int defaultBatchSize, int firstBatchSize, OrmQueryProperties queryProps) {
if (queryProps == null) {
return defaultBatchSize;
}
FetchConfig fetchConfig = queryProps.getFetchConfig();
if (fetchConfig.isQueryAll()) {
return firstBatchSize;
}
int lazyBatchSize = fetchConfig.getLazyBatchSize();
return (lazyBatchSize > 1) ? lazyBatchSize : defaultBatchSize;
private int initBatchSize(int batchSize, OrmQueryProperties queryProps) {
return queryProps == null ? batchSize : queryProps.getBatchSize();
}
/**
@@ -84,7 +54,6 @@ abstract class DLoadBaseContext {
* set onto the secondary query.
*/
void setLabel(SpiQuery<?> query) {
String label = parent.getPlanLabel();
if (label != null) {
query.setProfilePath(label, fullPath, parent.getProfileLocation());
@@ -35,7 +35,7 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
super(parent, desc, path, defaultBatchSize, queryProps);
// bufferList only required when using query joins (queryFetch)
this.bufferList = (!queryFetch) ? null : new ArrayList<>();
this.currentBuffer = createBuffer(firstBatchSize);
this.currentBuffer = createBuffer(batchSize);
this.cache = (queryProps != null) && queryProps.isCache();
}
@@ -52,11 +52,10 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
if (bufferList != null) {
bufferList.clear();
}
currentBuffer = createBuffer(secondaryBatchSize);
currentBuffer = createBuffer(batchSize);
}
private void configureQuery(SpiQuery<?> query, String lazyLoadProperty) {
if (cache) {
query.setBeanCacheMode(CacheMode.ON);
}
@@ -70,9 +69,8 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
}
protected void register(EntityBeanIntercept ebi) {
if (currentBuffer.isFull()) {
currentBuffer = createBuffer(secondaryBatchSize);
currentBuffer = createBuffer(batchSize);
}
ebi.setBeanLoader(currentBuffer, getPersistenceContext());
currentBuffer.add(ebi);
@@ -97,10 +95,6 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
for (LoadBuffer loadBuffer : bufferList) {
if (!loadBuffer.list.isEmpty()) {
parent.getEbeanServer().loadBean(new LoadBeanRequest(loadBuffer, parentRequest));
if (!queryProps.isQueryFetchAll()) {
// Stop - only fetch the first batch ... the rest will be lazy loaded
break;
}
}
if (forEach) {
clear();
@@ -198,8 +192,10 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
// lazy load property was a Many
return;
}
if (context.hitCache) {
if (list.isEmpty()) {
// re-add to the batch and lazy load from DB skipping l2 cache
list.add(ebi);
} else if (context.hitCache) {
Set<EntityBeanIntercept> hits = context.desc.cacheBeanLoadAll(list, persistenceContext, ebi.getLazyLoadPropertyIndex(), ebi.getLazyLoadProperty());
list.removeAll(hits);
if (list.isEmpty() || hits.contains(ebi)) {
@@ -188,7 +188,7 @@ public class DLoadContext implements LoadContext {
}
int maxBatch = 0;
for (OrmQueryProperties aSecQuery : secQuery) {
int batchSize = aSecQuery.getQueryFetchBatch();
int batchSize = aSecQuery.getBatchSize();
if (batchSize == 0) {
batchSize = defaultQueryBatch;
}
@@ -300,12 +300,9 @@ public class DLoadContext implements LoadContext {
}
private void registerSecondaryNode(boolean many, OrmQueryProperties props) {
int batchSize;
if (props.isQueryFetch()) {
batchSize = 100;
} else {
int lazyJoinBatch = props.getLazyFetchBatch();
batchSize = lazyJoinBatch > 0 ? lazyJoinBatch : defaultBatchSize;
int batchSize = props.getBatchSize();
if (batchSize == 0) {
batchSize = defaultBatchSize;
}
String path = props.getPath();
if (many) {
@@ -40,7 +40,7 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
this.docStoreMapped = property.isTargetDocStoreMapped();
// bufferList only required when using query joins (queryFetch)
this.bufferList = (!queryFetch) ? null : new ArrayList<>();
this.currentBuffer = createBuffer(firstBatchSize);
this.currentBuffer = createBuffer(batchSize);
}
private LoadBuffer createBuffer(int size) {
@@ -58,11 +58,10 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
if (bufferList != null) {
bufferList.clear();
}
currentBuffer = createBuffer(secondaryBatchSize);
currentBuffer = createBuffer(batchSize);
}
private void configureQuery(SpiQuery<?> query) {
setLabel(query);
parent.propagateQueryState(query, docStoreMapped);
query.setParentNode(objectGraphNode);
@@ -85,9 +84,8 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
}
public void register(BeanCollection<?> bc) {
if (currentBuffer.isFull()) {
currentBuffer = createBuffer(secondaryBatchSize);
currentBuffer = createBuffer(batchSize);
}
currentBuffer.add(bc);
bc.setLoader(currentBuffer);
@@ -105,13 +103,8 @@ class DLoadManyContext extends DLoadBaseContext implements LoadManyContext {
if (!loadBuffer.list.isEmpty()) {
LoadManyRequest req = new LoadManyRequest(loadBuffer, parentRequest);
parent.getEbeanServer().loadMany(req);
if (!queryProps.isQueryFetchAll()) {
// Stop - only fetch the first batch ... the rest will be lazy loaded
break;
}
}
}
if (forEach) {
clear();
} else {
@@ -298,18 +298,7 @@ public final class BatchControl {
// Nothing in queue to flush
return;
}
// convert entry map to array for sorting
BatchedBeanHolder[] bsArray = getBeanHolderArray();
// sort the entries by depth
Arrays.sort(bsArray, depthComparator);
if (transaction.isLogSummary()) {
transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray));
}
for (BatchedBeanHolder beanHolder : bsArray) {
beanHolder.executeNow();
}
executeAll();
persistedBeans.clear();
if (reset) {
beanHoldMap.clear();
@@ -323,6 +312,32 @@ public final class BatchControl {
}
}
private void executeAll() throws BatchedSqlException {
do {
// convert entry map to array for sorting
BatchedBeanHolder[] bsArray = getBeanHolderArray();
Arrays.sort(bsArray, depthComparator);
if (transaction.isLogSummary()) {
transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray));
}
for (BatchedBeanHolder beanHolder : bsArray) {
beanHolder.executeNow();
}
} while (!isBeanHoldersEmpty());
}
/**
* Return if all bean holders are empty.
*/
private boolean isBeanHoldersEmpty() {
for (BatchedBeanHolder beanHolder : beanHoldMap.values()) {
if (!beanHolder.isEmpty()) {
return false;
}
}
return true;
}
/**
* Return an entry for the given type description. The type description is
* typically the bean class name (or table name for MapBeans).
@@ -47,6 +47,11 @@ class BatchedBeanHolder {
*/
private ArrayList<PersistRequest> deletes;
/**
* The holder is empty
*/
private boolean empty = true;
/**
* Create a new entry with a given type and depth.
*/
@@ -63,6 +68,13 @@ class BatchedBeanHolder {
return order;
}
/**
* Returns if the BeanHolder is empty.
*/
public boolean isEmpty() {
return empty;
}
/**
* Execute all the persist requests in this entry.
* <p>
@@ -90,6 +102,7 @@ class BatchedBeanHolder {
updates = new ArrayList<>();
control.executeNow(bufferedUpdates);
}
empty = true;
}
@Override
@@ -112,9 +125,8 @@ class BatchedBeanHolder {
* Add the request to the appropriate persist list.
*/
public int append(PersistRequestBean<?> request) {
empty = false;
request.setBatched();
switch (request.getType()) {
case INSERT:
if (inserts == null) {
@@ -210,18 +210,6 @@ public class SaveManyBeans extends SaveManyBase {
return true;
}
private boolean hasNewOrDirtyBeans() {
if (collection == null) {
return false;
}
for (Object bean : collection) {
if (bean instanceof EntityBean && ((EntityBean) bean)._ebean_getIntercept().isNewOrDirty()) {
return true;
}
}
return false;
}
/**
* Collect the Id values of the details to remove 'missing children' for stateless updates.
*/
@@ -344,14 +332,18 @@ public class SaveManyBeans extends SaveManyBase {
transaction.depth(-1);
}
private boolean isChangedProperty() {
return parentBean._ebean_getIntercept().isChangedProperty(many.getPropertyIndex());
}
private void removeAssocManyOrphans() {
if (value == null) {
return;
}
if (!(value instanceof BeanCollection<?>)) {
// if (!insertedParent && cascade && hasNewOrDirtyBeans()) {
// persister.addToFlushQueue(many.deleteByParentId(request.getBeanId(), null), transaction, 0);
// }
if (!insertedParent && cascade && isChangedProperty()) {
persister.addToFlushQueue(many.deleteByParentId(request.getBeanId(), null), transaction, 0);
}
} else {
BeanCollection<?> c = (BeanCollection<?>) value;
Set<?> modifyRemovals = c.getModifyRemovals();
@@ -142,7 +142,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Tree that knows how to build the master and detail beans from the
* resultSet.
*/
private final SqlTreeNode rootNode;
private final SqlTreeRoot rootNode;
/**
* For master detail query.
@@ -296,10 +296,10 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
this.cancelled = true;
if (pstmt != null) {
try {
logger.debug("Cancelling query");
pstmt.cancel();
} catch (SQLException e) {
String msg = "Error cancelling query";
throw new PersistenceException(msg, e);
throw new PersistenceException("Error cancelling query", e);
}
}
} finally {
@@ -322,7 +322,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
}
private boolean prepareBindExecuteQueryWithOption(boolean forwardOnlyHint) throws SQLException {
ResultSet resultSet = prepareResultSet(forwardOnlyHint);
if (resultSet == null) {
return false;
@@ -334,19 +333,12 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
ResultSet prepareResultSet(boolean forwardOnlyHint) throws SQLException {
lock.lock();
try {
if (cancelled || query.isCancelled()) {
// cancelled before we started
cancelled = true;
return null;
if (cancelled) {
throw new SQLException("Query cancelled");
}
startNano = System.nanoTime();
// prepare
SpiTransaction t = request.getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
if (query.isRawSql()) {
ResultSet suppliedResultSet = query.getRawSql().getResultSet();
if (suppliedResultSet != null) {
@@ -356,6 +348,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
}
}
Connection conn = t.getInternalConnection();
if (forwardOnlyHint) {
// Use forward only hints for large resultSet processing (Issue 56, MySql specific)
pstmt = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
@@ -363,18 +356,13 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
} else {
pstmt = conn.prepareStatement(sql);
}
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
DataBind dataBind = queryPlan.bindEncryptedProperties(pstmt, conn);
bindLog = predicates.bind(dataBind);
// executeQuery
bindLog = predicates.bind(queryPlan.bindEncryptedProperties(pstmt, conn));
return pstmt.executeQuery();
} finally {
lock.unlock();
@@ -456,13 +444,13 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
if (manyProperty == null) {
// only single resultSet row required to build object so we are done
// read a single resultSet row into single bean
nextBean = rootNode.load(this, null, null);
nextBean = rootNode.load(this);
return true;
}
if (nextBean == null) {
// very first read
nextBean = rootNode.load(this, null, null);
nextBean = rootNode.load(this);
} else {
// nextBean set to previously read currentBean
nextBean = currentBean;
@@ -490,7 +478,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* is different to the nextBean (false if we need to read more rows).
*/
private boolean checkForDifferentBean() throws SQLException {
currentBean = rootNode.load(this, null, null);
currentBean = rootNode.load(this);
return currentBean != nextBean;
}
@@ -442,9 +442,10 @@ class CQueryBuilder {
int cols = 1 + metaData.getColumnCount();
List<String> propertyNames = new ArrayList<>(cols - 1);
for (int i = 1; i < cols; i++) {
String schemaName = metaData.getSchemaName(i).toLowerCase();
String tableName = metaData.getTableName(i).toLowerCase();
String columnName = metaData.getColumnName(i).toLowerCase();
String path = desc.findBeanPath(tableName, columnName);
String path = desc.findBeanPath(schemaName, tableName, columnName);
if (path != null) {
propertyNames.add(path);
} else {
@@ -571,8 +572,8 @@ class CQueryBuilder {
private final CQueryPredicates predicates;
private final SqlTree select;
private final boolean updateStatement;
private final boolean distinct;
private final boolean countSingleAttribute;
private final String dbOrderBy;
private boolean useSqlLimiter;
private boolean hasWhere;
@@ -590,6 +591,7 @@ class CQueryBuilder {
this.updateStatement = updateStatement;
this.distinct = query.isDistinct() || select.isSqlDistinct();
this.dbOrderBy = predicates.getDbOrderBy();
this.countSingleAttribute = query.isCountDistinct() && query.isSingleAttribute();
}
private void appendSelect() {
@@ -601,8 +603,13 @@ class CQueryBuilder {
if (!useSqlLimiter) {
appendSelectDistinct();
}
if (query.isCountDistinct() && query.isSingleAttribute()) {
sb.append("r1.attribute_, count(*) from (select ").append(select.getSelectSql()).append(" as attribute_");
if (countSingleAttribute) {
sb.append("r1.attribute_, count(*) from (select ");
if (distinct) {
sb.append("distinct t0.");
sb.append(request.getBeanDescriptor().getIdProperty().getDbColumn()).append(", ");
}
sb.append(select.getSelectSql()).append(" as attribute_");
} else {
sb.append(select.getSelectSql());
}
@@ -621,7 +628,7 @@ class CQueryBuilder {
private void appendSelectDistinct() {
sb.append("select ");
if (distinct) {
if (distinct && !countSingleAttribute) {
if (request.isInlineCountDistinct()) {
sb.append("count(");
}
@@ -730,7 +737,7 @@ class CQueryBuilder {
sb.append(" order by ").append(dbOrderBy);
}
if (query.isCountDistinct() && query.isSingleAttribute()) {
if (countSingleAttribute) {
sb.append(") r1 group by r1.attribute_");
sb.append(toSql(query.getCountDistinctOrder()));
}
@@ -401,12 +401,6 @@ public class CQueryEngine {
if (cquery != null) {
cquery.close();
}
if (request.getQuery().isFutureFetch()) {
// end the transaction for futureFindIds
// as it had it's own transaction
logger.debug("Future fetch completed!");
request.getTransaction().end();
}
}
}
@@ -11,11 +11,13 @@ import io.ebeaninternal.server.querydefn.SpiFetchGroup;
*/
class DFetchGroupBuilder<T> implements FetchGroupBuilder<T> {
private static final FetchConfig FETCH_CACHE = new FetchConfig().cache();
private static final FetchConfig DEFAULT_FETCH = FetchConfig.ofDefault();
private static final FetchConfig FETCH_QUERY = new FetchConfig().query();
private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache();
private static final FetchConfig FETCH_LAZY = new FetchConfig().lazy();
private static final FetchConfig FETCH_QUERY = FetchConfig.ofQuery();
private static final FetchConfig FETCH_LAZY = FetchConfig.ofLazy();
private final OrmQueryDetail detail;
@@ -31,13 +33,13 @@ class DFetchGroupBuilder<T> implements FetchGroupBuilder<T> {
@Override
public FetchGroupBuilder<T> fetch(String path) {
detail.fetch(path, null, null);
detail.fetchProperties(path, null, DEFAULT_FETCH);
return this;
}
@Override
public FetchGroupBuilder<T> fetch(String path, FetchGroup nestedGroup) {
return fetchNested(path, nestedGroup, null);
return fetchNested(path, nestedGroup, DEFAULT_FETCH);
}
@Override
@@ -51,7 +53,6 @@ class DFetchGroupBuilder<T> implements FetchGroupBuilder<T> {
}
private FetchGroupBuilder<T> fetchNested(String path, FetchGroup nestedGroup, FetchConfig fetchConfig) {
OrmQueryDetail nestedDetail = ((SpiFetchGroup) nestedGroup).underlying();
detail.addNested(path, nestedDetail, fetchConfig);
return this;
@@ -59,25 +60,25 @@ class DFetchGroupBuilder<T> implements FetchGroupBuilder<T> {
@Override
public FetchGroupBuilder<T> fetchQuery(String path) {
detail.fetch(path, null, FETCH_QUERY);
detail.fetchProperties(path, null, FETCH_QUERY);
return this;
}
@Override
public FetchGroupBuilder<T> fetchCache(String path) {
detail.fetch(path, null, FETCH_CACHE);
detail.fetchProperties(path, null, FETCH_CACHE);
return this;
}
@Override
public FetchGroupBuilder<T> fetchLazy(String path) {
detail.fetch(path, null, FETCH_LAZY);
detail.fetchProperties(path, null, FETCH_LAZY);
return this;
}
@Override
public FetchGroupBuilder<T> fetch(String path, String properties) {
detail.fetch(path, properties, null);
detail.fetch(path, properties, DEFAULT_FETCH);
return this;
}
@@ -25,6 +25,7 @@ import io.ebean.Transaction;
import io.ebean.UpdateQuery;
import io.ebean.Version;
import io.ebean.service.SpiFetchGroupQuery;
import io.ebeaninternal.api.SpiQueryFetch;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.SpiFetchGroup;
@@ -43,13 +44,13 @@ import java.util.stream.Stream;
/**
* Implementation of FetchGroup query for use to create FetchGroup via query beans.
*/
class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T> {
class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQueryFetch {
private static final FetchConfig FETCH_CACHE = new FetchConfig().cache();
private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache();
private static final FetchConfig FETCH_QUERY = new FetchConfig().query();
private static final FetchConfig FETCH_QUERY = FetchConfig.ofQuery();
private static final FetchConfig FETCH_LAZY = new FetchConfig().lazy();
private static final FetchConfig FETCH_LAZY = FetchConfig.ofLazy();
private OrmQueryDetail detail = new OrmQueryDetail();
@@ -262,6 +263,11 @@ class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T> {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public void findEach(int batch, Consumer<List<T>> consumer) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public void findEachWhile(Predicate<T> consumer) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
@@ -569,21 +575,6 @@ class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T> {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public Query<T> forUpdate(LockType lockType) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public Query<T> forUpdateNoWait(LockType lockType) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public Query<T> forUpdateSkipLocked(LockType lockType) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public boolean isForUpdate() {
return false;
@@ -643,4 +634,14 @@ class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T> {
public Query<T> orderById(boolean orderById) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public void selectProperties(Set<String> props) {
detail.selectProperties(props);
}
@Override
public void fetchProperties(String property, Set<String> columns, FetchConfig config) {
detail.fetchProperties(property, columns, config);
}
}
@@ -42,7 +42,28 @@ public class DtoQueryEngine {
}
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
public <T> void findEach(DtoQueryRequest<T> request, int batchSize, Consumer<List<T>> consumer) {
try {
List<T> buffer = new ArrayList<>();
request.executeSql(binder, SpiQuery.Type.ITERATE);
while (request.next()) {
buffer.add(request.readNextBean());
if (buffer.size() >= batchSize) {
consumer.accept(buffer);
buffer.clear();
}
}
if (!buffer.isEmpty()) {
// consume the remainder
consumer.accept(buffer);
}
} catch (Exception e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
@@ -36,4 +36,13 @@ public interface STreePropertyAssoc extends STreeProperty {
*/
void setValue(EntityBean parentBean, Object contextBean);
/**
* Return true if the associated type has soft delete.
*/
boolean isTargetSoftDelete();
/**
* Return the soft delete predicate.
*/
String getSoftDeletePredicate(String tableAlias);
}
@@ -122,8 +122,8 @@ class SqlTree {
return inheritanceWhereSql;
}
SqlTreeNode getRootNode() {
return rootNode;
SqlTreeRoot getRootNode() {
return (SqlTreeRoot)rootNode;
}
/**
@@ -74,7 +74,6 @@ public final class SqlTreeBuilder {
* Construct for RawSql query.
*/
SqlTreeBuilder(OrmQueryRequest<?> request, CQueryPredicates predicates, OrmQueryDetail queryDetail, boolean rawNoId) {
this.rawSql = true;
this.desc = request.getBeanDescriptor();
this.rawNoId = rawNoId;
@@ -97,7 +96,6 @@ public final class SqlTreeBuilder {
* to the root node.
*/
SqlTreeBuilder(String columnAliasPrefix, CQueryBuilder builder, OrmQueryRequest<?> request, CQueryPredicates predicates) {
this.rawSql = false;
this.rawNoId = false;
this.desc = request.getBeanDescriptor();
@@ -108,11 +106,9 @@ public final class SqlTreeBuilder {
this.includeJoin = query.getM2mIncludeJoin();
this.manyWhereJoins = query.getManyWhereJoins();
this.queryDetail = query.getDetail();
this.predicates = predicates;
this.alias = new SqlTreeAlias(request.getBaseTableAlias(), temporalMode);
this.distinctOnPlatform = builder.isPlatformDistinctOn();
String fromForUpdate = builder.fromForUpdate(query);
CQueryHistorySupport historySupport = builder.getHistorySupport(query);
CQueryDraftSupport draftSupport = builder.getDraftSupport(query);
@@ -124,10 +120,8 @@ public final class SqlTreeBuilder {
* Build based on the includes and using the BeanJoinTree.
*/
public SqlTree build() {
// build the appropriate chain of SelectAdapter's
buildRoot(desc);
// build the actual String
String distinctOn = null;
String selectSql = null;
@@ -149,7 +143,6 @@ public final class SqlTreeBuilder {
}
private String buildSelectClause() {
if (rawSql) {
return "Not Used";
}
@@ -158,8 +151,7 @@ public final class SqlTreeBuilder {
}
private String buildGroupByClause() {
if (rawSql || !rootNode.isAggregation()) {
if (rawSql || (!rootNode.isAggregation() && query.getHavingExpressions() == null)) {
return null;
}
ctx.startGroupBy();
@@ -168,7 +160,6 @@ public final class SqlTreeBuilder {
}
private String buildDistinctOn() {
if (rawSql || !distinctOnPlatform || !sqlDistinct || Type.COUNT == query.getType()) {
return null;
}
@@ -207,7 +198,6 @@ public final class SqlTreeBuilder {
}
private String buildWhereClause() {
if (rawSql) {
return "Not Used";
}
@@ -216,7 +206,6 @@ public final class SqlTreeBuilder {
}
private String buildFromClause() {
if (rawSql) {
return "Not Used";
}
@@ -225,17 +214,13 @@ public final class SqlTreeBuilder {
}
private void buildRoot(STreeType desc) {
rootNode = buildSelectChain(null, null, desc, null);
if (!rawSql) {
alias.addJoin(queryDetail.getFetchPaths(), desc);
alias.addJoin(predicates.getPredicateIncludes(), desc);
alias.addManyWhereJoins(manyWhereJoins.getPropertyNames());
// build set of table alias
alias.buildAlias();
predicates.parseTableAlias(alias);
}
}
@@ -246,9 +231,7 @@ public final class SqlTreeBuilder {
*/
private SqlTreeNode buildSelectChain(String prefix, STreePropertyAssoc prop,
STreeType desc, List<SqlTreeNode> joinList) {
List<SqlTreeNode> myJoinList = new ArrayList<>();
List<STreePropertyAssocOne> extraProps = new ArrayList<>();
for (STreePropertyAssocOne one : desc.propsOne()) {
String propPrefix = SplitName.add(prefix, one.getName());
@@ -295,7 +278,6 @@ public final class SqlTreeBuilder {
* </p>
*/
private void addManyWhereJoins(List<SqlTreeNode> myJoinList) {
Collection<PropertyJoin> includes = manyWhereJoins.getPropertyJoins();
for (PropertyJoin joinProp : includes) {
STreePropertyAssoc beanProperty = (STreePropertyAssoc) desc.findPropertyFromPath(joinProp.getProperty());
@@ -311,7 +293,6 @@ public final class SqlTreeBuilder {
}
private SqlTreeNode buildNode(String prefix, STreePropertyAssoc prop, STreeType desc, List<SqlTreeNode> myList, SqlTreeProperties props) {
if (prefix == null) {
buildExtraJoins(desc, myList);
@@ -340,13 +321,10 @@ public final class SqlTreeBuilder {
* already in select clause.
*/
private void buildExtraJoins(STreeType desc, List<SqlTreeNode> myList) {
if (rawSql) {
return;
}
Set<String> predicateIncludes = predicates.getPredicateIncludes();
if (predicateIncludes == null) {
return;
}
@@ -362,8 +340,7 @@ public final class SqlTreeBuilder {
// look for predicateIncludes that are not in selectIncludes and add
// them as extra joins to the query
IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes, predicateIncludes);
IncludesDistiller extraJoinDistill = new IncludesDistiller(desc, selectIncludes, predicateIncludes, temporalMode);
Collection<SqlTreeNodeExtraJoin> extraJoins = extraJoinDistill.getExtraJoinRootNodes();
if (!extraJoins.isEmpty()) {
// add extra joins required to support predicates
@@ -389,7 +366,6 @@ public final class SqlTreeBuilder {
* </p>
*/
private void addPropertyToSubQuery(SqlTreeProperties selectProps, STreeType desc, String propName) {
STreeProperty p = desc.findProperty(propName);
if (p == null) {
logger.error("property [" + propName + "]not found on " + desc + " for query - excluding it.");
@@ -402,13 +378,10 @@ public final class SqlTreeBuilder {
p = ((STreePropertyAssoc) p).target().findProperty(name);
}
}
selectProps.add(p);
}
private void addProperty(SqlTreeProperties selectProps, STreeType desc,
OrmQueryProperties queryProps, String propName) {
private void addProperty(SqlTreeProperties selectProps, STreeType desc, OrmQueryProperties queryProps, String propName) {
if (subQuery) {
addPropertyToSubQuery(selectProps, desc, propName);
return;
@@ -471,7 +444,6 @@ public final class SqlTreeBuilder {
}
private SqlTreeProperties getBaseSelectPartial(STreeType desc, OrmQueryProperties queryProps) {
SqlTreeProperties selectProps = new SqlTreeProperties();
// add properties in the order in which they appear
// in the query. Gives predictable sql/properties for
@@ -480,7 +452,7 @@ public final class SqlTreeBuilder {
// Also note that this can include transient properties.
// This makes sense for transient properties used to
// hold sum() count() type values (with SqlSelect)
final Set<String> selectInclude = queryProps.getSelectInclude();
final Set<String> selectInclude = queryProps.getIncluded();
for (String propName : selectInclude) {
if (!propName.isEmpty()) {
addProperty(selectProps, desc, queryProps, propName);
@@ -502,7 +474,6 @@ public final class SqlTreeBuilder {
}
private SqlTreeProperties getBaseSelect(STreeType desc, OrmQueryProperties queryProps) {
boolean partial = queryProps != null && !queryProps.allProperties();
if (partial) {
return getBaseSelectPartial(desc, queryProps);
@@ -541,13 +512,10 @@ public final class SqlTreeBuilder {
* Return true if this many node should be included in the query.
*/
private boolean isIncludeMany(String propName, STreePropertyAssocMany manyProp) {
if (queryDetail.isJoinsEmpty()) {
return false;
}
if (queryDetail.includesPath(propName)) {
if (manyProperty != null) {
// only one many associated allowed to be included in fetch
if (logger.isDebugEnabled()) {
@@ -555,7 +523,6 @@ public final class SqlTreeBuilder {
}
return false;
}
manyProperty = manyProp;
return true;
}
@@ -571,7 +538,6 @@ public final class SqlTreeBuilder {
* </p>
*/
private boolean isIncludeBean(String prefix) {
if (queryDetail.includesPath(prefix)) {
// explicitly included
String[] splitNames = SplitName.split(prefix);
@@ -592,26 +558,20 @@ public final class SqlTreeBuilder {
*/
private static class IncludesDistiller {
private final STreeType desc;
private final Set<String> selectIncludes;
private final Set<String> predicateIncludes;
private final SpiQuery.TemporalMode temporalMode;
/**
* Contains the 'root' extra joins. We only return the roots back.
*/
private final Map<String, SqlTreeNodeExtraJoin> joinRegister = new HashMap<>();
/**
* Register of all the extra join nodes.
*/
private final Map<String, SqlTreeNodeExtraJoin> rootRegister = new HashMap<>();
private final STreeType desc;
private IncludesDistiller(STreeType desc, Set<String> selectIncludes,
Set<String> predicateIncludes) {
Set<String> predicateIncludes, SpiQuery.TemporalMode temporalMode) {
this.desc = desc;
this.selectIncludes = selectIncludes;
this.predicateIncludes = predicateIncludes;
this.temporalMode = temporalMode;
}
/**
@@ -623,26 +583,21 @@ public final class SqlTreeBuilder {
* </p>
*/
private Collection<SqlTreeNodeExtraJoin> getExtraJoinRootNodes() {
String[] extras = findExtras();
if (extras.length == 0) {
return rootRegister.values();
}
// sort so we process only getting the leaves
// excluding nodes between root and the leaf
Arrays.sort(extras);
// reverse order so get the leaves first...
for (String extra : extras) {
createExtraJoin(extra);
}
return rootRegister.values();
}
private void createExtraJoin(String includeProp) {
SqlTreeNodeExtraJoin extraJoin = createJoinLeaf(includeProp);
if (extraJoin != null) {
// add the extra join...
@@ -650,7 +605,6 @@ public final class SqlTreeBuilder {
// find root of this extra join... linking back to the
// parents (creating the tree) as it goes.
SqlTreeNodeExtraJoin root = findExtraJoinRoot(includeProp, extraJoin);
// register the root because these are the only ones we
// return back.
rootRegister.put(root.getName(), root);
@@ -661,12 +615,11 @@ public final class SqlTreeBuilder {
* Create a SqlTreeNodeExtraJoin, register and return it.
*/
private SqlTreeNodeExtraJoin createJoinLeaf(String propertyName) {
ExtraJoin extra = desc.extraJoin(propertyName);
if (extra == null) {
return null;
} else {
SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, extra.getProperty(), extra.isContainsMany());
SqlTreeNodeExtraJoin extraJoin = new SqlTreeNodeExtraJoin(propertyName, extra.getProperty(), extra.isContainsMany(), temporalMode);
joinRegister.put(propertyName, extraJoin);
return extraJoin;
}
@@ -713,9 +666,7 @@ public final class SqlTreeBuilder {
* by the select.
*/
private String[] findExtras() {
List<String> extras = new ArrayList<>();
for (String predProp : predicateIncludes) {
if (!selectIncludes.contains(predProp)) {
extras.add(predProp);
@@ -73,11 +73,6 @@ interface SqlTreeNode {
*/
EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean contextBean) throws SQLException;
/**
* Load a version of a @History bean with effective dates.
*/
<T> Version<T> loadVersion(DbReadContext ctx) throws SQLException;
/**
* Return true if the query has a many join.
*/
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.query;
import io.ebean.Version;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
@@ -16,7 +15,6 @@ import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.id.IdBinder;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -56,6 +54,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
* False if report bean and has no id property.
*/
final boolean readId;
private final boolean readIdNormal;
private final boolean disableLazyLoad;
@@ -88,7 +87,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
*/
SqlTreeNodeBean(String prefix, STreePropertyAssoc beanProp, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
this(prefix, beanProp, beanProp.target(), props, myChildren, withId, null, temporalMode, disableLazyLoad);
}
@@ -120,7 +118,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
boolean aggregationRoot = props.isAggregationRoot();
// the bean has an Id property and we want to use it
this.readId = !aggregationRoot && withId && desc.hasId();
this.disableLazyLoad = disableLazyLoad || !readId || desc.isRawSqlBased() || temporalVersions;
this.readIdNormal = readId && !temporalVersions;
this.disableLazyLoad = disableLazyLoad || !readIdNormal || desc.isRawSqlBased();
this.partialObject = props.isPartialObject();
this.properties = props.getProps();
this.children = myChildren == null ? NO_CHILDREN : myChildren.toArray(new SqlTreeNode[0]);
@@ -192,26 +191,12 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
/**
* Read the version bean.
*/
@Override
@SuppressWarnings("unchecked")
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
// read the sys period lower and upper bounds
// these are always the first 2 columns in the resultSet
Timestamp start = ctx.getDataReader().getTimestamp();
Timestamp end = ctx.getDataReader().getTimestamp();
T bean = (T) load(ctx, null, null);
return new Version<>(bean, start, end);
}
/**
* Load that takes into account inheritance.
*/
private class LoadInherit extends Load {
LoadInherit(DbReadContext ctx, EntityBean parentBean) {
private LoadInherit(DbReadContext ctx, EntityBean parentBean) {
super(ctx, parentBean);
}
@@ -232,10 +217,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
@Override
void loadProperties() {
// take account of inheritance and due to subclassing approach
// need to get a 'local' version of the property
// take account of inheritance
for (STreeProperty property : properties) {
// get a local version of the BeanProperty
localDesc.inheritanceLoad(sqlBeanLoad, property, ctx);
}
}
@@ -244,7 +227,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
/**
* Load a bean instance.
*/
private class Load {
class Load {
final DbReadContext ctx;
final EntityBean parentBean;
@@ -262,12 +245,12 @@ class SqlTreeNodeBean implements SqlTreeNode {
SqlBeanLoad sqlBeanLoad;
boolean lazyLoadMany;
Load(DbReadContext ctx, EntityBean parentBean) {
private Load(DbReadContext ctx, EntityBean parentBean) {
this.ctx = ctx;
this.parentBean = parentBean;
}
void initLazyParent() throws SQLException {
private void initLazyParent() throws SQLException {
if (lazyLoadParentIdBinder != null) {
lazyLoadParentId = lazyLoadParentIdBinder.read(ctx);
}
@@ -279,12 +262,12 @@ class SqlTreeNodeBean implements SqlTreeNode {
localIdBinder = idBinder;
}
void initPersistenceContext() {
private void initPersistenceContext() {
queryMode = ctx.getQueryMode();
persistenceContext = (!readId || temporalVersions) ? null : ctx.getPersistenceContext();
persistenceContext = (!readIdNormal) ? null : ctx.getPersistenceContext();
}
void readId() throws SQLException {
private void readId() throws SQLException {
if (readId) {
id = localIdBinder.readSet(ctx, localBean);
if (id == null) {
@@ -325,7 +308,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
void initSqlLoadBean() {
private void initSqlLoadBean() {
ctx.setCurrentPrefix(prefix, pathMap);
ctx.propagateState(localBean);
sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode);
@@ -337,33 +320,29 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
void loadChildren() throws SQLException {
//boolean lazyLoadMany = false;
private void loadChildren() throws SQLException {
if (localBean == null && queryMode == Mode.LAZYLOAD_MANY) {
// batch lazy load many into existing contextBean
localBean = contextBean;
lazyLoadMany = true;
}
// recursively continue reading...
for (SqlTreeNode aChildren : children) {
// read each child... and let them set their
// values back to this localBean
aChildren.load(ctx, localBean, contextBean);
for (SqlTreeNode child : children) {
child.load(ctx, localBean, contextBean);
}
}
boolean isLazyLoadManyRoot() {
private boolean isLazyLoadManyRoot() {
return queryMode == Mode.LAZYLOAD_MANY && isRoot();
}
EntityBean getContextBean() {
private EntityBean getContextBean() {
return contextBean;
}
void postLoad() {
private void postLoad() {
if (!lazyLoadMany && localBean != null) {
ctx.setCurrentPrefix(prefix, pathMap);
if (readId && !temporalVersions) {
if (readIdNormal) {
createListProxies();
}
if (temporalMode == SpiQuery.TemporalMode.DRAFT) {
@@ -384,7 +363,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
if (disableLazyLoad) {
// bean does not have an Id or is SqlSelect based
ebi.setDisableLazyLoad(true);
} else if (partialObject) {
if (readId) {
// register for lazy loading
@@ -408,7 +386,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
private void createListProxies() {
STreePropertyAssocMany fetchedMany = ctx.getManyProperty();
boolean forceNewReference = queryMode == Mode.REFRESH_BEAN;
// load the List/Set/Map proxy objects (deferred fetching of lists)
for (STreePropertyAssocMany many : localDesc.propsMany()) {
if (many != fetchedMany) {
// create a proxy for the many (deferred fetching)
@@ -425,21 +402,20 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
void setBeanToParent() {
private void setBeanToParent() {
if (parentBean != null) {
// set this back to the parentBean
nodeBeanProp.setValue(parentBean, contextBean);
}
}
EntityBean complete() {
if (!readId || temporalVersions) {
private EntityBean complete() {
if (!readIdNormal) {
// a bean with no Id (never found in context)
if (lazyLoadParentId != null) {
ctx.setLazyLoadedChildBean(localBean, lazyLoadParentId);
}
return localBean;
} else {
if (lazyLoadParentId != null) {
ctx.setLazyLoadedChildBean(contextBean, lazyLoadParentId);
@@ -448,7 +424,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
void initialise() throws SQLException {
private void initialise() throws SQLException {
initLazyParent();
initBeanType();
initPersistenceContext();
@@ -457,6 +433,27 @@ class SqlTreeNodeBean implements SqlTreeNode {
loadProperties();
loadChildren();
}
/**
* Perform the load returning the loaded bean.
*/
EntityBean perform() throws SQLException {
initialise();
if (isLazyLoadManyRoot()) {
return getContextBean();
}
postLoad();
setBeanToParent();
return complete();
}
/**
* Return true if this bean was already in the context. If already in the
* context we need to check if it is already contained in the collection.
*/
boolean isContextBean() {
return localBean == null;
}
}
/**
@@ -464,14 +461,14 @@ class SqlTreeNodeBean implements SqlTreeNode {
*/
@Override
public EntityBean load(DbReadContext ctx, EntityBean parentBean, EntityBean contextParent) throws SQLException {
Load load = (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean);
load.initialise();
if (load.isLazyLoadManyRoot()) {
return load.getContextBean();
}
load.postLoad();
load.setBeanToParent();
return load.complete();
return createLoad(ctx, parentBean).perform();
}
/**
* Create the loader with or without inheritance.
*/
Load createLoad(DbReadContext ctx, EntityBean parentBean) {
return (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean);
}
@Override
@@ -489,8 +486,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
property.appendSelect(ctx, subQuery);
}
}
for (SqlTreeNode aChildren : children) {
aChildren.appendGroupBy(ctx, subQuery);
for (SqlTreeNode child : children) {
child.appendGroupBy(ctx, subQuery);
}
ctx.popTableAlias();
ctx.popJoin();
@@ -527,11 +524,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
appendSelectId(ctx, idBinder.getBeanProperty());
}
appendSelect(ctx, subQuery, properties);
for (SqlTreeNode aChildren : children) {
// read each child... and let them set their
// values back to this localBean
aChildren.appendSelect(ctx, subQuery);
for (SqlTreeNode child : children) {
child.appendSelect(ctx, subQuery);
}
ctx.popTableAlias();
ctx.popJoin();
@@ -579,10 +573,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
appendExtraWhere(ctx);
for (SqlTreeNode aChildren : children) {
// recursively add to the where clause any
// fixed predicates (extraWhere etc)
aChildren.appendWhere(ctx);
for (SqlTreeNode child : children) {
child.appendWhere(ctx);
}
}
@@ -618,10 +610,9 @@ class SqlTreeNodeBean implements SqlTreeNode {
property.appendFrom(ctx, joinType);
}
for (SqlTreeNode aChildren : children) {
aChildren.appendFrom(ctx, joinType);
for (SqlTreeNode child : children) {
child.appendFrom(ctx, joinType);
}
ctx.popTableAlias();
ctx.popJoin();
}
@@ -646,8 +637,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
if (intersectionAsOfTableAlias) {
query.incrementAsOfTableCount();
}
for (SqlTreeNode aChildren : children) {
aChildren.addAsOfTableAlias(query);
for (SqlTreeNode child : children) {
child.addAsOfTableAlias(query);
}
}
@@ -24,19 +24,17 @@ import java.util.Set;
class SqlTreeNodeExtraJoin implements SqlTreeNode {
private final STreePropertyAssoc assocBeanProperty;
private final SpiQuery.TemporalMode temporalMode;
private final String prefix;
private final boolean manyJoin;
private final boolean pathContainsMany;
private List<SqlTreeNodeExtraJoin> children;
SqlTreeNodeExtraJoin(String prefix, STreePropertyAssoc assocBeanProperty, boolean pathContainsMany) {
SqlTreeNodeExtraJoin(String prefix, STreePropertyAssoc assocBeanProperty, boolean pathContainsMany, SpiQuery.TemporalMode temporalMode) {
this.prefix = prefix;
this.assocBeanProperty = assocBeanProperty;
this.pathContainsMany = pathContainsMany;
this.temporalMode = temporalMode;
this.manyJoin = assocBeanProperty instanceof STreePropertyAssocMany;
}
@@ -113,9 +111,7 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
@Override
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
boolean manyToMany = false;
if (assocBeanProperty instanceof STreePropertyAssocMany) {
STreePropertyAssocMany manyProp = (STreePropertyAssocMany) assocBeanProperty;
if (manyProp.hasJoinTable()) {
@@ -144,15 +140,16 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
assocBeanProperty.appendFrom(ctx, joinType);
}
joinType = assocBeanProperty.addJoin(joinType, prefix, ctx);
if (assocBeanProperty.isTargetSoftDelete() && temporalMode != SpiQuery.TemporalMode.SOFT_DELETED) {
ctx.append(" and ").append(assocBeanProperty.getSoftDeletePredicate(ctx.getTableAlias(prefix)));
}
}
if (children != null) {
if (manyJoin || pathContainsMany) {
// if AUTO then make all descendants use OUTER JOIN
joinType = joinType.autoToOuter();
}
for (SqlTreeNodeExtraJoin child : children) {
child.appendFrom(ctx, joinType);
}
@@ -181,14 +178,6 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
return null;
}
/**
* Does nothing.
*/
@Override
public <T> Version<T> loadVersion(DbReadContext ctx) {
return null;
}
@Override
public boolean hasMany() {
return manyJoin;
@@ -99,12 +99,6 @@ class SqlTreeNodeFormulaWhereJoin implements SqlTreeNode {
return null;
}
@Override
public <T> Version<T> loadVersion(DbReadContext ctx) {
// nothing to do here
return null;
}
@Override
public boolean hasMany() {
return true;
@@ -18,6 +18,25 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
this.manyProp = prop;
}
@Override
public boolean hasMany() {
return true;
}
@Override
public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException {
// pass in null for parentBean because added to a collection rather than set to the parentBean
SqlTreeNodeBean.Load load = createLoad(cquery, null);
EntityBean detailBean = load.perform();
if (contextParent != null) {
// Add to the collection and initialise collection if needed
// A null detailBean may initialise an empty collection
// Check for bean existing in collection based on load.isContextBean()
manyProp.addBeanToCollectionWithCreate(contextParent, detailBean, load.isContextBean());
}
return detailBean;
}
/**
* Append the property columns to the buffer.
*/
@@ -28,20 +47,6 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
ctx.popTableAlias();
}
@Override
public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException {
// pass in null for parentBean because the localBean
// that is built is added to a collection rather than
// being set to the parentBean directly
EntityBean detailBean = super.load(cquery, null, null);
// initialise the collection and add detailBean if it is not null
if (contextParent != null) {
manyProp.addBeanToCollectionWithCreate(contextParent, detailBean, false);
}
return detailBean;
}
/**
* append extraWhere to the join.
*/
@@ -64,9 +69,4 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
super.appendFrom(ctx, joinType.autoToOuter());
}
@Override
public boolean hasMany() {
return true;
}
}
@@ -144,12 +144,6 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
return null;
}
@Override
public <T> Version<T> loadVersion(DbReadContext ctx) {
// nothing to do here
return null;
}
@Override
public boolean hasMany() {
return true;
@@ -1,16 +1,21 @@
package io.ebeaninternal.server.query;
import io.ebean.Version;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.TableJoin;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
/**
* Represents the root node of the Sql Tree.
*/
final class SqlTreeNodeRoot extends SqlTreeNodeBean {
final class SqlTreeNodeRoot extends SqlTreeNodeBean implements SqlTreeRoot {
private final TableJoin includeJoin;
@@ -35,6 +40,25 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean {
return true;
}
@Override
public EntityBean load(DbReadContext ctx) throws SQLException {
return load(ctx, null, null);
}
/**
* Read the version bean.
*/
@Override
@SuppressWarnings("unchecked")
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
// read the sys period lower and upper bounds
// these are always the first 2 columns in the resultSet
Timestamp start = ctx.getDataReader().getTimestamp();
Timestamp end = ctx.getDataReader().getTimestamp();
T bean = (T) load(ctx, null, null);
return new Version<>(bean, start, end);
}
@Override
public boolean isSqlDistinct() {
return sqlDistinct;
@@ -0,0 +1,34 @@
package io.ebeaninternal.server.query;
import io.ebean.Version;
import io.ebean.bean.EntityBean;
import io.ebean.core.type.ScalarDataReader;
import io.ebeaninternal.server.deploy.DbReadContext;
import java.sql.SQLException;
/**
* The root level node of the SqlTree.
*/
interface SqlTreeRoot {
/**
* Load the bean from the DbReadContext.
* <p>
* At a high level this actually controls the reading of the data from the
* jdbc resultSet and putting it into the bean etc.
* </p>
*/
EntityBean load(DbReadContext ctx) throws SQLException;
/**
* Load a version of a @History bean with effective dates.
*/
<T> Version<T> loadVersion(DbReadContext ctx) throws SQLException;
/**
* Return a Scalar single attribute reader based on the first property.
*/
ScalarDataReader<?> getSingleAttributeReader();
}

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