Compare commits

...
Author SHA1 Message Date
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
Rob Bygrave 674617eb9e [maven-release-plugin] prepare release ebean-11.15.1 2018-03-14 00:02:58 +13:00
Rob Bygrave afead2da41 #1221 - ENH: Add a merge(bean) and merge(bean, paths) 2018-03-13 23:55:47 +13:00
Rob Bygrave 607cf3156c #1347 - Improve cleanup of inactive transactions from threadLocal when no end() used 2018-03-13 00:15:42 +13:00
Rob Bygrave af2b8976fa #1346 - Since ebean version 11.12.1 executing docStore query with findPagedList fails with error (due to order by id) 2018-03-12 18:28:17 +13:00
Rob Bygrave 85bfe3d03e #1345 - Refactor io.ebean.meta.MetaInfoManager API (query execution metrics). Breaking change for people collecting query execution metrics. 2018-03-10 02:51:51 +13:00
Rob Bygrave eda6350d89 #1344 - ENH: Add initial Cockroach DB support ... need to review JSON
Adds in the DB ARRAY support (exactly the same as Postgres)
2018-03-09 17:40:34 +13:00
Rob Bygrave 8ac1b745df #1344 - ENH: Add initial Cockroach DB support ... need to review JSON and DB ARRAY support 2018-03-09 16:54:38 +13:00
Rob Bygrave 6c8cf0c5b7 #1343 - Update for ... #1283 ENH: Support simplified test configuration (with docker setup typically via application-test.yml)
DefaultContainer with config.loadFromProperties(PropertiesLoader.load()); effectively runs eval twice on properties which is unnecessary.

Additionally add ServerConfig.setRunMigration() because I think it makes more sense / easier to find.

Remote PropertiesLoader.setProperty() as it is no longer required for the ebean-test-config plugin.
2018-03-09 10:34:25 +13:00
Rob Bygrave d0b24dc340 #1343 - Update the #1283 ENH: Support simplified test configuration (with docker setup typically via application-test.yml) 2018-03-09 00:44:04 +13:00
Rob Bygrave 2042bdf29a #1342 - Refactor and rename DbTypeConfig to Platform config to support multi-platform db migration 2018-03-08 23:23:50 +13:00
Rob Bygrave 4f327c2751 No effective change - default h2 platform for testing 2018-03-08 20:47:48 +13:00
Rob Bygrave fbc2ba3912 No effective change - Test update DDL review files 2018-03-08 20:47:28 +13:00
Rob Bygrave 93d659050b No effective change - Test on rename of DDL review files 2018-03-08 20:44:15 +13:00
Rob Bygrave 2520837f5e No effective change - Test on rename of DDL review files for oracle 2018-03-08 20:42:36 +13:00
Rob Bygrave 2f349ff054 #1318 No effective change - DDL review for ... ENH: native UUID-support for SqlServer
Shows the use of uniqueidentifier DB column type for UUID with sql server.
2018-03-08 19:37:30 +13:00
Rob Bygrave 0aec597cf2 #1318 No effective change - DDL review for ... ENH: native UUID-support for SqlServer 2018-03-08 19:35:48 +13:00
Rob Bygrave 5faec61cd2 #1318 - ENH: native UUID-support for SqlServer
Merge branch 'native-uuid-sqlserver' of https://github.com/FOCONIS/ebean into FOCONIS-native-uuid-sqlserver

# Conflicts:
#	src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerBasePlatform.java
2018-03-08 19:34:36 +13:00
Rob Bygrave 7252e2d7d9 #1318 - ENH: native UUID-support for SqlServer
Merge branch 'native-uuid-sqlserver' of https://github.com/FOCONIS/ebean into FOCONIS-native-uuid-sqlserver

# Conflicts:
#	src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerBasePlatform.java
2018-03-08 19:34:09 +13:00
Rob Bygrave 0cc87009d0 #1340 - (test only change) Split SQL Server Platform into SqlServer16 and SqlServer17 (where 2017 which uses UTF8 types nvarchar etc and prefers Sequences).
Update DbMigrationGenerateTest ... for SqlServer17 platform
2018-03-08 18:37:36 +13:00
Rob Bygrave ef58954af5 Test only changes - related to #1336 - Change DDL generation such that it does create index before alter table add foreign key 2018-03-08 18:31:12 +13:00
Rob Bygrave 824f5e468e #1340 - (test only change) Split SQL Server Platform into SqlServer16 and SqlServer17 (where 2017 which uses UTF8 types nvarchar etc and prefers Sequences).
Reset Offline platform in DbMigrationSqlServerTest
2018-03-08 18:18:13 +13:00
Rob Bygrave d84cdab714 #1340 - Split SQL Server Platform into SqlServer16 and SqlServer17 (where 2017 which uses UTF8 types nvarchar etc and prefers Sequences).
Only need to run H2 based test TestAutoCommitDataSource with H2
2018-03-08 17:45:19 +13:00
Rob Bygrave 5a3550e603 #1340 - Split SQL Server Platform into SqlServer16 and SqlServer17 (where 2017 which uses UTF8 types nvarchar etc and prefers Sequences).
Unrelated - update dbmigration ddl for create index / add fk change in order (db2)
2018-03-08 17:43:25 +13:00
Rob Bygrave f840cfc2ae #1340 - Split SQL Server Platform into SqlServer16 and SqlServer17 (where 2017 which uses UTF8 types nvarchar etc and prefers Sequences).
test changes
2018-03-08 17:40:11 +13:00
Rob Bygrave 8e0d2f8749 #1340 - Split SQL Server Platform into SqlServer16 and SqlServer17 (where 2017 which uses UTF8 types nvarchar etc and prefers Sequences).
test - dbmigration ddl
2018-03-08 17:39:10 +13:00
Rob Bygrave eef514f922 #1340 - Split SQL Server Platform into SqlServer16 and SqlServer17 (where 2017 which uses UTF8 types nvarchar etc and prefers Sequences).
Changes to pom
2018-03-08 17:37:29 +13:00
Rob Bygrave 4fc7898f85 #1340 - Split SQL Server Platform into SqlServer16 and SqlServer17 (where 2017 which uses UTF8 types nvarchar etc and prefers Sequences).
Changes to main
2018-03-08 17:36:53 +13:00
Rob Bygrave 6258a5d263 Logging only change - Drop the logging from WARN to DEBUG for beans without an Id property (which is not usual but valid for Ebean) 2018-03-08 14:36:18 +13:00
Rob Bygrave dbf5f3b57b [maven-release-plugin] prepare for next development iteration 2018-03-08 14:16:45 +13:00
Rob Bygrave 4b594fb533 [maven-release-plugin] prepare release ebean-11.14.3 2018-03-08 14:16:30 +13:00
Rob Bygrave c0efd49c73 #1341 - Cascade persist with JDBC batch and BeanPersistAdapter with preUpdate() that invokes lazy loading on persisted bean ... results in OptimisticLockException 2018-03-08 14:12:32 +13:00
Rob Bygrave 54ba1480eb [maven-release-plugin] prepare for next development iteration 2018-03-07 20:22:38 +13:00
Roland Praml 20e46cad9b Merge remote-tracking branch 'ebean/master' into native-uuid-sqlserver 2018-03-02 03:18:20 +01:00
Roland Praml 9953ef35eb ENH: native UUID-support for SqlServer 2018-03-02 01:14:42 +01:00
419 changed files with 31925 additions and 12943 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
+8 -8
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.14.2</version>
<version>11.15.4</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -17,7 +17,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.14.2</tag>
<tag>ebean-11.15.4</tag>
</scm>
<profiles>
@@ -112,13 +112,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>3.8</version>
<version>3.11</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>3.1.1</version>
<version>3.2.2</version>
</dependency>
<dependency>
@@ -130,7 +130,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.3.1</version>
<version>11.5.1</version>
</dependency>
<dependency>
@@ -224,7 +224,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 +254,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 +297,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>
+10
View File
@@ -638,6 +638,16 @@ public final class Ebean {
serverMgr.getDefaultServer().updateAll(beans);
}
/**
* Merge the bean using the given merge options.
*
* @param bean The bean to merge
* @param options The options to control the merge
*/
public static void merge(Object bean, MergeOptions options) {
serverMgr.getDefaultServer().merge(bean, options);
}
/**
* Save all the beans from a Collection.
*/
+55 -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>
@@ -1615,6 +1628,22 @@ public interface EbeanServer {
*/
void updateAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
/**
* Merge the bean using the given merge options.
*
* @param bean The bean to merge
* @param options The options to control the merge
*/
void merge(Object bean, MergeOptions options);
/**
* Merge the bean using the given merge options and a transaction.
*
* @param bean The bean to merge
* @param options The options to control the merge
*/
void merge(Object bean, MergeOptions options, Transaction transaction);
/**
* Insert the bean.
* <p>
@@ -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>
+56 -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 =
@@ -545,6 +545,11 @@ public interface ExpressionList<T> {
*/
Query<T> setDisableReadAuditing();
/**
* Set a label on the query (to help identify query execution statistics).
*/
Query<T> setLabel(String label);
/**
* Add expressions to the having clause.
* <p>
@@ -873,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)
*/
@@ -883,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)
*/
@@ -893,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)
*/
@@ -991,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>
+29
View File
@@ -0,0 +1,29 @@
package io.ebean;
import java.util.Set;
/**
* Options used to control a merge. Use MergeOptionsBuilder to create an instance.
* <p>
* Instances of MergeOptions are thread safe and safe to share across threads.
*/
public interface MergeOptions {
/**
* Returns true if Id values are supplied by the client.
* <p>
* This would be the case when for example a mobile creates data in it's own local database
* and then sync's. In this case often the id values are UUID.
*/
boolean isClientGeneratedIds();
/**
* Return true if delete permanent should be used and false for 'normal' delete that allows soft deletes.
*/
boolean isDeletePermanent();
/**
* Return the paths included in the merge.
*/
Set<String> paths();
}
@@ -0,0 +1,80 @@
package io.ebean;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Builds a MergeOptions which is immutable and thread safe.
*/
public class MergeOptionsBuilder {
private Set<String> paths = new LinkedHashSet<>();
private boolean clientGeneratedIds;
private boolean deletePermanent = true;
/**
* Add a path that will included in the merge.
*
* @param path The path relative to the root type.
* @return The builder to chain another addPath() or build().
*/
public MergeOptionsBuilder addPath(String path) {
paths.add(path);
return this;
}
/**
* Set to true if Id values are supplied by the client.
* <p>
* This would be the case when for example a mobile creates data in it's own local database
* and then sync's. In this case often the id values are UUID.
*/
public MergeOptionsBuilder setClientGeneratedIds() {
this.clientGeneratedIds = true;
return this;
}
/**
* Set that deletions should use delete permanent (rather than default which allows soft deletes).
*/
public MergeOptionsBuilder setDeletePermanent() {
this.deletePermanent = true;
return this;
}
/**
* Build and return the MergeOptions instance.
*/
public MOptions build() {
return new MOptions(paths, clientGeneratedIds, deletePermanent);
}
private static class MOptions implements MergeOptions {
private final boolean clientGeneratedIds;
private final boolean deletePermanent;
private final Set<String> paths;
private MOptions(Set<String> paths, boolean clientGeneratedIds, boolean deletePermanent) {
this.paths = paths;
this.clientGeneratedIds = clientGeneratedIds;
this.deletePermanent = deletePermanent;
}
public Set<String> paths() {
return paths;
}
@Override
public boolean isClientGeneratedIds() {
return clientGeneratedIds;
}
@Override
public boolean isDeletePermanent() {
return deletePermanent;
}
}
}
+9
View File
@@ -1419,6 +1419,15 @@ public interface Query<T> {
*/
Query<T> setProfileLocation(ProfileLocation profileLocation);
/**
* Set a label on the query.
* <p>
* This label can be used to help identify query performance metrics but we can also use
* profile location enhancement on Finders so for some that would be a better option.
* </p>
*/
Query<T> setLabel(String label);
/**
* Set to true if this query should execute against the doc store.
* <p>
+5
View File
@@ -115,6 +115,11 @@ public interface SqlQuery extends Serializable {
*/
SqlQuery setTimeout(int secs);
/**
* Set a label that can be put on performance metrics that are collected.
*/
SqlQuery setLabel(String label);
/**
* A hint which for JDBC translates to the Statement.fetchSize().
* <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"
+5
View File
@@ -152,6 +152,11 @@ public interface Update<T> {
*/
Update<T> setNullParameter(String name, int jdbcType);
/**
* Set a label meaning performance metrics will be collected for the execution of this update.
*/
Update<T> setLabel(String label);
/**
* Return the sql that is actually executed.
*/
@@ -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.
*/
@@ -52,6 +52,8 @@ public final class EntityBeanIntercept implements Serializable {
*/
private int state;
private boolean forceUpdate;
private boolean readOnly;
private boolean dirty;
@@ -131,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.
*/
@@ -292,6 +308,20 @@ public final class EntityBeanIntercept implements Serializable {
this.readOnly = readOnly;
}
/**
* Set the bean to be updated when persisted (for merge).
*/
public void setForceUpdate(boolean forceUpdate) {
this.forceUpdate = forceUpdate;
}
/**
* Return true if the entity should be updated.
*/
public boolean isUpdate() {
return forceUpdate || state == STATE_LOADED;
}
/**
* Return true if the entity has been loaded.
*/
@@ -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();
}
@@ -6,10 +6,13 @@ package io.ebean.config;
public interface AutoConfigure {
/**
* Provide some configuration the ServerConfig prior to server creation.
* <p>
* Return true if the autoConfiguration applies to this ServerConfig.
* Perform configuration for the ServerConfig prior to properties load.
*/
void configure(ServerConfig serverConfig);
void preConfigure(ServerConfig serverConfig);
/**
* Provide some configuration the ServerConfig prior to server creation but after properties have been applied.
*/
void postConfigure(ServerConfig serverConfig);
}
@@ -1,124 +0,0 @@
package io.ebean.config;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.annotation.Platform;
import java.util.ArrayList;
import java.util.List;
/**
* Configuration for DB types such as UUID, Geometry etc.
*/
public class DbTypeConfig {
/**
* The Geometry SRID value (default 4326).
*/
private int geometrySRID = 4326;
/**
* Set for DB's that support both Sequence and Identity (and the default choice is not desired).
*/
private IdType idType;
/**
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
*/
private ServerConfig.DbUuid dbUuid = ServerConfig.DbUuid.AUTO_VARCHAR;
/**
* Modify the default mapping of standard types such as default precision for DECIMAL etc.
*/
private List<CustomDbTypeMapping> customDbTypeMappings = new ArrayList<>();
/**
* Return the Geometry SRID.
*/
public int getGeometrySRID() {
return geometrySRID;
}
/**
* Set the Geometry SRID.
*/
public void setGeometrySRID(int geometrySRID) {
this.geometrySRID = geometrySRID;
}
/**
* Return the DB type used to store UUID.
*/
public ServerConfig.DbUuid getDbUuid() {
return dbUuid;
}
/**
* Set the DB type used to store UUID.
*/
public void setDbUuid(ServerConfig.DbUuid dbUuid) {
this.dbUuid = dbUuid;
}
/**
* Return the IdType to use (or null for the default choice).
*/
public IdType getIdType() {
return idType;
}
/**
* Set the IdType to use (when the DB supports both SEQUENCE and IDENTITY and the default is not desired).
*/
public void setIdType(IdType idType) {
this.idType = idType;
}
/**
* Add a custom type mapping.
* <p>
* <pre>{@code
*
* // set the default mapping for BigDecimal.class/decimal
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
*
* // set the default mapping for String.class/varchar but only for Postgres
* serverConfig.addCustomMapping(DbType.VARCHAR, "text", Platform.POSTGRES);
*
* }</pre>
*
* @param type The DB type this mapping should apply to
* @param columnDefinition The column definition that should be used
* @param platform Optionally specify the platform this mapping should apply to.
*/
public void addCustomMapping(DbType type, String columnDefinition, Platform platform) {
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition, platform));
}
/**
* Add a custom type mapping that applies to all platforms.
* <p>
* <pre>{@code
*
* // set the default mapping for BigDecimal/decimal
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
*
* // set the default mapping for String/varchar
* serverConfig.addCustomMapping(DbType.VARCHAR, "text");
*
* }</pre>
*
* @param type The DB type this mapping should apply to
* @param columnDefinition The column definition that should be used
*/
public void addCustomMapping(DbType type, String columnDefinition) {
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition));
}
/**
* Return the list of custom type mappings.
*/
public List<CustomDbTypeMapping> getCustomTypeMappings() {
return customDbTypeMappings;
}
}
@@ -0,0 +1,335 @@
package io.ebean.config;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.util.StringHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Configuration for DB types such as UUID, Geometry etc.
*/
public class PlatformConfig {
private boolean allQuotedIdentifiers;
/**
* The database boolean true value (typically either 1, T, or Y).
*/
private String databaseBooleanTrue;
/**
* The database boolean false value (typically either 0, F or N).
*/
private String databaseBooleanFalse;
/**
* For DB's using sequences this is the number of sequence values prefetched.
*/
private int databaseSequenceBatchSize = 20;
/**
* Set for DB's that support both Sequence and Identity (and the default choice is not desired).
*/
private IdType idType;
/**
* The Geometry SRID value (default 4326).
*/
private int geometrySRID = 4326;
/**
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
*/
private DbUuid dbUuid = DbUuid.AUTO_VARCHAR;
/**
* Modify the default mapping of standard types such as default precision for DECIMAL etc.
*/
private List<CustomDbTypeMapping> customDbTypeMappings = new ArrayList<>();
/**
* Construct with defaults.
*/
public PlatformConfig() {
}
/**
* Construct based on given config - typically for DbMigration generation with many platforms.
*/
public PlatformConfig(PlatformConfig platformConfig) {
this.databaseBooleanFalse = platformConfig.databaseBooleanFalse;
this.databaseBooleanTrue = platformConfig.databaseBooleanTrue;
this.databaseSequenceBatchSize = platformConfig.databaseSequenceBatchSize;
this.idType = platformConfig.idType;
this.geometrySRID = platformConfig.geometrySRID;
this.dbUuid = platformConfig.dbUuid;
}
/**
* Return true if all DB column and table names should use quoted identifiers.
*/
public boolean isAllQuotedIdentifiers() {
return allQuotedIdentifiers;
}
/**
* Set to true if all DB column and table names should use quoted identifiers.
*/
public void setAllQuotedIdentifiers(boolean allQuotedIdentifiers) {
this.allQuotedIdentifiers = allQuotedIdentifiers;
}
/**
* Return a value used to represent TRUE in the database.
* <p>
* This is used for databases that do not support boolean natively.
* </p>
* <p>
* The value returned is either a Integer or a String (e.g. "1", or "T").
* </p>
*/
public String getDatabaseBooleanTrue() {
return databaseBooleanTrue;
}
/**
* Set the value to represent TRUE in the database.
* <p>
* This is used for databases that do not support boolean natively.
* </p>
* <p>
* The value set is either a Integer or a String (e.g. "1", or "T").
* </p>
*/
public void setDatabaseBooleanTrue(String databaseBooleanTrue) {
this.databaseBooleanTrue = databaseBooleanTrue;
}
/**
* Return a value used to represent FALSE in the database.
*/
public String getDatabaseBooleanFalse() {
return databaseBooleanFalse;
}
/**
* Set the value used to represent FALSE in the database.
*/
public void setDatabaseBooleanFalse(String databaseBooleanFalse) {
this.databaseBooleanFalse = databaseBooleanFalse;
}
/**
* Return the number of DB sequence values that should be preallocated.
*/
public int getDatabaseSequenceBatchSize() {
return databaseSequenceBatchSize;
}
/**
* Set the number of DB sequence values that should be preallocated.
*/
public void setDatabaseSequenceBatchSize(int databaseSequenceBatchSize) {
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
}
/**
* Return the Geometry SRID.
*/
public int getGeometrySRID() {
return geometrySRID;
}
/**
* Set the Geometry SRID.
*/
public void setGeometrySRID(int geometrySRID) {
this.geometrySRID = geometrySRID;
}
/**
* Return the DB type used to store UUID.
*/
public DbUuid getDbUuid() {
return dbUuid;
}
/**
* Set the DB type used to store UUID.
*/
public void setDbUuid(DbUuid dbUuid) {
this.dbUuid = dbUuid;
}
/**
* Return the IdType to use (or null for the default choice).
*/
public IdType getIdType() {
return idType;
}
/**
* Set the IdType to use (when the DB supports both SEQUENCE and IDENTITY and the default is not desired).
*/
public void setIdType(IdType idType) {
this.idType = idType;
}
/**
* Add a custom type mapping.
* <p>
* <pre>{@code
*
* // set the default mapping for BigDecimal.class/decimal
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
*
* // set the default mapping for String.class/varchar but only for Postgres
* serverConfig.addCustomMapping(DbType.VARCHAR, "text", Platform.POSTGRES);
*
* }</pre>
*
* @param type The DB type this mapping should apply to
* @param columnDefinition The column definition that should be used
* @param platform Optionally specify the platform this mapping should apply to.
*/
public void addCustomMapping(DbType type, String columnDefinition, Platform platform) {
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition, platform));
}
/**
* Add a custom type mapping that applies to all platforms.
* <p>
* <pre>{@code
*
* // set the default mapping for BigDecimal/decimal
* serverConfig.addCustomMapping(DbType.DECIMAL, "decimal(18,6)");
*
* // set the default mapping for String/varchar
* serverConfig.addCustomMapping(DbType.VARCHAR, "text");
*
* }</pre>
*
* @param type The DB type this mapping should apply to
* @param columnDefinition The column definition that should be used
*/
public void addCustomMapping(DbType type, String columnDefinition) {
customDbTypeMappings.add(new CustomDbTypeMapping(type, columnDefinition));
}
/**
* Return the list of custom type mappings.
*/
public List<CustomDbTypeMapping> getCustomTypeMappings() {
return customDbTypeMappings;
}
public void loadSettings(PropertiesWrapper p) {
idType = p.getEnum(IdType.class, "idType", idType);
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", databaseSequenceBatchSize);
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null);
if (dbUuid != null) {
setDbUuid(dbUuid);
}
if (p.getBoolean("uuidStoreAsBinary", false)) {
setDbUuid(DbUuid.BINARY);
}
int srid = p.getInt("geometrySRID", 0);
if (srid > 0) {
setGeometrySRID(srid);
}
// Mapping is specified in the form: BOOLEAN=int(1);BIT=int(1);
String mapping = p.get("mapping");
if (mapping != null && !mapping.isEmpty()) {
Map<String, String> map = StringHelper.delimitedToMap(mapping, ";", "=");
for (Entry<String, String> entry : map.entrySet()) {
addCustomMapping(DbType.valueOf(entry.getKey()), entry.getValue());
}
}
boolean quotedIdentifiers = p.getBoolean("allQuotedIdentifiers", allQuotedIdentifiers);
if (quotedIdentifiers != allQuotedIdentifiers) {
// potentially also set to use matching naming convention
setAllQuotedIdentifiers(quotedIdentifiers);
}
}
/**
* Specify how UUID is stored.
*/
public enum DbUuid {
/**
* Store using native UUID in H2 and Postgres and otherwise fallback to VARCHAR(40).
*/
AUTO_VARCHAR(true, false, false),
/**
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16).
*/
AUTO_BINARY(true, true, false),
/**
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16) with optimized packing.
*/
AUTO_BINARY_OPTIMIZED(true, true, true),
/**
* Store using DB VARCHAR(40).
*/
VARCHAR(false, false, false),
/**
* Store using DB BINARY(16).
*/
BINARY(false, true, false),
/**
* Store using DB BINARY(16).
*/
BINARY_OPTIMIZED(false, true, true);
boolean nativeType;
boolean binary;
boolean binaryOptimized;
DbUuid(boolean nativeType, boolean binary, boolean binaryOptimized) {
this.nativeType = nativeType;
this.binary = binary;
this.binaryOptimized = binaryOptimized;
}
/**
* Return true if native UUID type is preferred.
*/
public boolean useNativeType() {
return nativeType;
}
/**
* Return true if BINARY(16) storage is preferred over VARCHAR(40).
*/
public boolean useBinary() {
return binary;
}
/**
* Return true, if optimized packing should be used.
*/
public boolean useBinaryOptimized() {
return binaryOptimized;
}
}
}
@@ -10,20 +10,23 @@ public class PropertiesWrapper {
protected final String serverName;
private final ClassLoadConfig classLoadConfig;
/**
* Construct with a prefix, serverName and properties.
*/
public PropertiesWrapper(String prefix, String serverName, Properties properties) {
public PropertiesWrapper(String prefix, String serverName, Properties properties, ClassLoadConfig classLoadConfig) {
this.serverName = serverName;
this.prefix = prefix;
this.properties = properties;
this.classLoadConfig = classLoadConfig;
}
/**
* Construct without prefix of serverName.
*/
public PropertiesWrapper(Properties properties) {
this(null, null, properties);
public PropertiesWrapper(Properties properties, ClassLoadConfig classLoadConfig) {
this(null, null, properties, classLoadConfig);
}
/**
@@ -115,4 +118,30 @@ public class PropertiesWrapper {
return (level == null) ? defaultValue : Enum.valueOf(enumType, level.toUpperCase());
}
/**
* Return the instance to use (can be null) for the given plugin.
*
* @param pluginType the type of plugin
* @param key properties key
* @param instance existing instance
*/
public <T> T createInstance(Class<T> pluginType, String key, T instance) {
if (instance != null) {
return instance;
}
String classname = get(key, null);
return createInstance(pluginType, classname);
}
/**
* Return the instance to use (can be null) for the given plugin.
*
* @param pluginType the type of plugin
* @param classname the implementation class as per properties
*/
@SuppressWarnings("unchecked")
public <T> T createInstance(Class<T> pluginType, String classname) {
return classname == null ? null : (T) classLoadConfig.newInstance(classname);
}
}
+122 -178
View File
@@ -183,16 +183,6 @@ public class ServerConfig {
*/
private DatabasePlatform databasePlatform;
/**
* The preferred IdType (to override the default Platform type).
*/
private IdType idType;
/**
* For DB's using sequences this is the number of sequence values prefetched.
*/
private int databaseSequenceBatchSize = 20;
/**
* JDBC fetchSize hint when using findList. Defaults to 0 leaving it up to the JDBC driver.
*/
@@ -330,18 +320,6 @@ public class ServerConfig {
*/
private String dataSourceJndiName;
/**
* The database boolean true value (typically either 1, T, or Y).
*/
private String databaseBooleanTrue;
/**
* The database boolean false value (typically either 0, F or N).
*/
private String databaseBooleanFalse;
private boolean allQuotedIdentifiers;
/**
* The naming convention.
*/
@@ -368,9 +346,9 @@ public class ServerConfig {
private boolean updatesDeleteMissingChildren = true;
/**
* Database type configuration.
* Database platform configuration.
*/
private DbTypeConfig dbTypeConfig = new DbTypeConfig();
private PlatformConfig platformConfig = new PlatformConfig();
/**
* The UUID version to use.
@@ -510,6 +488,11 @@ public class ServerConfig {
*/
private boolean defaultOrderById = false;
/**
* The mappingLocations for searching xml mapping.
*/
private List<String> mappingLocations = new ArrayList<>();
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -946,7 +929,7 @@ public class ServerConfig {
* </p>
*/
public void setDatabaseSequenceBatchSize(int databaseSequenceBatchSize) {
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
platformConfig.setDatabaseSequenceBatchSize(databaseSequenceBatchSize);
}
/**
@@ -1128,14 +1111,14 @@ public class ServerConfig {
* Return the Geometry SRID.
*/
public int getGeometrySRID() {
return dbTypeConfig.getGeometrySRID();
return platformConfig.getGeometrySRID();
}
/**
* Set the Geometry SRID.
*/
public void setGeometrySRID(int geometrySRID) {
dbTypeConfig.setGeometrySRID(geometrySRID);
platformConfig.setGeometrySRID(geometrySRID);
}
/**
@@ -1425,15 +1408,21 @@ public class ServerConfig {
* Return true if all DB column and table names should use quoted identifiers.
*/
public boolean isAllQuotedIdentifiers() {
return allQuotedIdentifiers;
return platformConfig.isAllQuotedIdentifiers();
}
/**
* Set to true if all DB column and table names should use quoted identifiers.
*/
public void setAllQuotedIdentifiers(boolean allQuotedIdentifiers) {
this.allQuotedIdentifiers = allQuotedIdentifiers;
if (allQuotedIdentifiers && namingConvention instanceof UnderscoreNamingConvention) {
platformConfig.setAllQuotedIdentifiers(allQuotedIdentifiers);
if (allQuotedIdentifiers) {
adjustNamingConventionForAllQuoted();
}
}
private void adjustNamingConventionForAllQuoted() {
if (namingConvention instanceof UnderscoreNamingConvention) {
// we need to use matching naming convention
this.namingConvention = new MatchingNamingConvention();
}
@@ -1648,7 +1637,7 @@ public class ServerConfig {
* </p>
*/
public String getDatabaseBooleanTrue() {
return databaseBooleanTrue;
return platformConfig.getDatabaseBooleanTrue();
}
/**
@@ -1661,7 +1650,7 @@ public class ServerConfig {
* </p>
*/
public void setDatabaseBooleanTrue(String databaseTrue) {
this.databaseBooleanTrue = databaseTrue;
platformConfig.setDatabaseBooleanTrue(databaseTrue);
}
/**
@@ -1674,7 +1663,7 @@ public class ServerConfig {
* </p>
*/
public String getDatabaseBooleanFalse() {
return databaseBooleanFalse;
return platformConfig.getDatabaseBooleanFalse();
}
/**
@@ -1687,14 +1676,14 @@ public class ServerConfig {
* </p>
*/
public void setDatabaseBooleanFalse(String databaseFalse) {
this.databaseBooleanFalse = databaseFalse;
this.platformConfig.setDatabaseBooleanFalse(databaseFalse);
}
/**
* Return the number of DB sequence values that should be preallocated.
*/
public int getDatabaseSequenceBatchSize() {
return databaseSequenceBatchSize;
return platformConfig.getDatabaseSequenceBatchSize();
}
/**
@@ -1713,7 +1702,7 @@ public class ServerConfig {
* </p>
*/
public void setDatabaseSequenceBatch(int databaseSequenceBatchSize) {
this.databaseSequenceBatchSize = databaseSequenceBatchSize;
this.platformConfig.setDatabaseSequenceBatchSize(databaseSequenceBatchSize);
}
/**
@@ -1740,7 +1729,7 @@ public class ServerConfig {
* that you don't have access to.
* </p>
* <p>
* Values are oracle, h2, postgres, mysql, mssqlserver2005.
* Values are oracle, h2, postgres, mysql, sqlserver16, sqlserver17.
* </p>
*/
public void setDatabasePlatformName(String databasePlatformName) {
@@ -1769,14 +1758,14 @@ public class ServerConfig {
* Return the preferred DB platform IdType.
*/
public IdType getIdType() {
return idType;
return platformConfig.getIdType();
}
/**
* Set the preferred DB platform IdType.
*/
public void setIdType(IdType idType) {
this.idType = idType;
this.platformConfig.setIdType(idType);
}
/**
@@ -1889,15 +1878,22 @@ public class ServerConfig {
/**
* Return the configuration for DB types (such as UUID and custom mappings).
*/
public DbTypeConfig getDbTypeConfig() {
return dbTypeConfig;
public PlatformConfig getPlatformConfig() {
return platformConfig;
}
/**
* Set the configuration for DB platform (such as UUID and custom mappings).
*/
public void setPlatformConfig(PlatformConfig platformConfig) {
this.platformConfig = platformConfig;
}
/**
* Set the DB type used to store UUID.
*/
public void setDbUuid(DbUuid dbUuid) {
this.dbTypeConfig.setDbUuid(dbUuid);
public void setDbUuid(PlatformConfig.DbUuid dbUuid) {
this.platformConfig.setDbUuid(dbUuid);
}
/**
@@ -1965,6 +1961,16 @@ public class ServerConfig {
this.durationWithNanos = durationWithNanos;
}
/**
* Set to true to run DB migrations on server start.
* <p>
* 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){
migrationConfig.setRunMigration(runMigration);
}
/**
* Set to true to generate the "create all" DDL on startup.
* <p>
@@ -2353,7 +2359,7 @@ public class ServerConfig {
* @param platform Optionally specify the platform this mapping should apply to.
*/
public void addCustomMapping(DbType type, String columnDefinition, Platform platform) {
dbTypeConfig.addCustomMapping(type, columnDefinition, platform);
platformConfig.addCustomMapping(type, columnDefinition, platform);
}
/**
@@ -2373,7 +2379,7 @@ public class ServerConfig {
* @param columnDefinition The column definition that should be used
*/
public void addCustomMapping(DbType type, String columnDefinition) {
dbTypeConfig.addCustomMapping(type, columnDefinition);
platformConfig.addCustomMapping(type, columnDefinition);
}
/**
@@ -2659,19 +2665,24 @@ public class ServerConfig {
* Load the settings from the given properties
*/
private void configureFromProperties() {
autoConfiguration();
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
loadSettings(p);
List<AutoConfigure> autoConfigures = autoConfiguration();
loadSettings(new PropertiesWrapper("ebean", name, properties, classLoadConfig));
for (AutoConfigure autoConfigure : autoConfigures) {
autoConfigure.postConfigure(this);
}
}
/**
* Use a 'plugin' to provide automatic configuration. Intended for automatic testing
* configuration with Docker containers via ebean-test-config.
*/
private void autoConfiguration() {
private List<AutoConfigure> autoConfiguration() {
List<AutoConfigure> list = new ArrayList<>();
for (AutoConfigure autoConfigure : serviceLoad(AutoConfigure.class)) {
autoConfigure.configure(this);
autoConfigure.preConfigure(this);
list.add(autoConfigure);
}
return list;
}
/**
@@ -2695,34 +2706,6 @@ public class ServerConfig {
return properties;
}
/**
* Return the instance to use (can be null) for the given plugin.
*
* @param properties the properties
* @param pluginType the type of plugin
* @param key properties key
* @param instance existing instance
*/
protected <T> T createInstance(PropertiesWrapper properties, Class<T> pluginType, String key, T instance) {
if (instance != null) {
return instance;
}
String classname = properties.get(key, null);
return createInstance(pluginType, classname);
}
/**
* Return the instance to use (can be null) for the given plugin.
*
* @param pluginType the type of plugin
* @param classname the implementation class as per properties
*/
@SuppressWarnings("unchecked")
protected <T> T createInstance(Class<T> pluginType, String classname) {
return classname == null ? null : (T) classLoadConfig.newInstance(classname);
}
/**
* loads the data source settings to preserve existing behaviour. IMHO, if someone has set the datasource config already,
* they don't want the settings to be reloaded and reset. This allows a descending class to override this behaviour and prevent it
@@ -2756,11 +2739,9 @@ public class ServerConfig {
profilingConfig.loadSettings(p, name);
migrationConfig.loadSettings(p, name);
boolean quotedIdentifiers = p.getBoolean("allQuotedIdentifiers", allQuotedIdentifiers);
if (quotedIdentifiers != allQuotedIdentifiers) {
// potentially also set to use matching naming convention
setAllQuotedIdentifiers(quotedIdentifiers);
platformConfig.loadSettings(p);
if (platformConfig.isAllQuotedIdentifiers()) {
adjustNamingConventionForAllQuoted();
}
namingConvention = createNamingConvention(p, namingConvention);
if (namingConvention != null) {
@@ -2781,11 +2762,6 @@ public class ServerConfig {
}
loadDocStoreSettings(p);
int srid = p.getInt("geometrySRID", 0);
if (srid > 0) {
dbTypeConfig.setGeometrySRID(srid);
}
queryPlanTTLSeconds = p.getInt("queryPlanTTLSeconds", queryPlanTTLSeconds);
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
@@ -2800,19 +2776,17 @@ public class ServerConfig {
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
disableClasspathSearch = p.getBoolean("disableClasspathSearch", disableClasspathSearch);
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider", currentUserProvider);
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform", databasePlatform);
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager", encryptKeyManager);
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager", encryptDeployManager);
encryptor = createInstance(p, Encryptor.class, "encryptor", encryptor);
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt", dbEncrypt);
currentUserProvider = p.createInstance(CurrentUserProvider.class, "currentUserProvider", currentUserProvider);
databasePlatform = p.createInstance(DatabasePlatform.class, "databasePlatform", databasePlatform);
encryptKeyManager = p.createInstance(EncryptKeyManager.class, "encryptKeyManager", encryptKeyManager);
encryptDeployManager = p.createInstance(EncryptDeployManager.class, "encryptDeployManager", encryptDeployManager);
encryptor = p.createInstance(Encryptor.class, "encryptor", encryptor);
dbEncrypt = p.createInstance(DbEncrypt.class, "dbEncrypt", dbEncrypt);
dbOffline = p.getBoolean("dbOffline", dbOffline);
serverCachePlugin = createInstance(p, ServerCachePlugin.class, "serverCachePlugin", serverCachePlugin);
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);
@@ -2828,7 +2802,6 @@ public class ServerConfig {
throw new IllegalArgumentException("Property 'batch.mode' or 'persistBatching' is being set but no longer used. Please change to use 'persistBatchMode'");
}
idType = p.getEnum(IdType.class, "idType", idType);
persistBatch = p.getEnum(PersistBatch.class, "persistBatch", persistBatch);
persistBatchOnCascade = p.getEnum(PersistBatch.class, "persistBatchOnCascade", persistBatchOnCascade);
@@ -2849,20 +2822,9 @@ public class ServerConfig {
dataSourceJndiName = p.get("dataSourceJndiName", dataSourceJndiName);
jdbcFetchSizeFindEach = p.getInt("jdbcFetchSizeFindEach", jdbcFetchSizeFindEach);
jdbcFetchSizeFindList = p.getInt("jdbcFetchSizeFindList", jdbcFetchSizeFindList);
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", databaseSequenceBatchSize);
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
defaultOrderById = p.getBoolean("defaultOrderById", defaultOrderById);
DbUuid dbUuid = p.getEnum(DbUuid.class, "dbuuid", null);
if (dbUuid != null) {
dbTypeConfig.setDbUuid(dbUuid);
}
if (p.getBoolean("uuidStoreAsBinary", false)) {
dbTypeConfig.setDbUuid(DbUuid.BINARY);
}
uuidVersion = p.getEnum(UuidVersion.class, "uuidVersion", uuidVersion);
uuidStateFile = p.get("uuidStateFile", uuidStateFile);
@@ -2898,16 +2860,18 @@ public class ServerConfig {
}
}
currentTenantProvider = createInstance(p, CurrentTenantProvider.class, "tenant.currentTenantProvider", currentTenantProvider);
tenantCatalogProvider = createInstance(p, TenantCatalogProvider.class, "tenant.catalogProvider", tenantCatalogProvider);
tenantSchemaProvider = createInstance(p, TenantSchemaProvider.class, "tenant.schemaProvider", tenantSchemaProvider);
currentTenantProvider = p.createInstance(CurrentTenantProvider.class, "tenant.currentTenantProvider", currentTenantProvider);
tenantCatalogProvider = p.createInstance(TenantCatalogProvider.class, "tenant.catalogProvider", tenantCatalogProvider);
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 = createInstance(properties, NamingConvention.class, "namingconvention", null);
NamingConvention nc = properties.createInstance(NamingConvention.class, "namingconvention", null);
return (nc != null) ? nc : namingConvention;
}
@@ -2940,17 +2904,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;
}
}
@@ -3098,71 +3062,51 @@ public class ServerConfig {
}
/**
* Specify how UUID is stored.
* Create a new PlatformConfig based of the one held but with overridden properties by reading
* properties with the given path and prefix.
* <p>
* Typically used in Db Migration generation for many platform targets that might have different
* configuration for IdType, UUID, quoted identifiers etc.
* </p>
*
* @param propertiesPath The properties path used for loading and setting properties
* @param platformPrefix The prefix used for loading and setting properties
* @return A copy of the PlatformConfig with overridden properties
*/
public enum DbUuid {
public PlatformConfig newPlatformConfig(String propertiesPath, String platformPrefix) {
PropertiesWrapper p = new PropertiesWrapper(propertiesPath, platformPrefix, properties, classLoadConfig);
/**
* Store using native UUID in H2 and Postgres and otherwise fallback to VARCHAR(40).
*/
AUTO_VARCHAR(true, false, false),
PlatformConfig config = new PlatformConfig(platformConfig);
config.loadSettings(p);
return config;
}
/**
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16).
*/
AUTO_BINARY(true, true, false),
/**
* Store using native UUID in H2 and Postgres and otherwise fallback to BINARY(16) with optimized packing.
*/
AUTO_BINARY_OPTIMIZED(true, true, true),
/**
* Store using DB VARCHAR(40).
*/
VARCHAR(false, false, false),
/**
* Store using DB BINARY(16).
*/
BINARY(false, true, false),
/**
* Store using DB BINARY(16).
*/
BINARY_OPTIMIZED(false, true, true);
boolean nativeType;
boolean binary;
boolean binaryOptimized;
DbUuid(boolean nativeType, boolean binary, boolean binaryOptimized) {
this.nativeType = nativeType;
this.binary = binary;
this.binaryOptimized = binaryOptimized;
/**
* 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 true if native UUID type is preferred.
*/
public boolean useNativeType() {
return nativeType;
}
/**
* Return mapping locations to search for xml mapping via class path search.
*/
public List<String> getMappingLocations() {
return mappingLocations;
}
/**
* Return true if BINARY(16) storage is preferred over VARCHAR(40).
*/
public boolean useBinary() {
return binary;
}
/**
* Return true, if optimized packing should be used.
*/
public boolean useBinaryOptimized() {
return binaryOptimized;
}
/**
* 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;
}
public enum UuidVersion {
@@ -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);
}
/**
@@ -5,8 +5,7 @@ import io.ebean.Query;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.CustomDbTypeMapping;
import io.ebean.config.DbTypeConfig;
import io.ebean.config.ServerConfig;
import io.ebean.config.PlatformConfig;
import io.ebean.util.JdbcClose;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -215,16 +214,16 @@ public class DatabasePlatform {
/**
* Configure the platform given the server configuration.
*/
public void configure(ServerConfig config) {
public void configure(PlatformConfig config) {
this.sequenceBatchSize = config.getDatabaseSequenceBatchSize();
configureIdType(config.getIdType());
configure(config.getDbTypeConfig(), config.isAllQuotedIdentifiers());
configure(config, config.isAllQuotedIdentifiers());
}
/**
* Configure UUID Storage etc based on ServerConfig settings.
*/
protected void configure(DbTypeConfig config, boolean allQuotedIdentifiers) {
protected void configure(PlatformConfig config, boolean allQuotedIdentifiers) {
this.allQuotedIdentifiers = allQuotedIdentifiers;
addGeoTypes(config.getGeometrySRID());
configureIdType(config.getIdType());
@@ -382,6 +381,13 @@ public class DatabasePlatform {
this.historySupport = historySupport;
}
/**
* So no except for Postgres and CockroachDB.
*/
public boolean isNativeArrayType() {
return false;
}
/**
* Return true if the DB supports native UUID.
*/
@@ -518,6 +524,13 @@ public class DatabasePlatform {
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
}
/**
* Normally not needed - overridden in CockroachPlatform.
*/
public boolean isDdlCommitOnCreateIndex() {
return false;
}
/**
* Return the DB identity/sequence features for this platform.
*
@@ -1,6 +1,6 @@
package io.ebean.config.dbplatform;
import io.ebean.config.ServerConfig;
import io.ebean.config.PlatformConfig;
import java.util.EnumMap;
import java.util.Map;
@@ -119,8 +119,8 @@ public class DbPlatformTypeMapping {
put(DbType.JSONCLOB, JSON_CLOB_PLACEHOLDER);
put(DbType.JSONBLOB, JSON_BLOB_PLACEHOLDER);
put(DbType.JSONVARCHAR, JSON_VARCHAR_PLACEHOLDER);
// use reasonable default of varchar(40) - ideally set via DatabasePlatform.configure(DbTypeConfig)
put(DbType.UUID, get(DbType.VARCHAR).withLength(40));
// default to native UUID and override on platform configure()
put(DbType.UUID, UUID_NATIVE);
}
}
@@ -198,9 +198,9 @@ public class DbPlatformTypeMapping {
/**
* Map the UUID appropriately based on native DB support and ServerConfig.DbUuid.
*/
public void config(boolean nativeUuidType, ServerConfig.DbUuid dbUuid) {
public void config(boolean nativeUuidType, PlatformConfig.DbUuid dbUuid) {
if (nativeUuidType && dbUuid.useNativeType()) {
put(DbType.UUID, UUID_NATIVE);
// native UUID already set by default
} else if (dbUuid.useBinary()) {
put(DbType.UUID, get(DbType.BINARY).withLength(16));
} else {
@@ -0,0 +1,28 @@
package io.ebean.config.dbplatform.cockroach;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
/**
* CockroachDB based platform.
*/
public class CockroachPlatform extends PostgresPlatform {
public CockroachPlatform() {
super();
this.platform = Platform.COCKROACH;
// no like escape clause supported
this.likeSpecialCharacters = new char[]{'%', '_'};
this.likeClauseRaw = "like ?";
this.likeClauseEscaped = "like ?";
}
/**
* Needs a commit after create index such that alter table add foreign key ... succeeds.
*/
@Override
public boolean isDdlCommitOnCreateIndex() {
return true;
}
}
@@ -88,6 +88,14 @@ public class PostgresPlatform extends DatabasePlatform {
return new DbPlatformType("geometry(" + type + "," + srid + ")");
}
/**
* So we can generate varchar[], int[], uuid[] column definitions and use the associated scalar types.
*/
@Override
public boolean isNativeArrayType() {
return true;
}
/**
* Create a Postgres specific sequence IdGenerator.
*/
@@ -0,0 +1,26 @@
package io.ebean.config.dbplatform.sqlserver;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
/**
* Microsoft SQL Server platform that has non-UTF8 types (char, varchar, text) and default to Identity rather than Sequence.
*/
public class SqlServer16Platform extends SqlServerBasePlatform {
public SqlServer16Platform() {
super();
this.platform = Platform.SQLSERVER16;
// default to use Identity rather than sequences
this.dbIdentity.setIdType(IdType.IDENTITY);
// non-utf8 column types
dbTypeMap.put(DbType.CHAR, new DbPlatformType("char", 1));
dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("varchar", 255));
dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("text"));
dbTypeMap.put(DbType.CLOB, new DbPlatformType("text"));
}
}
@@ -0,0 +1,20 @@
package io.ebean.config.dbplatform.sqlserver;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
/**
* Microsoft SQL Server platform - NVarchar UTF types and Sequence preference.
*/
public class SqlServer17Platform extends SqlServerBasePlatform {
public SqlServer17Platform() {
super();
this.platform = Platform.SQLSERVER17;
this.columnAliasPrefix = null;
this.nativeUuidType = true;
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("uniqueidentifier", false));
}
}
@@ -3,7 +3,7 @@ package io.ebean.config.dbplatform.sqlserver;
/**
* SQL Server platform using the older ROW_NUMBER() mechanism.
*/
public class SqlServer2005Platform extends SqlServerPlatform {
public class SqlServer2005Platform extends SqlServer16Platform {
public SqlServer2005Platform() {
this.sqlLimiter = new SqlServer2005SqlLimiter();
@@ -4,7 +4,7 @@ import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.PlatformConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
@@ -16,11 +16,11 @@ import javax.sql.DataSource;
import java.sql.Types;
/**
* Microsoft SQL Server platform.
* Base Microsoft SQL Server platform - NVarchar UTF types and Sequence preference by default.
*/
public class SqlServerPlatform extends DatabasePlatform {
abstract class SqlServerBasePlatform extends DatabasePlatform {
public SqlServerPlatform() {
SqlServerBasePlatform() {
super();
this.platform = Platform.SQLSERVER;
// disable persistBatchOnCascade mode for
@@ -31,7 +31,7 @@ public class SqlServerPlatform extends DatabasePlatform {
this.sqlLimiter = new SqlServerSqlLimiter();
this.basicSqlLimiter = new SqlServerBasicSqlLimiter();
this.historySupport = new SqlServerHistorySupport();
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setIdType(IdType.SEQUENCE);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsIdentity(true);
this.dbIdentity.setSupportsSequence(true);
@@ -63,22 +63,26 @@ public class SqlServerPlatform extends DatabasePlatform {
dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint"));
dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("numeric", 28));
dbTypeMap.put(DbType.BLOB, new DbPlatformType("image"));
dbTypeMap.put(DbType.CLOB, new DbPlatformType("text"));
dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("image"));
dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("text"));
dbTypeMap.put(DbType.DATE, new DbPlatformType("date"));
dbTypeMap.put(DbType.TIME, new DbPlatformType("time"));
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("datetime2"));
// UTF8 aware types - overwritten in SqlServer16 platform
dbTypeMap.put(DbType.CHAR, new DbPlatformType("nchar", 1));
dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("nvarchar", 255));
dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
dbTypeMap.put(DbType.CLOB, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
dbTypeMap.put(DbType.JSON, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
dbTypeMap.put(DbType.JSONB, new DbPlatformType("nvarchar", Integer.MAX_VALUE));
dbTypeMap.put(DbType.BLOB, new DbPlatformType("image"));
dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("image"));
}
@Override
public void configure(ServerConfig config) {
public void configure(PlatformConfig config) {
super.configure(config);
if (dbIdentity.getIdType() == IdType.SEQUENCE) {
this.persistBatchOnCascade = PersistBatch.ALL;
@@ -56,17 +56,6 @@ public class PropertiesLoader {
return properties;
}
/**
* Set (override) a configuration property.
*
* This is expected to be only called by test configuration logic
* (ala automatic test configuration via ebean-test-config).
*/
public static synchronized void setProperty(String key, String value) {
load();
properties.setProperty(key, value);
}
/**
* Return a copy of the properties with 'eval' run on all the values.
* This resolves expressions like ${HOME} etc.
@@ -0,0 +1,43 @@
package io.ebean.meta;
/**
* An abstract MetricVisitor that handles the boolean flags - reset, collectTransactionMetrics and collectQueryMetrics.
*/
public abstract class AbstractMetricVisitor implements MetricVisitor {
private final boolean reset;
private final boolean collectTransactionMetrics;
private final boolean collectQueryMetrics;
public AbstractMetricVisitor(boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics) {
this.reset = reset;
this.collectTransactionMetrics = collectTransactionMetrics;
this.collectQueryMetrics = collectQueryMetrics;
}
@Override
public boolean isReset() {
return reset;
}
@Override
public boolean isCollectTransactionMetrics() {
return collectTransactionMetrics;
}
@Override
public boolean isCollectQueryMetrics() {
return collectQueryMetrics;
}
@Override
public void visitStart() {
// do nothing by default
}
@Override
public void visitEnd() {
// do nothing by default
}
}
@@ -0,0 +1,64 @@
package io.ebean.meta;
import java.util.ArrayList;
import java.util.List;
/**
* A simple MetricVisitor that can collect the desired metrics into lists.
*/
public class BasicMetricVisitor extends AbstractMetricVisitor {
private final List<MetaTimedMetric> timed = new ArrayList<>();
private final List<MetaQueryMetric> dtoQuery = new ArrayList<>();
private final List<MetaOrmQueryMetric> ormQuery = new ArrayList<>();
/**
* Construct to reset and collect everything.
*/
public BasicMetricVisitor() {
super(true, true, true);
}
/**
* Construct specifying reset and what to collect.
*/
public BasicMetricVisitor(boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics) {
super(reset, collectTransactionMetrics, collectQueryMetrics);
}
/**
* Return timed metrics for Transactions, labelled SqlQuery, labelled SqlUpdate.
*/
public List<MetaTimedMetric> getTimedMetrics() {
return timed;
}
/**
* Return the DTO query metrics.
*/
public List<MetaQueryMetric> getDtoQueryMetrics() {
return dtoQuery;
}
/**
* Return the ORM query metrics.
*/
public List<MetaOrmQueryMetric> getOrmQueryMetrics() {
return ormQuery;
}
@Override
public void visitTimed(MetaTimedMetric metric) {
timed.add(metric);
}
@Override
public void visitQuery(MetaQueryMetric metric) {
dtoQuery.add(metric);
}
@Override
public void visitOrmQuery(MetaOrmQueryMetric metric) {
ormQuery.add(metric);
}
}
@@ -8,23 +8,20 @@ import java.util.List;
public interface MetaInfoManager {
/**
* Collect and return the transaction execution metrics.
* Visit the metrics resetting and collecting/reporting as desired.
*/
List<MetaTimedMetric> collectTransactionStatistics(boolean reset);
void visitMetrics(MetricVisitor visitor);
/**
* Collect query plan statistics (new, will migrate ORM query stats over to this).
* Run a visit collecting all the metrics and returning BasicMetricVisitor
* which holds all the metrics in simple lists.
*/
List<MetaQueryMetric> collectQueryStatistics(boolean reset);
BasicMetricVisitor visitBasic();
/**
* Collect and return the non-empty query plan statistics for all the beans.
* <p>
* Note that this excludes the query plan statistics where there has been no
* executions (since the last collection with reset).
* </p>
* Just reset all the metrics. Maybe only useful for testing purposes.
*/
List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset);
void resetAllMetrics();
/**
* Collect and return the ObjectGraphNode statistics.
@@ -36,6 +33,6 @@ public interface MetaInfoManager {
*
* @param reset Set to true to reset the underlying statistics after collection.
*/
List<MetaObjectGraphNodeStats> collectNodeStatistics(boolean reset);
List<MetaOrmQueryNode> collectNodeStatistics(boolean reset);
}
@@ -0,0 +1,42 @@
package io.ebean.meta;
import io.ebean.ProfileLocation;
import java.util.List;
/**
* Query execution statistics for Orm queries.
*/
public interface MetaOrmQueryMetric extends MetaQueryMetric {
/**
* Return the profile location.
*/
ProfileLocation getProfileLocation();
/**
* Return true if this query plan was tuned by AutoTune.
*/
boolean isAutoTuned();
/**
* Return a string representation of the query plan hash.
*/
String getQueryPlanHash();
/**
* Return the time of the last query executed using this plan.
*/
long getLastQueryTime();
/**
* Return the 'origin' points and paths that resulted in the query being
* executed and the associated number of times the query was executed via that
* path.
* <p>
* This includes direct and lazy loading paths.
* </p>
*/
List<MetaOrmQueryOrigin> getOrigins();
}
@@ -8,10 +8,8 @@ import io.ebean.bean.ObjectGraphNode;
* These statistics can be used to identify origin queries that result in lots
* of lazy loading.
* </p>
*
* @see MetaInfoManager#collectNodeStatistics(boolean)
*/
public interface MetaObjectGraphNodeStats {
public interface MetaOrmQueryNode {
/**
* Return the ObjectGraphNode which has the origin point and relative path.
@@ -9,11 +9,8 @@ import io.ebean.bean.ObjectGraphNode;
* This basically points to the bit of original code and query that results in
* this query directly or via lazy loading.
* </p>
*
* @see MetaQueryPlanStatistic
* @see MetaInfoManager#collectQueryPlanStatistics(boolean)
*/
public interface MetaQueryPlanOriginCount {
public interface MetaOrmQueryOrigin {
/**
* The 'origin' and path which this query belongs to.
@@ -1,98 +0,0 @@
package io.ebean.meta;
import io.ebean.ProfileLocation;
import java.util.List;
/**
* Query execution statistics Meta data.
*
* @see MetaInfoManager#collectQueryPlanStatistics(boolean)
*/
public interface MetaQueryPlanStatistic {
/**
* Return the bean type this query plan is for.
*/
Class<?> getBeanType();
/**
* Return the profile location.
*/
ProfileLocation getProfileLocation();
/**
* Return true if this query plan was tuned by AutoTune.
*/
boolean isAutoTuned();
/**
* Return a string representation of the query plan hash.
*/
String getQueryPlanHash();
/**
* Return the sql executed.
*/
String getSql();
/**
* Return the total number of queries executed.
*/
long getExecutionCount();
/**
* Return the total number of beans loaded by the queries.
* <p>
* This excludes background fetching.
* </p>
*/
long getTotalLoadedBeans();
/**
* Return the total time taken by executions of this query.
*/
long getTotalTimeMicros();
/**
* Return the max execution time for this query.
*/
long getMaxTimeMicros();
/**
* Return the time collection started (or was last reset).
*/
long getCollectionStart();
/**
* Return the time of the last query executed using this plan.
*/
long getLastQueryTime();
/**
* Return the average query execution time in microseconds.
* <p>
* This excludes background fetching.
* </p>
*/
long getAvgTimeMicros();
/**
* Return the average number of bean loaded per query.
* <p>
* This excludes background fetching.
* </p>
*/
long getAvgLoadedBeans();
/**
* Return the 'origin' points and paths that resulted in the query being
* executed and the associated number of times the query was executed via that
* path.
* <p>
* This includes direct and lazy loading paths.
* </p>
*/
List<MetaQueryPlanOriginCount> getOrigins();
}
@@ -6,6 +6,11 @@ package io.ebean.meta;
*/
public interface MetaTimedMetric {
/**
* Return the metric type.
*/
MetricType getMetricType();
/**
* Return the metric name.
*/
@@ -0,0 +1,30 @@
package io.ebean.meta;
/**
* The type of Metric.
*/
public enum MetricType {
/**
* Transactions.
*/
TXN,
/**
* ORM queries.
*/
ORM,
/**
* DTO queries.
*/
DTO,
/**
* SQL queries with a label will have metrics collected.
* <p>
* SqlQuery and SqlUpdate without a label have no metrics collected.
*/
SQL
}
@@ -0,0 +1,48 @@
package io.ebean.meta;
/**
* Defines visitor to read and report the transaction and query metrics.
*/
public interface MetricVisitor {
/**
* Return true if the metrics should be reset.
*/
boolean isReset();
/**
* Return true if we should visit the transaction metrics.
*/
boolean isCollectTransactionMetrics();
/**
* Return true if we should visit the ORM and SQL query metrics.
*/
boolean isCollectQueryMetrics();
/**
* Visit has started.
*/
void visitStart();
/**
* Visit transaction metrics (and L2 cache metrics in future).
*/
void visitTimed(MetaTimedMetric metric);
/**
* Visit DTO and SQL query metrics.
*/
void visitQuery(MetaQueryMetric metric);
/**
* Visit ORM query metrics.
*/
void visitOrmQuery(MetaOrmQueryMetric metric);
/**
* Visit has completed.
*/
void visitEnd();
}
@@ -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);
@@ -21,6 +21,11 @@ public class ScopedTransaction extends SpiTransactionProxy {
private ScopeTrans current;
/**
* Flag set when we clear the thread scope (on commit/rollback or end).
*/
private boolean scopeCleared;
public ScopedTransaction(TransactionScopeManager manager) {
this.manager = manager;
}
@@ -47,30 +52,51 @@ public class ScopedTransaction extends SpiTransactionProxy {
*/
public void complete(Object returnOrThrowable, int opCode) {
current.complete(returnOrThrowable, opCode);
// no finally here for pop() as we come in here twice if an
// error is thrown on commit (due to enhancement finally block)
pop();
}
/**
* Programmatic complete - finally block, try to commit.
* Internal programmatic complete - finally block, try to commit.
*/
public void complete() {
current.complete();
pop();
try {
current.complete();
} finally {
pop();
}
}
private void clearScopeOnce() {
if (!scopeCleared) {
manager.set(null);
scopeCleared = true;
}
}
private boolean clearScope() {
if (stack.isEmpty()) {
clearScopeOnce();
return true;
}
return false;
}
private void pop() {
if (!stack.isEmpty()) {
if (!clearScope()) {
current = stack.pop();
transaction = current.getTransaction();
} else {
manager.set(null);
}
}
@Override
public void end() throws PersistenceException {
current.end();
pop();
try {
current.end();
} finally {
pop();
}
}
@Override
@@ -80,17 +106,29 @@ public class ScopedTransaction extends SpiTransactionProxy {
@Override
public void commit() {
current.commitTransaction();
try {
current.commitTransaction();
} finally {
clearScope();
}
}
@Override
public void rollback() throws PersistenceException {
current.rollback(null);
try {
current.rollback(null);
} finally {
clearScope();
}
}
@Override
public void rollback(Throwable e) throws PersistenceException {
current.rollback(e);
try {
current.rollback(e);
} finally {
clearScope();
}
}
@Override
@@ -14,6 +14,7 @@ import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
import io.ebeaninternal.server.core.SpiResultSet;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
@@ -257,4 +258,14 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* Execute the underlying ORM query returning as a JDBC ResultSet to map to DTO beans.
*/
SpiResultSet findResultSet(SpiQuery<?> ormQuery, SpiTransaction transaction);
/**
* Visit all the metrics (typically reporting them).
*/
void visitMetrics(MetricVisitor visitor);
/**
* Return true if a row for the bean type and id exists.
*/
boolean exists(Class<?> beanType, Object beanId, Transaction transaction);
}
@@ -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);
}
@@ -179,6 +179,11 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
ProfileLocation getProfileLocation();
/**
* Return the label set on the query.
*/
String getLabel();
/**
* Return true if this is a "find by id" query. This includes a check for a single "equal to" expression for the Id.
*/
@@ -15,6 +15,11 @@ public interface SpiSqlBinding {
*/
String getQuery();
/**
* Return the label (to collect metrics on when set).
*/
String getLabel();
/**
* Return the first row to fetch.
*/
@@ -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();
@@ -43,6 +43,11 @@ public interface SpiUpdate<T> extends Update<T> {
*/
Class<?> getBeanType();
/**
* Return the label (for metrics collection).
*/
String getLabel();
/**
* Return the type of this - insert, update or delete.
*/
@@ -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.
*/
@@ -37,6 +37,7 @@ public class DdlGenerator {
private final boolean runDdl;
private final boolean createOnly;
private final boolean jaxbPresent;
private final boolean ddlCommitOnCreateIndex;
private CurrentModel currentModel;
private String dropAllContent;
@@ -50,8 +51,10 @@ public class DdlGenerator {
if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) {
log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode());
this.runDdl = false;
this.ddlCommitOnCreateIndex = false;
} else {
this.runDdl = serverConfig.isDdlRun();
this.ddlCommitOnCreateIndex = server.getDatabasePlatform().isDdlCommitOnCreateIndex();
}
}
@@ -109,6 +112,8 @@ public class DdlGenerator {
try {
if (expectErrors) {
connection.setAutoCommit(true);
} else if (ddlCommitOnCreateIndex) {
runner.setCommitOnCreateIndex();
}
int count = runner.runAll(content, connection);
if (expectErrors) {
@@ -5,6 +5,7 @@ import io.ebean.EbeanServer;
import io.ebean.annotation.Platform;
import io.ebean.config.DbConstraintNaming;
import io.ebean.config.DbMigrationConfig;
import io.ebean.config.PlatformConfig;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.db2.DB2Platform;
@@ -15,7 +16,8 @@ import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServer16Platform;
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
import io.ebean.dbmigration.DbMigration;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
@@ -226,15 +228,17 @@ public class DefaultDbMigration implements DbMigration {
if (!online) {
DbOffline.setGenerateMigration();
if (databasePlatform == null && !platforms.isEmpty()) {
// for multiple platform generation set the general platform
// to H2 so that it runs offline without DB connection
// for multiple platform generation the first platform
// is used to generate the "logical" model diff
setPlatform(platforms.get(0).platform);
}
}
setDefaults();
if (!platforms.isEmpty()) {
configurePlatforms();
}
try {
Request request = createRequest();
if (platforms.isEmpty()) {
generateExtraDdl(request.migrationDir, databasePlatform);
}
@@ -253,6 +257,16 @@ public class DefaultDbMigration implements DbMigration {
}
}
/**
* Load the configuration for each of the target platforms.
*/
private void configurePlatforms() {
for (Pair pair : platforms) {
PlatformConfig config = serverConfig.newPlatformConfig("dbmigration.platform", pair.prefix);
pair.platform.configure(config);
}
}
/**
* Generate "repeatable" migration scripts.
* <p>
@@ -554,8 +568,12 @@ public class DefaultDbMigration implements DbMigration {
return new OraclePlatform();
case SQLANYWHERE:
return new SqlAnywherePlatform();
case SQLSERVER16:
return new SqlServer16Platform();
case SQLSERVER17:
return new SqlServer17Platform();
case SQLSERVER:
return new SqlServerPlatform();
throw new IllegalArgumentException("Please choose the more specific SQLSERVER16 or SQLSERVER17 platform. Refer to issue #1340 for details");
case DB2:
return new DB2Platform();
case SQLITE:
@@ -0,0 +1,57 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
/**
* CockroachDB specific DDL handling.
*/
public class CockroachDdl extends PlatformDdl {
public CockroachDdl(DatabasePlatform platform) {
super(platform);
this.historyDdl = new PostgresHistoryDdl();
this.dropTableCascade = " cascade";
this.columnSetType = "type ";
this.alterTableIfExists = "if exists ";
this.columnSetNull = "drop not null";
}
@Override
protected String convertArrayType(String logicalArrayType) {
return NativeDbArray.logicalToNative(logicalArrayType);
}
/**
* Map bigint, integer and smallint all into serial.
*/
@Override
public String asIdentityColumn(String columnDefn) {
if ("bigint".equalsIgnoreCase(columnDefn)) {
return "serial";
}
if ("integer".equalsIgnoreCase(columnDefn)) {
return "serial";
}
if ("smallint".equalsIgnoreCase(columnDefn)) {
return "serial";
}
return columnDefn;
}
@Override
public void addTableComment(DdlBuffer apply, String tableName, String tableComment) {
// do nothing
}
@Override
public void addColumnComment(DdlBuffer apply, String table, String column, String comment) {
// do nothing
}
@Override
public boolean isInlineComments() {
return false;
}
}
@@ -0,0 +1,20 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
/**
* Helper for logical type conversion.
*/
class NativeDbArray {
/**
* Covert the 'logical' array type to a native one (for Postgres and Cockroach).
*/
static String logicalToNative(String logicalArrayType) {
int colonPos = logicalArrayType.lastIndexOf(']');
if (colonPos == -1) {
return logicalArrayType;
} else {
// trim of the fallback varchar length
return logicalArrayType.substring(0, colonPos + 1);
}
}
}
@@ -18,13 +18,7 @@ public class PostgresDdl extends PlatformDdl {
@Override
protected String convertArrayType(String logicalArrayType) {
int colonPos = logicalArrayType.lastIndexOf(']');
if (colonPos == -1) {
return logicalArrayType;
} else {
// trim of the fallback varchar length
return logicalArrayType.substring(0, colonPos + 1);
}
return NativeDbArray.logicalToNative(logicalArrayType);
}
/**
@@ -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.
*/
@@ -1,5 +1,6 @@
package io.ebeaninternal.extraddl.model;
import io.ebean.annotation.Platform;
import io.ebean.util.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -16,6 +17,8 @@ public class ExtraDdlXmlReader {
private static final Logger logger = LoggerFactory.getLogger(ExtraDdlXmlReader.class);
private static final String SQLSERVER = Platform.SQLSERVER.name().toLowerCase();
/**
* Return the combined extra DDL that should be run given the platform name.
*/
@@ -52,14 +55,33 @@ public class ExtraDdlXmlReader {
return true;
}
String genericMatch = genericPlatformMatch(platformName);
for (String name : StringHelper.splitNames(platforms)) {
if (name.toLowerCase().contains(platformName)) {
return true;
} else if (genericMatch != null && genericMatch.equals(name.toLowerCase())) {
// allow sqlserver ... to match sqlserver17 and sqlserver16 platforms
return true;
}
}
return false;
}
/**
* Return a "generic" platform name that can be used. e.g. sqlserver17 -> sqlserver.
*/
private static String genericPlatformMatch(String platformName) {
switch (platformName) {
case "sqlserver17":
return SQLSERVER;
case "sqlserver16":
return SQLSERVER;
default:
return null;
}
}
/**
* Read and return a ExtraDdl from an xml document at the given resource path.
*/
@@ -1,5 +1,7 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetricType;
/**
* Factory to create timed metric counters.
*/
@@ -15,20 +17,16 @@ public interface MetricFactory {
/**
* Create a timed metric group.
*/
TimedMetricMap createTimedMetricMap(String name);
TimedMetricMap createTimedMetricMap(MetricType metricType, String name);
/**
* Create a Timed metric.
*/
TimedMetric createTimedMetric(String name);
TimedMetric createTimedMetric(MetricType metricType, String name);
/**
* Create a Timed metric.
*/
QueryPlanMetric createQueryPlanMetric(Class<?> type, String label, String sql);
QueryPlanMetric createQueryPlanMetric(MetricType metricType, Class<?> type, String label, String sql);
/**
* Return a instance used to collect Query plan metrics.
*/
QueryPlanCollector createCollector(boolean reset);
}
@@ -1,5 +1,7 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetricVisitor;
/**
* Internal Query plan metric holder.
*/
@@ -11,7 +13,7 @@ public interface QueryPlanMetric {
TimedMetric getMetric();
/**
* Collect the non-empty query plan metrics.
* Visit the underlying metric.
*/
void collect(QueryPlanCollector collector);
void visit(MetricVisitor visitor);
}
@@ -1,8 +1,6 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetaTimedMetric;
import java.util.List;
import io.ebean.meta.MetricVisitor;
/**
* Metric for timed events like transaction execution times.
@@ -25,12 +23,17 @@ public interface TimedMetric {
boolean isEmpty();
/**
* Collect the timed metric statistics.
* Reset the statistics.
*/
void reset();
/**
* Collect and return a snapshot of the metrics.
*/
TimedMetricStats collect(boolean reset);
/**
* Add non empty metrics to the result.
* Visit non empty metrics.
*/
void collect(boolean reset, List<MetaTimedMetric> result);
void visit(MetricVisitor visitor);
}
@@ -1,8 +1,6 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetaTimedMetric;
import java.util.List;
import io.ebean.meta.MetricVisitor;
/**
* A map of timed metrics keyed by a string.
@@ -15,7 +13,12 @@ public interface TimedMetricMap {
void add(String key, long exeMicros);
/**
* Add non empty metrics to the given result.
* Add an execution for the given key including row/bean count.
*/
void collect(boolean reset, List<MetaTimedMetric> result);
void add(String key, long exeMicros, int rows);
/**
* Visit the metric.
*/
void visit(MetricVisitor visitor);
}
@@ -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);
@@ -21,14 +21,16 @@ public class CachedBeanDataToBean {
}
// 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();
@@ -24,7 +24,7 @@ import java.sql.SQLException;
*/
public abstract class AbstractSqlQueryRequest {
private final SpiSqlBinding query;
protected final SpiSqlBinding query;
protected final SpiEbeanServer server;
@@ -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;
}
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.meta.MetaObjectGraphNodeStats;
import io.ebean.meta.MetaOrmQueryNode;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
@@ -35,7 +35,7 @@ public class CObjectGraphNodeStatistics {
totalBeans.add(beanCount);
}
public MetaObjectGraphNodeStats get(boolean reset) {
public MetaOrmQueryNode get(boolean reset) {
if (reset) {
return new Snapshot(node, startTime.getAndSet(System.currentTimeMillis()), count.sumThenReset(),
totalTime.sumThenReset(), totalBeans.sumThenReset());
@@ -44,7 +44,7 @@ public class CObjectGraphNodeStatistics {
}
}
private static class Snapshot implements MetaObjectGraphNodeStats {
private static class Snapshot implements MetaOrmQueryNode {
private final ObjectGraphNode node;
private final long startTime;
@@ -1,17 +1,20 @@
package io.ebeaninternal.server.core;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.cockroach.CockroachPlatform;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServerPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.Postgres8Platform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServer16Platform;
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
import io.ebean.migration.util.JdbcClose;
import io.ebeaninternal.dbmigration.DbOffline;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -20,6 +23,8 @@ import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
@@ -52,8 +57,7 @@ public class DatabasePlatformFactory {
}
if (serverConfig.getDataSourceConfig().isOffline()) {
String m = "You must specify a DatabasePlatformName when you are offline";
throw new PersistenceException(m);
throw new PersistenceException("You must specify a DatabasePlatformName when you are offline");
}
// guess using meta data from driver
return byDataSource(serverConfig.getDataSource());
@@ -84,8 +88,14 @@ public class DatabasePlatformFactory {
if (dbName.equals("oracle") || dbName.equals("oracle10") || dbName.equals("oracle9")) {
return new OraclePlatform();
}
if (dbName.equals("sqlserver16")) {
return new SqlServer16Platform();
}
if (dbName.equals("sqlserver17")) {
return new SqlServer17Platform();
}
if (dbName.equals("sqlserver")) {
return new SqlServerPlatform();
throw new IllegalArgumentException("Please choose the more specific sqlserver16 or sqlserver17 platform. Refer to issue #1340 for details");
}
if (dbName.equals("sqlanywhere")) {
return new SqlAnywherePlatform();
@@ -105,20 +115,20 @@ public class DatabasePlatformFactory {
*/
private DatabasePlatform byDataSource(DataSource dataSource) {
Connection conn = null;
Connection connection = null;
try {
conn = dataSource.getConnection();
DatabaseMetaData metaData = conn.getMetaData();
connection = dataSource.getConnection();
DatabaseMetaData metaData = connection.getMetaData();
return byDatabaseMeta(metaData);
return byDatabaseMeta(metaData, connection);
} catch (SQLException ex) {
throw new PersistenceException(ex);
} finally {
try {
if (conn != null) {
conn.close();
if (connection != null) {
connection.close();
}
} catch (SQLException ex) {
logger.error(null, ex);
@@ -129,7 +139,7 @@ public class DatabasePlatformFactory {
/**
* Find the platform by the metaData.getDatabaseProductName().
*/
private DatabasePlatform byDatabaseMeta(DatabaseMetaData metaData) throws SQLException {
private DatabasePlatform byDatabaseMeta(DatabaseMetaData metaData, Connection connection) throws SQLException {
String dbProductName = metaData.getDatabaseProductName();
dbProductName = dbProductName.toLowerCase();
@@ -137,7 +147,7 @@ public class DatabasePlatformFactory {
if (dbProductName.contains("oracle")) {
return new OraclePlatform();
} else if (dbProductName.contains("microsoft")) {
return new SqlServerPlatform();
throw new IllegalArgumentException("For SqlServer please explicitly choose either sqlserver16 or sqlserver17 as the platform via ServerConfig.setDatabasePlatformName. Refer to issue #1340 for more details");
} else if (dbProductName.contains("mysql")) {
return new MySqlPlatform();
} else if (dbProductName.contains("h2")) {
@@ -145,7 +155,7 @@ public class DatabasePlatformFactory {
} else if (dbProductName.contains("hsql database engine")) {
return new HsqldbPlatform();
} else if (dbProductName.contains("postgres")) {
return new PostgresPlatform();
return readPostgres(connection);
} else if (dbProductName.contains("sqlite")) {
return new SQLitePlatform();
} else if (dbProductName.contains("db2")) {
@@ -157,4 +167,32 @@ public class DatabasePlatformFactory {
// use the standard one
return new DatabasePlatform();
}
/**
* Use a select version() query as it could be Postgres or CockroachDB.
*/
private static PostgresPlatform readPostgres(Connection connection) {
// Postgres driver uses a hardcoded product name so use version() query
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
statement = connection.prepareStatement("SELECT version() AS \"version\"");
resultSet = statement.executeQuery();
if (resultSet.next()) {
String productVersion = resultSet.getString("version").toLowerCase();
if (productVersion.contains("cockroach")) {
return new CockroachPlatform();
}
}
} catch (SQLException e) {
logger.warn("Error running detection query on Postgres", e);
} finally {
JdbcClose.close(resultSet);
JdbcClose.close(statement);
}
// Real Postgres
return new PostgresPlatform();
}
}
@@ -83,7 +83,7 @@ public class DefaultContainer implements SpiContainer {
ServerConfig config = new ServerConfig();
config.setName(name);
config.loadFromProperties(PropertiesLoader.load());
config.loadFromProperties();
return createServer(config);
}
@@ -267,7 +267,7 @@ public class DefaultContainer implements SpiContainer {
config.setDatabasePlatform(platform);
}
logger.info("DatabasePlatform name:{} platform:{}", config.getName(), platform.getName());
platform.configure(config);
platform.configure(config.getPlatformConfig());
}
/**
@@ -1,12 +1,13 @@
package io.ebeaninternal.server.core;
import io.ebean.meta.AbstractMetricVisitor;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaObjectGraphNodeStats;
import io.ebean.meta.MetaOrmQueryMetric;
import io.ebean.meta.MetaOrmQueryNode;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaQueryPlanStatistic;
import io.ebean.meta.MetaTimedMetric;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.query.CQueryPlanStatsCollector;
import io.ebean.meta.MetricVisitor;
import java.util.ArrayList;
import java.util.List;
@@ -23,29 +24,26 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
}
@Override
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
return server.collectTransactionStatistics(reset);
public void visitMetrics(MetricVisitor visitor) {
server.visitMetrics(visitor);
}
@Override
public List<MetaQueryMetric> collectQueryStatistics(boolean reset) {
return server.collectQueryStatistics(reset);
public BasicMetricVisitor visitBasic() {
BasicMetricVisitor basic = new BasicMetricVisitor();
visitMetrics(basic);
return basic;
}
@Override
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
CQueryPlanStatsCollector collector = new CQueryPlanStatsCollector(reset);
for (BeanDescriptor<?> desc : server.getBeanDescriptors()) {
desc.collectQueryPlanStatistics(collector);
}
return collector.getList();
public void resetAllMetrics() {
server.visitMetrics(new ResetVisitor());
}
@Override
public List<MetaObjectGraphNodeStats> collectNodeStatistics(boolean reset) {
public List<MetaOrmQueryNode> collectNodeStatistics(boolean reset) {
List<MetaObjectGraphNodeStats> list = new ArrayList<>();
List<MetaOrmQueryNode> list = new ArrayList<>();
for (CObjectGraphNodeStatistics nodeStatistics : server.objectGraphStats.values()) {
if (!nodeStatistics.isEmpty()) {
list.add(nodeStatistics.get(reset));
@@ -54,4 +52,29 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
return list;
}
/**
* Visitor that resets the statistics but doesn't collect them.
*/
private static class ResetVisitor extends AbstractMetricVisitor {
ResetVisitor() {
super(true, true, true);
}
@Override
public void visitTimed(MetaTimedMetric metric) {
// ignore
}
@Override
public void visitQuery(MetaQueryMetric metric) {
// ignore
}
@Override
public void visitOrmQuery(MetaOrmQueryMetric metric) {
// ignore
}
}
}
@@ -12,6 +12,7 @@ import io.ebean.Filter;
import io.ebean.FutureIds;
import io.ebean.FutureList;
import io.ebean.FutureRowCount;
import io.ebean.MergeOptions;
import io.ebean.PagedList;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
@@ -48,8 +49,7 @@ import io.ebean.event.BeanPersistController;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.meta.MetricVisitor;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.Property;
@@ -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();
}
}
/**
@@ -916,6 +919,20 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultUpdateQuery<>(createQuery(beanType));
}
@Override
public void merge(Object bean, MergeOptions options) {
merge(bean, options, null);
}
@Override
public void merge(Object bean, MergeOptions options, Transaction transaction) {
BeanDescriptor<?> desc = getBeanDescriptor(bean.getClass());
if (desc == null) {
throw new PersistenceException(bean.getClass().getName() + " is NOT an Entity Bean registered with this server?");
}
executeInTrans((txn) -> persister.merge(desc, checkEntityBean(bean), options, txn), transaction);
}
@Override
public <T> Query<T> find(Class<T> beanType) {
return createQuery(beanType);
@@ -952,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;
@@ -985,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) {
@@ -1272,6 +1299,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public boolean exists(Class<?> beanType, Object beanId, Transaction transaction) {
List<Object> ids = findIds(find(beanType).setId(beanId), transaction);
return !ids.isEmpty();
}
@Override
public <A, T> List<A> findIds(Query<T> query, Transaction t) {
@@ -2192,10 +2225,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
public List<MetaTimedMetric> collectTransactionStatistics(boolean reset) {
return transactionManager.collectTransactionStatistics(reset);
}
@Override
public Set<Property> checkUniqueness(Object bean) {
return checkUniqueness(bean, null);
@@ -2237,11 +2266,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
/**
* Returns a set of properties if saving the bean will violate the unique constraints
* (definded by given properties).
* Returns a set of properties if saving the bean will violate the unique constraints (defined by given properties).
*/
private Set<Property> checkUniqueness(EntityBean entityBean, BeanDescriptor<?> beanDesc, BeanProperty[] props,
Transaction transaction) {
private Set<Property> checkUniqueness(EntityBean entityBean, BeanDescriptor<?> beanDesc, BeanProperty[] props, Transaction transaction) {
BeanProperty idProperty = beanDesc.getIdProperty();
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
ExpressionList<?> exprList = query.where();
@@ -2261,15 +2288,24 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (findCount(query, transaction) > 0) {
Set<Property> ret = new LinkedHashSet<>();
for (Property prop : props) {
ret.add(prop);
}
Collections.addAll(ret, props);
return ret;
}
return null;
}
public List<MetaQueryMetric> collectQueryStatistics(boolean reset) {
return dtoBeanManager.collectStats(reset);
@Override
public void visitMetrics(MetricVisitor visitor) {
visitor.visitStart();
if (visitor.isCollectTransactionMetrics()) {
transactionManager.visitMetrics(visitor);
}
if (visitor.isCollectQueryMetrics()) {
beanDescriptorManager.visitMetrics(visitor);
dtoBeanManager.visitMetrics(visitor);
relationalQueryEngine.visitMetrics(visitor);
persister.visitMetrics(visitor);
}
visitor.visitEnd();
}
}
@@ -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,108 @@
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();
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() {
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) {
@@ -304,7 +297,7 @@ public class InternalConfiguration {
}
public RelationalQueryEngine createRelationalQueryEngine() {
return new DefaultRelationalQueryEngine(binder, serverConfig.getDatabaseBooleanTrue(), serverConfig.getDbTypeConfig().getDbUuid().useBinaryOptimized());
return new DefaultRelationalQueryEngine(binder, serverConfig.getDatabaseBooleanTrue(), serverConfig.getPlatformConfig().getDbUuid().useBinaryOptimized());
}
public OrmQueryEngine createOrmQueryEngine() {
@@ -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;
}
/**
@@ -22,6 +22,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
UPDATESQL(EVT_UPDATESQL),
CALLABLESQL(EVT_CALLABLESQL);
String profileEventId;
Type(String profileEventId) {
this.profileEventId = profileEventId;
}
@@ -36,12 +37,30 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
final PersistExecute persistExecute;
protected String label;
protected long startNanos;
PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute) {
super(server, t);
this.persistExecute = persistExecute;
}
/**
* Used by CallableSqlRequest and UpdateSqlRequest.
*/
public PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute) {
super(server, t);
this.persistExecute = persistExecute;
PersistRequest(SpiEbeanServer server, SpiTransaction t, PersistExecute persistExecute, String label) {
this(server, t, persistExecute);
this.label = label;
}
/**
* Effectively set start nanos if we are collecting metrics on a label.
*/
public void startBind(boolean batchThisRequest) {
if (!batchThisRequest && label != null) {
startNanos = System.nanoTime();
}
}
/**
@@ -23,6 +23,7 @@ import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.BatchedSqlException;
import io.ebeaninternal.server.persist.Flags;
import io.ebeaninternal.server.persist.PersistExecute;
import io.ebeaninternal.server.transaction.BeanPersistIdMap;
import io.ebeanservice.docstore.api.DocStoreUpdate;
@@ -34,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;
@@ -75,6 +77,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private final boolean publish;
private int flags;
private DocStoreMode docStoreMode;
private ConcurrencyMode concurrencyMode;
@@ -104,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).
*/
@@ -151,8 +160,13 @@ 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, boolean saveRecurse, boolean publish) {
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
super(server, t, persistExecute);
this.entityBean = (EntityBean) bean;
@@ -165,7 +179,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
this.controller = beanDescriptor.getPersistController();
this.type = type;
this.docStoreMode = calcDocStoreMode(transaction, type);
if (saveRecurse) {
this.flags = flags;
if (Flags.isRecurse(flags)) {
this.persistCascade = t.isPersistCascade();
}
@@ -179,7 +194,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
beanDescriptor.checkMutableProperties(intercept);
}
this.concurrencyMode = beanDescriptor.getConcurrencyMode(intercept);
this.publish = publish;
this.publish = Flags.isPublish(flags);
if (isMarkDraftDirty(publish)) {
beanDescriptor.setDraftDirty(entityBean, true);
}
@@ -231,6 +246,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
if (createImplicitTransIfRequired()) {
docStoreMode = calcDocStoreMode(transaction, type);
}
checkBatchEscalationOnCascade();
}
/**
* Check for batch escalation on cascade.
*/
public void checkBatchEscalationOnCascade() {
if (transaction.checkBatchEscalationOnCascade(this)) {
// we escalated to use batch mode so flush when done
// but if createdTransaction then commit will flush it
@@ -326,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;
}
@@ -847,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.
*/
@@ -894,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);
@@ -1020,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();
}
/**
@@ -1076,6 +1136,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return requestUpdateAllLoadedProps;
}
/**
* Return the flags set on this persist request.
*/
public int getFlags() {
return flags;
}
/**
* Return true if this request is a 'publish' action.
*/
@@ -1226,4 +1293,71 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public void profile() {
profileBase(type.profileEventId, profileOffset, beanDescriptor.getProfileId(), 1);
}
/**
* Set the request flags indicating this is an insert.
*/
public void flagInsert() {
flags = Flags.setInsert(flags);
}
/**
* Unset the request insert flag indicating this is an update.
*/
public void flagUpdate() {
flags = Flags.unsetInsert(flags);
}
/**
* Return true if this request is an insert.
*/
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());
}
}
}
@@ -31,10 +31,9 @@ public final class PersistRequestCallableSql extends PersistRequest {
/**
* Create.
*/
public PersistRequestCallableSql(SpiEbeanServer server,
CallableSql cs, SpiTransaction t, PersistExecute persistExecute) {
public PersistRequestCallableSql(SpiEbeanServer server, CallableSql cs, SpiTransaction t, PersistExecute persistExecute) {
super(server, t, persistExecute);
super(server, t, persistExecute, cs.getLabel());
this.type = PersistRequest.Type.CALLABLESQL;
this.callableSql = (SpiCallableSql) cs;
}
@@ -88,7 +87,9 @@ public final class PersistRequestCallableSql extends PersistRequest {
*/
@Override
public void postExecute() {
if (startNanos > 0) {
persistExecute.collectSqlCall(label, startNanos, rowCount);
}
if (transaction.isLogSummary()) {
String m = "CallableSql label[" + callableSql.getLabel() + "]" + " rows[" + rowCount + "]" + " bind[" + bindLog + "]";
transaction.logSummary(m);
@@ -27,7 +27,7 @@ public final class PersistRequestOrmUpdate extends PersistRequest {
public PersistRequestOrmUpdate(SpiEbeanServer server, BeanManager<?> mgr, SpiUpdate<?> ormUpdate,
SpiTransaction t, PersistExecute persistExecute) {
super(server, t, persistExecute);
super(server, t, persistExecute, ormUpdate.getLabel());
this.beanDescriptor = mgr.getBeanDescriptor();
this.ormUpdate = ormUpdate;
}
@@ -86,7 +86,9 @@ public final class PersistRequestOrmUpdate extends PersistRequest {
*/
@Override
public void postExecute() {
if (startNanos > 0) {
persistExecute.collectOrmUpdate(label, startNanos, rowCount);
}
OrmUpdateType ormUpdateType = ormUpdate.getOrmUpdateType();
String tableName = ormUpdate.getBaseTable();
@@ -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);
super(server, t, persistExecute, sqlUpdate.getLabel());
this.type = Type.UPDATESQL;
this.updateSql = (SpiSqlUpdate) updateSql;
this.updateSql = (SpiSqlUpdate) sqlUpdate;
updateSql.reset();
}
@Override
@@ -102,7 +103,9 @@ public final class PersistRequestUpdateSql extends PersistRequest {
*/
@Override
public void postExecute() {
if (startNanos > 0) {
persistExecute.collectSqlUpdate(label, startNanos, rowCount);
}
if (transaction.isLogSummary()) {
String m = description + " table[" + tableName + "] rows[" + rowCount + "] bind[" + bindLog + "]";
transaction.logSummary(m);
@@ -1,11 +1,15 @@
package io.ebeaninternal.server.core;
import io.ebean.CallableSql;
import io.ebean.MergeOptions;
import io.ebean.Query;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.Update;
import io.ebean.bean.EntityBean;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.Collection;
import java.util.List;
@@ -15,6 +19,11 @@ import java.util.List;
*/
public interface Persister {
/**
* Merge the bean.
*/
int merge(BeanDescriptor<?> desc, EntityBean entityBean, MergeOptions options, SpiTransaction transaction);
/**
* Update the bean.
*/
@@ -78,4 +87,8 @@ public interface Persister {
*/
<T> List<T> draftRestore(Query<T> query, Transaction transaction);
/**
* Visit the metrics.
*/
void visitMetrics(MetricVisitor visitor);
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.CockroachDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DB2Ddl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.H2Ddl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HsqldbDdl;
@@ -38,10 +39,14 @@ public class PlatformDdlBuilder {
return new PlatformDdl(platform);
case POSTGRES:
return new PostgresDdl(platform);
case SQLSERVER:
return new SqlServerDdl(platform);
case COCKROACH:
return new CockroachDdl(platform);
case SQLANYWHERE:
return new PlatformDdl(platform);
case SQLSERVER16:
case SQLSERVER17:
case SQLSERVER:
return new SqlServerDdl(platform);
default:
return new PlatformDdl(platform);
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.core;
import io.ebean.SqlRow;
import io.ebean.meta.MetricVisitor;
import java.util.List;
import java.util.function.Consumer;
@@ -29,4 +30,13 @@ public interface RelationalQueryEngine {
*/
void findEach(RelationalQueryRequest request, Predicate<SqlRow> consumer);
/**
* Collect SQL query execution statistics.
*/
void collect(String label, long exeMicros, int rows);
/**
* Visit the metrics.
*/
void visitMetrics(MetricVisitor visitor);
}
@@ -46,7 +46,11 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
@Override
protected void requestComplete() {
String label = query.getLabel();
if (label != null) {
long exeMicros = (System.nanoTime() - startNano) / 1000L;
queryEngine.collect(label, exeMicros, rows);
}
}
public void findEach(Consumer<SqlRow> consumer) {
@@ -0,0 +1,67 @@
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) {
return ((BeanCollection)value).getActualDetails();
} else {
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,20 +7,27 @@ 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;
/**
* 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.
*/
void setLoader(BeanCollectionLoader loader);
/**
* Return the underlying collection of beans.
*/
Collection underlying(Object value);
/**
* Return the mechanism to add beans to the underlying collection.
* <p>
@@ -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.
@@ -27,6 +27,7 @@ import io.ebean.event.changelog.ChangeType;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.meta.MetricVisitor;
import io.ebean.plugin.BeanDocType;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.ExpressionPath;
@@ -40,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;
@@ -61,12 +64,15 @@ 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.CQueryPlanStatsCollector;
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.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 +106,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 +120,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 +235,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 +245,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 +406,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 +428,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 +607,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 +662,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 +694,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 +721,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 +730,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 +780,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 +802,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 +864,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 +949,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 +963,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 +1033,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 +1438,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);
}
}
@@ -1598,10 +1615,13 @@ public class BeanDescriptor<T> implements BeanType<T> {
return new DeployUpdateParser(this).parse(ormUpdateStatement);
}
public void collectQueryPlanStatistics(CQueryPlanStatsCollector collector) {
/**
* Visit all the ORM query plan metrics (includes UpdateQuery with updates and deletes).
*/
public void visitMetrics(MetricVisitor visitor) {
for (CQueryPlan queryPlan : queryPlanCache.values()) {
if (!queryPlan.isEmptyStats()) {
collector.add(queryPlan.getSnapshot(collector.isReset()));
visitor.visitOrmQuery(queryPlan.getSnapshot(visitor.isReset()));
}
}
}
@@ -1636,6 +1656,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);
@@ -1784,6 +1805,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.
*/
@@ -1871,12 +1897,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.
*/
@@ -1948,7 +1981,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) {
@@ -1980,7 +2014,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];
@@ -2221,7 +2255,7 @@ public class BeanDescriptor<T> implements BeanType<T> {
@Override
public Property getProperty(String propName) {
return findBeanProperty(propName);
return findProperty(propName);
}
/**
@@ -2455,17 +2489,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);
}
@@ -2479,7 +2514,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
@@ -2537,6 +2573,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.
@@ -2806,6 +2846,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);
@@ -2930,16 +3005,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();
@@ -3128,6 +3193,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.
@@ -3270,6 +3355,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);
}
@@ -3282,11 +3379,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);
}
}
}
@@ -730,38 +756,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);
}
/**

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