Compare commits

...
Author SHA1 Message Date
rob bygrave 37b6a321ab [maven-release-plugin] prepare release ebean-11.15.12 2018-05-29 00:44:22 +12:00
rob bygrave 896f7d9ea8 No effective change - improve error message when entity not registered to include entity annotation question 2018-05-29 00:38:41 +12:00
rob bygrave 141a0d9358 #1390 - ENH: Support explicit @Id @GeneratedValue ... mapping (where absence of @GeneratedValue means externally supplied id values) 2018-05-29 00:35:31 +12:00
Roland PramlandRob Bygrave 5a7f24da5d FIX: jsonRead of transient maps (#1389)
FIX: jsonRead of transient maps - to skip the read of start_array - Avoid error "Unexpected token start_object - expecting start_array "
2018-05-28 23:06:45 +12:00
rob bygrave 8ea86d6c6d [maven-release-plugin] prepare for next development iteration 2018-05-27 19:19:52 +12:00
rob bygrave fb5eeb60b1 [maven-release-plugin] prepare release ebean-11.15.11 2018-05-27 19:19:37 +12:00
rob bygrave 33ec8abb9d No effective change - update test remove unused exception 2018-05-27 17:38:33 +12:00
rob bygrave bbd7baf1be #1388 - AutoTune - tuned query includes "null" as fetched property 2018-05-27 17:33:26 +12:00
rob bygrave 2e36402a76 #1387 - Revert #1374 and expose io.ebean.event.ServletContextListener (as public API) 2018-05-27 15:54:32 +12:00
rob bygrave fa4af4ef98 Revert "#1374 - Remove io.ebeaninternal.server.core.ServletContextListener - should be unused"
This reverts commit 95737cd3c5.
2018-05-27 15:41:51 +12:00
rob bygrave ed6a9e2dbb #1386 - ENH: For DbMigration generation - add support for setting version, name, generatePendingDrop, applyPrefix, ddl header 2018-05-27 15:37:45 +12:00
rob bygrave 42d83705b7 #1384 - Change MigrationXmlWriter to not write <!DOCTYPE xml> (as it is not needed/expected now) 2018-05-24 00:15:25 +12:00
rob bygrave ae58d9223a No effective change - add test for PrimaryKeyJoinColumn - insertWithoutExtra 2018-05-24 00:07:30 +12:00
rob bygrave a9a89938f9 #1368 - Remove unused resource - default-create-table.sql ... which is in ebean-migration 2018-05-24 00:06:18 +12:00
rob bygrave 4b91f5fe0b #1380 - Make javax.xml an optional dependency (as it is not part of JDK from 9 onwards) 2018-05-21 01:06:26 +12:00
rob bygrave 85d3caf079 #1367 - Modularized project cannot locate ebean.properties file on Java 10 2018-05-21 00:29:04 +12:00
rob bygrave f6d8c8b618 [maven-release-plugin] prepare for next development iteration 2018-05-21 00:13:59 +12:00
rob bygrave d0fa3f0cb1 [maven-release-plugin] prepare release ebean-11.15.10 2018-05-21 00:13:43 +12:00
Roland PramlandRob Bygrave 41a31c1aa0 trigger based history will recreate view properly and handle default … (#1370)
* trigger based history will recreate view properly and handle default value also for history view

* HistoryTriggers are generated at the end

* Reference Script

* MYSQL: lock all tables at once
2018-05-20 17:19:10 +12:00
Roland PramlandRob Bygrave ab301f970c FIX: correct caching when collection with different inherited beans is put to the cache (#1379)
* FIX: correct cahcing when collection with different inherited beans is put to the cache

* removed some sysouts
2018-05-20 16:53:56 +12:00
rob bygrave d1b7b082a2 [maven-release-plugin] prepare for next development iteration 2018-05-17 12:50:26 +12:00
rob bygrave 5fb3a1e019 [maven-release-plugin] prepare release ebean-11.15.9 2018-05-17 12:50:12 +12:00
rob bygrave cb0dee998b #1375 - Bug: RawSql parsing fails when using placeholders 2018-05-17 12:48:08 +12:00
Roland PramlandRob Bygrave 53b606289d FIX: use LinkedHashMap in enum types to get deterministic ordering (#1378) 2018-05-17 12:26:37 +12:00
rob bygrave 574d5e215b [maven-release-plugin] prepare for next development iteration 2018-05-14 22:19:37 +12:00
rob bygrave 508f2129f5 [maven-release-plugin] prepare release ebean-11.15.8 2018-05-14 22:19:21 +12:00
rob bygrave cdfcbeec43 #1377 - Bump dependency - jsnakeyaml 1.19 to 1.21 2018-05-14 22:17:27 +12:00
rob bygrave 0e9782e6b3 #1376 - Bump dependency - jackson-core from 2.9.0 to 2.9.5 2018-05-14 22:15:58 +12:00
rob bygrave 927fa5f7d3 [maven-release-plugin] prepare for next development iteration 2018-05-14 22:10:34 +12:00
rob bygrave 53a80c7dd1 [maven-release-plugin] prepare release ebean-11.15.7 2018-05-14 22:10:18 +12:00
rob bygrave 89789f3347 #1372 - Missing @OneToOne mapping yields unusable error messages.
Restore mapping.
2018-05-14 21:33:11 +12:00
rob bygrave 9dc5ca602c #1372 - Missing @OneToOne mapping yields unusable error messages. 2018-05-14 21:32:48 +12:00
rob bygrave 95737cd3c5 #1374 - Remove io.ebeaninternal.server.core.ServletContextListener - should be unused 2018-05-14 21:23:13 +12:00
Roland PramlandRob Bygrave 0a49fc9b62 FIX: NPE when fetching inherited beans from cache (#1373) 2018-05-14 21:12:42 +12:00
rob bygrave f8c80c7fd2 [maven-release-plugin] prepare for next development iteration 2018-05-10 17:22:18 +12:00
rob bygrave fc837b6472 [maven-release-plugin] prepare release ebean-11.15.6 2018-05-10 17:22:04 +12:00
rob bygrave cfaedcdbc5 #1371 - Regression in 11.15.5 with @Inheritance on abstract class with discriminatorType = INTEGER 2018-05-10 17:20:09 +12:00
rob bygrave 111bac7514 [maven-release-plugin] prepare for next development iteration 2018-05-06 20:50:56 +12:00
rob bygrave 1a2ca63d65 [maven-release-plugin] prepare release ebean-11.15.5 2018-05-06 20:50:40 +12:00
rob bygrave 33031d1c69 #1369 - BeanListener and/or BeanPersistAdapter not fired on delete of children when cascaded 2018-05-06 20:21:10 +12:00
Roland PramlandRob Bygrave 49d9d88fbe FIX: load all props from cache when inheritance is involved (#1366) 2018-04-27 14:44:18 +12:00
Rob Bygrave 756f867ed2 #1365 - Regression: property ebean.namingConvention=io.ebean.config.MatchingNamingConvention ... no longer works 2018-04-18 21:24:37 +12:00
Rob Bygrave 12108ada38 #1363 - Update ebean-migration to 11.5.4 2018-04-18 20:47:59 +12:00
rob bygrave fb2fdc9b53 Merge branch 'master' of github.com:ebean-orm/ebean 2018-04-12 20:44:57 +12:00
rob bygrave d570971fd1 #1363 - Update ebean-migration to 11.5.2 2018-04-12 20:44:39 +12:00
André CamiloandRob Bygrave a8434f22ff Inheritance - Add support for @DiscriminatorValue to be optional (#1361) 2018-04-12 20:38:09 +12:00
rob bygrave b0d3921264 [maven-release-plugin] prepare for next development iteration 2018-04-05 22:23:18 +12:00
rob bygrave 4167dfc4af [maven-release-plugin] prepare release ebean-11.15.4 2018-04-05 22:23:03 +12:00
rob bygrave e49a0c9ffb #1360 - ENH: Add transaction.setNestedUseSavepoint() ... such that nested transactions are actually Savepoints
Additional tests
2018-04-05 21:48:54 +12:00
rob bygrave dd6f223ae1 #1360 - ENH: Add transaction.setNestedUseSavepoint() ... such that nested transactions are actually Savepoints 2018-04-05 21:35:29 +12:00
Roman ParshikovandRob Bygrave 05aff53241 Fix Maven links in README (#1358) 2018-04-03 21:17:46 +12:00
Rob Bygrave 9a3362e842 [maven-release-plugin] prepare for next development iteration 2018-03-29 14:35:40 +13:00
Rob Bygrave 04e0069eb3 [maven-release-plugin] prepare release ebean-11.15.3 2018-03-29 14:35:25 +13:00
Rob Bygrave b8c4e1c3d3 #1357 - Regression in 11.15.2 - for nested Ebean.beginTransaction(TxScope.requiresNew()); ... where closed via ebeanServer.endTransaction(); rather than transaction.end() 2018-03-29 10:22:12 +13:00
Rob Bygrave 6b136248fa [maven-release-plugin] prepare for next development iteration 2018-03-29 01:49:43 +13:00
Rob Bygrave aa004f11af [maven-release-plugin] prepare release ebean-11.15.2 2018-03-29 01:49:28 +13:00
Rob Bygrave acd5578d8a #115 - Mapping - Add support for @ElementCollection enhancement
Add element collection L2 cache support
2018-03-29 01:15:58 +13:00
Rob Bygrave 07946cd505 #115 - Mapping - Add support for @ElementCollection enhancement
Add element collection notification - combine into preUpdate/postUpdate of the bean
2018-03-28 17:31:53 +13:00
Rob Bygrave 57870aaa05 #115 - Mapping - Add support for @ElementCollection enhancement
Add embedded bean support
2018-03-28 13:22:46 +13:00
Rob Bygrave cca837bcfa #1354 - ENH: Add SqlUpdate setNextParameter() ... as alternative for positioned parameters 2018-03-28 00:45:12 +13:00
Rob Bygrave 534eb2f0e2 #1354 - ENH: Add SqlUpdate setNextParameter() ... as alternative for positioned parameters 2018-03-27 23:24:30 +13:00
Rob Bygrave dfa7b765f9 #1354 - ENH: Add SqlUpdate setNextParameter() ... as alternative for positioned parameters 2018-03-27 22:58:19 +13:00
Rob Bygrave 4ef581f87c No effective change - Update agent in pom to 11.11.1 2018-03-27 17:16:35 +13:00
Rob Bygrave eb96c726fd No effective change - Modify some tests to use transaction try with resources 2018-03-27 10:47:24 +13:00
Rob Bygrave abbcd35ee6 #1353 - Use of ebeanServer.commitTransaction() in nested transaction doesn't match transction.commit() and closes parent scope 2018-03-27 09:14:45 +13:00
Rob Bygrave db5a2b194d No effective change - add extra test coverage on DefaultTransactionThreadLocal 2018-03-26 23:31:31 +13:00
Rob Bygrave 74e0a16531 #1351 - ENH: Add named DtoQuery
Merge branch 'feature/namedDtoQuery' of https://github.com/hexagonframework/ebean into hexagonframework-feature/namedDtoQuery
2018-03-26 20:45:39 +13:00
Rob Bygrave 8601f51a6c #1351 - ENH: Add named DtoQuery
Merge branch 'feature/namedDtoQuery' of https://github.com/hexagonframework/ebean into hexagonframework-feature/namedDtoQuery
2018-03-26 20:45:22 +13:00
Rob Bygrave 6534feb5da #1350 - ENH: Add mappingsLocation config to search xml mapping files.
Minor adjustments - read "mappingLocations" only etc.
2018-03-26 16:55:25 +13:00
Yuan XueguiandRob Bygrave 6cf5e3386d ENH: Add mappingsLocation config to search xml mapping files. (#1350)
ENH: Add mappingsLocation config and search xml mapping files feature.
2018-03-26 16:37:25 +13:00
Rob Bygrave 7651256083 #1352 - Bump default datasource - with better defaults for Idle connection cleanup (5 mins) 2018-03-26 15:47:20 +13:00
Rob Bygrave 057d7480b0 #115 - Mapping - Add support for @ElementCollection enhancement
Add simple Map support with Json
2018-03-21 00:15:40 +13:00
Rob Bygrave 85c4385498 #115 - Mapping - Add support for @ElementCollection enhancement
Initial support - add simple Set support and Json support for simple List and Set.
2018-03-20 17:24:35 +13:00
Rob Bygrave 74a9019535 #115 - Mapping - Add support for @ElementCollection enhancement
Initial support - simple List.
2018-03-20 14:22:29 +13:00
Rob Bygrave c44931ea25 #115 - Supporting changes for Element collection support 2018-03-20 09:04:58 +13:00
Rob Bygrave 012322503f #115 - Supporting changes for Element collection support 2018-03-20 09:03:16 +13:00
yuanxuegui 5575273c96 ENH: Add named DtoQuery, such that we can define native SQL queries in ebean mapping xml for DtoQuery. 2018-03-19 18:04:49 +08:00
Rob Bygrave f3251691e2 Test only change - Add test for SavePoint and compare to transaction.setRollbackOnly() 2018-03-19 09:18:00 +13:00
yuanxuegui 9d336cb20b ENH: Add mappingsLocation config to search xml mapping files.
ENH: Add mappingsLocation config to search xml mapping files.

ENH: Add mappingsLocation config to search xml mapping files.

ENH: Add mappingsLocation config to search xml mapping files.

ENH: Add mappingsLocation config to search xml mapping files.

ENH: Add mappingsLocation config to search xml mapping files.

remove unused import.

ENH: Add mappingsLocation config and search xml mapping files feature.
2018-03-17 17:27:51 +08:00
Rob Bygrave 9895e48485 #1349 - Example passing test for - RawSql fetch object with inheritance / 2 2018-03-15 22:47:37 +13:00
Rob Bygrave 4799c56e25 #225 - Mapping - Add / review support for @IdClass 2018-03-15 18:19:17 +13:00
Rob Bygrave 602a35d0b6 #1348 - ENH: Add support for bitwiseAnd / bitwiseAny / bitwiseAll .. expressions for use with integer/long "Flags" properties 2018-03-14 16:23:16 +13:00
Rob Bygrave 4bc1f2ede4 [maven-release-plugin] prepare for next development iteration 2018-03-14 00:03:11 +13:00
347 changed files with 11040 additions and 2305 deletions
+3 -3
View File
@@ -11,7 +11,7 @@ Goto [https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Web
[Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cebean "maven central all related projects")
## Current versions
* [![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean) - ebean
* [![Maven Central : ebean-agent](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-agent) - ebean-agent
* [![Maven Central : ebean-maven-plugin](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.avaje.ebean/ebean-maven-plugin) - ebean-maven-plugin
* [![Maven Central : ebean](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean) - ebean
* [![Maven Central : ebean-agent](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent) - ebean-agent
* [![Maven Central : ebean-maven-plugin](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin) - ebean-maven-plugin
+15 -10
View File
@@ -9,15 +9,20 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.15.1</version>
<version>11.15.12</version>
<packaging>jar</packaging>
<name>ebean</name>
<url>http://ebean-orm.github.io/</url>
<properties>
<jackson-core.version>2.9.5</jackson-core.version>
<snakeyaml.version>1.21</snakeyaml.version>
</properties>
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.15.1</tag>
<tag>ebean-11.15.12</tag>
</scm>
<profiles>
@@ -89,7 +94,7 @@
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.19</version>
<version>${snakeyaml.version}</version>
</dependency>
<!--
@@ -118,7 +123,7 @@
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>3.1.1</version>
<version>3.2.2</version>
</dependency>
<dependency>
@@ -130,7 +135,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.5.1</version>
<version>11.5.4</version>
</dependency>
<dependency>
@@ -143,14 +148,14 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
<version>${jackson-core.version}</version>
</dependency>
<!-- provided scope for JsonNode support -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
<version>${jackson-core.version}</version>
<scope>provided</scope>
</dependency>
@@ -224,7 +229,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>11.10.1</version>
<version>11.11.1</version>
<scope>test</scope>
</dependency>
@@ -254,7 +259,7 @@
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>6.1.3.jre8-preview</version>
<version>6.4.0.jre8</version>
<scope>test</scope>
</dependency>
@@ -297,7 +302,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>11.10.1</version>
<version>11.11.1</version>
<executions>
<execution>
<id>test</id>
+39 -26
View File
@@ -437,6 +437,19 @@ public interface EbeanServer {
*/
<T> DtoQuery<T> findDto(Class<T> dtoType, String sql);
/**
* Create a named Query for DTO beans.
* <p>
* DTO beans are just normal bean like classes with public constructor(s) and setters.
* They do not need to be registered with Ebean before use.
* </p>
*
* @param dtoType The type of the DTO bean the rows will be mapped into.
* @param namedQuery The name of the query
* @param <T> The type of the DTO bean.
*/
<T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery);
/**
* Create a SqlQuery for executing native sql
* query statements.
@@ -508,30 +521,44 @@ public interface EbeanServer {
* etc.
* </p>
* <p>
* <h3>Using try with resources</h3>
* <pre>{@code
*
* // start a transaction (stored in a ThreadLocal)
* ebeanServer.beginTransaction();
*
* try (Transaction txn = ebeanServer.beginTransaction()) {
*
* Order order = ebeanServer.find(Order.class,10);
* ...
* ebeanServer.save(order);
*
* txn.commit();
* }
*
* }</pre>
* <p>
* <h3>Using try finally block</h3>
* <pre>{@code
*
* // start a transaction (stored in a ThreadLocal)
* Transaction txn = ebeanServer.beginTransaction();
* try {
* Order order = ebeanServer.find(Order.class,10);
*
* ebeanServer.save(order);
*
* ebeanServer.commitTransaction();
* txn.commit();
*
* } finally {
* // rollback if we didn't commit
* // i.e. an exception occurred before commitTransaction().
* ebeanServer.endTransaction();
* txn.end();
* }
*
* }</pre>
* <p>
* <h3>Transaction options:</h3>
* <h3>Transaction options</h3>
* <pre>{@code
*
* Transaction txn = ebeanServer.beginTransaction();
* try {
* try (Transaction txn = ebeanServer.beginTransaction()) {
* // explicitly turn on/off JDBC batch use
* txn.setBatchMode(true);
* txn.setBatchSize(50);
@@ -552,10 +579,6 @@ public interface EbeanServer {
* ...
*
* txn.commit();
*
* } finally {
* // rollback if necessary
* txn.end();
* }
*
* }</pre>
@@ -585,19 +608,16 @@ public interface EbeanServer {
* <pre>{@code
* // Start a new transaction. If there is a current transaction
* // suspend it until this transaction ends
* Transaction txn = server.beginTransaction(TxScope.requiresNew());
* try {
* try (Transaction txn = server.beginTransaction(TxScope.requiresNew())) {
*
* ...
*
* // commit the transaction
* txn.commit();
*
* } finally {
* // end this transaction which:
* // A) will rollback transaction if it has not been committed already
* // At end this transaction will:
* // A) will rollback transaction if it has not been committed
* // B) will restore a previously suspended transaction
* txn.end();
* }
*
* }</pre>
@@ -606,20 +626,13 @@ public interface EbeanServer {
* <pre>{@code
*
* // start a new transaction if there is not a current transaction
* Transaction txn = server.beginTransaction(TxScope.required());
* try {
* try (Transaction txn = server.beginTransaction(TxScope.required())) {
*
* ...
*
* // commit the transaction if it was created or
* // do nothing if there was already a current transaction
* txn.commit();
*
* } finally {
* // end this transaction which will rollback the transaction
* // if it was created for this try finally scope and has not
* // already been committed
* txn.end();
* }
*
* }</pre>
@@ -327,6 +327,30 @@ public interface ExpressionFactory {
*/
Expression allEq(Map<String, Object> propertyMap);
/**
* Add expression for ANY of the given bit flags to be set.
*
* @param propertyName The property that holds the flags value
* @param flags The flags we are looking for
*/
Expression bitwiseAny(String propertyName, long flags);
/**
* Add expression for ALL of the given bit flags to be set.
*
* @param propertyName The property that holds the flags value
* @param flags The flags we are looking for
*/
Expression bitwiseAll(String propertyName, long flags);
/**
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
*
* @param propertyName The property that holds the flags value
* @param flags The flags we are looking for
*/
Expression bitwiseAnd(String propertyName, long flags, long match);
/**
* Add raw expression with a single parameter.
* <p>
+51 -4
View File
@@ -482,7 +482,7 @@ public interface ExpressionList<T> {
/**
* Extended version for setDistinct in conjunction with "findSingleAttributeList";
*
* <p>
* <pre>{@code
*
* List<CountedValue<Order.Status>> orderStatusCount =
@@ -878,7 +878,7 @@ public interface ExpressionList<T> {
/**
* In - using a subQuery.
*
* <p>
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
*/
@@ -888,7 +888,7 @@ public interface ExpressionList<T> {
/**
* In - property has a value in the array of values.
*
* <p>
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
*/
@@ -898,7 +898,7 @@ public interface ExpressionList<T> {
/**
* In - property has a value in the collection of values.
*
* <p>
* This is exactly the same as in() and provided due to "in" being a Kotlin keyword
* (and hence to avoid the slightly ugly escaping when using in() in Kotlin)
*/
@@ -996,6 +996,53 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> arrayIsNotEmpty(String propertyName);
/**
* Add expression for ANY of the given bit flags to be set.
* <pre>{@code
*
* where().bitwiseAny("flags", BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
*
* }</pre>
*
* @param propertyName The property that holds the flags value
* @param flags The flags we are looking for
*/
ExpressionList<T> bitwiseAny(String propertyName, long flags);
/**
* Add expression for ALL of the given bit flags to be set.
* <p>
* <pre>{@code
*
* where().bitwiseAll("flags", BwFlags.HAS_BULK + BwFlags.HAS_COLOUR)
*
* }</pre>
*
* @param propertyName The property that holds the flags value
* @param flags The flags we are looking for
*/
ExpressionList<T> bitwiseAll(String propertyName, long flags);
/**
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
* <p>
* <pre>{@code
*
* // Flags Bulk + Size = Size
* // ... meaning Bulk is not set and Size is set
*
* long selectedFlags = BwFlags.HAS_BULK + BwFlags.HAS_SIZE;
* long mask = BwFlags.HAS_SIZE; // Only Size flag set
*
* where().bitwiseAnd("flags", selectedFlags, mask)
*
* }</pre>
*
* @param propertyName The property that holds the flags value
* @param flags The flags we are looking for
*/
ExpressionList<T> bitwiseAnd(String propertyName, long flags, long match);
/**
* Add raw expression with a single parameter.
* <p>
+37 -1
View File
@@ -30,6 +30,35 @@ package io.ebean;
* String msg = "There were " + modifiedCount + " rows updated";
*
* }</pre>
* <p>
* <h3>Example: Using setNextParameter()</h3>
* <pre>{@code
*
* String sql = "insert into audit_log (id, description, modified_description) values (?,?,?)";
* SqlUpdate insert = Ebean.createSqlUpdate(sql);
*
* try (Transaction txn = Ebean.beginTransaction()) {
* txn.setBatchMode(true);
*
* insert.setNextParameter(10000);
* insert.setNextParameter("hello");
* insert.setNextParameter("rob");
* insert.execute();
*
* insert.setNextParameter(10001);
* insert.setNextParameter("goodbye");
* insert.setNextParameter("rob");
* insert.execute();
*
* insert.setNextParameter(10002);
* insert.setNextParameter("chow");
* insert.setNextParameter("bob");
* insert.execute();
*
* txn.commit();
* }
*
* }</pre>
*
* @see Update
* @see SqlQuery
@@ -61,7 +90,7 @@ public interface SqlUpdate {
/**
* Execute and return the generated key. This is effectively a short cut for:
*
* <p>
* <pre>{@code
*
* sqlUpdate.execute();
@@ -137,6 +166,13 @@ public interface SqlUpdate {
*/
SqlUpdate setTimeout(int secs);
/**
* Set the next positioned parameter.
*
* @param value The value to bind
*/
SqlUpdate setNextParameter(Object value);
/**
* Set a parameter via its index position.
*/
+15 -3
View File
@@ -23,6 +23,7 @@ public interface Transaction extends AutoCloseable {
* This returns the current transaction for the 'default server'. If you are using
* multiple EbeanServer's then use {@link EbeanServer#currentTransaction()}.
* </p>
*
* @see Ebean#currentTransaction()
* @see EbeanServer#currentTransaction()
*/
@@ -136,6 +137,18 @@ public interface Transaction extends AutoCloseable {
*/
void rollback(Throwable e) throws PersistenceException;
/**
* Set when we want nested transactions to use Savepoint's.
* <p>
* This means that for a nested transaction:
* <ul>
* <li>begin transaction maps to creating a savepoint</li>
* <li>commit transaction maps to releasing a savepoint</li>
* <li>rollback transaction maps to rollback a savepoint</li>
* </ul>
*/
void setNestedUseSavepoint();
/**
* Mark the transaction for rollback only.
*/
@@ -151,7 +164,6 @@ public interface Transaction extends AutoCloseable {
*/
void end();
/**
* Synonym for end() to support AutoClosable.
*/
@@ -227,7 +239,7 @@ public interface Transaction extends AutoCloseable {
* Refer to {@link ServerConfig#setSkipCacheAfterWrite(boolean)} for configuring the default behavior
* for using the L2 bean cache in transactions spanning multiple query/persist requests.
* </p>
* <p>
*
* <pre>{@code
*
* // assume Customer has L2 bean caching enabled ...
@@ -309,7 +321,7 @@ public interface Transaction extends AutoCloseable {
* <p>
* Example: batch processing executing every 3 rows
* </p>
* <p>
*
* <pre>{@code
*
* String data = "This is a simple test of the batch processing"
@@ -72,6 +72,11 @@ public interface BeanCollection<E> extends Serializable {
*/
boolean isSkipSave();
/**
* Return true if the collection holds modifications.
*/
boolean holdsModifications();
/**
* Return the bean that owns this collection.
*/
@@ -133,6 +133,20 @@ public final class EntityBeanIntercept implements Serializable {
this.nodeUsageCollector = usageCollector;
}
/**
* Return the ownerId (IdClass).
*/
public Object getOwnerId() {
return ownerId;
}
/**
* Set the ownerId (IdClass).
*/
public void setOwnerId(Object ownerId) {
this.ownerId = ownerId;
}
/**
* Return the owning bean for an embedded bean.
*/
@@ -6,7 +6,6 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import javax.persistence.PersistenceException;
import java.util.Set;
/**
@@ -64,7 +63,7 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
this.ebeanServerName = loader.getName();
this.ownerBean = ownerBean;
this.propertyName = propertyName;
this.readOnly = ownerBean._ebean_getIntercept().isReadOnly();
this.readOnly = ownerBean != null && ownerBean._ebean_getIntercept().isReadOnly();
}
@Override
@@ -96,13 +95,6 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
if (loader == null) {
loader = (BeanCollectionLoader) Ebean.getServer(ebeanServerName);
}
if (loader == null) {
String msg = "Lazy loading but LazyLoadEbeanServer is null?"
+ " The LazyLoadEbeanServer needs to be set after deserialization"
+ " to support lazy loading.";
throw new PersistenceException(msg);
}
loader.loadMany(this, onlyIds);
checkEmptyLazyLoad();
}
@@ -215,7 +207,8 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
/**
* Return true if there are underlying additions or removals.
*/
boolean holdsModifications() {
@Override
public boolean holdsModifications() {
return modifyHolder != null && modifyHolder.hasModifications();
}
@@ -402,9 +402,20 @@ public class DbMigrationConfig {
* You can use placeholders like ${version} or ${timestamp} in properties file.
*/
public String getDdlHeader() {
if (ddlHeader != null && !ddlHeader.isEmpty()) {
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
}
return ddlHeader;
}
/**
* Set the header prepended to the DDL.
*/
public void setDdlHeader(String ddlHeader) {
this.ddlHeader = ddlHeader;
}
/**
* Set migration versions that should have their checksum reset and not run.
* <p>
@@ -475,10 +486,6 @@ public class DbMigrationConfig {
String adminPwd = properties.get("datasource." + serverName + ".adminpassword", dbPassword);
dbPassword = properties.get("migration.dbpassword", adminPwd);
ddlHeader = properties.get("ddl.header", ddlHeader);
if (ddlHeader != null && !ddlHeader.isEmpty()) {
ddlHeader = StringHelper.replaceString(ddlHeader, "${version}", EbeanVersion.getVersion());
ddlHeader = StringHelper.replaceString(ddlHeader, "${timestamp}", ZonedDateTime.now().format( DateTimeFormatter.ISO_INSTANT ));
}
}
/**
+66 -10
View File
@@ -488,6 +488,16 @@ public class ServerConfig {
*/
private boolean defaultOrderById = false;
/**
* The mappingLocations for searching xml mapping.
*/
private List<String> mappingLocations = new ArrayList<>();
/**
* When true we do not need explicit GeneratedValue mapping.
*/
private boolean idGeneratorAutomatic = true;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -1962,7 +1972,7 @@ public class ServerConfig {
* This is the same as serverConfig.getMigrationConfig().setRunMigration(). We have added this method here
* as it is often the only thing we need to configure for migrations.
*/
public void setRunMigration(boolean runMigration){
public void setRunMigration(boolean runMigration) {
migrationConfig.setRunMigration(runMigration);
}
@@ -2767,6 +2777,7 @@ public class ServerConfig {
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
@@ -2780,10 +2791,8 @@ public class ServerConfig {
dbOffline = p.getBoolean("dbOffline", dbOffline);
serverCachePlugin = p.createInstance(ServerCachePlugin.class, "serverCachePlugin", serverCachePlugin);
if (packages != null) {
String packagesProp = p.get("search.packages", p.get("packages", null));
packages = getSearchJarsPackages(packagesProp);
}
String packagesProp = p.get("search.packages", p.get("packages", null));
packages = getSearchList(packagesProp, packages);
collectQueryStatsByNode = p.getBoolean("collectQueryStatsByNode", collectQueryStatsByNode);
collectQueryOrigins = p.getBoolean("collectQueryOrigins", collectQueryOrigins);
@@ -2862,10 +2871,13 @@ public class ServerConfig {
tenantSchemaProvider = p.createInstance(TenantSchemaProvider.class, "tenant.schemaProvider", tenantSchemaProvider);
tenantPartitionColumn = p.get("tenant.partitionColumn", tenantPartitionColumn);
classes = getClasses(p);
String mappingsProp = p.get("mappingLocations", null);
mappingLocations = getSearchList(mappingsProp, mappingLocations);
}
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
NamingConvention nc = properties.createInstance(NamingConvention.class, "namingconvention", null);
NamingConvention nc = properties.createInstance(NamingConvention.class, "namingConvention", null);
return (nc != null) ? nc : namingConvention;
}
@@ -2898,17 +2910,17 @@ public class ServerConfig {
return classes;
}
private List<String> getSearchJarsPackages(String searchPackages) {
private List<String> getSearchList(String searchNames, List<String> defaultValue) {
if (searchPackages != null) {
String[] entries = StringHelper.splitNames(searchPackages);
if (searchNames != null) {
String[] entries = StringHelper.splitNames(searchNames);
List<String> hitList = new ArrayList<>(entries.length);
Collections.addAll(hitList, entries);
return hitList;
} else {
return new ArrayList<>();
return defaultValue;
}
}
@@ -3076,6 +3088,50 @@ public class ServerConfig {
return config;
}
/**
* Add a mapping location to search for xml mapping via class path search.
*/
public void addMappingLocation(String mappingLocation) {
if (mappingLocations == null) {
mappingLocations = new ArrayList<>();
}
mappingLocations.add(mappingLocation);
}
/**
* Return mapping locations to search for xml mapping via class path search.
*/
public List<String> getMappingLocations() {
return mappingLocations;
}
/**
* Set mapping locations to search for xml mapping via class path search.
* <p>
* This is only used if classes have not been explicitly specified.
* </p>
*/
public void setMappingLocations(List<String> mappingLocations) {
this.mappingLocations = mappingLocations;
}
/**
* When false we need explicit <code>@GeneratedValue</code> mapping to assign
* Identity or Sequence generated values. When true Id properties are automatically
* assigned Identity or Sequence without the GeneratedValue mapping.
*/
public boolean isIdGeneratorAutomatic() {
return idGeneratorAutomatic;
}
/**
* Set to false such that Id properties require explicit <code>@GeneratedValue</code>
* mapping before they are assigned Identity or Sequence generation based on platform.
*/
public void setIdGeneratorAutomatic(boolean idGeneratorAutomatic) {
this.idGeneratorAutomatic = idGeneratorAutomatic;
}
public enum UuidVersion {
VERSION4,
VERSION1,
@@ -112,39 +112,7 @@ public class UnderscoreNamingConvention extends AbstractNamingConvention {
* Convert and return the string to underscore from camel case.
*/
protected String toUnderscoreFromCamel(String camelCase) {
int lastUpper = -1;
StringBuilder sb = new StringBuilder(camelCase.length() + 4);
for (int i = 0; i < camelCase.length(); i++) {
char c = camelCase.charAt(i);
if ('_' == c) {
// Underscores should just be passed through
sb.append(c);
lastUpper = i;
} else if (Character.isDigit(c)) {
if (i > lastUpper + 1 && !digitsCompressed) {
sb.append("_");
}
sb.append(c);
lastUpper = i;
} else if (Character.isUpperCase(c)) {
if (i > lastUpper + 1) {
sb.append("_");
}
sb.append(Character.toLowerCase(c));
lastUpper = i;
} else {
sb.append(c);
}
}
String ret = sb.toString();
if (forceUpperCase) {
ret = ret.toUpperCase();
}
return ret;
return CamelCaseHelper.toUnderscoreFromCamel(camelCase, digitsCompressed, forceUpperCase);
}
/**
@@ -37,7 +37,7 @@ class LoadContext {
InputStream is = null;
if (source == Loader.Source.RESOURCE) {
is = getClass().getResourceAsStream("/" + resourcePath);
is = resourceStream(resourcePath);
if (is != null) {
loadedResources.add(resourcePath);
}
@@ -56,6 +56,15 @@ class LoadContext {
return is;
}
private InputStream resourceStream(String resourcePath) {
InputStream is = getClass().getResourceAsStream("/" + resourcePath);
if (is == null) {
// search the module path for top level resource
is = ClassLoader.getSystemResourceAsStream(resourcePath);
}
return is;
}
/**
* Add a property entry.
*/
@@ -93,6 +93,38 @@ public interface DbMigration {
*/
void setStrictMode(boolean strictMode);
/**
* Set to true to include a generated header comment in the DDL script.
*/
void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment);
/**
* Set the header that is included in the generated DDL script.
*/
void setHeader(String header);
/**
* Set the prefix for the version. Set this to "V" for use with Flyway.
*/
void setApplyPrefix(String applyPrefix);
/**
* Set the version of the migration to be generated.
*/
void setVersion(String version);
/**
* Set the name of the migration to be generated.
*/
void setName(String name);
/**
* Generate a migration for the version specified that contains pending drops.
*
* @param generatePendingDrop The version of a prior migration that holds pending drops.
*/
void setGeneratePendingDrop(String generatePendingDrop);
/**
* Add an additional platform to write the migration DDL.
* <p>
@@ -0,0 +1,38 @@
package io.ebean.event;
import io.ebeaninternal.server.lib.ShutdownManager;
import javax.servlet.ServletContextEvent;
/**
* Listens for webserver server starting and stopping events.
* <p>
* This should be used when the deployment is into a servlet container where the webapp
* can be shutdown or redeployed without the JVM stopping.
* </p>
* <p>
* If deployment is into a container where the JVM is completely shutdown (like spring boot,
* runnable war or when using a servlet container that only contains the single webapp and
* the JVM is shutdown then this isn't required. Instead we can just rely on the JVM shutdown
* hook that Ebean registers.
* </p>
*/
public class ServletContextListener implements javax.servlet.ServletContextListener {
/**
* The servlet container is stopping.
*/
@Override
public void contextDestroyed(ServletContextEvent event) {
ShutdownManager.shutdown();
}
/**
* Do nothing on startup.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
}
}
@@ -89,8 +89,7 @@ public class PathProperties implements FetchPath {
}
Props getProps(String path) {
Props props = pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
return props;
return pathMap.computeIfAbsent(path, p -> new Props(this, null, p));
}
public Collection<Props> getPathProps() {
@@ -2,6 +2,52 @@ package io.ebean.util;
public class CamelCaseHelper {
/**
* To underscore from camel case using digits compressed true and force upper case false.
*/
public static String toUnderscoreFromCamel(String camelCase) {
return toUnderscoreFromCamel(camelCase, true, false);
}
/**
* Convert and return the string to underscore from camel case.
*/
public static String toUnderscoreFromCamel(String camelCase, boolean digitsCompressed, boolean forceUpperCase) {
int lastUpper = -1;
StringBuilder sb = new StringBuilder(camelCase.length() + 4);
for (int i = 0; i < camelCase.length(); i++) {
char c = camelCase.charAt(i);
if ('_' == c) {
// Underscores should just be passed through
sb.append(c);
lastUpper = i;
} else if (Character.isDigit(c)) {
if (i > lastUpper + 1 && !digitsCompressed) {
sb.append("_");
}
sb.append(c);
lastUpper = i;
} else if (Character.isUpperCase(c)) {
if (i > lastUpper + 1) {
sb.append("_");
}
sb.append(Character.toLowerCase(c));
lastUpper = i;
} else {
sb.append(c);
}
}
String ret = sb.toString();
if (forceUpperCase) {
ret = ret.toUpperCase();
}
return ret;
}
/**
* To camel from underscore.
*
@@ -1,6 +1,5 @@
package io.ebeaninternal.api;
import io.ebean.EbeanServer;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.util.StringHelper;
@@ -124,11 +123,11 @@ public class LoadManyRequest extends LoadRequest {
return loadContext.getBeanProperty();
}
public SpiQuery<?> createQuery(EbeanServer server, int batchSize) {
public SpiQuery<?> createQuery(SpiEbeanServer server, int batchSize) {
BeanPropertyAssocMany<?> many = getMany();
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(many.getTargetType());
SpiQuery<?> query = many.newQuery(server);
String orderBy = many.getLazyFetchOrderBy();
if (orderBy != null) {
query.orderBy(orderBy);
@@ -1,8 +1,8 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.core.DbExpressionHandler;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import java.util.List;
@@ -1,9 +1,11 @@
package io.ebeaninternal.api;
import com.fasterxml.jackson.core.JsonGenerator;
import io.ebean.plugin.BeanType;
import io.ebean.text.json.JsonContext;
import io.ebean.text.json.JsonWriteOptions;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.Writer;
@@ -21,4 +23,10 @@ public interface SpiJsonContext extends JsonContext {
* Create a Json Writer for writing beans as JSON supplying a writer.
*/
SpiJsonWriter createJsonWriter(Writer writer);
/**
* Create a Json Reader for reading the JSON content.
*/
SpiJsonReader createJsonRead(BeanType<?> beanType, String json);
}
@@ -23,4 +23,9 @@ public interface SpiSqlUpdate extends SqlUpdate {
* Set the generated key value.
*/
void setGeneratedKey(Object idValue);
/**
* Reset bind position to be ready for another bind execute.
*/
void reset();
}
@@ -319,4 +319,9 @@ public interface SpiTransaction extends Transaction {
* Return the profile location for this transaction.
*/
ProfileLocation getProfileLocation();
/**
* Return true when nested transactions should create Savepoints.
*/
boolean isNestedUseSavepoint();
}
@@ -21,7 +21,7 @@ import java.sql.SQLException;
/**
* Proxy for an underlying SpiTransaction (most of the API).
*/
abstract class SpiTransactionProxy implements SpiTransaction {
public abstract class SpiTransactionProxy implements SpiTransaction {
protected SpiTransaction transaction;
@@ -50,6 +50,16 @@ abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.isRollbackOnly();
}
@Override
public void setNestedUseSavepoint() {
transaction.setNestedUseSavepoint();
}
@Override
public boolean isNestedUseSavepoint() {
return transaction.isNestedUseSavepoint();
}
@Override
public long profileOffset() {
return transaction.profileOffset();
@@ -0,0 +1,36 @@
package io.ebeaninternal.api.json;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.io.IOException;
import java.util.Map;
public interface SpiJsonReader {
PersistenceContext getPersistenceContext();
SpiJsonReader forJson(JsonParser moreJson, boolean resetContext);
<T> void persistenceContextPut(Object beanId, T currentBean);
Object persistenceContextPutIfAbsent(Object id, EntityBean bean, BeanDescriptor<?> beanDesc);
ObjectMapper getObjectMapper();
JsonParser getParser();
JsonToken nextToken() throws IOException;
void pushPath(String path);
void popPath();
void beanVisitor(Object bean, Map<String, Object> unmappedProperties);
Object readValueUsingObjectMapper(Class<?> propertyType) throws IOException;
}
@@ -1,9 +1,10 @@
package io.ebeaninternal.server.text.json;
package io.ebeaninternal.api.json;
import io.ebean.bean.EntityBean;
import io.ebean.text.json.JsonWriter;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.io.IOException;
import java.util.Collection;
/**
@@ -11,6 +12,11 @@ import java.util.Collection;
*/
public interface SpiJsonWriter extends JsonWriter {
/**
* Flush the buffer.
*/
void flush() throws IOException;
/**
* Return true if the value is a parent bean.
*/
@@ -60,7 +66,7 @@ public interface SpiJsonWriter extends JsonWriter {
* Write value using underlying Jaskson object mapper if available.
*/
void writeValueUsingObjectMapper(String name, Object value);
/**
* Write the bean properties.
*/
@@ -91,6 +91,12 @@ public class DefaultDbMigration implements DbMigration {
protected DbConstraintNaming constraintNaming;
protected Boolean strictMode;
protected Boolean includeGeneratedFileComment;
protected String header;
protected String applyPrefix;
protected String version;
protected String name;
protected String generatePendingDrop;
/**
* Create for offline migration generation.
@@ -148,6 +154,36 @@ public class DefaultDbMigration implements DbMigration {
this.strictMode = strictMode;
}
@Override
public void setApplyPrefix(String applyPrefix) {
this.applyPrefix = applyPrefix;
}
@Override
public void setVersion(String version) {
this.version = version;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setGeneratePendingDrop(String generatePendingDrop) {
this.generatePendingDrop = generatePendingDrop;
}
@Override
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
this.includeGeneratedFileComment = includeGeneratedFileComment;
}
@Override
public void setHeader(String header) {
this.header = header;
}
/**
* Set the specific platform to generate DDL for.
* <p>
@@ -512,8 +548,28 @@ public class DefaultDbMigration implements DbMigration {
databasePlatform = server.getDatabasePlatform();
logger.debug("set platform to {}", databasePlatform.getName());
}
if (strictMode != null && migrationConfig != null) {
migrationConfig.setStrictMode(strictMode);
if (migrationConfig != null) {
if (strictMode != null) {
migrationConfig.setStrictMode(strictMode);
}
if (applyPrefix != null) {
migrationConfig.setApplyPrefix(applyPrefix);
}
if (header != null) {
migrationConfig.setDdlHeader(header);
}
if (includeGeneratedFileComment != null) {
migrationConfig.setIncludeGeneratedFileComment(includeGeneratedFileComment);
}
if (version != null) {
migrationConfig.setVersion(version);
}
if (name != null) {
migrationConfig.setName(name);
}
if (generatePendingDrop != null) {
migrationConfig.setGeneratePendingDrop(generatePendingDrop);
}
}
}
@@ -18,7 +18,9 @@ public class DdlWrite {
private final DdlBuffer applyForeignKeys;
private final DdlBuffer applyHistory;
private final DdlBuffer applyHistoryView;
private final DdlBuffer applyHistoryTrigger;
private final DdlBuffer dropAllForeignKeys;
@@ -39,7 +41,8 @@ public class DdlWrite {
this.applyDropDependencies = new BaseDdlBuffer(configuration);
this.apply = new BaseDdlBuffer(configuration);
this.applyForeignKeys = new BaseDdlBuffer(configuration);
this.applyHistory = new BaseDdlBuffer(configuration);
this.applyHistoryView = new BaseDdlBuffer(configuration);
this.applyHistoryTrigger = new BaseDdlBuffer(configuration);
this.dropAllForeignKeys = new BaseDdlBuffer(configuration);
this.dropAll = new BaseDdlBuffer(configuration);
}
@@ -61,7 +64,8 @@ public class DdlWrite {
public boolean isApplyEmpty() {
return apply.getBuffer().isEmpty()
&& applyForeignKeys.getBuffer().isEmpty()
&& applyHistory.getBuffer().isEmpty()
&& applyHistoryView.getBuffer().isEmpty()
&& applyHistoryTrigger.getBuffer().isEmpty()
&& applyDropDependencies.getBuffer().isEmpty();
}
@@ -90,10 +94,17 @@ public class DdlWrite {
}
/**
* Return the buffer that apply history DDL is written to.
* Return the buffer that apply history-view DDL is written to.
*/
public DdlBuffer applyHistory() {
return applyHistory;
public DdlBuffer applyHistoryView() {
return applyHistoryView;
}
/**
* Return the buffer that apply history-trigger DDL is written to.
*/
public DdlBuffer applyHistoryTrigger() {
return applyHistoryTrigger;
}
/**
@@ -42,6 +42,12 @@ import java.util.Map;
*/
public class BaseTableDdl implements TableDdl {
enum HistorySupport {
NONE,
SQL2011,
TRIGGER_BASED
}
protected final DbConstraintNaming naming;
protected final NamingConvention namingConvention;
@@ -78,22 +84,23 @@ public class BaseTableDdl implements TableDdl {
private boolean strictMode;
private final boolean sql2011History;
private final HistorySupport historySupport;
/**
* Helper class that is used to execute the migration ddl before and after the migration action.
*/
private class DdlMigrationHelp {
private List<String> before;
private List<String> after;
private String tableName;
private String columnName;
private String defaultValue;
private final List<String> before;
private final List<String> after;
private final String tableName;
private final String columnName;
private final String defaultValue;
private final boolean withHistory;
/**
* Constructor for DdlMigrationHelp when adding a NEW column.
*/
DdlMigrationHelp(String tableName, Column column) {
DdlMigrationHelp(String tableName, Column column, boolean withHistory) {
this.tableName = tableName;
this.columnName = column.getName();
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
@@ -105,6 +112,7 @@ public class BaseTableDdl implements TableDdl {
before = getScriptsForPlatform(column.getBefore(), platformDdl.getPlatform().getName());
after = getScriptsForPlatform(column.getAfter(), platformDdl.getPlatform().getName());
this.withHistory = withHistory;
}
/**
@@ -119,6 +127,7 @@ public class BaseTableDdl implements TableDdl {
boolean alterNotNull = Boolean.TRUE.equals(alter.isNotnull());
// here we add the platform's default update script
withHistory = isTrue(alter.isWithHistory());
if (alter.getBefore().isEmpty() && alterNotNull) {
if (defaultValue == null) {
handleStrictError(tableName, columnName);
@@ -134,6 +143,10 @@ public class BaseTableDdl implements TableDdl {
if (!before.isEmpty()) {
buffer.end();
}
if (!before.isEmpty() && withHistory) {
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
}
for (String ddlScript : before) {
buffer.append(translate(ddlScript, tableName, columnName, this.defaultValue));
buffer.endOfStatement();
@@ -141,6 +154,9 @@ public class BaseTableDdl implements TableDdl {
}
void writeAfter(DdlBuffer buffer) throws IOException {
if (!after.isEmpty() && withHistory) {
buffer.append("-- NOTE: table has @History - special migration may be necessary").newLine();
}
// here we run post migration scripts
for (String ddlScript : after) {
buffer.append(translate(ddlScript, tableName, columnName, defaultValue));
@@ -198,7 +214,11 @@ public class BaseTableDdl implements TableDdl {
this.platformDdl.configure(serverConfig);
this.strictMode = serverConfig.getMigrationConfig().isStrictMode();
DbHistorySupport hist = platformDdl.getPlatform().getHistorySupport();
this.sql2011History = hist != null && hist.isStandardsBased();
if (hist == null) {
this.historySupport = HistorySupport.NONE;
} else {
this.historySupport = hist.isStandardsBased() ? HistorySupport.SQL2011 : HistorySupport.TRIGGER_BASED;
}
}
/**
@@ -700,8 +720,14 @@ public class BaseTableDdl implements TableDdl {
*/
@Override
public void generateEpilog(DdlWrite write) throws IOException {
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
platformDdl.regenerateHistoryTriggers(write, update);
if (!regenerateHistoryTriggers.isEmpty()) {
platformDdl.lockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
for (HistoryTableUpdate update : this.regenerateHistoryTriggers.values()) {
platformDdl.regenerateHistoryTriggers(write, update);
}
platformDdl.unlockTables(write.applyHistoryTrigger(), regenerateHistoryTriggers.keySet());
}
platformDdl.generateEpilog(write);
}
@@ -722,15 +748,15 @@ public class BaseTableDdl implements TableDdl {
String tableName = addColumn.getTableName();
List<Column> columns = addColumn.getColumn();
for (Column column : columns) {
alterTableAddColumn(writer.apply(), tableName, column, false);
alterTableAddColumn(writer.apply(), tableName, column, false, isTrue(addColumn.isWithHistory()));
}
if (isTrue(addColumn.isWithHistory()) && !sql2011History) {
if (isTrue(addColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
// make same changes to the history table
String historyTable = historyTable(tableName);
for (Column column : columns) {
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.ADD, column.getName());
alterTableAddColumn(writer.apply(), historyTable, column, true);
alterTableAddColumn(writer.apply(), historyTable, column, true, true);
}
}
@@ -771,7 +797,8 @@ public class BaseTableDdl implements TableDdl {
String tableName = dropColumn.getTableName();
alterTableDropColumn(writer.apply(), tableName, dropColumn.getColumnName());
if (isTrue(dropColumn.isWithHistory()) && !sql2011History) {
if (isTrue(dropColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
// also drop from the history table
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName());
alterTableDropColumn(writer.apply(), historyTable(tableName), dropColumn.getColumnName());
@@ -872,8 +899,9 @@ public class BaseTableDdl implements TableDdl {
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
if (isTrue(alter.isWithHistory()) && alter.getType() != null && !sql2011History) {
if (isTrue(alter.isWithHistory()) && alter.getType() != null && historySupport == historySupport.TRIGGER_BASED) {
// mysql and sql server column type change allowing nulls in the history table column
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
AlterColumn alterHistoryColumn = new AlterColumn();
alterHistoryColumn.setTableName(historyTable(alter.getTableName()));
alterHistoryColumn.setColumnName(alter.getColumnName());
@@ -923,7 +951,8 @@ public class BaseTableDdl implements TableDdl {
String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType());
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
if (isTrue(alter.isWithHistory()) && !sql2011History) {
if (isTrue(alter.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
regenerateHistoryTriggers(alter.getTableName(), HistoryTableUpdate.Change.ALTER, alter.getColumnName());
// apply same type change to matching column in the history table
ddl = platformDdl.alterColumnType(historyTable(alter.getTableName()), alter.getColumnName(), alter.getType());
writer.apply().append(ddl).endOfStatement();
@@ -979,8 +1008,8 @@ public class BaseTableDdl implements TableDdl {
platformDdl.alterTableDropColumn(buffer, tableName, columnName);
}
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable) throws IOException {
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column);
protected void alterTableAddColumn(DdlBuffer buffer, String tableName, Column column, boolean onHistoryTable, boolean withHistory) throws IOException {
DdlMigrationHelp help = new DdlMigrationHelp(tableName, column, withHistory);
if (!onHistoryTable) {
help.writeBefore(buffer);
}
@@ -69,7 +69,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
*/
protected void updateTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
writer.applyHistory().append("-- changes: ").append(update.description()).newLine();
writer.applyHistoryTrigger().append("-- changes: ").append(update.description()).newLine();
updateHistoryTriggers(createDbTriggerUpdate(writer, table));
}
@@ -154,7 +154,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
String baseTableName = table.getName();
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryView();
addSysPeriodColumns(apply, baseTableName, whenCreatedColumn);
createHistoryTable(apply, table);
@@ -210,19 +210,19 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
.endOfStatement().end();
}
/**
* Create or replace the with_history view with explicit columns.
*/
protected void createWithHistoryView(DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
apply.append("create or replace view ").append(update.getBaseTable()).append(viewSuffix).append(" as select ");
appendColumnNames(apply, update.getColumns(), "");
appendSysPeriodColumns(apply, ", ");
apply.append(" from ").append(update.getBaseTable()).append(" union all select ");
appendColumnNames(apply, update.getColumns(), "");
appendSysPeriodColumns(apply, ", ");
apply.append(" from ").append(update.getHistoryTable()).endOfStatement().end();
/**
* For postgres/h2/mysql we need to drop and recreate the view. Well, we could add columns to the end of the view
* but otherwise we need to drop and create it.
*/
protected void recreateHistoryView(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.dropDependencyBuffer();
// we need to drop the view early/first before any changes to the tables etc
buffer.append("drop view if exists ").append(update.getBaseTable()).append(viewSuffix).endOfStatement();
// recreate the view after all ddl modifications - the view requires ALL columns, also the historyExclude ones.
createWithHistoryView(update.historyViewBuffer(), update.getBaseTable());
}
protected void appendSysPeriodColumns(DdlBuffer apply, String prefix) throws IOException {
@@ -28,10 +28,18 @@ public class DbTriggerUpdate {
/**
* Return the appropriate buffer for the current mode.
*/
public DdlBuffer historyBuffer() {
return writer.applyHistory();
public DdlBuffer historyViewBuffer() {
return writer.applyHistoryView();
}
/**
* Return the appropriate buffer for the current mode.
*/
public DdlBuffer historyTriggerBuffer() {
return writer.applyHistoryTrigger();
}
/**
* Return the appropriate drop dependency buffer for the current mode.
*/
@@ -27,7 +27,7 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
String baseTableName = table.getName();
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryTrigger();
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName);
}
@@ -35,7 +35,9 @@ public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
@Override
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.historyBuffer();
recreateHistoryView(update);
DdlBuffer buffer = update.historyTriggerBuffer();
dropTriggers(buffer, update.getBaseTable());
addCreateTrigger(buffer, updateTriggerName(update.getBaseTable()), update.getBaseTable());
}
@@ -17,7 +17,8 @@ public class HistoryTableUpdate {
ADD,
DROP,
INCLUDE,
EXCLUDE
EXCLUDE,
ALTER
}
private static class Column {
@@ -7,6 +7,7 @@ import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.Column;
import java.io.IOException;
import java.util.Collection;
/**
* MySql specific DDL.
@@ -113,10 +114,39 @@ public class MySqlDdl extends PlatformDdl {
}
apply.append(String.format("alter table %s comment = '%s'", tableName, tableComment)).endOfStatement();
}
@Override
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) throws IOException {
// alter comment currently not supported as it requires to repeat whole column definition
}
/**
* Locks all tables for triggers that have to be updated.
*/
@Override
public void lockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
if (!tables.isEmpty()) {
buffer.append("lock tables ");
int i = 0;
for (String table : tables) {
if (i > 0) {
buffer.append(", ");
}
buffer.append(table).append(" write");
i++;
}
buffer.endOfStatement();
}
}
/**
* Unlocks all tables for triggers that have to be updated.
*/
@Override
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
buffer.append("unlock tables").endOfStatement();
}
}
@@ -34,19 +34,19 @@ public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
@Override
protected void updateHistoryTriggers(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.historyBuffer();
recreateHistoryView(update);
DdlBuffer buffer = update.historyTriggerBuffer();
String baseTable = update.getBaseTable();
buffer.append("lock tables ").append(baseTable).append(" write").endOfStatement();
dropTriggers(buffer, baseTable);
addBeforeUpdate(updateTriggerName(baseTable), update);
addBeforeDelete(deleteTriggerName(baseTable), update);
buffer.append("unlock tables").endOfStatement();
}
private void addBeforeUpdate(String triggerName, DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
DdlBuffer apply = update.historyTriggerBuffer();
apply
.append("delimiter $$").newLine()
.append("create trigger ").append(triggerName).append(" before update on ").append(update.getBaseTable())
@@ -59,7 +59,7 @@ public class MySqlHistoryDdl extends DbTriggerBasedHistoryDdl {
private void addBeforeDelete(String triggerName, DbTriggerUpdate update) throws IOException {
DdlBuffer apply = update.historyBuffer();
DdlBuffer apply = update.historyTriggerBuffer();
apply
.append("delimiter $$").newLine()
.append("create trigger ").append(triggerName).append(" before delete on ").append(update.getBaseTable())
@@ -20,10 +20,12 @@ import io.ebeaninternal.dbmigration.migration.Column;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.migration.IdentityType;
import io.ebeaninternal.dbmigration.model.MTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
/**
@@ -442,13 +444,16 @@ public class PlatformDdl {
.append(" ").append(addColumn).append(" ").append(column.getName())
.append(" ").append(convertedType);
if (!onHistoryTable) {
if (defaultValue != null) {
// Add default value also to history table if it is not excluded
if (defaultValue != null) {
if (!onHistoryTable || !isTrue(column.isHistoryExclude())) {
buffer.append(" default ");
buffer.append(defaultValue);
}
}
if (!onHistoryTable) {
if (isTrue(column.isNotnull())) {
buffer.append(" not null");
}
@@ -456,7 +461,8 @@ public class PlatformDdl {
// check constraints cannot be added in one statement for h2
if (!StringHelper.isNull(column.getCheckConstraint())) {
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(), column.getCheckConstraint());
String ddl = alterTableAddCheckConstraint(tableName, column.getCheckConstraintName(),
column.getCheckConstraint());
buffer.append(ddl).endOfStatement();
}
} else {
@@ -641,4 +647,18 @@ public class PlatformDdl {
}
return name;
}
/**
* Mysql-specific: Locks all tables for triggers that have to be updated.
*/
public void lockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
}
/**
* Mysql-specific: Unlocks all tables for triggers that have to be updated.
*/
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
}
}
@@ -62,7 +62,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
String procedureName = procedureName(baseTableName);
String triggerName = triggerName(baseTableName);
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryTrigger();
apply
.append("create trigger ").append(triggerName).newLine()
.append(" before update or delete on ").append(baseTableName).newLine()
@@ -107,7 +107,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
String historyTable = historyTableName(table.getName());
List<String> columnNames = columnNamesForApply(table);
createOrReplaceFunction(writer.applyHistory(), procedureName, historyTable, columnNames);
createOrReplaceFunction(writer.applyHistoryTrigger(), procedureName, historyTable, columnNames);
}
@Override
@@ -116,21 +116,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
String procedureName = procedureName(update.getBaseTable());
recreateHistoryView(update);
createOrReplaceFunction(update.historyBuffer(), procedureName, update.getHistoryTable(), update.getColumns());
}
/**
* For postgres we need to drop and recreate the view. Well, we could add columns to the end of the view
* but otherwise we need to drop and create it.
*/
private void recreateHistoryView(DbTriggerUpdate update) throws IOException {
DdlBuffer buffer = update.dropDependencyBuffer();
// we need to drop the view early/first before any changes to the tables etc
buffer.append("drop view if exists ").append(update.getBaseTable()).append(viewSuffix).endOfStatement();
// recreate the view with specific columns specified (the columns generally are not dropped until later)
createWithHistoryView(update);
createOrReplaceFunction(update.historyTriggerBuffer(), procedureName, update.getHistoryTable(), update.getColumns());
}
@Override
@@ -31,19 +31,24 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
enableSystemVersioning(writer, baseTable);
}
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
DdlBuffer apply = writer.applyHistory();
apply.append("alter table ").append(baseTable).newLine()
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
.append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
private String getHistoryTable(String baseTable) {
String historyTable = baseTable + "_history"; // history must contain schema, otherwise you'll get
// Setting SYSTEM_VERSIONING to ON failed because history table 'xxx_history' is not specified in two-part name format.
if (historyTable.indexOf('.') == -1) {
historyTable = "dbo." +historyTable; // so add the default schema, if none was specified.
}
apply.append("alter table ").append(baseTable).append(" set (system_versioning = on (history_table=").append(historyTable).append("))").endOfStatement();
return historyTable;
}
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
DdlBuffer apply = writer.applyHistoryView();
apply.append("alter table ").append(baseTable).newLine()
.append(" add ").append(systemPeriodStart).append(" datetime2 GENERATED ALWAYS AS ROW START NOT NULL DEFAULT SYSUTCDATETIME(),").newLine()
.append(" ").append(systemPeriodEnd).append(" datetime2 GENERATED ALWAYS AS ROW END NOT NULL DEFAULT '9999-12-31T23:59:59.9999999',").newLine()
.append("period for system_time (").append(systemPeriodStart).append(", ").append(systemPeriodEnd).append(")").endOfStatement();
apply.append("alter table ").append(baseTable).append(" set (system_versioning = on (history_table=")
.append(getHistoryTable(baseTable)).append("))").endOfStatement();
DdlBuffer drop = writer.dropAll();
drop.append("IF OBJECT_ID('").append(baseTable).append("', 'U') IS NOT NULL alter table ").append(baseTable).append(" set (system_versioning = off)").endOfStatement();
@@ -53,7 +58,7 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
@Override
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
String baseTable = dropHistoryTable.getBaseTable();
DdlBuffer apply = writer.applyHistory();
DdlBuffer apply = writer.applyHistoryView();
apply.append("-- dropping history support for ").append(baseTable).endOfStatement();
// drop default constraints
@@ -76,7 +81,15 @@ public class SqlServerHistoryDdl implements PlatformHistoryDdl {
}
@Override
public void updateTriggers(DdlWrite write, HistoryTableUpdate baseTable) throws IOException {
public void updateTriggers(DdlWrite writer, HistoryTableUpdate baseTable) throws IOException {
// SQL Server 2016 does not need triggers
DdlBuffer apply = writer.applyHistoryView();
String baseTableName = baseTable.getBaseTable();
apply.append("-- alter table ").append(baseTableName).append(" set (system_versioning = off (history_table=")
.append(getHistoryTable(baseTableName)).append("))").endOfStatement();
apply.append("-- history migration goes here").newLine();
apply.append("-- alter table ").append(baseTableName).append(" set (system_versioning = on (history_table=")
.append(getHistoryTable(baseTableName)).append("))").endOfStatement();
}
}
@@ -29,7 +29,6 @@ public class MigrationXmlWriter {
try (FileWriter writer = new FileWriter(file)) {
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
writer.write("<!DOCTYPE xml>\n");
if (comment != null) {
writer.write("<!-- ");
writer.write(comment);
@@ -113,7 +113,8 @@ public class CurrentModel {
}
ddl.append(write.apply().getBuffer());
ddl.append(write.applyForeignKeys().getBuffer());
ddl.append(write.applyHistory().getBuffer());
ddl.append(write.applyHistoryView().getBuffer());
ddl.append(write.applyHistoryTrigger().getBuffer());
return ddl.toString();
}
@@ -527,5 +527,8 @@ public class MColumn {
if (hasValue(alterColumn.getForeignKeyOnUpdate())) {
fkeyOnUpdate = fkeyMode(alterColumn.getForeignKeyOnUpdate());
}
if (hasValue(alterColumn.isHistoryExclude())) {
historyExclude = alterColumn.isHistoryExclude();
}
}
}
@@ -621,7 +621,7 @@ public class MTable {
DropColumn dropColumn = new DropColumn();
dropColumn.setTableName(name);
dropColumn.setColumnName(existingColumn.getName());
if (withHistory && !existingColumn.isHistoryExclude()) {
if (withHistory) {
// These dropColumns should occur on the history
// table as well as the base table
dropColumn.setWithHistory(Boolean.TRUE);
@@ -97,7 +97,8 @@ public class PlatformDdlWriter {
writer.append("-- apply changes\n");
writer.append(write.apply().getBuffer());
writer.append(write.applyForeignKeys().getBuffer());
writer.append(write.applyHistory().getBuffer());
writer.append(write.applyHistoryView().getBuffer());
writer.append(write.applyHistoryTrigger().getBuffer());
}
private void prependDropDependencies(Writer writer, DdlBuffer buffer) throws IOException {
@@ -4,13 +4,18 @@ import io.ebean.config.DbConstraintNaming;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbPlatformTypeMapping;
import io.ebeaninternal.dbmigration.model.MColumn;
import io.ebeaninternal.dbmigration.model.MCompoundForeignKey;
import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.dbmigration.model.ModelContainer;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.TableJoinColumn;
import io.ebeaninternal.server.type.ScalarType;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The context used during DDL generation.
@@ -187,4 +192,49 @@ public class ModelBuildContext {
addTable(draftTable);
}
/**
* Return a builder to add foreign keys.
*/
public FkeyBuilder fkeyBuilder(MTable destTable) {
return new FkeyBuilder(this, destTable);
}
public static class FkeyBuilder {
private final AtomicInteger count = new AtomicInteger();
private final ModelBuildContext ctx;
private final MTable destTable;
private final String tableName;
FkeyBuilder(ModelBuildContext ctx, MTable destTable) {
this.ctx = ctx;
this.destTable = destTable;
this.tableName = destTable.getName();
}
/**
* Add a foreign key based on the table join.
*/
public FkeyBuilder addForeignKey(BeanDescriptor<?> desc, TableJoin tableJoin, boolean direction) {
String baseTable = ctx.normaliseTable(desc.getBaseTable());
String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, count.incrementAndGet());
String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, count.get());
MCompoundForeignKey foreignKey = new MCompoundForeignKey(fkName, desc.getBaseTable(), fkIndex);
for (TableJoinColumn column : tableJoin.columns()) {
String localCol = direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
String refCol = !direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
foreignKey.addColumnPair(localCol, refCol);
}
destTable.addForeignKey(foreignKey);
return this;
}
}
}
@@ -0,0 +1,34 @@
package io.ebeaninternal.dbmigration.model.build;
import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanTable;
/**
* Add the element collection table to the model.
*/
public class ModelBuildElementTable {
/**
* Build and add the MTable model for the ElementCollection property.
*/
public static void build(ModelBuildContext ctx, BeanPropertyAssocMany<?> manyProp) {
BeanTable beanTable = manyProp.getBeanTable();
BeanDescriptor<?> targetDescriptor = manyProp.getTargetDescriptor();
MTable table = new MTable(beanTable.getBaseTable());
VisitAllUsing.visitOne(targetDescriptor, new ModelBuildPropertyVisitor(ctx, table, targetDescriptor));
ctx.fkeyBuilder(table)
.addForeignKey(manyProp.getBeanDescriptor(), manyProp.getTableJoin(), true);
ctx.addTable(table);
}
}
@@ -1,7 +1,6 @@
package io.ebeaninternal.dbmigration.model.build;
import io.ebeaninternal.dbmigration.model.MColumn;
import io.ebeaninternal.dbmigration.model.MCompoundForeignKey;
import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
@@ -12,7 +11,7 @@ import io.ebeaninternal.server.deploy.TableJoinColumn;
/**
* Add the intersection table to the model.
*/
public class ModelBuildIntersectionTable {
class ModelBuildIntersectionTable {
private final ModelBuildContext ctx;
@@ -22,9 +21,7 @@ public class ModelBuildIntersectionTable {
private MTable intersectionTable;
private int countForeignKey;
public ModelBuildIntersectionTable(ModelBuildContext ctx, BeanPropertyAssocMany<?> manyProp) {
ModelBuildIntersectionTable(ModelBuildContext ctx, BeanPropertyAssocMany<?> manyProp) {
this.ctx = ctx;
this.manyProp = manyProp;
this.intersectionTableJoin = manyProp.getIntersectionTableJoin();
@@ -51,33 +48,13 @@ public class ModelBuildIntersectionTable {
private void buildFkConstraints() {
BeanDescriptor<?> localDesc = manyProp.getBeanDescriptor();
buildFkConstraints(localDesc, intersectionTableJoin.columns(), true);
BeanDescriptor<?> targetDesc = manyProp.getTargetDescriptor();
buildFkConstraints(targetDesc, tableJoin.columns(), false);
ctx.fkeyBuilder(intersectionTable)
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
intersectionTable.checkDuplicateForeignKeys();
}
private void buildFkConstraints(BeanDescriptor<?> desc, TableJoinColumn[] columns, boolean direction) {
String tableName = intersectionTableJoin.getTable();
String baseTable = ctx.normaliseTable(desc.getBaseTable());
String fkName = ctx.foreignKeyConstraintName(tableName, baseTable, ++countForeignKey);
String fkIndex = ctx.foreignKeyIndexName(tableName, baseTable, countForeignKey);
MCompoundForeignKey foreignKey = new MCompoundForeignKey(fkName, desc.getBaseTable(), fkIndex);
intersectionTable.addForeignKey(foreignKey);
for (TableJoinColumn column : columns) {
String localCol = direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
String refCol = !direction ? column.getForeignDbColumn() : column.getLocalDbColumn();
foreignKey.addColumnPair(localCol, refCol);
}
}
private MTable createTable() {
BeanDescriptor<?> localDesc = manyProp.getBeanDescriptor();
@@ -123,27 +123,24 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
@Override
public void visitMany(BeanPropertyAssocMany<?> p) {
if (p.hasJoinTable()) {
if (p.getMappedBy() == null) {
// only create on other 'owning' side
if (p.hasJoinTable() && p.getMappedBy() == null) {
// only create on other 'owning' side
//TableJoin intersectionTableJoin = p.getIntersectionTableJoin();
// check if the intersection table has already been created
// build the create table and fkey constraints
// putting the DDL into ctx for later output as we are
// in the middle of rendering the create table DDL
MTable intersectionTable = new ModelBuildIntersectionTable(ctx, p).build();
if (p.isO2mJoinTable()) {
intersectionTable.clearForeignKeyIndexes();
Collection<MColumn> cols = intersectionTable.allColumns();
if (cols.size() == 2) {
// always the second column that we put the unique constraint on
MColumn col = new ArrayList<>(cols).get(1);
col.setUnique(determineUniqueConstraintName(col.getName()));
}
// build the create table and fkey constraints
// putting the DDL into ctx for later output as we are
// in the middle of rendering the create table DDL
MTable intersectionTable = new ModelBuildIntersectionTable(ctx, p).build();
if (p.isO2mJoinTable()) {
intersectionTable.clearForeignKeyIndexes();
Collection<MColumn> cols = intersectionTable.allColumns();
if (cols.size() == 2) {
// always the second column that we put the unique constraint on
MColumn col = new ArrayList<>(cols).get(1);
col.setUnique(determineUniqueConstraintName(col.getName()));
}
}
} else if (p.isElementCollection()) {
ModelBuildElementTable.build(ctx, p);
}
}
@@ -16,6 +16,14 @@ import java.util.List;
*/
public class VisitAllUsing {
/**
* Visit a single Descriptor using the given visitor.
*/
public static void visitOne(BeanDescriptor<?> descriptor, BeanPropertyVisitor visitor) {
new VisitAllUsing().visitProperties(descriptor, visitor);
}
protected final BeanVisitor visitor;
protected final List<BeanDescriptor<?>> descriptors;
@@ -36,6 +44,11 @@ public class VisitAllUsing {
this.descriptors = descriptors;
}
private VisitAllUsing() {
this.visitor = null;
this.descriptors = null;
}
public void visitAllBeans() {
for (BeanDescriptor<?> desc : descriptors) {
if (desc.isBaseTable()) {
@@ -51,29 +64,32 @@ public class VisitAllUsing {
BeanPropertyVisitor propertyVisitor = visitor.visitBean(desc);
if (propertyVisitor != null) {
BeanProperty idProp = desc.getIdProperty();
if (idProp != null) {
visit(propertyVisitor, idProp);
}
BeanPropertyAssocOne<?> unidirectional = desc.getUnidirectional();
if (unidirectional != null) {
visit(propertyVisitor, unidirectional);
}
BeanProperty[] propertiesNonTransient = desc.propertiesNonTransient();
for (BeanProperty p : propertiesNonTransient) {
if (p.isDDLColumn()) {
visit(propertyVisitor, p);
}
}
visitInheritanceProperties(desc, propertyVisitor);
propertyVisitor.visitEnd();
visitProperties(desc, propertyVisitor);
}
}
private void visitProperties(BeanDescriptor<?> desc, BeanPropertyVisitor propertyVisitor) {
BeanProperty idProp = desc.getIdProperty();
if (idProp != null) {
visit(propertyVisitor, idProp);
}
BeanPropertyAssocOne<?> unidirectional = desc.getUnidirectional();
if (unidirectional != null) {
visit(propertyVisitor, unidirectional);
}
BeanProperty[] propertiesNonTransient = desc.propertiesNonTransient();
for (BeanProperty p : propertiesNonTransient) {
if (p.isDDLColumn()) {
visit(propertyVisitor, p);
}
}
visitInheritanceProperties(desc, propertyVisitor);
propertyVisitor.visitEnd();
}
/**
* Visit the property.
*/
@@ -2,11 +2,11 @@ package io.ebeaninternal.server.autotune.service;
import io.ebean.bean.NodeUsageCollector;
import io.ebean.text.PathProperties;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebean.util.SplitName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -56,7 +56,7 @@ public class ProfileOriginNodeUsage {
}
for (String propName : aggregateUsed) {
BeanProperty beanProp = desc.getBeanPropertyFromPath(propName);
BeanProperty beanProp = desc.findPropertyFromPath(propName);
if (beanProp == null) {
logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName());
@@ -4,6 +4,7 @@ import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -33,6 +34,12 @@ public class CachedBeanDataFromBean {
}
}
for (BeanPropertyAssocMany<?> prop : desc.propertiesMany()) {
if (prop.isElementCollection()) {
data.put(prop.getName(), prop.getCacheDataValue(bean));
}
}
long version = desc.getVersion(bean);
EntityBean sharableBean = createSharableBean(desc, bean, ebi);
return new CachedBeanData(sharableBean, desc.getDiscValue(), data, version);
@@ -15,20 +15,26 @@ public class CachedBeanDataToBean {
EntityBeanIntercept ebi = bean._ebean_getIntercept();
BeanProperty idProperty = desc.getIdProperty();
if (desc.getInheritInfo() != null) {
desc = desc.getInheritInfo().readType(bean.getClass()).desc();
}
if (idProperty != null) {
// load the id property
loadProperty(bean, cacheBeanData, ebi, idProperty, context);
}
// load the non-many properties
BeanProperty[] props = desc.propertiesNonMany();
for (BeanProperty prop : props) {
for (BeanProperty prop : desc.propertiesNonMany()) {
loadProperty(bean, cacheBeanData, ebi, prop, context);
}
BeanPropertyAssocMany<?>[] many = desc.propertiesMany();
for (BeanPropertyAssocMany<?> aMany : many) {
aMany.createReferenceIfNull(bean);
for (BeanPropertyAssocMany<?> prop : desc.propertiesMany()) {
if (prop.isElementCollection()) {
loadProperty(bean, cacheBeanData, ebi, prop, context);
} else {
prop.createReferenceIfNull(bean);
}
}
ebi.setLoadedLazy();
@@ -1,19 +0,0 @@
package io.ebeaninternal.server.core;
/**
* Not supported JSON or ARRAY expression handler.
*/
abstract class BaseDbExpression implements DbExpressionHandler {
private final String concatOperator;
BaseDbExpression(String concatOperator) {
this.concatOperator = concatOperator;
}
@Override
public String getConcatOperator() {
return concatOperator;
}
}
@@ -802,7 +802,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void commitTransaction() {
transactionManager.scope().commit();
currentTransaction().commit();
}
/**
@@ -810,7 +810,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void rollbackTransaction() {
transactionManager.scope().rollback();
currentTransaction().rollback();
}
/**
@@ -843,7 +843,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void endTransaction() {
transactionManager.scope().end();
Transaction transaction = transactionManager.getInScope();
if (transaction != null) {
transaction.end();
}
}
/**
@@ -966,7 +969,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
@Override
public <T> Query<T> createQuery(Class<T> beanType, String eql) {
public <T> DefaultOrmQuery<T> createQuery(Class<T> beanType, String eql) {
DefaultOrmQuery<T> query = createQuery(beanType);
EqlParser.parse(eql, query);
return query;
@@ -999,6 +1002,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultDtoQuery<>(this, descriptor, sql.trim());
}
@Override
public <T> DtoQuery<T> createNamedDtoQuery(Class<T> dtoType, String namedQuery) {
DtoBeanDescriptor<T> descriptor = dtoBeanManager.getDescriptor(dtoType);
String sql = descriptor.getNamedRawSql(namedQuery);
if (sql == null) {
throw new PersistenceException("No named query called " + namedQuery + " for bean:" + dtoType.getName());
}
return new DefaultDtoQuery<>(this, descriptor, sql);
}
@Override
public <T> DtoQuery<T> findDto(Class<T> dtoType, SpiQuery<?> ormQuery) {
@@ -97,6 +97,11 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
this(null, sql, new BindParams());
}
@Override
public void reset() {
addPos = 0;
}
@Override
public Object executeGetKey() {
execute();
@@ -182,8 +187,9 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
return this;
}
public void addParameter(Object value) {
public SqlUpdate setNextParameter(Object value) {
setParameter(++addPos, value);
return this;
}
@Override
@@ -0,0 +1,111 @@
package io.ebeaninternal.server.core;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.server.dto.DtoNamedQueries;
import io.ebeaninternal.xmlmapping.XmlMappingReader;
import io.ebeaninternal.xmlmapping.model.XmDto;
import io.ebeaninternal.xmlmapping.model.XmEbean;
import io.ebeaninternal.xmlmapping.model.XmRawSql;
import org.avaje.classpath.scanner.ClassPathScanner;
import org.avaje.classpath.scanner.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Reads the Xml deployment information.
*/
class InternalConfigXmlRead {
private static final Logger log = LoggerFactory.getLogger(InternalConfigXmlRead.class);
private final ServerConfig serverConfig;
private final ClassLoader classLoader;
private final Map<Class<?>, DtoNamedQueries> dtoNamedQueries = new HashMap<>();
private List<XmEbean> xmlEbeanList;
InternalConfigXmlRead(ServerConfig serverConfig) {
this.serverConfig = serverConfig;
this.classLoader = serverConfig.getClassLoadConfig().getClassLoader();
if (serverConfig.getClassLoadConfig().isJavaxJAXBPresent()) {
init();
}
}
private void init() {
this.xmlEbeanList = XmlMappingReader.readByResourceName(classLoader, "ebean.xml");
xmlEbeanList.addAll(XmlMappingReader.readByResourceList(xmlMappingResources()));
}
private List<Resource> xmlMappingResources() {
List<ClassPathScanner> scanners = ClassPathScanners.find(serverConfig);
List<String> mappingLocations = serverConfig.getMappingLocations();
List<Resource> resourceList = new ArrayList<>();
long st = System.currentTimeMillis();
if (mappingLocations != null && !mappingLocations.isEmpty()) {
for (ClassPathScanner finder : scanners) {
for (String mappingLocation : mappingLocations) {
resourceList.addAll(finder.scanForResources(mappingLocation, resourceName -> resourceName.endsWith(".xml")));
}
}
}
long searchTime = System.currentTimeMillis() - st;
log.debug("Classpath search mappings[{}] searchTime[{}]", resourceList.size(), searchTime);
return resourceList;
}
/**
* Return the XML deployment information for entity beans.
*/
List<XmEbean> xmlDeployment() {
return xmlEbeanList;
}
/**
* Return the named queries for Dto beans.
*/
Map<Class<?>, DtoNamedQueries> readDtoMapping() {
if (xmlEbeanList != null) {
for (XmEbean mapping : xmlEbeanList) {
List<XmDto> dtoList = mapping.getDto();
for (XmDto dto : dtoList) {
readDtoMapping(dto);
}
}
}
return dtoNamedQueries;
}
private void readDtoMapping(XmDto dto) {
String dtoClassName = dto.getClazz();
Class<?> dtoClass;
try {
dtoClass = Class.forName(dtoClassName, false, classLoader);
} catch (Exception e) {
log.error("Could not load dto bean class " + dtoClassName + " for ebean xml entry");
return;
}
DtoNamedQueries namedQueries = dtoNamedQueries.computeIfAbsent(dtoClass, aClass -> new DtoNamedQueries());
for (XmRawSql sql : dto.getRawSql()) {
namedQueries.addRawSql(sql.getName(), sql.getQuery().getValue());
}
if (!dto.getNamedQuery().isEmpty()) {
log.error("Only raw-sql named queries supported for DTO beans - bean:" + dtoClass);
}
}
}
@@ -42,6 +42,8 @@ import io.ebeaninternal.server.deploy.parse.DeployInherit;
import io.ebeaninternal.server.deploy.parse.DeployUtil;
import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.expression.platform.DbExpressionHandlerFactory;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.DefaultPersister;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
@@ -153,10 +155,12 @@ public class InternalConfiguration {
this.deployCreateProperties = new DeployCreateProperties(typeManager);
this.deployUtil = new DeployUtil(typeManager, serverConfig);
this.dtoBeanManager = new DtoBeanManager(typeManager);
InternalConfigXmlRead xmlRead = new InternalConfigXmlRead(serverConfig);
this.dtoBeanManager = new DtoBeanManager(typeManager, xmlRead.readDtoMapping());
this.beanDescriptorManager = new BeanDescriptorManager(this);
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy();
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy(xmlRead.xmlDeployment());
Map<String, String> draftTableMap = beanDescriptorManager.getDraftTableMap();
beanDescriptorManager.scheduleBackgroundTrim();
@@ -267,18 +271,7 @@ public class InternalConfiguration {
* Return the JSON expression handler for the given database platform.
*/
private DbExpressionHandler getDbExpressionHandler(DatabasePlatform databasePlatform) {
Platform platform = databasePlatform.getPlatform();
String concatOperator = databasePlatform.getConcatOperator();
switch (platform) {
case POSTGRES:
return new PostgresDbExpression(concatOperator);
case ORACLE:
return new OracleDbExpression(concatOperator);
case SQLSERVER:
return new SqlServerDbExpression(concatOperator);
default:
return new BasicDbExpression(concatOperator);
}
return DbExpressionHandlerFactory.from(databasePlatform);
}
private MultiValueBind createMultiValueBind(Platform platform) {
@@ -79,6 +79,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private List<T> cacheBeans;
private BeanPropertyAssocMany<?> manyProperty;
/**
* Create the InternalQueryRequest.
*/
@@ -496,11 +498,18 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
/**
* Return the many property that is fetched in the query or null if there is
* not one.
* Determine and return the ToMany property that is included in the query.
*/
public BeanPropertyAssocMany<?> determineMany() {
manyProperty = beanDescriptor.getManyProperty(query);
return manyProperty;
}
/**
* Return the many property that is fetched in the query or null if there is not one.
*/
public BeanPropertyAssocMany<?> getManyProperty() {
return beanDescriptor.getManyProperty(query);
return manyProperty;
}
/**
@@ -35,6 +35,7 @@ import javax.persistence.PersistenceException;
import java.io.IOException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -107,6 +108,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private boolean updatedManysOnly;
/**
* Element collection change as part of bean cache.
*/
private Map<String, Object> collectionChanges;
/**
* Many properties that were cascade saved (and hence might need caches updated later).
*/
@@ -154,6 +160,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private boolean getterCallback;
/**
* postUpdate notifications. Used to combine bean and element update updates into single postUpdate event.
*/
private int pendingPostUpdateNotify;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
@@ -337,11 +348,16 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
@Override
public void preGetterTrigger(int propertyIndex) {
if (propertyIndex < 0 || beanDescriptor.isGeneratedProperty(propertyIndex)) {
if (flushBatchOnGetter(propertyIndex)) {
transaction.flushBatch();
}
}
private boolean flushBatchOnGetter(int propertyIndex) {
// propertyIndex of -1 the Id property, no flush for get Id on UPDATE
return propertyIndex == -1 ? type == Type.INSERT : beanDescriptor.isGeneratedProperty(propertyIndex);
}
public void setSkipBatchForTopLevel() {
skipBatchForTopLevel = true;
}
@@ -858,6 +874,16 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
private void postUpdateNotify() {
if (pendingPostUpdateNotify > 0) {
// invoke the delayed postUpdate notification (combined with element collection update)
controller.postUpdate(this);
} else {
// batched update with no element collection, send postUpdate notification once it executes
pendingPostUpdateNotify = -1;
}
}
/**
* Aggressive L1 and L2 cache cleanup for deletes.
*/
@@ -905,13 +931,42 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
/**
* Ensure the preUpdate event fires (for case where only element collection has changed).
*/
public void preElementCollectionUpdate() {
if (controller != null && !dirty) {
// fire preUpdate notification when only element collection updated
controller.preUpdate(this);
}
}
/**
* Combine with the beans postUpdate event notification.
*/
public boolean postElementCollectionUpdate() {
if (controller != null) {
pendingPostUpdateNotify += 2;
}
if (!dirty) {
setNotifyCache();
}
return notifyCache;
}
private void controllerPost() {
switch (type) {
case INSERT:
controller.postInsert(this);
break;
case UPDATE:
controller.postUpdate(this);
if (pendingPostUpdateNotify == -1) {
// notify now - batched bean update with no element collection
controller.postUpdate(this);
} else {
// delay notify to combine with element collection update
pendingPostUpdateNotify++;
}
break;
case SOFT_DELETE:
controller.postSoftDelete(this);
@@ -1031,13 +1086,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
setNotifyCache();
addEvent();
}
}
/**
* Return true if only many properties where updated.
*/
public boolean isUpdatedManysOnly() {
return updatedManysOnly;
postUpdateNotify();
}
/**
@@ -1265,4 +1314,50 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public boolean isInsertedParent() {
return Flags.isInsert(flags);
}
/**
* Add an element collection change to L2 bean cache update.
*/
public void addCollectionChange(String name, Object value) {
if (collectionChanges == null) {
collectionChanges = new LinkedHashMap<>();
}
collectionChanges.put(name, value);
}
/**
* Build the bean update for the L2 cache.
*/
public void addBeanUpdate(CacheChangeSet changeSet) {
if (!updatedManysOnly || collectionChanges != null) {
boolean updateNaturalKey = false;
Map<String, Object> changes = new LinkedHashMap<>();
EntityBean bean = getEntityBean();
boolean[] dirtyProperties = getDirtyProperties();
if (dirtyProperties != null) {
for (int i = 0; i < dirtyProperties.length; i++) {
if (dirtyProperties[i]) {
BeanProperty property = beanDescriptor.propertyByIndex(i);
if (property.isCacheDataInclude()) {
Object val = property.getCacheDataValue(bean);
changes.put(property.getName(), val);
if (property.isNaturalKey()) {
updateNaturalKey = true;
changeSet.addNaturalKeyPut(beanDescriptor, idValue, val);
}
}
}
}
}
if (collectionChanges != null) {
// add element collection update
changes.putAll(collectionChanges);
}
changeSet.addBeanUpdate(beanDescriptor, idValue, changes, updateNaturalKey, getVersion());
}
}
}
@@ -30,12 +30,13 @@ public final class PersistRequestUpdateSql extends PersistRequest {
/**
* Create.
*/
public PersistRequestUpdateSql(SpiEbeanServer server, SqlUpdate updateSql,
public PersistRequestUpdateSql(SpiEbeanServer server, SqlUpdate sqlUpdate,
SpiTransaction t, PersistExecute persistExecute) {
super(server, t, persistExecute, updateSql.getLabel());
super(server, t, persistExecute, sqlUpdate.getLabel());
this.type = Type.UPDATESQL;
this.updateSql = (SpiSqlUpdate) updateSql;
this.updateSql = (SpiSqlUpdate) sqlUpdate;
updateSql.reset();
}
@Override
@@ -5,12 +5,15 @@ import io.ebeaninternal.server.lib.ShutdownManager;
import javax.servlet.ServletContextEvent;
/**
* Deprecated - migrate to io.ebean.event.ServletContextListener.
* <p>
* Listens for webserver server starting and stopping events.
* <p>
* Register this listener in the web.xml configuration file. This will listen
* for startup and shutdown events.
* </p>
*/
@Deprecated
public class ServletContextListener implements javax.servlet.ServletContextListener {
/**
@@ -1,11 +1,47 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.Collection;
abstract class BaseCollectionHelp<T> implements BeanCollectionHelp<T> {
final BeanPropertyAssocMany<T> many;
final BeanDescriptor<T> targetDescriptor;
final String propertyName;
BeanCollectionLoader loader;
BaseCollectionHelp(BeanPropertyAssocMany<T> many) {
this.many = many;
this.targetDescriptor = many.getTargetDescriptor();
this.propertyName = many.getName();
}
BaseCollectionHelp() {
this.many = null;
this.targetDescriptor = null;
this.propertyName = null;
}
@Override
public void setLoader(BeanCollectionLoader loader) {
this.loader = loader;
}
@Override
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (withCheck) {
collection.internalAddWithCheck(bean);
} else {
collection.internalAdd(bean);
}
}
@Override
public Collection underlying(Object value) {
if (value instanceof BeanCollection) {
@@ -14,4 +50,18 @@ abstract class BaseCollectionHelp<T> implements BeanCollectionHelp<T> {
return (Collection)value;
}
}
void jsonWriteCollection(SpiJsonWriter ctx, String name, Collection<?> list) throws IOException {
if (!list.isEmpty() || ctx.isIncludeEmpty()) {
ctx.beginAssocMany(name);
for (Object bean : list) {
jsonWriteElement(ctx, bean);
}
ctx.endAssocMany();
}
}
void jsonWriteElement(SpiJsonWriter ctx, Object bean) throws IOException {
targetDescriptor.jsonWrite(ctx, (EntityBean) bean);
}
}
@@ -2,7 +2,7 @@ package io.ebeaninternal.server.deploy;
import io.ebean.PersistenceIOException;
import io.ebean.bean.BeanDiffVisitor;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.util.ArrayStack;
import java.io.IOException;
@@ -82,10 +82,10 @@ class BeanChangeJson implements BeanDiffVisitor {
void flush() {
try {
newJson.writeEndObject();
newJson.gen().flush();
newJson.flush();
if (oldJson != null) {
oldJson.writeEndObject();
oldJson.gen().flush();
oldJson.flush();
}
} catch (IOException e) {
throw new PersistenceIOException(e);
@@ -7,7 +7,8 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.query.CQueryCollectionAdd;
import java.io.IOException;
import java.util.Collection;
@@ -15,7 +16,7 @@ import java.util.Collection;
/**
* Helper functions for performing tasks on Lists Sets or Maps.
*/
public interface BeanCollectionHelp<T> {
public interface BeanCollectionHelp<T> extends CQueryCollectionAdd<T> {
/**
* Set the EbeanServer that owns the configuration.
@@ -18,16 +18,17 @@ public class BeanCollectionHelpFactory {
/**
* Create the helper based on the many property.
*/
public static <T> BeanCollectionHelp<T> create(BeanPropertyAssocMany<T> manyProperty) {
public static <T> BeanCollectionHelp<T> create(BeanPropertyAssocMany<T> many) {
ManyType manyType = manyProperty.getManyType();
boolean elementCollection = many.isElementCollection();
ManyType manyType = many.getManyType();
switch (manyType) {
case LIST:
return new BeanListHelp<>(manyProperty);
return elementCollection ? new BeanListHelpElement<>(many) : new BeanListHelp<>(many);
case SET:
return new BeanSetHelp<>(manyProperty);
return elementCollection ? new BeanSetHelpElement<>(many) : new BeanSetHelp<>(many);
case MAP:
return new BeanMapHelp<>(manyProperty);
return elementCollection ? new BeanMapHelpElement<>(many) :new BeanMapHelp<>(many);
default:
throw new RuntimeException("Invalid type " + manyType);
}
@@ -35,9 +36,7 @@ public class BeanCollectionHelpFactory {
}
@SuppressWarnings("unchecked")
public static <T> BeanCollectionHelp<T> create(OrmQueryRequest<T> request) {
SpiQuery.Type manyType = request.getQuery().getType();
public static <T> BeanCollectionHelp<T> create(SpiQuery.Type manyType, OrmQueryRequest<T> request) {
if (manyType == SpiQuery.Type.LIST) {
return LIST_HELP;
@@ -11,6 +11,16 @@ import java.util.Map;
*/
public class BeanCollectionUtil {
/**
* Return true if this is a bean collection and not considered dirty.
*/
public static boolean isModified(Object collection) {
if ((collection instanceof BeanCollection<?>)) {
return ((BeanCollection<?>) collection).holdsModifications();
}
return true;
}
/**
* Return the details of the collection or map taking care to avoid
* unnecessary fetching of the data.
@@ -41,6 +41,8 @@ import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiUpdatePlan;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cache.CachedBeanData;
import io.ebeaninternal.server.cache.CachedManyIds;
@@ -62,11 +64,16 @@ import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.DmlUtil;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.query.ExtraJoin;
import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.STreePropertyAssoc;
import io.ebeaninternal.server.query.STreePropertyAssocMany;
import io.ebeaninternal.server.query.STreePropertyAssocOne;
import io.ebeaninternal.server.query.STreeType;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.querydefn.DefaultOrmQuery;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.util.SortByClause;
@@ -100,7 +107,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* Describes Beans including their deployment information.
*/
public class BeanDescriptor<T> implements BeanType<T> {
public class BeanDescriptor<T> implements BeanType<T>, STreeType {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptor.class);
@@ -114,7 +121,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
private final ConcurrentHashMap<String, ElComparator<T>> comparatorCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, SqlTreeProperty> dynamicProperty = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, STreeProperty> dynamicProperty = new ConcurrentHashMap<>();
private final Map<String, SpiRawSql> namedRawSql;
@@ -229,7 +236,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
/**
* The type of bean this describes.
*/
private final Class<T> beanType;
final Class<T> beanType;
protected final Class<?> rootBeanType;
@@ -239,7 +246,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
private final BeanDescriptorMap owner;
private final String[] properties;
final String[] properties;
/**
* Intercept pre post on insert,update, and delete .
@@ -400,7 +407,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
private final BeanDescriptorDraftHelp<T> draftHelp;
private final BeanDescriptorCacheHelp<T> cacheHelp;
private final BeanDescriptorJsonHelp<T> jsonHelp;
final BeanDescriptorJsonHelp<T> jsonHelp;
private DocStoreBeanAdapter<T> docStoreAdapter;
private DocumentMapping docMapping;
private boolean docStoreEmbeddedInvalidation;
@@ -422,8 +429,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
this.name = InternString.intern(deploy.getName());
this.baseTableAlias = "t0";
this.fullName = InternString.intern(deploy.getFullName());
this.locationById = ProfileLocation.createAt(fullName+".byId");
this.locationAll = ProfileLocation.createAt(fullName+".all");
this.locationById = ProfileLocation.createAt(fullName + ".byId");
this.locationAll = ProfileLocation.createAt(fullName + ".all");
this.profileBeanId = deploy.getProfileId();
this.beanType = deploy.getBeanType();
this.rootBeanType = PersistenceContextUtil.root(beanType);
@@ -601,7 +608,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
/**
* Create an entity bean that is used as a prototype/factory to create new instances.
*/
private EntityBean createPrototypeEntityBean(Class<T> beanType) {
protected EntityBean createPrototypeEntityBean(Class<T> beanType) {
if (Modifier.isAbstract(beanType.getModifiers())) {
return null;
}
@@ -656,27 +663,29 @@ public class BeanDescriptor<T> implements BeanType<T> {
return properties;
}
public BeanProperty propertyByIndex(int pos) {
return propertiesIndex[pos];
}
/**
* Initialise the Id properties first.
* <p>
* These properties need to be initialised prior to the association properties
* as they are used to get the imported and exported properties.
* </p>
*
* @param withHistoryTables map populated if @History is supported on this entity bean
*/
public void initialiseId(Map<String, String> withHistoryTables, Map<String, String> draftTables) {
public void initialiseId(BeanDescriptorInitContext initContext) {
if (logger.isTraceEnabled()) {
logger.trace("BeanDescriptor initialise " + fullName);
}
if (draftable) {
draftTables.put(baseTable, draftTable);
initContext.addDraft(baseTable, draftTable);
}
if (historySupport) {
// add mapping (used to swap out baseTable for asOf queries)
withHistoryTables.put(baseTable, baseTableAsOf);
initContext.addHistory(baseTable, baseTableAsOf);
}
if (inheritInfo != null) {
@@ -686,28 +695,24 @@ public class BeanDescriptor<T> implements BeanType<T> {
if (isEmbedded()) {
// initialise all the properties
for (BeanProperty prop : propertiesAll()) {
prop.initialise();
prop.initialise(initContext);
}
} else {
// initialise just the Id properties
if (idProperty != null) {
idProperty.initialise();
idProperty.initialise(initContext);
}
}
}
/**
* Initialise the exported and imported parts for associated properties.
*
* @param asOfTableMap the map of base tables to associated 'with history' tables
* @param asOfViewSuffix the suffix added to the table name to derive the 'with history' view name
* @param draftTableMap the map of base tables to associated 'draft' tables.
*/
public void initialiseOther(Map<String, String> asOfTableMap, String asOfViewSuffix, Map<String, String> draftTableMap) {
public void initialiseOther(BeanDescriptorInitContext initContext) {
for (BeanPropertyAssocMany<?> aPropertiesManyToMany1 : propertiesManyToMany) {
for (BeanPropertyAssocMany<?> many : propertiesManyToMany) {
// register associated draft table for M2M intersection
aPropertiesManyToMany1.registerDraftIntersectionTable(draftTableMap);
many.registerDraftIntersectionTable(initContext);
}
if (historySupport) {
@@ -717,8 +722,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
// register associated history table for M2M intersection
if (!aPropertiesManyToMany.isExcludedFromHistory()) {
TableJoin intersectionTableJoin = aPropertiesManyToMany.getIntersectionTableJoin();
String intersectionTableName = intersectionTableJoin.getTable();
asOfTableMap.put(intersectionTableName, intersectionTableName + asOfViewSuffix);
initContext.addHistoryIntersection(intersectionTableJoin.getTable());
}
}
}
@@ -727,14 +731,14 @@ public class BeanDescriptor<T> implements BeanType<T> {
// initialise all the non-id properties
for (BeanProperty prop : propertiesAll()) {
if (!prop.isId()) {
prop.initialise();
prop.initialise(initContext);
}
prop.registerColumn(this, null);
}
}
if (unidirectional != null) {
unidirectional.initialise();
unidirectional.initialise(initContext);
}
idBinder.initialise();
@@ -777,7 +781,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
for (BeanProperty prop : propertiesNonTransient) {
if (prop.isUnique()) {
propertiesUnique.add(new BeanProperty[] { prop });
propertiesUnique.add(new BeanProperty[]{prop});
}
}
// convert unique columns to properties
@@ -799,7 +803,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
if (propName == null) {
return;
}
props[i] = findBeanProperty(propName);
props[i] = findProperty(propName);
}
if (props.length == 1) {
for (BeanProperty[] inserted : propertiesUnique) {
@@ -861,6 +865,16 @@ public class BeanDescriptor<T> implements BeanType<T> {
}
}
/**
* Bind all the property values to the SqlUpdate.
*/
public void bindElementValue(SqlUpdate insert, Object value) {
EntityBean bean = (EntityBean) value;
for (BeanProperty property : propertiesBaseScalar) {
insert.setNextParameter(property.getValue(bean));
}
}
/**
* Return the ReadAuditLogger for logging read audit events.
*/
@@ -936,7 +950,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
SpiJsonWriter jsonWriter = createJsonWriter(writer);
jsonWriteForInsert(jsonWriter, request.getEntityBean());
jsonWriter.gen().flush();
jsonWriter.flush();
return beanChange(ChangeType.INSERT, request.getBeanId(), writer.toString(), null);
@@ -950,6 +964,10 @@ public class BeanDescriptor<T> implements BeanType<T> {
return ebeanServer.jsonExtended().createJsonWriter(writer);
}
SpiJsonReader createJsonReader(String json) {
return ebeanServer.jsonExtended().createJsonRead(this, json);
}
/**
* Populate the diff for inserts with flattened non-null property values.
*/
@@ -1016,7 +1034,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
Object[] bindValues = idBinder.getBindValues(id);
for (Object bindValue : bindValues) {
sqlDelete.addParameter(bindValue);
sqlDelete.setNextParameter(bindValue);
}
return sqlDelete;
@@ -1421,7 +1439,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
@SuppressWarnings("unchecked")
public void cacheBeanPutAll(Collection<?> beans) {
if (!beans.isEmpty()) {
cacheHelp.beanPutAll((Collection<EntityBean>)beans);
cacheHelp.beanPutAll((Collection<EntityBean>) beans);
}
}
@@ -1639,6 +1657,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
/**
* Execute the postLoad if a BeanPostLoad exists for this bean.
*/
@Override
public void postLoad(Object bean) {
if (beanPostLoad != null) {
beanPostLoad.postLoad(bean);
@@ -1705,7 +1724,15 @@ public class BeanDescriptor<T> implements BeanType<T> {
* associated L2 bean caching.
*/
public boolean isDeleteByStatement() {
return deleteRecurseSkippable && !isBeanCaching();
return persistListener == null
&& persistController == null
&& deleteRecurseSkippable && !isBeanCaching();
}
public boolean isDeleteByBulk() {
return persistListener == null
&& persistController == null
&& propertiesManyToMany.length == 0;
}
/**
@@ -1787,6 +1814,11 @@ public class BeanDescriptor<T> implements BeanType<T> {
return idBinder instanceof IdBinderSimple;
}
@Override
public boolean hasId() {
return idProperty != null;
}
/**
* Return true if this type has a simple Id and the platform supports mutli-value binding.
*/
@@ -1847,6 +1879,9 @@ public class BeanDescriptor<T> implements BeanType<T> {
* on first access (lazy load) or immediately (eager load)
*/
public EntityBean createEntityBean(boolean isNew) {
if (prototypeEntityBean == null) {
throw new UnsupportedOperationException("cannot create entity bean for abstract entity " + getName());
}
try {
EntityBean bean = (EntityBean) prototypeEntityBean._ebean_newInstance();
@@ -1874,12 +1909,19 @@ public class BeanDescriptor<T> implements BeanType<T> {
}
/**
* Creates a new entitybean without invoking {@link BeanPostConstructListener#postCreate(Object)}
* Creates a new entity bean without invoking {@link BeanPostConstructListener#postCreate(Object)}
*/
public EntityBean createEntityBean() {
return createEntityBean(false);
}
/**
* Create an entity bean for JSON marshalling (which differs for the element collection case).
*/
public EntityBean createEntityBeanForJson() {
return createEntityBean();
}
/**
* Create a reference bean based on the id.
*/
@@ -1931,6 +1973,13 @@ public class BeanDescriptor<T> implements BeanType<T> {
public T createReference(Object id, PersistenceContext pc) {
try {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
// we actually need to do a query because we don't know the type without the discriminator
// value, just select the id property and discriminator column (auto added)
DefaultOrmQuery<T> query = new DefaultOrmQuery<>(this, ebeanServer, ebeanServer.getExpressionFactory());
return query.select(getIdProperty().getName()).setId(id).findOne();
}
EntityBean eb = createEntityBean();
id = convertSetId(id, eb);
EntityBeanIntercept ebi = eb._ebean_getIntercept();
@@ -1951,7 +2000,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
* Return the bean property traversing the object graph and taking into
* account inheritance.
*/
public BeanProperty getBeanPropertyFromPath(String path) {
@Override
public BeanProperty findPropertyFromPath(String path) {
BeanDescriptor<?> other = this;
while (true) {
@@ -1983,7 +2033,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
}
String[] splitBegin = SplitName.splitBegin(path);
BeanProperty beanProperty = result.findBeanProperty(splitBegin[0]);
BeanProperty beanProperty = result.findProperty(splitBegin[0]);
if (beanProperty instanceof BeanPropertyAssoc<?>) {
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProperty;
path = splitBegin[1];
@@ -2224,7 +2274,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
@Override
public Property getProperty(String propName) {
return findBeanProperty(propName);
return findProperty(propName);
}
/**
@@ -2458,17 +2508,18 @@ public class BeanDescriptor<T> implements BeanType<T> {
/**
* Return a 'dynamic property' used to read a formula.
*/
private SqlTreeProperty findSqlTreeFormula(String formulaExpression) {
private STreeProperty findSqlTreeFormula(String formulaExpression) {
return dynamicProperty.computeIfAbsent(formulaExpression, (formula) -> new FormulaPropertyPath(this, formula).build());
}
/**
* Return a property that is part of the SQL tree.
*
* <p>
* The property can be a dynamic formula or a well known bean property.
*/
public SqlTreeProperty findSqlTreeProperty(String propName) {
@Override
public STreeProperty findPropertyWithDynamic(String propName) {
if (propName.indexOf('(') > -1) {
return findSqlTreeFormula(propName);
}
@@ -2482,7 +2533,8 @@ public class BeanDescriptor<T> implements BeanType<T> {
* inheritance tree (not up).
* </p>
*/
public BeanProperty findBeanProperty(String propName) {
@Override
public BeanProperty findProperty(String propName) {
int basePos = propName.indexOf('.');
if (basePos > -1) {
// embedded property
@@ -2540,6 +2592,10 @@ public class BeanDescriptor<T> implements BeanType<T> {
return autoTunable;
}
public boolean isElementType() {
return false;
}
/**
* Returns the Inheritance mapping information. This will be null if this type
* of bean is not involved in any ORM inheritance mapping.
@@ -2809,6 +2865,41 @@ public class BeanDescriptor<T> implements BeanType<T> {
return draftableElement;
}
@Override
public boolean isEmbeddedPath(String propertyPath) {
ElPropertyDeploy elProp = getElPropertyDeploy(propertyPath);
if (elProp == null) {
throw new PersistenceException("Invalid path " + propertyPath + " from " + getFullName());
}
return elProp.getBeanProperty().isEmbedded();
}
@Override
public ExtraJoin extraJoin(String propertyPath) {
ElPropertyValue elGetValue = getElGetValue(propertyPath);
if (elGetValue != null) {
BeanProperty beanProperty = elGetValue.getBeanProperty();
if (beanProperty instanceof BeanPropertyAssoc<?>) {
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProperty;
if (!assocProp.isEmbedded()) {
return new ExtraJoin(assocProp, elGetValue.containsMany());
}
}
}
return null;
}
@Override
public void inheritanceLoad(SqlBeanLoad sqlBeanLoad, STreeProperty property, DbReadContext ctx) {
BeanProperty p = getBeanProperty(property.getName());
if (p != null) {
p.load(sqlBeanLoad);
} else {
property.loadIgnore(ctx);
}
}
public void setUnmappedJson(EntityBean bean, Map<String, Object> unmappedProperties) {
if (unmappedJson != null) {
unmappedJson.setValueIntercept(bean, unmappedProperties);
@@ -2933,16 +3024,6 @@ public class BeanDescriptor<T> implements BeanType<T> {
return selectLastInsertedId;
}
/**
* Return the TableJoins.
* <p>
* For properties mapped to secondary tables rather than the base table.
* </p>
*/
public TableJoin[] tableJoins() {
return derivedTableJoins;
}
@Override
public Collection<? extends Property> allProperties() {
return propertiesAll();
@@ -3131,6 +3212,26 @@ public class BeanDescriptor<T> implements BeanType<T> {
}
}
@Override
public STreeProperty[] propsBaseScalar() {
return propertiesBaseScalar;
}
@Override
public STreePropertyAssoc[] propsEmbedded() {
return propertiesEmbedded;
}
@Override
public STreePropertyAssocOne[] propsOne() {
return propertiesOne;
}
@Override
public STreePropertyAssocMany[] propsMany() {
return propertiesMany;
}
/**
* All the BeanPropertyAssocOne that are not embedded. These are effectively
* joined beans. For ManyToOne and OneToOne associations.
@@ -3273,6 +3374,18 @@ public class BeanDescriptor<T> implements BeanType<T> {
jsonHelp.jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
}
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
throw new IllegalStateException("Unexpected - expect Element override");
}
public void jsonWriteElement(SpiJsonWriter ctx, Object element) {
throw new IllegalStateException("Unexpected - expect Element override");
}
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
throw new IllegalStateException("Unexpected - expect Element override");
}
public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean) throws IOException {
jsonHelp.jsonWrite(writeJson, bean, null);
}
@@ -3285,11 +3398,11 @@ public class BeanDescriptor<T> implements BeanType<T> {
jsonHelp.jsonWriteProperties(writeJson, bean);
}
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException {
return jsonHelp.jsonRead(jsonRead, path);
}
protected T jsonReadObject(ReadJson jsonRead, String path) throws IOException {
protected T jsonReadObject(SpiJsonReader jsonRead, String path) throws IOException {
return jsonHelp.jsonReadObject(jsonRead, path);
}
@@ -20,6 +20,7 @@ import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -272,6 +273,11 @@ final class BeanDescriptorCacheHelp<T> {
*/
boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId, Boolean readOnly) {
if (many.isElementCollection()) {
// held as part of the bean cache so skip
return false;
}
CachedManyIds entry = manyPropGet(parentId, many.getName());
if (entry == null) {
// not in cache so return unsuccessful
@@ -298,9 +304,29 @@ final class BeanDescriptorCacheHelp<T> {
*/
void manyPropPut(BeanPropertyAssocMany<?> many, Object details, Object parentId) {
CachedManyIds entry = createManyIds(many, details);
if (entry != null) {
cachePutManyIds(parentId, many.getName(), entry);
if (many.isElementCollection()) {
CachedBeanData data = (CachedBeanData) beanCache.get(parentId);
if (data != null) {
try {
// add as JSON to bean cache
String asJson = many.jsonWriteCollection(details);
Map<String,Object> changes = new HashMap<>();
changes.put(many.getName(), asJson);
CachedBeanData newData = data.update(changes, data.getVersion());
if (beanLog.isDebugEnabled()) {
beanLog.debug(" UPDATE {}({}) changes:{}", cacheName, parentId, changes);
}
beanCache.put(parentId, newData);
} catch (IOException e) {
logger.error("Error updating L2 cache", e);
}
}
} else {
CachedManyIds entry = createManyIds(many, details);
if (entry != null) {
cachePutManyIds(parentId, many.getName(), entry);
}
}
}
@@ -421,7 +447,21 @@ final class BeanDescriptorCacheHelp<T> {
void beanPutAll(Collection<EntityBean> beans) {
if (desc.inheritInfo != null) {
Class<?> aClass = theClassOf(beans);
desc.descOf(aClass).cacheBeanPutAllDirect(beans);
// check if all beans have the same class
for (EntityBean bean : beans) {
if (!bean.getClass().equals(aClass)) {
aClass = null;
break;
}
}
if (aClass == null) {
// there are different bean types in the collection, so we add one by one to the cache
for (EntityBean bean : beans) {
desc.descOf(bean.getClass()).cacheBeanPutDirect(bean);
}
} else {
desc.descOf(aClass).cacheBeanPutAllDirect(beans);
}
} else {
beanCachePutAllDirect(beans);
}
@@ -730,38 +770,17 @@ final class BeanDescriptorCacheHelp<T> {
List<BeanPropertyAssocMany<?>> manyCollections = updateRequest.getUpdatedManyCollections();
if (manyCollections != null) {
for (BeanPropertyAssocMany<?> many : manyCollections) {
Object details = many.getValue(updateRequest.getEntityBean());
CachedManyIds entry = createManyIds(many, details);
if (entry != null) {
changeSet.addManyPut(desc, many.getName(), id, entry);
}
}
}
// check if the bean itself was updated
if (!updateRequest.isUpdatedManysOnly()) {
boolean updateNaturalKey = false;
Map<String, Object> changes = new LinkedHashMap<>();
EntityBean bean = updateRequest.getEntityBean();
boolean[] dirtyProperties = updateRequest.getDirtyProperties();
for (int i = 0; i < dirtyProperties.length; i++) {
if (dirtyProperties[i]) {
BeanProperty property = desc.propertiesIndex[i];
if (property.isCacheDataInclude()) {
Object val = property.getCacheDataValue(bean);
changes.put(property.getName(), val);
if (property.isNaturalKey()) {
updateNaturalKey = true;
changeSet.addNaturalKeyPut(desc, id, val);
}
if (!many.isElementCollection()) {
Object details = many.getValue(updateRequest.getEntityBean());
CachedManyIds entry = createManyIds(many, details);
if (entry != null) {
changeSet.addManyPut(desc, many.getName(), id, entry);
}
}
}
changeSet.addBeanUpdate(desc, id, changes, updateNaturalKey, updateRequest.getVersion());
}
updateRequest.addBeanUpdate(changeSet);
}
/**
@@ -0,0 +1,49 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.type.ScalarType;
import java.util.Arrays;
/**
* Bean descriptor used with ElementCollection (where we don't have a mapped type/class).
* <p>
* This is somewhat a BeanDescriptor created 'on the fly' for a specific element collection property
* with a unidirectional property and mapping etc specific to the property (and not the type if embedded).
*/
abstract class BeanDescriptorElement<T> extends BeanDescriptor<T> {
final ElementHelp elementHelp;
BeanDescriptorElement(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
super(owner, deploy);
this.elementHelp = elementHelp;
}
/**
* Find and return the first base scalar type (and we expect only 1).
*/
ScalarType<Object> firstBaseScalarType() {
BeanProperty[] props = propertiesBaseScalar();
if (props.length != 1) {
throw new IllegalStateException("Expecting 1 property for element scalar but got " + Arrays.toString(props));
}
return props[0].getScalarType();
}
@Override
public boolean isElementType() {
return true;
}
/**
* Our entity beans used are somewhat fake ones (ElementEntityBean) such that we hold the unidirectional property
* value (foreign key) and the actual element collection value (scalar or embedded plus map key).
*/
@Override
protected EntityBean createPrototypeEntityBean(Class<T> beanType) {
return new ElementEntityBean(properties);
}
}
@@ -0,0 +1,79 @@
package io.ebeaninternal.server.deploy;
import io.ebean.PersistenceIOException;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import java.io.IOException;
/**
* Bean descriptor used with element collection of list/set of embeddable.
*/
class BeanDescriptorElementEmbedded<T> extends BeanDescriptorElement<T> {
private final BeanPropertyAssocOne embeddedProperty;
private final EntityBean prototype;
private BeanDescriptor targetDescriptor;
BeanDescriptorElementEmbedded(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
super(owner, deploy, elementHelp);
try {
this.prototype = (EntityBean) beanType.newInstance();
} catch (Exception e) {
throw new IllegalStateException("Unable to create entity bean prototype for "+beanType);
}
BeanPropertyAssocOne<?>[] embedded = propertiesEmbedded();
if (embedded.length == 1) {
embeddedProperty = embedded[0];
} else {
embeddedProperty = null;
}
}
@Override
public void initialiseOther(BeanDescriptorInitContext initContext) {
super.initialiseOther(initContext);
this.targetDescriptor = embeddedProperty.getTargetDescriptor();
}
@Override
public EntityBean createEntityBeanForJson() {
return (EntityBean)prototype._ebean_newInstance();
}
public void bindElementValue(SqlUpdate insert, Object value) {
targetDescriptor.bindElementValue(insert, value);
}
@Override
public void jsonWriteElement(SpiJsonWriter ctx, Object element) {
writeJsonElement(ctx, element);
}
@Override
public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException {
return readJsonElement(jsonRead, path);
}
@SuppressWarnings("unchecked")
T readJsonElement(SpiJsonReader jsonRead, String path) throws IOException {
return (T)targetDescriptor.jsonRead(jsonRead, path);
}
void writeJsonElement(SpiJsonWriter ctx, Object element) {
try {
if (element == null) {
ctx.writeNull();
} else {
targetDescriptor.jsonWrite(ctx, (EntityBean)element);
}
} catch (IOException e) {
throw new PersistenceIOException(e);
}
}
}
@@ -0,0 +1,84 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.type.ScalarType;
import java.io.IOException;
import java.util.Map;
/**
* Descriptor for element collection using Map with the value holding an embedded bean.
* <p>
* The expected limitation is that the key is a scalar type.
*/
class BeanDescriptorElementEmbeddedMap<T> extends BeanDescriptorElementEmbedded<T> {
private final ScalarType scalarTypeKey;
private final boolean stringKey;
BeanDescriptorElementEmbeddedMap(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
super(owner, deploy, elementHelp);
this.scalarTypeKey = firstBaseScalarType();
this.stringKey = String.class.equals(scalarTypeKey.getType());
}
@Override
@SuppressWarnings("unchecked")
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
ctx.writeStartObject();
if (stringKey) {
Object key = entry.getKey();
String keyName = (key == null) ? "null" : key.toString();
ctx.writeFieldName(keyName);
writeJsonElement(ctx, entry.getValue());
} else {
ctx.writeFieldName("key");
scalarTypeKey.jsonWrite(ctx.gen(), entry.getKey());
ctx.writeFieldName("value");
writeJsonElement(ctx, entry.getValue());
}
ctx.writeEndObject();
}
@Override
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.getParser();
ElementCollector add = elementHelp.createCollector();
do {
JsonToken token = parser.nextToken();
if (token != JsonToken.START_OBJECT) {
break;
}
if (stringKey) {
String key = parser.nextFieldName();
parser.nextToken();
Object val = readJsonElement(readJson, null);
add.addKeyValue(key, val);
} else {
parser.nextFieldName();
Object key = scalarTypeKey.jsonRead(parser);
parser.nextFieldName();
Object val = readJsonElement(readJson, null);
add.addKeyValue(key, val);
}
token = parser.nextToken();
if (token != JsonToken.END_OBJECT) {
break;
}
} while (true);
return add.collection();
}
}
@@ -0,0 +1,56 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.PersistenceIOException;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.type.ScalarType;
import java.io.IOException;
/**
* Bean descriptor used with element collection mapped to a list or set of scalar values.
*/
class BeanDescriptorElementScalar<T> extends BeanDescriptorElement<T> {
private final ScalarType<Object> scalarType;
BeanDescriptorElementScalar(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
super(owner, deploy, elementHelp);
this.scalarType = firstBaseScalarType();
}
public void bindElementValue(SqlUpdate insert, Object value) {
insert.setNextParameter(value);
}
@Override
public void jsonWriteElement(SpiJsonWriter ctx, Object element) {
try {
scalarType.jsonWrite(ctx.gen(), element);
} catch (IOException e) {
throw new PersistenceIOException(e);
}
}
@Override
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.getParser();
ElementCollector add = elementHelp.createCollector();
do {
JsonToken token = parser.nextToken();
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
break;
}
add.addElement(scalarType.jsonRead(parser));
} while (true);
return add.collection();
}
}
@@ -0,0 +1,88 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.type.ScalarType;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
/**
* Bean descriptor used with element collection mapped to Map where key and value are scalar types.
*/
class BeanDescriptorElementScalarMap<T> extends BeanDescriptorElement<T> {
private final ScalarType scalarTypeKey;
private final ScalarType scalarTypeVal;
private final boolean stringKey;
BeanDescriptorElementScalarMap(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy, ElementHelp elementHelp) {
super(owner, deploy, elementHelp);
BeanProperty[] props = propertiesNonTransient();
if (props.length != 2) {
throw new IllegalStateException("Expecting 2 properties for key and value but got " + Arrays.toString(props));
}
this.scalarTypeKey = props[0].getScalarType();
this.scalarTypeVal = props[1].getScalarType();
this.stringKey = String.class.equals(scalarTypeKey.getType());
}
@Override
@SuppressWarnings("unchecked")
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
ctx.writeStartObject();
if (stringKey) {
Object key = entry.getKey();
String keyName = (key == null) ? "null" : key.toString();
ctx.writeFieldName(keyName);
scalarTypeVal.jsonWrite(ctx.gen(), entry.getValue());
} else {
ctx.writeFieldName("key");
scalarTypeKey.jsonWrite(ctx.gen(), entry.getKey());
ctx.writeFieldName("value");
scalarTypeVal.jsonWrite(ctx.gen(), entry.getValue());
}
ctx.writeEndObject();
}
@Override
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
JsonParser parser = readJson.getParser();
ElementCollector add = elementHelp.createCollector();
do {
JsonToken token = parser.nextToken();
if (token != JsonToken.START_OBJECT) {
break;
}
if (stringKey) {
String key = parser.nextFieldName();
parser.nextToken();
Object val = scalarTypeVal.jsonRead(parser);
add.addKeyValue(key, val);
} else {
parser.nextFieldName();
Object key = scalarTypeKey.jsonRead(parser);
parser.nextFieldName();
Object val = scalarTypeVal.jsonRead(parser);
add.addKeyValue(key, val);
}
token = parser.nextToken();
if (token != JsonToken.END_OBJECT) {
break;
}
} while (true);
return add.collection();
}
}
@@ -0,0 +1,32 @@
package io.ebeaninternal.server.deploy;
import java.util.Map;
class BeanDescriptorInitContext {
private final Map<String, String> withHistoryTables;
private final Map<String, String> draftTables;
private final String asOfViewSuffix;
BeanDescriptorInitContext(Map<String, String> withHistoryTables, Map<String, String> draftTables, String asOfViewSuffix) {
this.withHistoryTables = withHistoryTables;
this.draftTables = draftTables;
this.asOfViewSuffix = asOfViewSuffix;
}
void addDraft(String baseTable, String draftTable) {
draftTables.put(baseTable, draftTable);
}
void addHistory(String baseTable, String baseTableAsOf) {
withHistoryTables.put(baseTable, baseTableAsOf);
}
void addHistoryIntersection(String intersectionTableName) {
withHistoryTables.put(intersectionTableName, intersectionTableName + asOfViewSuffix);
}
void addDraftIntersection(String intersectionPublishTable, String intersectionDraftTable) {
draftTables.put(intersectionPublishTable, intersectionDraftTable);
}
}
@@ -5,8 +5,8 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.bean.EntityBean;
import io.ebean.text.json.EJson;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
@@ -70,7 +70,7 @@ public class BeanDescriptorJsonHelp<T> {
}
@SuppressWarnings("unchecked")
public T jsonRead(ReadJson jsonRead, String path) throws IOException {
public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException {
JsonParser parser = jsonRead.getParser();
//noinspection StatementWithEmptyBody
@@ -116,14 +116,14 @@ public class BeanDescriptorJsonHelp<T> {
return (T) inheritInfo.readType(discValue).desc().jsonReadObject(jsonRead, path);
}
protected T jsonReadObject(ReadJson readJson, String path) throws IOException {
protected T jsonReadObject(SpiJsonReader readJson, String path) throws IOException {
EntityBean bean = desc.createEntityBean();
EntityBean bean = desc.createEntityBeanForJson();
return jsonReadProperties(readJson, bean, path);
}
@SuppressWarnings("unchecked")
protected T jsonReadProperties(ReadJson readJson, EntityBean bean, String path) throws IOException {
protected T jsonReadProperties(SpiJsonReader readJson, EntityBean bean, String path) throws IOException {
if (path != null) {
readJson.pushPath(path);
@@ -56,7 +56,6 @@ import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeInteger;
import io.ebeaninternal.server.type.TypeManager;
import io.ebeaninternal.xmlmapping.XmlMappingReader;
import io.ebeaninternal.xmlmapping.model.XmAliasMapping;
import io.ebeaninternal.xmlmapping.model.XmColumnMapping;
import io.ebeaninternal.xmlmapping.model.XmEbean;
@@ -72,16 +71,12 @@ import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceException;
import javax.persistence.Transient;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -184,8 +179,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final BeanLifecycleAdapterFactory beanLifecycleAdapterFactory;
private final boolean eagerFetchLobs;
private final String asOfViewSuffix;
/**
@@ -215,12 +208,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
this.databasePlatform = serverConfig.getDatabasePlatform();
this.multiValueBind = config.getMultiValueBind();
this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm(), multiValueBind);
this.eagerFetchLobs = serverConfig.isEagerFetchLobs();
this.queryPlanTTLSeconds = serverConfig.getQueryPlanTTLSeconds();
this.asOfViewSuffix = getAsOfViewSuffix(databasePlatform, serverConfig);
String versionsBetweenSuffix = getVersionsBetweenSuffix(databasePlatform, serverConfig);
this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix, versionsBetweenSuffix, serverConfig.isDisableL2Cache());
this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix, versionsBetweenSuffix, serverConfig);
this.bootupClasses = config.getBootupClasses();
this.createProperties = config.getDeployCreateProperties();
this.namingConvention = serverConfig.getNamingConvention();
@@ -365,12 +357,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
/**
* Deploy returning the asOfTableMap (which is required by the SQL builders).
*/
public Map<String, String> deploy() {
public Map<String, String> deploy(List<XmEbean> mappings) {
try {
createListeners();
readEntityDeploymentInitial();
readXmlMapping();
readXmlMapping(mappings);
readEmbeddedDeployment();
readEntityBeanTable();
readEntityDeploymentAssociations();
@@ -401,30 +393,15 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
}
private void readXmlMapping() {
try {
private void readXmlMapping(List<XmEbean> mappings) {
if (mappings != null) {
ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();
Enumeration<URL> resources = classLoader.getResources("ebean.xml");
List<XmEbean> mappings = new ArrayList<>();
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
try (InputStream is = url.openStream()) {
mappings.add(XmlMappingReader.read(is));
}
}
for (XmEbean mapping : mappings) {
List<XmEntity> entityDeploy = mapping.getEntity();
for (XmEntity deploy : entityDeploy) {
readEntityMapping(classLoader, deploy);
}
}
} catch (IOException e) {
throw new RuntimeException("Error reading ebean.xml", e);
}
}
@@ -575,12 +552,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// we can initialise them which sorts out circular
// dependencies for OneToMany and ManyToOne etc
BeanDescriptorInitContext initContext = new BeanDescriptorInitContext(asOfTableMap, draftTableMap, asOfViewSuffix);
// PASS 1:
// initialise the ID properties of all the beans
// first (as they are needed to initialise the
// associated properties in the second pass).
for (BeanDescriptor<?> d : descMap.values()) {
d.initialiseId(asOfTableMap, draftTableMap);
d.initialiseId(initContext);
}
// PASS 2:
@@ -595,7 +574,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// also look for intersection tables with
// associated history support and register them
// into the asOfTableMap
d.initialiseOther(asOfTableMap, asOfViewSuffix, draftTableMap);
d.initialiseOther(initContext);
}
// PASS 4:
@@ -656,6 +635,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return beanTableMap.get(type);
}
/**
* Return a BeanTable for an ElementCollection.
*/
public BeanTable createCollectionBeanTable(String fullTableName, Class<?> targetType) {
return new BeanTable(this, fullTableName, targetType);
}
@SuppressWarnings("unchecked")
public <T> BeanManager<T> getBeanManager(Class<T> entityType) {
@@ -1025,7 +1011,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* </p>
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private void makeUnidirectional(DeployBeanInfo<?> info, DeployBeanPropertyAssocMany<?> oneToMany) {
private void makeUnidirectional(DeployBeanPropertyAssocMany<?> oneToMany) {
DeployBeanDescriptor<?> targetDesc = getTargetDescriptor(oneToMany);
@@ -1045,34 +1031,36 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// mark this property as unidirectional
oneToMany.setUnidirectional();
// create the 'shadow' unidirectional property
// which is put on the target descriptor
DeployBeanPropertyAssocOne<?> unidirectional = new DeployBeanPropertyAssocOne(targetDesc, owningType);
unidirectional.setUndirectionalShadow();
unidirectional.setNullable(false);
unidirectional.setDbRead(true);
unidirectional.setDbInsertable(true);
unidirectional.setDbUpdateable(false);
targetDesc.setUnidirectional(unidirectional);
// specify table and table alias...
BeanTable beanTable = getBeanTable(owningType);
unidirectional.setBeanTable(beanTable);
unidirectional.setName(beanTable.getBaseTable());
info.setBeanJoinType(unidirectional, true);
// define the TableJoin
DeployTableJoin oneToManyJoin = oneToMany.getTableJoin();
if (!oneToManyJoin.hasJoinColumns()) {
throw new RuntimeException("No join columns");
}
createUnidirectional(targetDesc, owningType, beanTable, oneToManyJoin);
}
// inverse of the oneToManyJoin
DeployTableJoin unidirectionalJoin = unidirectional.getTableJoin();
unidirectionalJoin.setColumns(oneToManyJoin.columns(), true);
/**
* Create and add a Unidirectional property (for ElementCollection) which maps to the foreign key.
*/
public <A> void createUnidirectional(DeployBeanDescriptor<?> targetDesc, Class<A> targetType, BeanTable beanTable, DeployTableJoin oneToManyJoin) {
// create the 'shadow' unidirectional property
// which is put on the target descriptor
DeployBeanPropertyAssocOne<A> unidirectional = new DeployBeanPropertyAssocOne<>(targetDesc, targetType);
unidirectional.setUndirectionalShadow();
unidirectional.setNullable(false);
unidirectional.setDbRead(true);
unidirectional.setDbInsertable(true);
unidirectional.setDbUpdateable(false);
unidirectional.setBeanTable(beanTable);
unidirectional.setName(beanTable.getBaseTable());
unidirectional.setJoinType(true);
unidirectional.setJoinColumns(oneToManyJoin.columns(), true);
targetDesc.setUnidirectional(unidirectional);
}
private void checkMappedByOneToOne(DeployBeanPropertyAssocOne<?> prop) {
@@ -1137,6 +1125,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
private void checkMappedByOneToMany(DeployBeanInfo<?> info, DeployBeanPropertyAssocMany<?> prop) {
if (prop.isElementCollection()) {
// skip mapping check
return;
}
DeployBeanDescriptor<?> targetDesc = getTargetDescriptor(prop);
if (targetDesc.isDraftableElement()) {
@@ -1159,7 +1151,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
if (!findMappedBy(prop)) {
if (!prop.isO2mJoinTable()) {
makeUnidirectional(info, prop);
makeUnidirectional(prop);
}
return;
}
@@ -1311,7 +1303,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
DeployBeanInfo<T> info = new DeployBeanInfo<>(deployUtil, desc);
readAnnotations.readInitial(info, eagerFetchLobs);
readAnnotations.readInitial(info);
return info;
}
@@ -1350,7 +1342,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// already assigned (So custom or UUID)
return;
}
if (desc.propertiesId().isEmpty()) {
if (desc.idProperty() == null) {
// bean doesn't have an Id property
if (desc.isBaseTableType() && desc.getBeanFinder() == null) {
// expecting an id property
@@ -1377,9 +1369,15 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
desc.setIdType(IdType.EXTERNAL);
return;
}
// use the default. IDENTITY or SEQUENCE.
desc.setIdType(dbIdentity.getIdType());
desc.setIdTypePlatformDefault();
if (desc.isIdGeneratorAuto() || serverConfig.isIdGeneratorAutomatic()) {
// use IDENTITY or SEQUENCE based on platform
desc.setIdType(dbIdentity.getIdType());
desc.setIdTypePlatformDefault();
} else {
// externally/application supplied Id values
desc.setIdType(IdType.EXTERNAL);
return;
}
}
if (desc.getBaseTable() == null) {
@@ -1395,21 +1393,23 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return;
}
String seqName = desc.getIdGeneratorName();
if (seqName != null) {
logger.debug("explicit sequence {} on {}", seqName, desc.getFullName());
} else {
String primaryKeyColumn = desc.getSinglePrimaryKeyColumn();
// use namingConvention to define sequence name
seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn);
}
if (IdType.SEQUENCE == desc.getIdType()) {
String seqName = desc.getIdGeneratorName();
if (seqName != null) {
logger.debug("explicit sequence {} on {}", seqName, desc.getFullName());
} else {
String primaryKeyColumn = desc.getSinglePrimaryKeyColumn();
// use namingConvention to define sequence name
seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn);
}
if (databasePlatform.isSequenceBatchMode()) {
// use sequence next step 1 as we are going to batch fetch them instead
desc.setSequenceAllocationSize(1);
if (databasePlatform.isSequenceBatchMode()) {
// use sequence next step 1 as we are going to batch fetch them instead
desc.setSequenceAllocationSize(1);
}
int stepSize = desc.getSequenceAllocationSize();
desc.setIdGenerator(createSequenceIdGenerator(seqName, stepSize));
}
int stepSize = desc.getSequenceAllocationSize();
desc.setIdGenerator(createSequenceIdGenerator(seqName, stepSize));
}
private PlatformIdGenerator createSequenceIdGenerator(String seqName, int stepSize) {
@@ -1491,6 +1491,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private boolean isPersistentField(DeployBeanProperty prop) {
Field field = prop.getField();
if (field == null) {
return false;
}
int modifiers = field.getModifiers();
return !(Modifier.isStatic(modifiers) || Modifier.isTransient(modifiers)) && !field.isAnnotationPresent(Transient.class);
}
@@ -1633,6 +1636,43 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
target.setPrimaryKeyJoin(inverseJoin);
}
/**
* Create a DeployBeanDescriptor for an ElementCollection target.
*/
public <A> DeployBeanDescriptor<A> createDeployDescriptor(Class<A> targetType) {
return new DeployBeanDescriptor<>(this, targetType, serverConfig);
}
/**
* Create a BeanDescriptor for an ElementCollection target.
*/
public <A> BeanDescriptor<A> createElementDescriptor(DeployBeanDescriptor<A> elementDescriptor, ManyType manyType, boolean scalar) {
ElementHelp elementHelp = elementHelper(manyType);
if (manyType.isMap()) {
if (scalar) {
return new BeanDescriptorElementScalarMap<>(this, elementDescriptor, elementHelp);
} else {
return new BeanDescriptorElementEmbeddedMap<>(this, elementDescriptor, elementHelp);
}
}
if (scalar) {
return new BeanDescriptorElementScalar<>(this, elementDescriptor, elementHelp);
} else {
return new BeanDescriptorElementEmbedded<>(this, elementDescriptor, elementHelp);
}
}
private ElementHelp elementHelper(ManyType manyType) {
switch (manyType) {
case LIST: return new ElementHelpList();
case SET: return new ElementHelpSet();
case MAP: return new ElementHelpMap();
default:
throw new IllegalStateException("manyType unexpected "+manyType);
}
}
public void visitMetrics(MetricVisitor visitor) {
for (BeanDescriptor<?> desc : immutableDescriptorList) {
desc.visitMetrics(visitor);
@@ -102,7 +102,6 @@ class BeanLifecycleAdapterFactory {
postInserts.add(method);
hasPersistMethods = true;
}
if (method.isAnnotationPresent(PreUpdate.class)) {
preUpdates.add(method);
hasPersistMethods = true;
@@ -111,7 +110,6 @@ class BeanLifecycleAdapterFactory {
postUpdates.add(method);
hasPersistMethods = true;
}
if (method.isAnnotationPresent(PreRemove.class)) {
preDeletes.add(method);
hasPersistMethods = true;
@@ -128,7 +126,6 @@ class BeanLifecycleAdapterFactory {
postSoftDeletes.add(method);
hasPersistMethods = true;
}
if (method.isAnnotationPresent(PostLoad.class)) {
postLoads.add(method);
}
@@ -5,10 +5,9 @@ import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanList;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.ArrayList;
@@ -17,48 +16,20 @@ import java.util.List;
/**
* Helper object for dealing with Lists.
*/
public final class BeanListHelp<T> extends BaseCollectionHelp<T> {
public class BeanListHelp<T> extends BaseCollectionHelp<T> {
private final BeanPropertyAssocMany<T> many;
private final BeanDescriptor<T> targetDescriptor;
private final String propertyName;
private BeanCollectionLoader loader;
public BeanListHelp(BeanPropertyAssocMany<T> many) {
this.many = many;
this.targetDescriptor = many.getTargetDescriptor();
this.propertyName = many.getName();
BeanListHelp(BeanPropertyAssocMany<T> many) {
super(many);
}
public BeanListHelp() {
this.many = null;
this.targetDescriptor = null;
this.propertyName = null;
}
@Override
public void setLoader(BeanCollectionLoader loader) {
this.loader = loader;
}
/**
* Internal add bypassing any modify listening.
*/
@Override
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (withCheck) {
collection.internalAddWithCheck(bean);
} else {
collection.internalAdd(bean);
}
BeanListHelp() {
super();
}
@Override
public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
if (bc instanceof BeanList<?>) {
BeanList<?> bl = (BeanList<?>) bc;
if (bl.getActualList() == null) {
bl.setActualList(new ArrayList<>());
@@ -144,13 +115,7 @@ public final class BeanListHelp<T> extends BaseCollectionHelp<T> {
list = (List<?>) collection;
}
if (!list.isEmpty() || ctx.isIncludeEmpty()) {
ctx.beginAssocMany(name);
for (Object aList : list) {
targetDescriptor.jsonWrite(ctx, (EntityBean) aList);
}
ctx.endAssocMany();
}
jsonWriteCollection(ctx, name, list);
}
}
@@ -0,0 +1,30 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonWriter;
/**
* Helper for element collection List.
*/
public class BeanListHelpElement<T> extends BeanListHelp<T> {
BeanListHelpElement(BeanPropertyAssocMany<T> many) {
super(many);
}
@Override
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
Object elementValue = bean._ebean_getField(0);
if (withCheck) {
collection.internalAddWithCheck(elementValue);
} else {
collection.internalAdd(elementValue);
}
}
@Override
void jsonWriteElement(SpiJsonWriter ctx, Object element) {
many.jsonWriteElementValue(ctx, element);
}
}
@@ -5,10 +5,9 @@ import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanMap;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.LinkedHashMap;
@@ -18,18 +17,17 @@ import java.util.Map.Entry;
/**
* Helper specifically for dealing with Maps.
*/
public final class BeanMapHelp<T> extends BaseCollectionHelp<T> {
public class BeanMapHelp<T> extends BaseCollectionHelp<T> {
private final BeanPropertyAssocMany<T> many;
private final BeanDescriptor<T> targetDescriptor;
private final String propertyName;
private final BeanProperty beanProperty;
private BeanCollectionLoader loader;
/**
* When created for a given query that will return a map.
*/
public BeanMapHelp(BeanDescriptor<T> targetDescriptor, String mapKey) {
BeanMapHelp(BeanDescriptor<T> targetDescriptor, String mapKey) {
this.targetDescriptor = targetDescriptor;
this.beanProperty = targetDescriptor.getBeanProperty(mapKey);
this.many = null;
@@ -39,18 +37,13 @@ public final class BeanMapHelp<T> extends BaseCollectionHelp<T> {
/**
* When help is attached to a specific many property.
*/
public BeanMapHelp(BeanPropertyAssocMany<T> many) {
BeanMapHelp(BeanPropertyAssocMany<T> many) {
this.many = many;
this.targetDescriptor = many.getTargetDescriptor();
this.propertyName = many.getName();
this.beanProperty = targetDescriptor.getBeanProperty(many.getMapKey());
}
@Override
public void setLoader(BeanCollectionLoader loader) {
this.loader = loader;
}
@Override
@SuppressWarnings("unchecked")
public BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
@@ -182,8 +175,7 @@ public final class BeanMapHelp<T> extends BaseCollectionHelp<T> {
if (!map.isEmpty() || ctx.isIncludeEmpty()) {
ctx.beginAssocMany(name);
for (Entry<?, ?> entry : map.entrySet()) {
//FIXME: json write map key ...
targetDescriptor.jsonWrite(ctx, (EntityBean) entry.getValue());
many.jsonWriteMapEntry(ctx, entry);
}
ctx.endAssocMany();
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanMap;
import io.ebeaninternal.api.json.SpiJsonWriter;
public class BeanMapHelpElement<T> extends BeanMapHelp<T> {
BeanMapHelpElement(BeanPropertyAssocMany<T> many) {
super(many);
}
@Override
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
Object key = bean._ebean_getField(0);
Object val = bean._ebean_getField(1);
BeanMap<?, ?> map = ((BeanMap<?, ?>) collection);
if (withCheck) {
map.internalPutWithCheck(key, val);
} else {
map.internalPut(key, val);
}
}
@Override
void jsonWriteElement(SpiJsonWriter ctx, Object element) {
throw new IllegalStateException("not called");
}
}
@@ -13,6 +13,8 @@ import io.ebean.util.SplitName;
import io.ebean.util.StringHelper;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedWhenCreated;
@@ -22,11 +24,9 @@ import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.properties.BeanPropertyGetter;
import io.ebeaninternal.server.properties.BeanPropertySetter;
import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeBoolean;
@@ -56,15 +56,17 @@ import java.util.Set;
* Description of a property of a bean. Includes its deployment information such
* as database column mapping information.
*/
public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty {
public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
private static final Logger logger = LoggerFactory.getLogger(BeanProperty.class);
/**
* Flag to mark this at part of the unique id.
* Flag to mark this is the id property.
*/
final boolean id;
final boolean importedPrimaryKey;
/**
* Flag to make this as a dummy property for unidirecitonal relationships.
*/
@@ -274,6 +276,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
this.name = InternString.intern(deploy.getName());
this.propertyIndex = deploy.getPropertyIndex();
this.unidirectionalShadow = deploy.isUndirectionalShadow();
this.importedPrimaryKey = deploy.isImportedPrimaryKey();
this.discriminator = deploy.isDiscriminator();
this.localEncrypted = deploy.isLocalEncrypted();
this.dbEncrypted = deploy.isDbEncrypted();
@@ -395,6 +398,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
this.softDeleteDbSet = source.softDeleteDbSet;
this.softDeleteDbPredicate = source.softDeleteDbPredicate;
this.fetchEager = source.fetchEager;
this.importedPrimaryKey = source.importedPrimaryKey;
this.unidirectionalShadow = source.unidirectionalShadow;
this.discriminator = source.discriminator;
this.localEncrypted = source.isLocalEncrypted();
@@ -451,11 +455,10 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
* initialise variables that can't be done in construction due to recursive
* issues.
*/
public void initialise() {
public void initialise(BeanDescriptorInitContext initContext) {
// do nothing for normal BeanProperty
if (!isTransient && scalarType == null) {
String msg = "No ScalarType assigned to " + descriptor.getFullName() + "." + getName();
throw new RuntimeException(msg);
throw new RuntimeException("No ScalarType assigned to " + descriptor.getFullName() + "." + getName());
}
}
@@ -473,8 +476,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
this.deployOrder = deployOrder;
}
public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain,
boolean propertyDeploy) {
public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, boolean propertyDeploy) {
return null;
}
@@ -502,6 +504,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
/**
* Return true if this property is based on a formula.
*/
@Override
public boolean isFormula() {
return formula;
}
@@ -527,6 +530,11 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
return descriptor.getEncryptKey(this);
}
@Override
public String getEncryptKeyAsString() {
return getEncryptKey().getStringValue();
}
public String getDecryptProperty(String propertyName) {
return dbEncryptFunction.getDecryptSql(propertyName);
}
@@ -549,6 +557,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
* Add any extra joins required to support this property. Generally a no
* operation except for a OneToOne exported.
*/
@Override
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
if (formula && sqlFormulaJoin != null) {
ctx.appendFormulaJoin(sqlFormulaJoin, joinType);
@@ -573,6 +582,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
return aggregation != null;
}
@Override
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
if (aggregation != null) {
@@ -583,8 +593,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
} else if (!isTransient && !ignoreDraftOnlyProperty(ctx.isDraftQuery())) {
if (secondaryTableJoin != null) {
String relativePrefix = ctx.getRelativePrefix(secondaryTableJoinPrefix);
ctx.pushTableAlias(relativePrefix);
ctx.pushTableAlias(ctx.getRelativePrefix(secondaryTableJoinPrefix));
}
if (dbEncrypted) {
@@ -611,14 +620,17 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
return owningType.isAssignableFrom(type);
}
@Override
public void loadIgnore(DbReadContext ctx) {
scalarType.loadIgnore(ctx.getDataReader());
}
@Override
public void load(SqlBeanLoad sqlBeanLoad) {
sqlBeanLoad.load(this);
}
@Override
public void buildRawSqlSelectChain(String prefix, List<String> selectChain) {
if (prefix == null) {
selectChain.add(name);
@@ -805,13 +817,6 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
setValue(bean, cacheData);
}
/**
* Get the property value from a compound value type.
*/
public Object getValueObject(Object bean) {
throw new RuntimeException("Expected to be called only on BeanPropertyCompoundScalar");
}
@Override
public Object getVal(Object bean) {
return getValue((EntityBean) bean);
@@ -935,6 +940,13 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
return null;
}
/**
* If true this bean maps to the primary key.
*/
public boolean isImportedPrimaryKey() {
return importedPrimaryKey;
}
@Override
public boolean isAssocMany() {
// Returns false - override in BeanPropertyAssocMany.
@@ -1358,6 +1370,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
/**
* Return true if this is included in the unique id.
*/
@Override
public boolean isId() {
return id;
}
@@ -1366,6 +1379,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
* Return true if this is an Embedded property. In this case it shares the
* table and primary key of its owner object.
*/
@Override
public boolean isEmbedded() {
return embedded;
}
@@ -1439,7 +1453,7 @@ public class BeanProperty implements ElPropertyValue, Property, SqlTreeProperty
}
}
public void jsonRead(ReadJson ctx, EntityBean bean) throws IOException {
public void jsonRead(SpiJsonReader ctx, EntityBean bean) throws IOException {
JsonToken event = ctx.nextToken();
if (JsonToken.VALUE_NULL == event) {
@@ -1,10 +1,11 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.bean.EntityBean;
import io.ebean.text.PathProperties;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.id.IdBinder;
@@ -15,7 +16,10 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import io.ebeaninternal.server.query.STreePropertyAssoc;
import io.ebeaninternal.server.query.STreeType;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.querydefn.DefaultOrmQuery;
import io.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import io.ebeanservice.docstore.api.mapping.DocPropertyMapping;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
@@ -30,7 +34,7 @@ import java.util.List;
/**
* Abstract base for properties mapped to an associated bean, list, set or map.
*/
public abstract class BeanPropertyAssoc<T> extends BeanProperty {
public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STreePropertyAssoc {
private static final Logger logger = LoggerFactory.getLogger(BeanPropertyAssoc.class);
@@ -101,15 +105,18 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
* Initialise post construction.
*/
@Override
public void initialise() {
public void initialise(BeanDescriptorInitContext initContext) {
// this *MUST* execute after the BeanDescriptor is
// put into the map to stop infinite recursion
initialiseTargetDescriptor(initContext);
}
void initialiseTargetDescriptor(BeanDescriptorInitContext initContext) {
targetDescriptor = descriptor.getBeanDescriptor(targetType);
if (!isTransient) {
targetIdBinder = targetDescriptor.getIdBinder();
targetInheritInfo = targetDescriptor.getInheritInfo();
saveRecurseSkippable = targetDescriptor.isSaveRecurseSkippable();
if (!targetIdBinder.isComplexId()) {
targetIdProperty = targetIdBinder.getIdProperty();
}
@@ -142,6 +149,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
/**
* Add table join with table alias based on prefix.
*/
@Override
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) {
return tableJoin.addJoin(joinType, prefix, ctx);
}
@@ -149,6 +157,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
/**
* Add table join with explicit table alias.
*/
@Override
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
return tableJoin.addJoin(joinType, a1, a2, ctx);
}
@@ -186,6 +195,29 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
return targetDescriptor;
}
SpiEbeanServer server() {
return descriptor.getEbeanServer();
}
/**
* Create a new query for the target type.
*
* We use target descriptor rather than target property type to support ElementCollection.
*/
public SpiQuery<T> newQuery(SpiEbeanServer server) {
return new DefaultOrmQuery<>(targetDescriptor, server, server.getExpressionFactory());
}
@Override
public IdBinder getIdBinder() {
return descriptor.getIdBinder();
}
@Override
public STreeType target() {
return targetDescriptor;
}
/**
* Return true if the target side has soft delete.
*/
@@ -325,6 +357,13 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
return tableJoin.columns().length <= 0 || tableJoin.columns()[0].isInsertable();
}
/**
* Return the underlying BeanTable for this property.
*/
public BeanTable getBeanTable() {
return beanTable;
}
/**
* return the join to use for the bean.
*/
@@ -440,19 +479,15 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
}
}
EbeanServer server() {
return getBeanDescriptor().getEbeanServer();
}
void bindParentIds(DefaultSqlUpdate delete, List<Object> parentIds) {
if (isExportedSimple()) {
delete.addParameter(new MultiValueWrapper(parentIds));
delete.setNextParameter(new MultiValueWrapper(parentIds));
} else {
// embedded ids etc
List<Object> bindValues = flattenParentIds(parentIds);
for (Object bindValue : bindValues) {
delete.addParameter(bindValue);
delete.setNextParameter(bindValue);
}
}
}
@@ -460,12 +495,12 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
void bindParentId(DefaultSqlUpdate sqlUpd, Object parentId) {
if (isExportedSimple()) {
sqlUpd.addParameter(parentId);
sqlUpd.setNextParameter(parentId);
return;
}
EntityBean parent = (EntityBean) parentId;
for (ExportedProperty exportedProperty : exportedProperties) {
sqlUpd.addParameter(exportedProperty.getValue(parent));
sqlUpd.setNextParameter(exportedProperty.getValue(parent));
}
}
@@ -492,4 +527,26 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty {
private boolean isExportedSimple() {
return exportedProperties.length == 1;
}
/**
* Find and return the exported property matching to this property.
*/
ExportedProperty findMatch(boolean embedded, BeanProperty prop, String matchColumn, TableJoin tableJoin) {
String searchTable = tableJoin.getTable();
for (TableJoinColumn column : tableJoin.columns()) {
String matchTo = column.getLocalDbColumn();
if (matchColumn.equalsIgnoreCase(matchTo)) {
String foreignCol = column.getForeignDbColumn();
return new ExportedProperty(embedded, foreignCol, prop);
}
}
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?";
throw new PersistenceException(msg);
}
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.Query;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
@@ -9,22 +9,24 @@ import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.text.PathProperties;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.query.STreePropertyAssocMany;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.io.StringWriter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
@@ -35,7 +37,7 @@ import java.util.Map;
/**
* Property mapped to a List Set or Map.
*/
public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements STreePropertyAssocMany {
private static final Logger logger = LoggerFactory.getLogger(BeanPropertyAssocMany.class);
@@ -51,7 +53,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
/**
* For ManyToMany this is the Inverse join used to build reference queries.
*/
private final TableJoin inverseJoin;
final TableJoin inverseJoin;
/**
* Flag to indicate that this is a unidirectional relationship.
@@ -70,6 +72,13 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
*/
private final boolean manyToMany;
private final boolean elementCollection;
/**
* Descriptor for the 'target' when the property maps to an element collection.
*/
BeanDescriptor<T> elementDescriptor;
/**
* Order by used when fetch joining the associated many.
*/
@@ -91,8 +100,6 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
private BeanProperty mapKeyProperty;
private String exportedPropertyBindProto = "?";
/**
* Property on the 'child' bean that links back to the 'master'.
*/
@@ -106,9 +113,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
private ImportedId importedId;
private String deleteByParentIdSql;
private String deleteByParentIdInSql;
private BeanPropertyAssocManySqlHelp<T> sqlHelp;
/**
* Create this property.
@@ -119,6 +124,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
this.o2mJoinTable = deploy.isO2mJoinTable();
this.hasOrderColumn = deploy.hasOrderColumn();
this.manyToMany = deploy.isManyToMany();
this.elementCollection = deploy.isElementCollection();
this.elementDescriptor = deploy.getElementDescriptor();
this.manyType = deploy.getManyType();
this.mapKey = deploy.getMapKey();
this.fetchOrderBy = deploy.getFetchOrderBy();
@@ -136,13 +143,28 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
@Override
public void initialise() {
super.initialise();
public void initialise(BeanDescriptorInitContext initContext) {
super.initialise(initContext);
initialiseAssocMany();
if (elementCollection) {
// initialise all non-id properties (we don't have an Id property)
elementDescriptor.initialiseOther(initContext);
}
}
@Override
void initialiseTargetDescriptor(BeanDescriptorInitContext initContext) {
if (elementCollection) {
targetDescriptor = elementDescriptor;
} else {
super.initialiseTargetDescriptor(initContext);
}
}
private void initialiseAssocMany() {
if (!isTransient) {
this.help = BeanCollectionHelpFactory.create(this);
if (hasJoinTable()) {
if (hasJoinTable() || elementCollection) {
importedId = createImportedId(this, targetDescriptor, tableJoin);
} else {
@@ -159,37 +181,21 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
exportedProperties = createExported();
this.sqlHelp = new BeanPropertyAssocManySqlHelp<>(this, exportedProperties);
if (exportedProperties.length > 0) {
embeddedExportedProperties = exportedProperties[0].isEmbedded();
exportedPropertyBindProto = deriveExportedPropertyBindProto();
if (fetchOrderBy != null) {
// derive lazyFetchOrderBy
StringBuilder sb = new StringBuilder(50);
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(", ");
}
// these fk columns are either on the intersection (int_) or base table (t0)
String fkTableAlias = hasJoinTable() ? "int_" : "t0";
sb.append(fkTableAlias).append(".").append(exportedProperties[i].getForeignDbColumn());
}
sb.append(", ").append(fetchOrderBy);
lazyFetchOrderBy = sb.toString().trim();
lazyFetchOrderBy = sqlHelp.lazyFetchOrderBy(fetchOrderBy);
}
}
String delStmt;
if (hasJoinTable()) {
delStmt = "delete from " + inverseJoin.getTable() + " where ";
} else {
delStmt = "delete from " + targetDescriptor.getBaseTable() + " where ";
}
deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false, "");
deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true, "");
}
}
String targetTable() {
return beanTable.getBaseTable();
}
/**
* Initialise after the target bean descriptors have been all set.
*/
@@ -295,44 +301,23 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public SqlUpdate deleteByParentId(Object parentId, List<Object> parentIdist) {
if (parentId != null) {
return deleteByParentId(parentId);
return sqlHelp.deleteByParentId(parentId);
} else {
return deleteByParentIdList(parentIdist);
return sqlHelp.deleteByParentIdList(parentIdist);
}
}
private SqlUpdate deleteByParentId(Object parentId) {
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByParentIdSql);
bindParentId(sqlDelete, parentId);
return sqlDelete;
}
/**
* Find the Id's of detail beans given a parent Id or list of parent Id's.
*/
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdList, Transaction t, List<Object> excludeDetailIds) {
if (parentId != null) {
return findIdsByParentId(parentId, t, excludeDetailIds);
return sqlHelp.findIdsByParentId(parentId, t, excludeDetailIds);
} else {
return findIdsByParentIdList(parentIdList, t, excludeDetailIds);
return sqlHelp.findIdsByParentIdList(parentIdList, t, excludeDetailIds);
}
}
private List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(false, "");
EbeanServer server = server();
Query<?> q = server.find(getPropertyType());
bindParentIdEq(rawWhere, parentId, q);
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
}
/**
* Exclude many properties from bean cache data.
*/
@@ -359,88 +344,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
// assumes the ManyToOne property is included
query.where().in(childMasterIdProperty, parentIds);
} else {
addWhereParentIdIn(query, parentIds);
sqlHelp.addWhereParentIdIn(query, parentIds);
}
}
/**
* Add a where clause to the query for a given list of parent Id's.
*/
private void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds) {
String tableAlias = hasJoinTable() ? "int_." : "t0.";
if (hasJoinTable()) {
query.setM2MIncludeJoin(inverseJoin);
}
String rawWhere = deriveWhereParentIdSql(true, tableAlias);
String expr = descriptor.getParentIdInExpr(parentIds.size(), rawWhere);
bindParentIdsIn(expr, parentIds, query);
}
private List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(true, "");
String inClause = buildInClauseBinding(parentIds.size(), exportedPropertyBindProto);
String expr = rawWhere + inClause;
EbeanServer server = descriptor.getEbeanServer();
Query<?> q = server.find(propertyType);
bindParentIdsIn(expr, parentIds, q);
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
}
private SqlUpdate deleteByParentIdList(List<Object> parentIds) {
StringBuilder sb = new StringBuilder(100);
sb.append(deleteByParentIdInSql);
sb.append(buildInClauseBinding(parentIds.size(), exportedPropertyBindProto));
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
bindParentIds(delete, parentIds);
return delete;
}
private String deriveExportedPropertyBindProto() {
if (exportedProperties.length == 1) {
return "?";
}
StringBuilder sb = new StringBuilder();
sb.append("(");
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append("?");
}
sb.append(")");
return sb.toString();
}
private String buildInClauseBinding(int size, String bindProto) {
if (descriptor.isSimpleId()) {
return descriptor.getIdBinder().getIdInValueExpr(false, size);
}
StringBuilder sb = new StringBuilder(10 + (size * (bindProto.length() + 1)));
sb.append(" in");
sb.append(" (");
for (int i = 0; i < size; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(bindProto);
}
sb.append(") ");
return sb.toString();
}
/**
* Set the lazy load server to help create reference collections (that lazy
* load on demand).
@@ -476,7 +383,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
@Override
public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
public Object readSet(DbReadContext ctx, EntityBean bean) {
return null;
}
@@ -616,6 +523,10 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
return manyToMany;
}
public boolean isElementCollection() {
return elementCollection;
}
/**
* ManyToMany only, join from local table to intersection table.
*/
@@ -705,30 +616,6 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
}
private String deriveWhereParentIdSql(boolean inClause, String tableAlias) {
StringBuilder sb = new StringBuilder();
if (inClause) {
sb.append("(");
}
for (int i = 0; i < exportedProperties.length; i++) {
String fkColumn = exportedProperties[i].getForeignDbColumn();
if (i > 0) {
String s = inClause ? "," : " and ";
sb.append(s);
}
sb.append(tableAlias).append(fkColumn);
if (!inClause) {
sb.append("=? ");
}
}
if (inClause) {
sb.append(")");
}
return sb.toString();
}
/**
* Create the array of ExportedProperty used to build reference objects.
*/
@@ -741,12 +628,9 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
if (idProp != null && idProp.isEmbedded()) {
BeanPropertyAssocOne<?> one = (BeanPropertyAssocOne<?>) idProp;
BeanDescriptor<?> targetDesc = one.getTargetDescriptor();
BeanProperty[] emIds = targetDesc.propertiesBaseScalar();
try {
for (BeanProperty emId : emIds) {
ExportedProperty expProp = findMatch(true, emId);
list.add(expProp);
for (BeanProperty emId : one.getTargetDescriptor().propertiesBaseScalar()) {
list.add(findMatch(true, emId));
}
} catch (PersistenceException e) {
// not found as individual scalar properties
@@ -755,8 +639,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
} else {
if (idProp != null) {
ExportedProperty expProp = findMatch(false, idProp);
list.add(expProp);
list.add(findMatch(false, idProp));
}
}
@@ -768,32 +651,12 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
*/
private ExportedProperty findMatch(boolean embedded, BeanProperty prop) {
String matchColumn = prop.getDbColumn();
String searchTable;
TableJoinColumn[] columns;
if (hasJoinTable()) {
// look for column going to intersection
columns = intersectionJoin.columns();
searchTable = intersectionJoin.getTable();
return findMatch(embedded, prop, prop.getDbColumn(), intersectionJoin);
} else {
columns = tableJoin.columns();
searchTable = tableJoin.getTable();
return findMatch(embedded, prop, prop.getDbColumn(), tableJoin);
}
for (TableJoinColumn column : columns) {
String matchTo = column.getLocalDbColumn();
if (matchColumn.equalsIgnoreCase(matchTo)) {
String foreignCol = column.getForeignDbColumn();
return new ExportedProperty(embedded, foreignCol, prop);
}
}
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?";
throw new PersistenceException(msg);
}
/**
@@ -813,8 +676,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
Class<?> beanType = descriptor.getBeanType();
BeanDescriptor<?> targetDesc = getTargetDescriptor();
BeanPropertyAssocOne<?>[] ones = targetDesc.propertiesOne();
for (BeanPropertyAssocOne<?> prop : ones) {
for (BeanPropertyAssocOne<?> prop : targetDesc.propertiesOne()) {
if (mappedBy != null) {
// match using mappedBy as property name
if (mappedBy.equalsIgnoreCase(prop.getName())) {
@@ -880,9 +742,9 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
/**
* Register the mapping of intersection table to associated draft table.
*/
public void registerDraftIntersectionTable(Map<String, String> draftTableMap) {
public void registerDraftIntersectionTable(BeanDescriptorInitContext initContext) {
if (hasDraftIntersection()) {
draftTableMap.put(intersectionPublishTable, intersectionDraftTable);
initContext.addDraftIntersection(intersectionPublishTable, intersectionDraftTable);
}
}
@@ -928,7 +790,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
* Skip JSON write value for ToMany property.
*/
@Override
public void jsonWriteValue(SpiJsonWriter writeJson, Object value) throws IOException {
public void jsonWriteValue(SpiJsonWriter writeJson, Object value) {
// do nothing, exclude ToMany properties
}
@@ -946,7 +808,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
if (value != null) {
ctx.pushParentBeanMany(bean);
if (help != null) {
help.jsonWrite(ctx, name, value, include != null);
jsonWriteCollection(ctx, name, value, include != null);
} else {
if (isTransient && targetDescriptor == null) {
ctx.writeValueUsingObjectMapper(name, value);
@@ -962,7 +824,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
@Override
public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException {
public void jsonRead(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
jsonHelp.jsonRead(readJson, parentBean);
}
@@ -1030,4 +892,118 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public boolean isIncludeCascadeDelete() {
return cascadeInfo.isDelete() || o2mJoinTable || ModifyListenMode.REMOVALS == modifyListenMode;
}
public String insertElementCollection() {
return sqlHelp.insertElementCollection();
}
public boolean isTargetDocStoreMapped() {
return targetDescriptor.isDocStoreMapped();
}
public BeanCollectionHelp<T> getHelp() {
return help;
}
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
elementDescriptor.jsonWriteMapEntry(ctx, entry);
}
public void jsonWriteElementValue(SpiJsonWriter ctx, Object element) {
elementDescriptor.jsonWriteElement(ctx, element);
}
/**
* A Many (element collection) property in bean cache to held as JSON.
*/
@Override
public void setCacheDataValue(EntityBean bean, Object cacheData, PersistenceContext context) {
try {
String asJson = (String) cacheData;
Object collection = jsonReadCollection(asJson);
setValue(bean, collection);
} catch (Exception e) {
logger.error("Error setting value from L2 cache", e);
}
}
@Override
public Object getCacheDataValue(EntityBean bean) {
try {
Object collection = getValue(bean);
if (collection == null) {
return null;
}
return jsonWriteCollection(collection);
} catch (Exception e) {
logger.error("Error building value element collection json for L2 cache", e);
return null;
}
}
/**
* Write the collection to JSON.
*/
public String jsonWriteCollection(Object value) throws IOException {
StringWriter writer = new StringWriter(300);
SpiJsonWriter ctx = descriptor.createJsonWriter(writer);
help.jsonWrite(ctx, null, value, false);
ctx.flush();
return writer.toString();
}
/**
* Read the collection as JSON.
*/
public Object jsonReadCollection(String json) throws IOException {
SpiJsonReader ctx = descriptor.createJsonReader(json);
JsonParser parser = ctx.getParser();
JsonToken event = parser.nextToken();
if (JsonToken.VALUE_NULL == event) {
return null;
}
return jsonReadCollection(ctx, null);
}
/**
* Write the collection to JSON.
*/
public void jsonWriteCollection(SpiJsonWriter ctx, String name, Object value, boolean explicitInclude) throws IOException {
help.jsonWrite(ctx, name, value, explicitInclude);
}
/**
* Read the collection (JSON Array) containing entity beans.
*/
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
if (elementDescriptor != null && manyType.isMap()) {
return elementDescriptor.jsonReadCollection(readJson, parentBean);
}
BeanCollection<?> collection = createEmpty(parentBean);
BeanCollectionAdd add = getBeanCollectionAdd(collection, null);
do {
EntityBean detailBean = (EntityBean) targetDescriptor.jsonRead(readJson, name);
if (detailBean == null) {
// read the entire array
break;
}
add.addEntityBean(detailBean);
if (parentBean != null && childMasterProperty != null) {
// bind detail bean back to master via mappedBy property
childMasterProperty.setValue(detailBean, parentBean);
}
} while (true);
return collection;
}
/**
* Bind all the property values to the SqlUpdate.
*/
public void bindElementValue(SqlUpdate insert, Object value) {
targetDescriptor.bindElementValue(insert, value);
}
}
@@ -3,17 +3,15 @@ package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.api.json.SpiJsonReader;
import java.io.IOException;
/**
* Help BeanPropertyAssocMany with JSON processing.
*/
public class BeanPropertyAssocManyJsonHelp {
class BeanPropertyAssocManyJsonHelp {
/**
* The associated many property.
@@ -28,7 +26,7 @@ public class BeanPropertyAssocManyJsonHelp {
/**
* Construct for the owning many property.
*/
public BeanPropertyAssocManyJsonHelp(BeanPropertyAssocMany<?> many) {
BeanPropertyAssocManyJsonHelp(BeanPropertyAssocMany<?> many) {
this.many = many;
boolean objectMapperPresent = many.getBeanDescriptor().getServerConfig().getClassLoadConfig().isJacksonObjectMapperPresent();
this.jsonTransient = !objectMapperPresent ? null : new BeanPropertyAssocManyJsonTransient();
@@ -37,7 +35,7 @@ public class BeanPropertyAssocManyJsonHelp {
/**
* Read the JSON for this property.
*/
public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException {
public void jsonRead(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
if (!this.many.jsonDeserialize) {
return;
@@ -48,38 +46,23 @@ public class BeanPropertyAssocManyJsonHelp {
if (JsonToken.VALUE_NULL == event) {
return;
}
if (JsonToken.START_ARRAY != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array ");
}
if (many.isTransient()) {
jsonReadTransientUsingObjectMapper(readJson, parentBean);
return;
}
BeanCollection<?> collection = many.createEmpty(parentBean);
BeanCollectionAdd add = many.getBeanCollectionAdd(collection, null);
do {
EntityBean detailBean = (EntityBean) many.targetDescriptor.jsonRead(readJson, many.name);
if (detailBean == null) {
// read the entire array
break;
}
add.addEntityBean(detailBean);
if (JsonToken.START_ARRAY != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array");
}
if (parentBean != null && many.childMasterProperty != null) {
// bind detail bean back to master via mappedBy property
many.childMasterProperty.setValue(detailBean, parentBean);
}
} while (true);
many.setValue(parentBean, collection);
many.setValue(parentBean, many.jsonReadCollection(readJson, parentBean));
}
/**
* Read a Transient property using Jackson ObjectMapper.
*/
private void jsonReadTransientUsingObjectMapper(ReadJson readJson, EntityBean parentBean) throws IOException {
private void jsonReadTransientUsingObjectMapper(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
if (jsonTransient == null) {
throw new IllegalStateException("Jackson ObjectMapper is required to read this Transient property " + many.getFullBeanName());
@@ -6,7 +6,7 @@ import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.api.json.SpiJsonReader;
import java.io.IOException;
import java.util.LinkedHashMap;
@@ -14,12 +14,12 @@ import java.util.LinkedHashMap;
/**
* Helper used to read transient many properties using Jackson ObjectMapper.
*/
public class BeanPropertyAssocManyJsonTransient {
class BeanPropertyAssocManyJsonTransient {
/**
* Use Jackson ObjectMapper to read the transient 'many' property.
*/
public void jsonReadUsingObjectMapper(BeanPropertyAssocMany<?> many, ReadJson readJson, EntityBean parentBean) throws IOException {
void jsonReadUsingObjectMapper(BeanPropertyAssocMany<?> many, SpiJsonReader readJson, EntityBean parentBean) throws IOException {
ObjectMapper mapper = readJson.getObjectMapper();
@@ -0,0 +1,258 @@
package io.ebeaninternal.server.deploy;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.dbmigration.model.visitor.BaseTablePropertyVisitor;
import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import java.util.List;
class BeanPropertyAssocManySqlHelp<T> {
private final BeanPropertyAssocMany<T> many;
private final ExportedProperty[] exportedProperties;
private final boolean hasJoinTable;
private final BeanDescriptor<?> descriptor;
private final String exportedPropertyBindProto;
private final String deleteByParentIdSql;
private final String deleteByParentIdInSql;
private final String elementCollectionInsertSql;
BeanPropertyAssocManySqlHelp(BeanPropertyAssocMany<T> many, ExportedProperty[] exportedProperties) {
this.many = many;
this.exportedProperties = exportedProperties;
this.hasJoinTable = many.hasJoinTable();
this.descriptor = many.getBeanDescriptor();
this.exportedPropertyBindProto = deriveExportedPropertyBindProto();
String delStmt;
if (hasJoinTable) {
delStmt = "delete from " + many.inverseJoin.getTable() + " where ";
} else {
delStmt = "delete from " + many.targetTable() + " where ";
}
deleteByParentIdSql = delStmt + deriveWhereParentIdSql(false, "");
deleteByParentIdInSql = delStmt + deriveWhereParentIdSql(true, "");
if (many.isElementCollection()) {
elementCollectionInsertSql = elementCollectionInsert();
} else {
elementCollectionInsertSql = null;
}
}
private String elementCollectionInsert() {
StringBuilder sb = new StringBuilder(200);
sb.append("insert into ").append(many.targetTable()).append(" (");
append(sb, "", ",", "");
Cols cols = new Cols(sb);
VisitAllUsing.visitOne(many.targetDescriptor, cols);
sb.append(") values (");
appendBind(sb, exportedProperties.length, true);
appendBind(sb, cols.colCount, false);
sb.append(")");
return sb.toString();
}
public String insertElementCollection() {
return elementCollectionInsertSql;
}
private static class Cols extends BaseTablePropertyVisitor {
int colCount;
private final StringBuilder sb;
private Cols(StringBuilder sb) {
this.sb = sb;
}
@Override
public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne<?> embedded) {
sb.append(",").append(p.getDbColumn());
colCount++;
}
@Override
public void visitOneImported(BeanPropertyAssocOne<?> p) {
}
@Override
public void visitScalar(BeanProperty p) {
sb.append(",").append(p.getDbColumn());
colCount++;
}
@Override
public void visitEnd() {
}
}
String lazyFetchOrderBy(String fetchOrderBy) {
// derive lazyFetchOrderBy
StringBuilder sb = new StringBuilder(50);
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(", ");
}
// these fk columns are either on the intersection (int_) or base table (t0)
String fkTableAlias = hasJoinTable ? "int_" : "t0";
sb.append(fkTableAlias).append(".").append(exportedProperties[i].getForeignDbColumn());
}
sb.append(", ").append(fetchOrderBy);
return sb.toString().trim();
}
/**
* Add a where clause to the query for a given list of parent Id's.
*/
void addWhereParentIdIn(SpiQuery<?> query, List<Object> parentIds) {
String tableAlias = hasJoinTable ? "int_." : "t0.";
if (hasJoinTable) {
query.setM2MIncludeJoin(many.inverseJoin);
}
String rawWhere = deriveWhereParentIdSql(true, tableAlias);
String expr = descriptor.getParentIdInExpr(parentIds.size(), rawWhere);
many.bindParentIdsIn(expr, parentIds, query);
}
List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(false, "");
SpiEbeanServer server = descriptor.getEbeanServer();
SpiQuery<?> q = many.newQuery(server);
many.bindParentIdEq(rawWhere, parentId, q);
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
}
List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds) {
String rawWhere = deriveWhereParentIdSql(true, "");
String inClause = buildInClauseBinding(parentIds.size(), exportedPropertyBindProto);
String expr = rawWhere + inClause;
SpiEbeanServer server = descriptor.getEbeanServer();
SpiQuery<?> q = many.newQuery(server);
//Query<?> q = server.find(propertyType);
many.bindParentIdsIn(expr, parentIds, q);
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
return server.findIds(q, t);
}
SqlUpdate deleteByParentId(Object parentId) {
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(deleteByParentIdSql);
many.bindParentId(sqlDelete, parentId);
return sqlDelete;
}
SqlUpdate deleteByParentIdList(List<Object> parentIds) {
StringBuilder sb = new StringBuilder(100);
sb.append(deleteByParentIdInSql);
sb.append(buildInClauseBinding(parentIds.size(), exportedPropertyBindProto));
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
many.bindParentIds(delete, parentIds);
return delete;
}
private void appendBind(StringBuilder sb, int count, boolean skipComma) {
for (int i = 0; i < count; i++) {
if (!skipComma || i > 0) {
sb.append(",");
}
sb.append("?");
}
}
private void append(StringBuilder sb, String tableAlias, String prefix, String suffix) {
for (int i = 0; i < exportedProperties.length; i++) {
String fkColumn = exportedProperties[i].getForeignDbColumn();
if (i > 0) {
sb.append(prefix);
}
sb.append(tableAlias).append(fkColumn);
sb.append(suffix);
}
}
private String deriveWhereParentIdSql(boolean inClause, String tableAlias) {
StringBuilder sb = new StringBuilder();
if (inClause) {
sb.append("(");
}
for (int i = 0; i < exportedProperties.length; i++) {
String fkColumn = exportedProperties[i].getForeignDbColumn();
if (i > 0) {
String s = inClause ? "," : " and ";
sb.append(s);
}
sb.append(tableAlias).append(fkColumn);
if (!inClause) {
sb.append("=? ");
}
}
if (inClause) {
sb.append(")");
}
return sb.toString();
}
private String buildInClauseBinding(int size, String bindProto) {
if (descriptor.isSimpleId()) {
return descriptor.getIdBinder().getIdInValueExpr(false, size);
}
StringBuilder sb = new StringBuilder(10 + (size * (bindProto.length() + 1)));
sb.append(" in");
sb.append(" (");
for (int i = 0; i < size; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(bindProto);
}
sb.append(") ");
return sb.toString();
}
private String deriveExportedPropertyBindProto() {
if (exportedProperties.length == 1) {
return "?";
}
StringBuilder sb = new StringBuilder();
sb.append("(");
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append("?");
}
sb.append(")");
return sb.toString();
}
}
@@ -9,6 +9,8 @@ import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cache.CachedBeanData;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
@@ -16,10 +18,10 @@ import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.query.STreePropertyAssocOne;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.server.type.ScalarType;
import javax.persistence.PersistenceException;
import java.io.IOException;
@@ -32,7 +34,7 @@ import java.util.Map;
/**
* Property mapped to a joined bean.
*/
public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STreePropertyAssocOne {
private final boolean oneToOne;
@@ -40,8 +42,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
private final boolean orphanRemoval;
private final boolean importedPrimaryKey;
private final boolean primaryKeyExport;
private final PropertyForeignKey foreignKey;
@@ -76,7 +76,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
foreignKey = deploy.getForeignKey();
primaryKeyExport = deploy.isPrimaryKeyExport();
importedPrimaryKey = deploy.isImportedPrimaryKey();
oneToOne = deploy.isOneToOne();
oneToOneExported = deploy.isOneToOneExported();
orphanRemoval = deploy.isOrphanRemoval();
@@ -97,8 +96,12 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
@Override
public void initialise() {
super.initialise();
public void initialise(BeanDescriptorInitContext initContext) {
super.initialise(initContext);
initialiseAssocOne();
}
private void initialiseAssocOne() {
localHelp = createHelp(embedded, oneToOneExported);
if (!isTransient) {
@@ -307,6 +310,10 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
String discProperty = prefix + "." + discriminatorColumn;
selectChain.add(discProperty);
}
if (targetIdBinder == null) {
throw new IllegalStateException("No Id binding property for " + getFullBeanName()
+ ". Probably a missing @OneToOne mapping annotation on this relationship?");
}
targetIdBinder.buildRawSqlSelectChain(prefix, selectChain);
} else {
@@ -346,13 +353,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
return orphanRemoval;
}
/**
* If true this bean maps to the primary key.
*/
public boolean isImportedPrimaryKey() {
return importedPrimaryKey;
}
@Override
public void diff(String prefix, Map<String, ValuePair> map, EntityBean newBean, EntityBean oldBean) {
@@ -427,6 +427,11 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
}
@Override
public ScalarType<?> getIdScalarType() {
return targetDescriptor.getIdProperty().getScalarType();
}
/**
* Return the Id values from the given bean.
*/
@@ -549,24 +554,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
*/
private ExportedProperty findMatch(boolean embeddedProp, BeanProperty prop) {
String matchColumn = prop.getDbColumn();
String searchTable = tableJoin.getTable();
TableJoinColumn[] columns = tableJoin.columns();
for (TableJoinColumn column : columns) {
String matchTo = column.getLocalDbColumn();
if (matchColumn.equalsIgnoreCase(matchTo)) {
String foreignCol = column.getForeignDbColumn();
return new ExportedProperty(embeddedProp, foreignCol, prop);
}
}
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?";
throw new PersistenceException(msg);
return findMatch(embeddedProp, prop, prop.getDbColumn(), tableJoin);
}
@@ -626,6 +614,14 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
}
@Override
public void setValueIntercept(EntityBean bean, Object value) {
super.setValueIntercept(bean, value);
if (embedded && value instanceof EntityBean) {
setEmbeddedOwner(bean, value);
}
}
/**
* For embedded bean set the owner and all properties to be loaded (recursively).
*/
@@ -638,18 +634,10 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
}
private void setEmbeddedOwner(EntityBean bean, Object value) {
void setEmbeddedOwner(EntityBean bean, Object value) {
((EntityBean) value)._ebean_getIntercept().setEmbeddedOwner(bean, propertyIndex);
}
@Override
public void setValueIntercept(EntityBean bean, Object value) {
super.setValueIntercept(bean, value);
if (embedded && value instanceof EntityBean) {
setEmbeddedOwner(bean, value);
}
}
@Override
public void loadIgnore(DbReadContext ctx) {
localHelp.loadIgnore(ctx);
@@ -708,10 +696,10 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
if (embedded) {
writeJson.writeFieldName(name);
BeanDescriptor<?> refDesc = descriptor.getBeanDescriptor(value.getClass());
refDesc.jsonWriteForInsert(writeJson, (EntityBean)value);
refDesc.jsonWriteForInsert(writeJson, (EntityBean) value);
} else {
jsonWriteTargetId(writeJson, (EntityBean)value);
jsonWriteTargetId(writeJson, (EntityBean) value);
}
}
}
@@ -756,7 +744,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
}
@Override
public void jsonRead(ReadJson readJson, EntityBean bean) throws IOException {
public void jsonRead(SpiJsonReader readJson, EntityBean bean) throws IOException {
if (jsonDeserialize && targetDescriptor != null) {
T assocBean = targetDescriptor.jsonRead(readJson, name);
setValue(bean, assocBean);
@@ -0,0 +1,35 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
/**
* Bean property for an IdClass embeddedId.
*/
public class BeanPropertyIdClass extends BeanPropertyAssocOne {
public BeanPropertyIdClass(BeanDescriptorMap owner, BeanDescriptor descriptor, DeployBeanPropertyAssocOne deploy) {
super(owner, descriptor, deploy);
}
@Override
public void setValue(EntityBean bean, Object value) {
bean._ebean_getIntercept().setOwnerId(value);
}
@Override
public void setValueIntercept(EntityBean bean, Object value) {
bean._ebean_getIntercept().setOwnerId(value);
}
@Override
public Object getValue(EntityBean bean) {
return bean._ebean_getIntercept().getOwnerId();
}
@Override
public Object getValueIntercept(EntityBean bean) {
return bean._ebean_getIntercept().getOwnerId();
}
}
@@ -1,11 +1,25 @@
package io.ebeaninternal.server.deploy;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertySimpleCollection;
import java.io.IOException;
public class BeanPropertySimpleCollection<T> extends BeanPropertyAssocMany<T> {
public BeanPropertySimpleCollection(BeanDescriptor<?> descriptor, DeployBeanPropertySimpleCollection<T> deploy) {
super(descriptor, deploy);
}
@Override
public void bindElementValue(SqlUpdate insert, Object value) {
insert.setNextParameter(value);
}
@Override
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
return elementDescriptor.jsonReadCollection(readJson, parentBean);
}
}
@@ -5,10 +5,9 @@ import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanSet;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.LinkedHashSet;
@@ -17,34 +16,20 @@ import java.util.Set;
/**
* Helper specifically for dealing with Sets.
*/
public final class BeanSetHelp<T> extends BaseCollectionHelp<T> {
private final BeanPropertyAssocMany<T> many;
private final BeanDescriptor<T> targetDescriptor;
private final String propertyName;
private BeanCollectionLoader loader;
public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
/**
* When attached to a specific many property.
*/
public BeanSetHelp(BeanPropertyAssocMany<T> many) {
this.many = many;
this.targetDescriptor = many.getTargetDescriptor();
this.propertyName = many.getName();
BeanSetHelp(BeanPropertyAssocMany<T> many) {
super(many);
}
/**
* For a query that returns a set.
*/
public BeanSetHelp() {
this.many = null;
this.targetDescriptor = null;
this.propertyName = null;
}
@Override
public void setLoader(BeanCollectionLoader loader) {
this.loader = loader;
BeanSetHelp() {
super();
}
@Override
@@ -61,15 +46,6 @@ public final class BeanSetHelp<T> extends BaseCollectionHelp<T> {
}
}
@Override
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
if (withCheck) {
collection.internalAddWithCheck(bean);
} else {
collection.internalAdd(bean);
}
}
@Override
public BeanCollection<T> createEmptyNoParent() {
return new BeanSet<>();
@@ -142,13 +118,6 @@ public final class BeanSetHelp<T> extends BaseCollectionHelp<T> {
} else {
set = (Set<?>) collection;
}
if (!set.isEmpty() || ctx.isIncludeEmpty()) {
ctx.beginAssocMany(name);
for (Object bean : set) {
targetDescriptor.jsonWrite(ctx, (EntityBean) bean);
}
ctx.endAssocMany();
}
jsonWriteCollection(ctx, name, set);
}
}
@@ -0,0 +1,30 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.json.SpiJsonWriter;
/**
* Helper for element collection List.
*/
public class BeanSetHelpElement<T> extends BeanSetHelp<T> {
BeanSetHelpElement(BeanPropertyAssocMany<T> many) {
super(many);
}
@Override
public void add(BeanCollection<?> collection, EntityBean bean, boolean withCheck) {
Object elementValue = bean._ebean_getField(0);
if (withCheck) {
collection.internalAddWithCheck(elementValue);
} else {
collection.internalAdd(elementValue);
}
}
@Override
void jsonWriteElement(SpiJsonWriter ctx, Object element) {
many.jsonWriteElementValue(ctx, element);
}
}
@@ -30,7 +30,7 @@ public class BeanTable {
*/
private final String baseTable;
private final BeanProperty[] idProperties;
private final BeanProperty idProperty;
/**
* Create the BeanTable.
@@ -39,7 +39,17 @@ public class BeanTable {
this.owner = owner;
this.beanType = mutable.getBeanType();
this.baseTable = InternString.intern(mutable.getBaseTable());
this.idProperties = mutable.createIdProperties(owner);
this.idProperty = mutable.createIdProperty(owner);
}
/**
* Construct for element collection.
*/
public BeanTable(BeanDescriptorMap owner, String tableName, Class<?> beanType) {
this.owner = owner;
this.beanType = beanType;
this.baseTable = tableName;
this.idProperty = null;
}
@Override
@@ -69,8 +79,8 @@ public class BeanTable {
/**
* Return the Id properties.
*/
public BeanProperty[] getIdProperties() {
return idProperties;
public BeanProperty getIdProperty() {
return idProperty;
}
/**
@@ -82,52 +92,51 @@ public class BeanTable {
public void createJoinColumn(String foreignKeyPrefix, DeployTableJoin join, boolean reverse, String sqlFormulaSelect) {
boolean complexKey = false;
BeanProperty[] props = idProperties;
if (idProperties.length == 1) {
if (idProperties[0] instanceof BeanPropertyAssocOne<?>) {
BeanPropertyAssocOne<?> assocOne = (BeanPropertyAssocOne<?>) idProperties[0];
props = assocOne.getProperties();
complexKey = true;
}
if (idProperty == null) {
return;
}
for (BeanProperty prop : props) {
if (idProperty instanceof BeanPropertyAssocOne<?>) {
BeanPropertyAssocOne<?> assocOne = (BeanPropertyAssocOne<?>) idProperty;
BeanProperty[] props = assocOne.getProperties();
for (BeanProperty prop : props) {
addToJoin(foreignKeyPrefix, join, reverse, sqlFormulaSelect, true, prop);
}
} else {
addToJoin(foreignKeyPrefix, join, reverse, sqlFormulaSelect, false, idProperty);
}
}
String lc = prop.getDbColumn();
String fk = lc;
if (foreignKeyPrefix != null) {
fk = owner.getNamingConvention().getForeignKey(foreignKeyPrefix, fk);
}
private void addToJoin(String foreignKeyPrefix, DeployTableJoin join, boolean reverse, String sqlFormulaSelect, boolean complexKey, BeanProperty prop) {
if (complexKey) {
// just to copy the column name rather than prefix with the foreignKeyPrefix.
// I think that with complex keys this is the more common approach.
logger.debug("On table[{}] foreign key column [{}]", baseTable, lc);
fk = lc;
}
if (sqlFormulaSelect != null) {
fk = sqlFormulaSelect;
}
DeployTableJoinColumn joinCol = new DeployTableJoinColumn(lc, fk);
joinCol.setForeignSqlFormula(sqlFormulaSelect);
if (reverse) {
joinCol = joinCol.reverse();
}
join.addJoinColumn(joinCol);
String lc = prop.getDbColumn();
String fk = lc;
if (foreignKeyPrefix != null) {
fk = owner.getNamingConvention().getForeignKey(foreignKeyPrefix, fk);
}
if (complexKey) {
// just to copy the column name rather than prefix with the foreignKeyPrefix.
// I think that with complex keys this is the more common approach.
logger.debug("On table[{}] foreign key column [{}]", baseTable, lc);
fk = lc;
}
if (sqlFormulaSelect != null) {
fk = sqlFormulaSelect;
}
DeployTableJoinColumn joinCol = new DeployTableJoinColumn(lc, fk);
joinCol.setForeignSqlFormula(sqlFormulaSelect);
if (reverse) {
joinCol = joinCol.reverse();
}
join.addJoinColumn(joinCol);
}
/**
* Return the primary key DB column.
*/
public String getIdColumn() {
if (idProperties.length != 1) {
throw new IllegalStateException("Expecting only one Id column to join to on "+beanType);
}
return idProperties[0].dbColumn;
return idProperty.dbColumn;
}
}
@@ -5,6 +5,7 @@ import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.query.STreePropertyAssocMany;
import io.ebeaninternal.server.type.DataReader;
import java.util.Map;
@@ -68,7 +69,7 @@ public interface DbReadContext {
* Return the property that is associated with the many. There can only be
* one. This can be null.
*/
BeanPropertyAssocMany<?> getManyProperty();
STreePropertyAssocMany getManyProperty();
/**
* Set back the bean that has just been loaded with its id.
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.deploy;
import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.query.SqlTreeProperty;
import io.ebeaninternal.server.type.ScalarType;
import java.util.List;
@@ -9,14 +9,14 @@ import java.util.List;
/**
* Abstract base for dynamic properties.
*/
abstract class DynamicPropertyBase implements SqlTreeProperty {
abstract class DynamicPropertyBase implements STreeProperty {
final String name;
final String fullName;
final String elPrefix;
final ScalarType<?> scalarType;
public DynamicPropertyBase(String name, String fullName, String elPrefix, ScalarType<?> scalarType) {
DynamicPropertyBase(String name, String fullName, String elPrefix, ScalarType<?> scalarType) {
this.name = name;
this.fullName = fullName;
this.elPrefix = elPrefix;
@@ -43,6 +43,11 @@ abstract class DynamicPropertyBase implements SqlTreeProperty {
return false;
}
@Override
public boolean isFormula() {
return false;
}
@Override
public String getElPrefix() {
return elPrefix;
@@ -67,4 +72,9 @@ abstract class DynamicPropertyBase implements SqlTreeProperty {
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
// do not add to from usually
}
@Override
public String getEncryptKeyAsString() {
return null;
}
}
@@ -0,0 +1,22 @@
package io.ebeaninternal.server.deploy;
/**
* Collects (List/Set/Map) of elements.
*/
public interface ElementCollector {
/**
* Add an element.
*/
void addElement(Object element);
/**
* Add an element.
*/
void addKeyValue(Object key, Object element);
/**
* Return the populated collection/map.
*/
Object collection();
}
@@ -0,0 +1,81 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
class ElementEntityBean implements EntityBean {
private final String[] properties;
private Object[] data;
private EntityBeanIntercept intercept;
ElementEntityBean(String[] properties) {
this.properties = properties;
this.intercept = new EntityBeanIntercept(this);
}
@Override
public String[] _ebean_getPropertyNames() {
return properties;
}
@Override
public String _ebean_getPropertyName(int pos) {
return properties[pos];
}
@Override
public String _ebean_getMarker() {
return null;
}
@Override
public Object _ebean_newInstance() {
return new ElementEntityBean(properties);
}
@Override
public void _ebean_setEmbeddedLoaded() {
}
@Override
public boolean _ebean_isEmbeddedNewOrDirty() {
return false;
}
@Override
public EntityBeanIntercept _ebean_getIntercept() {
return intercept;
}
@Override
public EntityBeanIntercept _ebean_intercept() {
return intercept;
}
@Override
public void _ebean_setField(int fieldIndex, Object value) {
if (data == null) {
data = new Object[properties.length];
}
data[fieldIndex] = value;
}
@Override
public void _ebean_setFieldIntercept(int fieldIndex, Object value) {
_ebean_setField(fieldIndex, value);
}
@Override
public Object _ebean_getField(int fieldIndex) {
return data == null ? null : data[fieldIndex];
}
@Override
public Object _ebean_getFieldIntercept(int fieldIndex) {
return _ebean_getField(fieldIndex);
}
}
@@ -0,0 +1,13 @@
package io.ebeaninternal.server.deploy;
/**
* Helper for handling ElementCollection.
*/
interface ElementHelp {
/**
* Return a new collector.
*/
ElementCollector createCollector();
}
@@ -0,0 +1,37 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanList;
import java.util.ArrayList;
import java.util.List;
class ElementHelpList implements ElementHelp {
@Override
public ElementCollector createCollector() {
return new Collector();
}
private static class Collector implements ElementCollector {
private List<Object> list = new ArrayList<>();
@Override
public void addElement(Object element) {
list.add(element);
}
@Override
public void addKeyValue(Object key, Object element) {
throw new IllegalStateException("never called");
}
@Override
public Object collection() {
BeanList<Object> beanList = new BeanList<>(list);
beanList.setModifyListening(BeanCollection.ModifyListenMode.ALL);
return beanList;
}
}
}
@@ -0,0 +1,37 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanMap;
import java.util.LinkedHashMap;
import java.util.Map;
class ElementHelpMap implements ElementHelp {
@Override
public ElementCollector createCollector() {
return new Collector();
}
private static class Collector implements ElementCollector {
private Map<Object, Object> map = new LinkedHashMap<>();
@Override
public void addElement(Object element) {
throw new IllegalStateException("never called");
}
@Override
public void addKeyValue(Object key, Object element) {
map.put(key, element);
}
@Override
public Object collection() {
BeanMap<Object, Object> beanMap = new BeanMap<>(map);
beanMap.setModifyListening(BeanCollection.ModifyListenMode.ALL);
return beanMap;
}
}
}
@@ -0,0 +1,37 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanSet;
import java.util.LinkedHashSet;
import java.util.Set;
class ElementHelpSet implements ElementHelp {
@Override
public ElementCollector createCollector() {
return new Collector();
}
private static class Collector implements ElementCollector {
private Set<Object> set = new LinkedHashSet<>();
@Override
public void addElement(Object element) {
set.add(element);
}
@Override
public void addKeyValue(Object key, Object element) {
throw new IllegalStateException("never called");
}
@Override
public Object collection() {
BeanSet<Object> beanSet = new BeanSet<>(set);
beanSet.setModifyListening(BeanCollection.ModifyListenMode.ALL);
return beanSet;
}
}
}

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