Compare commits

...
Author SHA1 Message Date
rob bygrave b7469da5b1 [maven-release-plugin] prepare release ebean-12.3.7 2020-07-14 12:52:39 +12:00
rob bygrave 2897968a54 Bump maven plugin - no effective change 2020-07-14 12:50:47 +12:00
rob bygrave 6360fad701 Refactor tidy NoopCallOriginFactory 2020-07-14 11:11:14 +12:00
rob bygrave dbc97aa28c Remove the duplicate TestSoftDeleteBasic.testFindSoftDeletedList()
Hmmm, my bad. I merged the branch manually and that didn't remove the PR so I've merged this twice now and hence ended up with a duplicate method. All good, just removing this duplicate method.
2020-07-14 09:47:07 +12:00
SnōwballandGitHub a231c136a9 Add failing test case for softdeletes (#2034)
Example:
One X can have many Ys. Ys can be soft deleted.

Y1 -> X1 -> Y1

Y1 is soft deleted

When Y1 is loaded, lazy-loading X1 and then lazy-loading the list of Ys associated with X1,
the list should contain Y1. This test asserts that is true.
2020-07-14 09:43:54 +12:00
rob bygrave c6487e56cf Merge branch 'tobias--soft_deletes_not_included' 2020-07-14 09:33:26 +12:00
rob bygrave dac0a97a3f #2035 - lazy-loading soft-deleted list from lazy-loaded bean doesn't remember includeSoftDeletes 2020-07-14 09:32:52 +12:00
rob bygrave ff0274f72c Merge branch 'soft_deletes_not_included' of https://github.com/tobias-/ebean into tobias--soft_deletes_not_included 2020-07-13 18:02:04 +12:00
rob bygrave ddee202bcc No effective change - tidy SqlTreeNodeBean 2020-07-10 10:52:22 +12:00
rob bygrave e1522a4a68 No effective change - tidy test TestSoftDeleteBasic 2020-07-10 10:48:28 +12:00
rob bygrave 7bc6170587 #2032 - Objects that occur in query is not filtered by @SoftDelete (when query has predicates on a ToMany path the associated soft delete predicates are not included) 2020-07-10 10:00:52 +12:00
rob bygrave 0322c19261 Merge branch 'tobias--softdeleted_where' 2020-07-10 09:57:45 +12:00
Tobias 18ae0a7833 Add failing test case for softdeletes
Example:
One X can have many Ys. Ys can be soft deleted.

Y1 -> X1 -> Y1

Y1 is soft deleted

When Y1 is loaded, lazy-loading X1 and then lazy-loading the list of Ys associated with X1,
the list should contain Y1. This test asserts that is true.
2020-07-09 20:56:33 +02:00
Tobias e99db44ddf Add test for soft deleted children
When a child is referenced in the query, Ebean should make sure
that the child (or any of the objects connecting it in the query)
is deleted.

Ie. if a query requires a child to have a certain value, it should
filter out those children that are soft deleted. It should also perform
the deleted check on all objects leading up to that child.

E.g. assuming that all objects are soft deletable, the following where():

DB.find(Alpha.class).where()
.eq("alpha.beta.gamma.name", "bar")

should generate a query with
"t0.deleted = false AND t1.deleted = false AND t2.deleted = false AND t2.name = 'bar'"
2020-07-07 12:33:43 +02:00
rob bygrave 361de9ea99 Update error thrown to use DatabaseConfig rather than ServerConfig 2020-07-06 14:37:56 +12:00
rob bygrave 99732b30b1 No effective change - tidy test TestHstore 2020-07-01 15:38:58 +12:00
rob bygrave dd5e5819b8 No effective change - Javadoc for ServerConfig 2020-06-30 23:07:27 +12:00
rob bygrave b4330679b8 #2029 Opps, fix tests 2020-06-30 10:59:03 +12:00
rob bygrave 59447ad261 #2029 - ENH: Add database.getPlatform(); ... to obtain the underlying database platform being used 2020-06-29 21:56:18 +12:00
rob bygrave 3bd20a424a [maven-release-plugin] prepare for next development iteration 2020-06-29 18:07:38 +12:00
rob bygrave 4ac3a8d1ad [maven-release-plugin] prepare release ebean-12.3.6 2020-06-29 18:07:27 +12:00
rob bygrave 5a8f8b3a83 #2027 - querybean generator support for @EbeanComponent 2020-06-29 18:01:28 +12:00
rob bygrave 7c01cec466 [maven-release-plugin] prepare for next development iteration 2020-06-29 17:21:19 +12:00
rob bygrave 30ad4260f0 [maven-release-plugin] prepare release ebean-12.3.5 2020-06-29 17:21:08 +12:00
rob bygrave ab421d6f2b No effective change - trim whitespace in BaseTableDdl 2020-06-29 17:15:35 +12:00
rob bygrave 0b807addc7 No effective change - test tidy 2020-06-29 17:09:02 +12:00
rob bygrave bb03ef5693 No effective change - test tidy 2020-06-29 15:51:34 +12:00
rob bygrave 248a7b1062 #2004 - MariaDB DDL review 2020-06-29 15:50:08 +12:00
rob bygrave 9da28262b4 #2004 - MariaDB History useLegacyDatetimeCode=false required to properly handle server timezone 2020-06-29 14:51:24 +12:00
rob bygrave fc8dd18fd1 #2004 - MariaDB History DDL using add/drop system versioning 2020-06-29 14:03:21 +12:00
rob bygrave 70ba658442 #2026 - Postgres DDL - modify type generates wrong alter sql. Needs explicit cast / using clause. 2020-06-29 12:12:10 +12:00
rob bygrave 70b730657f #2028 - Refactor internals of smart persistence context to reference "parent" rather than copy beans (adjustment of #2021) 2020-06-29 08:48:24 +12:00
rob bygrave bf3cb7e2e2 No effective change - tidy test BeanDescriptor_registerTest 2020-06-26 15:49:35 +12:00
rob bygrave c2708ecc76 No effective change - Refactor tidy BeanDescriptor internals - trimQueryPlans() etc 2020-06-23 22:14:11 +12:00
rob bygrave 896aba3053 No effective change - trim whitespace 2020-06-23 22:03:21 +12:00
rob bygrave f78b602a90 No effective change - trim whitespace 2020-06-23 21:08:30 +12:00
rob bygrave 306c1b5cf6 No effective change - EntityBeanIntercept tidy javadoc 2020-06-23 20:57:31 +12:00
rob bygrave cdf2718ca9 No effective change - refactor EntityBeanIntercept reverse internal notEqual() method 2020-06-23 20:43:08 +12:00
rob bygrave 967ca0c9f3 No effective change - add some more tests for EntityBeanIntercept 2020-06-23 17:09:15 +12:00
rob bygrave 607acb1b4e No effective change - tidy DefaultPersistenceContext 2020-06-23 16:35:53 +12:00
rob bygrave 5f923945e9 No effective change - trim whitespace 2020-06-23 16:35:36 +12:00
rob bygrave 11fbe8bf5b [maven-release-plugin] prepare for next development iteration 2020-06-19 23:23:32 +12:00
rob bygrave 0544fa7ab4 [maven-release-plugin] prepare release ebean-12.3.4 2020-06-19 23:23:21 +12:00
rob bygrave 51eed9bfe4 Testing - default to h2 2020-06-19 23:20:46 +12:00
Rob BygraveandGitHub 32ce1f1d59 #2004 - MariaDb specific platform with @History support (initial) (#2023)
#2004 - MariaDb specific platform with @History support (initial)
2020-06-19 23:20:07 +12:00
rob bygrave f445c118a0 #2025 - Make findStream & findLargeStream the same - deprecate findLargeStream 2020-06-19 23:17:47 +12:00
rob bygrave a8ff918bb3 #2024 - Change default "fetchBuffer" for findEach/iterate/stream queries to 100 2020-06-19 22:36:24 +12:00
rob bygrave 729aeae0d7 No effective change - trim whitespace 2020-06-19 22:13:50 +12:00
rob bygrave 17a9341287 #2006 - Auto register AttributeConverter with Ebean (using @Converter) 2020-06-19 20:33:58 +12:00
rob bygrave c863014fec Javadoc improvements for #2022 - use of index params like ?1, ?2, ?3 etc 2020-06-18 16:04:13 +12:00
rob bygrave 8170db4101 Bump ebean-annotation which has MariaDB platform enum
As a step for #2004
Means we can add a specific platform for MariaDB separate from MySql
2020-06-18 11:30:43 +12:00
rob bygrave 434a5117a2 Bump to ebean-datasource 5.1
This will expose pool size which can be exposed as a gauge metric (the current size of main connection pool and readonly connection pool)
2020-06-18 11:10:01 +12:00
rob bygrave 85ed8cd784 Test update only - TestSqlUpdateBindMultipleLists 2020-06-18 11:07:45 +12:00
rob bygrave 9b70900267 #2021 - Use smarter persistence context for findEach/iterate/stream queries - improve performance of un-tuned findEach/iterate/stream queries that invoke lazy loading 2020-06-17 21:50:11 +12:00
rob bygrave 0e33d58bb2 Bump ebean-maven-plugin to 12.3.4 2020-06-17 21:47:14 +12:00
rob bygrave 9d9d13060c #2020 - Fix potential connection leak in query-iterator WITH lazyLoadBufferSize (Refer #1164) 2020-06-17 17:10:16 +12:00
rob bygrave e071eecd59 Tidy test only - TestQueryFindEachWhile 2020-06-17 15:37:58 +12:00
rob bygrave bc8e254a65 Tidy tests - delete duplicate test TestQueryFindVisit.java 2020-06-17 13:31:18 +12:00
rob bygrave e0e369904c Tidy test only - TestQueryFindIterate 2020-06-17 13:30:34 +12:00
rob bygrave bd85ba17a1 No effective change - tidy TestQueryFindEach 2020-06-16 17:00:23 +12:00
rob bygrave 8801b8d8ca No effective change - tidy TestQueryFindEach 2020-06-16 16:55:49 +12:00
rob bygrave ebd6c535ab No effective change - tidy TestRawSqlParsing 2020-06-12 15:04:36 +12:00
rob bygrave 5c771a7219 Add tests for RawSql with ${where} and ${andWhere} placeholders 2020-06-12 15:03:05 +12:00
rob bygrave eb6f722385 Put back javadoc maven plugin 2020-06-09 20:03:47 +12:00
rob bygrave cef4f59b2c [maven-release-plugin] prepare for next development iteration 2020-06-08 21:58:31 +12:00
rob bygrave 67352ab3b2 [maven-release-plugin] prepare release ebean-12.3.3 2020-06-08 21:58:19 +12:00
rob bygrave 4a21395cda Comment out javadoc maven plugin temporarily 2020-06-08 21:56:07 +12:00
rob bygrave fcd7f8b6cf Bump ebean-maven-plugin 2020-06-08 20:00:22 +12:00
rob bygrave 4565719728 #2017 - ENH: Add option for different initialDelay to BackgroundExecutor.executePeriodically() 2020-06-05 18:31:07 +12:00
rob bygrave cbdf5609b2 #2015 - Dto instance id fields are not populated when queried using DtoQuery (and no select clause is specified) 2020-05-27 22:05:39 +12:00
rob bygrave f97359502b Refactor internals - DtoMeta DtoMappingRequest
Refactor move most of the code mapping DB columns to bean setters from DtoMeta to DtoMappingRequest
2020-05-27 22:04:03 +12:00
rob bygrave ffaf00c064 Improve error message throw on DTO unmapped DB column 2020-05-27 21:39:16 +12:00
rob bygrave f6f9511d2a #2014 - IllegalArgumentException: No rule for io.ebeaninternal.dbmigration.migration.RenameColumn 2020-05-27 21:00:42 +12:00
rob bygrave 5e94c26094 No effective change - update TestQueryInIdTypeConversion 2020-05-27 15:53:01 +12:00
rob bygrave 5aaed4e12b #2016 - L2 Cache - missing puts with explicit bean cache + findByIds/keys + after transaction writes 2020-05-26 23:42:44 +12:00
rob bygrave c29a57b5ec No effective change - update TestBeanCache Ebean -> DB 2020-05-26 23:19:23 +12:00
rob bygrave 998b8cc192 #2011 - Change query.orderBy() to not be deprecated 2020-05-15 14:06:28 +12:00
rob bygrave 9c1d930fb8 #2012 - Change ExpressionList.orderBy(String) to return ExpressionList<T>
Plus #2011 remove @Deprecated for orderBy
2020-05-15 14:06:06 +12:00
rob bygrave d6d7537a9c #2012 - Change ExpressionList.orderBy(String) to return ExpressionList<T> 2020-05-15 14:05:15 +12:00
rob bygrave 92e83c49d9 [maven-release-plugin] prepare for next development iteration 2020-05-12 22:39:42 +12:00
111 changed files with 8555 additions and 1235 deletions
+12 -5
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>12.3.2</version>
<version>12.3.7</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -22,7 +22,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-12.3.2</tag>
<tag>ebean-12.3.7</tag>
</scm>
<profiles>
@@ -87,7 +87,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>6.11</version>
<version>6.13</version>
</dependency>
<dependency>
@@ -99,7 +99,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-datasource</artifactId>
<version>4.9.2</version>
<version>5.1</version>
</dependency>
<dependency>
@@ -248,6 +248,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sap.cloud.db.jdbc</groupId>
<artifactId>ngdbc</artifactId>
@@ -292,7 +299,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>12.3.2</version>
<version>12.3.7</version>
<executions>
<execution>
<id>test</id>
@@ -31,15 +31,18 @@ public interface BackgroundExecutor {
* Execute a task periodically with a fixed delay between each execution.
* <p>
* For example, execute a runnable every minute.
* </p>
* <p>
* The delay is the time between executions no matter how long the task took.
* That is, this method has the same behaviour characteristics as
* {@link ScheduledExecutorService#scheduleWithFixedDelay(Runnable, long, long, TimeUnit)}
* </p>
*/
void executePeriodically(Runnable r, long delay, TimeUnit unit);
/**
* Execute a task periodically additionally with an initial delay different from delay.
*/
void executePeriodically(Runnable r, long initialDelay, long delay, TimeUnit unit);
/**
* Schedules a Runnable for one-shot action that becomes enabled after the given delay.
*
+20
View File
@@ -1,5 +1,6 @@
package io.ebean;
import io.ebean.annotation.Platform;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.DatabaseConfig;
@@ -132,6 +133,25 @@ public interface Database {
*/
MetaInfoManager getMetaInfoManager();
/**
* Return the platform used for this database instance.
* <p>
* Note many platforms have multiple specific platform types so often we want to
* get the base platform via {@link Platform#base()}.
* </p>
* <pre>{@code
*
* Platform platform = database.getPlatform().base();
* if (platform == Platform.MYSQL) {
* // do MySql specific function
* }
*
* }</pre>
*
* @return platform for this database instance
*/
Platform getPlatform();
/**
* Return the extended API intended for use by plugins.
*/
+50 -18
View File
@@ -70,30 +70,40 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> order(String orderByClause);
/**
* Set the order by clause replacing the existing order by clause if there is
* one.
* <p>
* This follows SQL syntax using commas between each property with the
* optional asc and desc keywords representing ascending and descending order
* respectively.
*/
ExpressionList<T> orderBy(String orderBy);
/**
* Return the OrderBy so that you can append an ascending or descending
* property to the order by clause.
* <p>
* This will never return a null. If no order by clause exists then an 'empty'
* OrderBy object is returned.
* </p>
* <p>
* This is the same as <code>orderBy()</code>
*/
OrderBy<T> order();
/**
* Deprecated migrate to order().
* Return the OrderBy so that you can append an ascending or descending
* property to the order by clause.
* <p>
* This will never return a null. If no order by clause exists then an 'empty'
* OrderBy object is returned.
* <p>
* This is the same as <code>order()</code>
*/
@Deprecated
OrderBy<T> orderBy();
/**
* Deprecated migrate to {@link #order(String)}
*/
@Deprecated
Query<T> orderBy(String orderBy);
/**
* Deprecated migrate to {@link #order(String)}
* Deprecated migrate to {@link #orderBy(String)}
*/
@Deprecated
Query<T> setOrderBy(String orderBy);
@@ -1241,26 +1251,37 @@ public interface ExpressionList<T> {
/**
* Add raw expression with a single parameter.
* <p>
* The raw expression should contain a single ? at the location of the
* parameter.
* The raw expression should contain a single ? or ?1
* at the location of the parameter. We use ?1 when binding a
* collection for an IN expression.
* </p>
* <p>
* <p>>
* When properties in the clause are fully qualified as table-column names
* then they are not translated. logical property name names (not fully
* qualified) will still be translated to their physical name.
* </p>
* <p>
* <h4>Example:</h4>
* <h4>Examples:</h4>
* <pre>{@code
*
* // use a database function
* raw("add_days(orderDate, 10) < ?", someDate)
*
* raw("name like ?", "Rob%")
*
* raw("name in (?1)", asList("Rob", "Fiona", "Jack"))
*
* raw("name = any(?)", asList("Rob", "Fiona", "Jack"))
*
* }</pre>
*
* <h4>Subquery example:</h4>
* <h4>Subquery examples:</h4>
* <pre>{@code
*
* // Bind collection using ?1
* .raw("id in (select c.id from o_customer c where c.name in (?1))", asList("Rob", "Fiona", "Jack"))
*
* // Using Postgres ANY expression
* .raw("t0.customer_id in (select customer_id from customer_group where group_id = any(?::uuid[]))", groupIds)
*
* }</pre>
@@ -1270,14 +1291,25 @@ public interface ExpressionList<T> {
/**
* Add raw expression with an array of parameters.
* <p>
* The raw expression should contain the same number of ? as there are
* parameters.
* </p>
* The raw expression should contain the same number of ? or ?1, ?2 ... bind parameters
* as there are values. We use ?1, ?2 etc when binding a collection for an IN expression.
* <p>
* When properties in the clause are fully qualified as table-column names
* then they are not translated. logical property name names (not fully
* qualified) will still be translated to their physical name.
* </p>
*
* <h4>Examples:</h4>
* <pre>{@code
*
* raw("unitPrice > ? and product.id > ?", 2, 3)
*
* raw("(status = ? or (orderDate < ? and shipDate is null) or customer.name like ?)",
* Order.Status.APPROVED,
* new Timestamp(System.currentTimeMillis()),
* "Rob")
*
* }</pre></pre>
*/
ExpressionList<T> raw(String raw, Object... values);
+11 -5
View File
@@ -92,11 +92,9 @@ public interface ExtendedServer {
* {@link #findEachWhile(Query, Predicate, Transaction)} is preferred
* to findIterate(). The reason is that those methods automatically take care of
* closing the queryIterator (and the underlying jdbc statement and resultSet).
* </p>
* <p>
* This is similar to findEach in that not all the result beans need to be held
* in memory at the same time and as such is good for processing large queries.
* </p>
*
* @see Query#findIterate()
* @see Query#findEach(Consumer)
@@ -106,7 +104,10 @@ public interface ExtendedServer {
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
* Return the query result as a Stream using a single persistence context.
* Execute the query returning the result as a Stream.
* <p>
* Note that this can support very large queries iterating any number of results.
* To do so internally it can use multiple persistence contexts.
* <p>
* Note that the stream needs to be closed so use with try with resources.
* </p>
@@ -115,12 +116,17 @@ public interface ExtendedServer {
<T> Stream<T> findStream(Query<T> query, Transaction transaction);
/**
* Return the query result as a Stream (with multiple persistence contexts).
* Deprecated - migrate to findStream().
* <p>
* Execute the query returning the result as a Stream.
* <p>
* Note that this can support very large queries iterating any number of results.
* To do so internally it can use multiple persistence contexts.
* <p>
* Note that the stream needs to be closed so use with try with resources.
* </p>
*/
@Nonnull
@Deprecated
<T> Stream<T> findLargeStream(Query<T> query, Transaction transaction);
/**
+22 -9
View File
@@ -711,9 +711,9 @@ public interface Query<T> {
/**
* Execute the query returning the result as a Stream.
* <p>
* Note that this will hold all resulting beans in memory using a single
* persistence context. Use findLargeStream() for queries that expect to
* return a large number of results.
* Note that this can support very large queries iterating
* any number of results. To do so internally it can use
* multiple persistence contexts.
* </p>
* <pre>{@code
*
@@ -731,6 +731,8 @@ public interface Query<T> {
Stream<T> findStream();
/**
* Deprecated - migrate to findStream.
* <p>
* Execute the query returning the result as a Stream.
* <p>
* Note that this uses multiple persistence contexts such that we can use
@@ -749,6 +751,7 @@ public interface Query<T> {
* }</pre>
*/
@Nonnull
@Deprecated
Stream<T> findLargeStream();
/**
@@ -1352,9 +1355,13 @@ public interface Query<T> {
Query<T> having(Expression addExpressionToHaving);
/**
* Deprecated migrate to {@link #order(String)}
* Set the order by clause replacing the existing order by clause if there is
* one.
* <p>
* This follows SQL syntax using commas between each property with the
* optional asc and desc keywords representing ascending and descending order
* respectively.
*/
@Deprecated
Query<T> orderBy(String orderByClause);
/**
@@ -1373,13 +1380,20 @@ public interface Query<T> {
* <p>
* This will never return a null. If no order by clause exists then an 'empty'
* OrderBy object is returned.
* <p>
* This is the same as <code>orderBy()</code>
*/
OrderBy<T> order();
/**
* Deprecated migrate to order().
* Return the OrderBy so that you can append an ascending or descending
* property to the order by clause.
* <p>
* This will never return a null. If no order by clause exists then an 'empty'
* OrderBy object is returned.
* <p>
* This is the same as <code>order()</code>
*/
@Deprecated
OrderBy<T> orderBy();
/**
@@ -1388,9 +1402,8 @@ public interface Query<T> {
Query<T> setOrder(OrderBy<T> orderBy);
/**
* Deprecated migrate to {@link #setOrder(OrderBy)}
* Set an OrderBy object to replace any existing OrderBy clause.
*/
@Deprecated
Query<T> setOrderBy(OrderBy<T> orderBy);
/**
+37
View File
@@ -224,12 +224,49 @@ public interface SqlQuery extends Serializable {
*
* }</pre>
*
* <p>
* When binding a collection of values into a IN expression we should use
* indexed parameters like ?1, ?2, ?3 etc rather than just ?.
* </p>
*
* <pre>{@code
*
* String sql = "select c.id, c.name from customer c where c.name in (?1)";
*
* List<SqlRow> rows = DB.sqlQuery(sql)
* .setParameter(asList("Rob", "Fiona", "Jack"))
* .findList();
*
*
* List<SqlRow> rows = DB.sqlQuery(sql)
* .setParameter(1, asList("Rob", "Fiona", "Jack"))
* .findList();
* }</pre>
*
* @param value The value to bind
*/
SqlQuery setParameter(Object value);
/**
* Bind the parameter by its index position (1 based like JDBC).
* <p>
* When binding a collection of values into a IN expression we should use
* indexed parameters like ?1, ?2, ?3 etc rather than just ?.
* </p>
*
* <pre>{@code
*
* String sql = "select c.id, c.name from customer c where c.name in (?1)";
*
* List<SqlRow> rows = DB.sqlQuery(sql)
* .setParameter(asList("Rob", "Fiona", "Jack"))
* .findList();
*
*
* List<SqlRow> rows = DB.sqlQuery(sql)
* .setParameter(1, asList("Rob", "Fiona", "Jack"))
* .findList();
* }</pre>
*/
SqlQuery setParameter(int position, Object value);
+44 -22
View File
@@ -5,18 +5,16 @@ package io.ebean;
* <p>
* Provides a simple way to execute raw SQL insert update or delete statements
* without having to resort to JDBC.
* </p>
* <p>
* Supports the use of positioned or named parameters and can automatically
* notify Ebean of the table modified so that Ebean can maintain its cache.
* </p>
* <p>
* Note that {@link #setAutoTableMod(boolean)} and
* Ebean#externalModification(String, boolean, boolean, boolean)} can be to
* notify Ebean of external changes and enable Ebean to maintain it's "L2"
* server cache.
* </p>
*
* <h2>Positioned parameter example</h2>
* <pre>{@code
*
* // example using 'positioned' parameters
@@ -30,6 +28,7 @@ package io.ebean;
*
* }</pre>
*
* <h2>Named parameter example</h2>
* <pre>{@code
*
* // example using 'named' parameters
@@ -45,8 +44,31 @@ package io.ebean;
* String msg = "There were " + rows + " rows updated";
*
* }</pre>
*
* <h2>Index parameter examples (e.g. ?1, ?2, ?3 ...)</h2>
* <p>
* <h3>Example: Using setNextParameter()</h3>
* We can use index parameters like ?1, ?2, ?3 etc when binding arrays/collections
* of values into an IN expression.
* </p>
* <pre>{@code
*
* // Binding a list of 3 values (9991, 9992, 9993) into an IN expression
*
* DB.sqlUpdate("delete from o_customer where name = ? and id in (?2)")
* .setParameter(1, "Foo")
* .setParameter(2, asList(9991, 9992, 9993))
* .execute();
*
* // note this effectively is the same as
*
* DB.sqlUpdate("delete from o_customer where name = ? and id in (?2)")
* .setParameter("Foo")
* .setParameter(asList(9991, 9992, 9993))
* .execute();
*
* }</pre>
*
* <h3>Example: Using setParameter()</h3>
* <pre>{@code
*
* String sql = "insert into audit_log (id, description, modified_description) values (?,?,?)";
@@ -56,19 +78,19 @@ package io.ebean;
* try (Transaction txn = DB.beginTransaction()) {
* txn.setBatchMode(true);
*
* insert.setNextParameter(10000);
* insert.setNextParameter("hello");
* insert.setNextParameter("rob");
* insert.setParameter(10000);
* insert.setParameter("hello");
* insert.setParameter("rob");
* insert.execute();
*
* insert.setNextParameter(10001);
* insert.setNextParameter("goodbye");
* insert.setNextParameter("rob");
* insert.setParameter(10001);
* insert.setParameter("goodbye");
* insert.setParameter("rob");
* insert.execute();
*
* insert.setNextParameter(10002);
* insert.setNextParameter("chow");
* insert.setNextParameter("bob");
* insert.setParameter(10002);
* insert.setParameter("chow");
* insert.setParameter("bob");
* insert.execute();
*
* txn.commit();
@@ -81,19 +103,19 @@ package io.ebean;
*
* try (Transaction txn = DB.beginTransaction()) {
*
* insert.setNextParameter(10000);
* insert.setNextParameter("hello");
* insert.setNextParameter("rob");
* insert.setParameter(10000);
* insert.setParameter("hello");
* insert.setParameter("rob");
* insert.addBatch();
*
* insert.setNextParameter(10001);
* insert.setNextParameter("goodbye");
* insert.setNextParameter("rob");
* insert.setParameter(10001);
* insert.setParameter("goodbye");
* insert.setParameter("rob");
* insert.addBatch();
*
* insert.setNextParameter(10002);
* insert.setNextParameter("chow");
* insert.setNextParameter("bob");
* insert.setParameter(10002);
* insert.setParameter("chow");
* insert.setParameter("bob");
* insert.addBatch();
*
* int[] rows = insert.executeBatch();
@@ -82,14 +82,14 @@ public final class EntityBeanIntercept implements Serializable {
private static final byte FLAG_CHANGED_PROP = 2;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distingush
* Flags indicating if a property is a dirty embedded bean. Used to distinguish
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
private static final byte FLAG_EMBEDDED_DIRTY = 4;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distingush
* Flags indicating if a property is a dirty embedded bean. Used to distinguish
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
@@ -371,13 +371,11 @@ public final class EntityBeanIntercept implements Serializable {
/**
* Set the loaded state to true.
* <p>
* Calls to setter methods after the bean is loaded can result in 'Old Values'
* being created to support ConcurrencyMode.ALL
* </p>
* Calls to setter methods after the bean is loaded can result in
* 'Old Values' being created.
* <p>
* Worth noting that this is also set after a insert/update. By doing so it
* 'resets' the bean for making further changes and saving again.
* </p>
*/
public void setLoaded() {
this.state = STATE_LOADED;
@@ -391,8 +389,7 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
* When finished loading for lazy or refresh on an already partially populated
* bean.
* When finished loading for lazy or refresh on an already partially populated bean.
*/
public void setLoadedLazy() {
this.state = STATE_LOADED;
@@ -423,9 +420,6 @@ public final class EntityBeanIntercept implements Serializable {
/**
* Set true to turn off lazy loading.
* <p>
* Typically used to disable lazy loading on SQL based report beans.
* </p>
*/
public void setDisableLazyLoad(boolean disableLazyLoad) {
this.disableLazyLoad = disableLazyLoad;
@@ -445,7 +439,6 @@ public final class EntityBeanIntercept implements Serializable {
* Return true if the embedded bean is new or dirty and hence needs saving.
*/
public boolean isEmbeddedNewOrDirty(Object embeddedBean) {
if (embeddedBean == null) {
// if it was previously set then the owning bean would
// have oldValues containing the previous embedded bean
@@ -453,7 +446,6 @@ public final class EntityBeanIntercept implements Serializable {
}
if (embeddedBean instanceof EntityBean) {
return ((EntityBean) embeddedBean)._ebean_getIntercept().isNewOrDirty();
} else {
// non-enhanced so must assume it is new and needs to be saved
return true;
@@ -471,8 +463,8 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
* Finds the index position of a given property. Returns -1 if the property
* can not be found.
* Finds the index position of a given property. Returns -1 if the
* property can not be found.
*/
public int findProperty(String propertyName) {
String[] names = owner._ebean_getPropertyNames();
@@ -495,7 +487,7 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
* Return the number of properties.s
* Return the number of properties.
*/
public int getPropertyLength() {
return owner._ebean_getPropertyNames().length;
@@ -517,8 +509,7 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
* Set the property to be treated as unloaded. Used for properties initialised in default
* constructor.
* Set the property to be treated as unloaded. Used for properties initialised in default constructor.
*/
public void setPropertyUnloaded(int propertyIndex) {
flags[propertyIndex] &= ~FLAG_LOADED_PROP;
@@ -605,7 +596,6 @@ public final class EntityBeanIntercept implements Serializable {
* For forced update on a 'New' bean set all the loaded properties to changed.
*/
public void setNewBeanForUpdate() {
for (int i = 0; i < flags.length; i++) {
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
flags[i] |= FLAG_CHANGED_PROP;
@@ -660,8 +650,7 @@ public final class EntityBeanIntercept implements Serializable {
for (int i = 0; i < len; i++) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
// the property has been changed on this bean
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
props.add(propName);
props.add((prefix == null ? getProperty(i) : prefix + getProperty(i)));
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
@@ -674,12 +663,10 @@ public final class EntityBeanIntercept implements Serializable {
* Return true if any of the given property names are dirty.
*/
public boolean hasDirtyProperty(Set<String> propertyNames) {
String[] names = owner._ebean_getPropertyNames();
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
// the property has been changed on this bean
if (propertyNames.contains(names[i])) {
return true;
}
@@ -712,10 +699,9 @@ public final class EntityBeanIntercept implements Serializable {
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
if (!areEqual(oldVal, newVal)) {
if (notEqual(oldVal, newVal)) {
dirtyValues.put(propName, new ValuePair(newVal, oldVal));
}
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
@@ -734,10 +720,9 @@ public final class EntityBeanIntercept implements Serializable {
// the property has been changed on this bean
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
if (!areEqual(oldVal, newVal)) {
if (notEqual(oldVal, newVal)) {
visitor.visit(i, newVal, oldVal);
}
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
@@ -758,7 +743,7 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
* Add and return a dirty property hash recursing into embedded beans.
* Add and return a dirty property hash.
*/
private void addDirtyPropertyKey(StringBuilder sb) {
if (sortOrder > 0) {
@@ -818,21 +803,18 @@ public final class EntityBeanIntercept implements Serializable {
* Load the bean when it is a reference.
*/
protected void loadBean(int loadProperty) {
synchronized (this) {
if (beanLoader == null) {
final Database database = DB.byName(ebeanServerName);
if (database == null) {
throw new PersistenceException("Database [" + ebeanServerName + "] was not found?");
}
// For stand alone reference bean or after deserialisation lazy load
// using the ebeanServer. Synchronise only on the bean.
loadBeanInternal(loadProperty, database.getPluginApi());
return;
}
}
synchronized (beanLoader) {
// Lazy loading using LoadBeanContext which supports batch loading
// Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
@@ -844,32 +826,24 @@ public final class EntityBeanIntercept implements Serializable {
* Invoke the lazy loading. This method is synchronised externally.
*/
private void loadBeanInternal(int loadProperty, BeanLoader loader) {
if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
// race condition where multiple threads calling preGetter concurrently
return;
}
if (lazyLoadFailure) {
// failed when batch lazy loaded by another bean in the batch
throw new EntityNotFoundException("(Lazy) loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted");
}
if (lazyLoadProperty == -1) {
lazyLoadProperty = loadProperty;
if (nodeUsageCollector != null) {
nodeUsageCollector.setLoadProperty(getProperty(lazyLoadProperty));
}
loader.loadBean(this);
if (lazyLoadFailure) {
// failed when lazy loading this bean
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted.");
}
// bean should be loaded and intercepting now. setLoaded() has
// been called by the lazy loading mechanism
}
@@ -879,32 +853,31 @@ public final class EntityBeanIntercept implements Serializable {
* Helper method to check if two objects are equal.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
protected boolean areEqual(Object obj1, Object obj2) {
protected static boolean notEqual(Object obj1, Object obj2) {
if (obj1 == null) {
return (obj2 == null);
return (obj2 != null);
}
if (obj2 == null) {
return false;
return true;
}
if (obj1 == obj2) {
return true;
return false;
}
if (obj1 instanceof BigDecimal) {
// Use comparable for BigDecimal as equals
// uses scale in comparison...
if (obj2 instanceof BigDecimal) {
Comparable com1 = (Comparable) obj1;
return (com1.compareTo(obj2) == 0);
return (com1.compareTo(obj2) != 0);
} else {
return false;
return true;
}
}
if (obj1 instanceof URL) {
// use the string format to determine if dirty
return obj1.toString().equals(obj2.toString());
return !obj1.toString().equals(obj2.toString());
}
return obj1.equals(obj2);
return !obj1.equals(obj2);
}
/**
@@ -945,25 +918,20 @@ public final class EntityBeanIntercept implements Serializable {
}
/**
* OneToMany and ManyToMany don't have any interception so just check for
* PropertyChangeSupport.
* OneToMany and ManyToMany only set loaded state.
*/
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setLoadedProperty(propertyIndex);
}
private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setChangedProperty(propertyIndex);
if (setDirtyState) {
setOriginalValue(propertyIndex, origValue);
setDirtyStatus();
@@ -988,10 +956,9 @@ public final class EntityBeanIntercept implements Serializable {
* the old values for use with ConcurrencyMode.ALL.
*/
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!areEqual(oldValue, newValue)) {
} else if (notEqual(oldValue, newValue)) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
}
}
@@ -1001,7 +968,6 @@ public final class EntityBeanIntercept implements Serializable {
* Check for primitive boolean.
*/
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
@@ -1013,7 +979,6 @@ public final class EntityBeanIntercept implements Serializable {
* Check for primitive int.
*/
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
@@ -1025,7 +990,6 @@ public final class EntityBeanIntercept implements Serializable {
* long.
*/
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
@@ -1037,7 +1001,6 @@ public final class EntityBeanIntercept implements Serializable {
* double.
*/
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (Double.compare(oldValue, newValue) != 0) {
@@ -1049,7 +1012,6 @@ public final class EntityBeanIntercept implements Serializable {
* float.
*/
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (Float.compare(oldValue, newValue) != 0) {
@@ -1061,7 +1023,6 @@ public final class EntityBeanIntercept implements Serializable {
* short.
*/
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
@@ -1073,7 +1034,6 @@ public final class EntityBeanIntercept implements Serializable {
* char.
*/
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
@@ -1085,7 +1045,6 @@ public final class EntityBeanIntercept implements Serializable {
* byte.
*/
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
@@ -1097,7 +1056,6 @@ public final class EntityBeanIntercept implements Serializable {
* char[].
*/
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!Arrays.equals(oldValue, newValue)) {
@@ -1109,7 +1067,6 @@ public final class EntityBeanIntercept implements Serializable {
* byte[].
*/
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!Arrays.equals(oldValue, newValue)) {
@@ -61,6 +61,22 @@ public interface PersistenceContext {
*/
int size(Class<?> rootType);
/**
* Return a copy of the Persistence context to use for large query iteration.
*/
PersistenceContext forIterate();
/**
* Return a new Persistence context during iteration of large query result.
*/
PersistenceContext forIterateReset();
/**
* Return true if the persistence context has grown and hit the 'reset limit'
* during large query iteration.
*/
boolean resetLimit();
/**
* Wrapper on a bean to also indicate if a bean has been deleted.
* <p>
@@ -18,4 +18,9 @@ public @interface ModuleInfo {
* The db name prefix is added to entity classes for non default databases.
*/
String[] entities() default {};
/**
* Other classes like Attribute Converters.
*/
String[] other() default {};
}
@@ -188,7 +188,7 @@ public class ServerConfig {
private JsonConfig.Date jsonDate = JsonConfig.Date.ISO8601;
/**
* For writing JSON specify if null values or empty collections should be exluded.
* For writing JSON specify if null values or empty collections should be excluded.
* By default all values are included.
*/
private JsonConfig.Include jsonInclude = JsonConfig.Include.ALL;
@@ -528,7 +528,6 @@ public class ServerConfig {
* Construct a Database Configuration for programmatically creating an Database.
*/
public ServerConfig() {
}
/**
@@ -593,7 +592,6 @@ public class ServerConfig {
* Put a service object into configuration such that it can be passed to a plugin.
* <p>
* For example, put IgniteConfiguration in to be passed to the Ignite plugin.
* </p>
*/
public void putServiceObject(String key, Object configObject) {
serviceObject.put(key, configObject);
@@ -705,7 +703,6 @@ public class ServerConfig {
* Set the JSON include mode used when writing JSON.
* <p>
* Set to NON_NULL or NON_EMPTY to suppress nulls or null & empty collections respectively.
* </p>
*/
public void setJsonInclude(JsonConfig.Include jsonInclude) {
this.jsonInclude = jsonInclude;
@@ -750,7 +747,6 @@ public class ServerConfig {
* when it is created.
* <p>
* By default this is set to true.
* </p>
*/
public boolean isRegister() {
return register;
@@ -761,7 +757,6 @@ public class ServerConfig {
* singleton when it is created.
* <p>
* By default this is set to true.
* </p>
*/
public void setRegister(boolean register) {
this.register = register;
@@ -772,7 +767,6 @@ public class ServerConfig {
* with the Ebean singleton.
* <p>
* This is only used when {@link #setRegister(boolean)} is also true.
* </p>
*/
public boolean isDefaultServer() {
return defaultServer;
@@ -783,7 +777,6 @@ public class ServerConfig {
* with the DB singleton.
* <p>
* This is only used when {@link #setRegister(boolean)} is also true.
* </p>
*/
public void setDefaultServer(boolean defaultServer) {
this.defaultServer = defaultServer;
@@ -895,7 +888,6 @@ public class ServerConfig {
* When INSERT or ALL is used then save(), delete() etc do not execute immediately but instead go into
* a JDBC batch execute buffer that is flushed. The buffer is flushed if a query is executed, transaction ends
* or the batch size is meet.
* </p>
*/
public PersistBatch getPersistBatch() {
return persistBatch;
@@ -907,7 +899,6 @@ public class ServerConfig {
* When INSERT or ALL is used then save(), delete() etc do not execute immediately but instead go into
* a JDBC batch execute buffer that is flushed. The buffer is flushed if a query is executed, transaction ends
* or the batch size is meet.
* </p>
*/
public void setPersistBatch(PersistBatch persistBatch) {
this.persistBatch = persistBatch;
@@ -918,10 +909,8 @@ public class ServerConfig {
* <p>
* This makes sense when a save() or delete() cascades and executes multiple child statements. The best case
* for this is when saving a master/parent bean this cascade inserts many detail/child beans.
* </p>
* <p>
* This only takes effect when the persistBatch mode at the transaction level does not take effect.
* </p>
*/
public PersistBatch getPersistBatchOnCascade() {
return persistBatchOnCascade;
@@ -932,10 +921,8 @@ public class ServerConfig {
* <p>
* This makes sense when a save() or delete() etc cascades and executes multiple child statements. The best caase
* for this is when saving a master/parent bean this cascade inserts many detail/child beans.
* </p>
* <p>
* This only takes effect when the persistBatch mode at the transaction level does not take effect.
* </p>
*/
public void setPersistBatchOnCascade(PersistBatch persistBatchOnCascade) {
this.persistBatchOnCascade = persistBatchOnCascade;
@@ -944,17 +931,13 @@ public class ServerConfig {
/**
* Deprecated, please migrate to using setPersistBatch().
* <p>
* Set to true if you what to use JDBC batching for persisting and deleting
* beans.
* </p>
* Set to true if you what to use JDBC batching for persisting and deleting beans.
* <p>
* With this Ebean will batch up persist requests and use the JDBC batch api.
* This is a performance optimisation designed to reduce the network chatter.
* </p>
* <p>
* When true this is equivalent to {@code setPersistBatch(PersistBatch.ALL)} or
* when false to {@code setPersistBatch(PersistBatch.NONE)}
* </p>
*/
public void setPersistBatching(boolean persistBatching) {
this.persistBatch = (persistBatching) ? PersistBatch.ALL : PersistBatch.NONE;
@@ -971,7 +954,6 @@ public class ServerConfig {
* Set the batch size used for JDBC batching. If unset this defaults to 20.
* <p>
* You can also set the batch size on the transaction.
* </p>
*
* @see Transaction#setBatchSize(int)
*/
@@ -1033,14 +1015,11 @@ public class ServerConfig {
* <p>
* This is the number of beans or collections loaded when lazy loading is
* invoked by default.
* </p>
* <p>
* The default value is for this is 10 (load 10 beans or collections).
* </p>
* <p>
* You can explicitly control the lazy loading batch size for a given join on
* a query using +lazy(batchSize) or JoinConfig.
* </p>
*/
public void setLazyLoadBatchSize(int lazyLoadBatchSize) {
this.lazyLoadBatchSize = lazyLoadBatchSize;
@@ -1052,7 +1031,7 @@ public class ServerConfig {
* This is a performance optimisation to reduce the number times Ebean
* requests a sequence to be used as an Id for a bean (aka reduce network
* chatter).
* </p>
*/
public void setDatabaseSequenceBatchSize(int databaseSequenceBatchSize) {
platformConfig.setDatabaseSequenceBatchSize(databaseSequenceBatchSize);
@@ -1091,7 +1070,6 @@ public class ServerConfig {
* <p>
* This is used to set user context information to the ChangeSet in the
* foreground thread prior to the logging occurring in a background thread.
* </p>
*/
public ChangeLogPrepare getChangeLogPrepare() {
return changeLogPrepare;
@@ -1102,7 +1080,6 @@ public class ServerConfig {
* <p>
* This is used to set user context information to the ChangeSet in the
* foreground thread prior to the logging occurring in a background thread.
* </p>
*/
public void setChangeLogPrepare(ChangeLogPrepare changeLogPrepare) {
this.changeLogPrepare = changeLogPrepare;
@@ -1199,7 +1176,6 @@ public class ServerConfig {
* It is expected that an implementation is used that read user context information
* (user id, user ip address etc) and sets it on the ReadEvent bean before it is sent
* to the ReadAuditLogger.
* </p>
*/
public void setReadAuditPrepare(ReadAuditPrepare readAuditPrepare) {
this.readAuditPrepare = readAuditPrepare;
@@ -1270,7 +1246,6 @@ public class ServerConfig {
* Return the time zone to use when reading/writing Timestamps via JDBC.
* <p>
* When set a Calendar object is used in JDBC calls when reading/writing Timestamp objects.
* </p>
*/
public String getDataTimeZone() {
return System.getProperty("ebean.dataTimeZone", dataTimeZone);
@@ -1533,7 +1508,6 @@ public class ServerConfig {
* Return the NamingConvention.
* <p>
* If none has been set the default UnderscoreNamingConvention is used.
* </p>
*/
public NamingConvention getNamingConvention() {
return namingConvention;
@@ -1543,7 +1517,6 @@ public class ServerConfig {
* Set the NamingConvention.
* <p>
* If none is set the default UnderscoreNamingConvention is used.
* </p>
*/
public void setNamingConvention(NamingConvention namingConvention) {
this.namingConvention = namingConvention;
@@ -1655,11 +1628,9 @@ public class ServerConfig {
* <p>
* Note that the DataSource is expected to use AutoCommit true mode avoiding the need
* for explicit commit (or rollback).
* </p>
* <p>
* This read only DataSource will be used for implicit query only transactions. It is not
* used if the transaction is created explicitly or if the query is an update or delete query.
* </p>
*/
public void setReadOnlyDataSource(DataSource readOnlyDataSource) {
this.readOnlyDataSource = readOnlyDataSource;
@@ -1699,12 +1670,10 @@ public class ServerConfig {
* Return the configuration for the read only DataSource.
* <p>
* This is only used if autoReadOnlyDataSource is true.
* </p>
* <p>
* The driver, url, username and password default to the configuration for the main DataSource if they are not
* set on this configuration. This means there is actually no need to set any configuration here and we only
* set configuration for url, username and password etc if it is different from the main DataSource.
* </p>
*/
public DataSourceConfig getReadOnlyDataSourceConfig() {
return readOnlyDataSourceConfig;
@@ -1730,7 +1699,6 @@ public class ServerConfig {
* By default a prefix of "java:comp/env/jdbc/" is used to lookup the
* DataSource. This prefix is not used if dataSourceJndiName starts with
* "java:".
* </p>
*/
public void setDataSourceJndiName(String dataSourceJndiName) {
this.dataSourceJndiName = dataSourceJndiName;
@@ -1740,10 +1708,8 @@ public class ServerConfig {
* 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 platformConfig.getDatabaseBooleanTrue();
@@ -1753,10 +1719,8 @@ public class ServerConfig {
* 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 databaseTrue) {
platformConfig.setDatabaseBooleanTrue(databaseTrue);
@@ -1766,10 +1730,8 @@ public class ServerConfig {
* Return a value used to represent FALSE 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. "0", or "F").
* </p>
*/
public String getDatabaseBooleanFalse() {
return platformConfig.getDatabaseBooleanFalse();
@@ -1779,10 +1741,8 @@ public class ServerConfig {
* Set the value to represent FALSE 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. "0", or "F").
* </p>
*/
public void setDatabaseBooleanFalse(String databaseFalse) {
this.platformConfig.setDatabaseBooleanFalse(databaseFalse);
@@ -1802,13 +1762,11 @@ public class ServerConfig {
* This is only used for DB's that use sequences and is a performance
* optimisation. This reduces the number of times Ebean needs to get a
* sequence value from the Database reducing network chatter.
* </p>
* <p>
* By default this value is 10 so when we need another Id (and don't have one
* in our cache) Ebean will fetch 10 id's from the database. Note that when
* the cache drops to have full (which is 5 by default) Ebean will fetch
* another batch of Id's in a background thread.
* </p>
*/
public void setDatabaseSequenceBatch(int databaseSequenceBatchSize) {
this.platformConfig.setDatabaseSequenceBatchSize(databaseSequenceBatchSize);
@@ -1819,7 +1777,6 @@ public class ServerConfig {
* <p>
* If null then the platform is determined automatically via the JDBC driver
* information.
* </p>
*/
public String getDatabasePlatformName() {
return databasePlatformName;
@@ -1830,16 +1787,13 @@ public class ServerConfig {
* <p>
* If none is set then the platform is determined automatically via the JDBC
* driver information.
* </p>
* <p>
* This can be used when the Database Platform can not be automatically
* detected from the JDBC driver (possibly 3rd party JDBC driver). It is also
* useful when you want to do offline DDL generation for a database platform
* that you don't have access to.
* </p>
* <p>
* Values are oracle, h2, postgres, mysql, sqlserver16, sqlserver17.
* </p>
*/
public void setDatabasePlatformName(String databasePlatformName) {
this.databasePlatformName = databasePlatformName;
@@ -1857,7 +1811,6 @@ public class ServerConfig {
* <p>
* If none is set then the platform is determined via the databasePlatformName
* or automatically via the JDBC driver information.
* </p>
*/
public void setDatabasePlatform(DatabasePlatform databasePlatform) {
this.databasePlatform = databasePlatform;
@@ -1888,10 +1841,8 @@ public class ServerConfig {
* Set the EncryptKeyManager.
* <p>
* This is required when you want to use encrypted properties.
* </p>
* <p>
* You can also set this in ebean.proprerties:
* </p>
* <p>
* <pre>{@code
* # set via ebean.properties
@@ -1907,7 +1858,6 @@ public class ServerConfig {
* <p>
* This is optionally used to programmatically define which columns are
* encrypted instead of using the {@link Encrypted} Annotation.
* </p>
*/
public EncryptDeployManager getEncryptDeployManager() {
return encryptDeployManager;
@@ -1918,7 +1868,6 @@ public class ServerConfig {
* <p>
* This is optionally used to programmatically define which columns are
* encrypted instead of using the {@link Encrypted} Annotation.
* </p>
*/
public void setEncryptDeployManager(EncryptDeployManager encryptDeployManager) {
this.encryptDeployManager = encryptDeployManager;
@@ -1938,7 +1887,6 @@ public class ServerConfig {
* <p>
* Ebean has a default implementation that it will use if you do not set your
* own Encryptor implementation.
* </p>
*/
public void setEncryptor(Encryptor encryptor) {
this.encryptor = encryptor;
@@ -1956,7 +1904,6 @@ public class ServerConfig {
* <p>
* Typically used to create an Database instance for DDL Migration generation
* without requiring a real DataSource / Database to connect to.
* </p>
*/
public void setDbOffline(boolean dbOffline) {
this.dbOffline = dbOffline;
@@ -1967,7 +1914,6 @@ public class ServerConfig {
* <p>
* Note that if this is not set then the DbPlatform may already have a
* DbEncrypt set and that will be used.
* </p>
*/
public DbEncrypt getDbEncrypt() {
return dbEncrypt;
@@ -1978,7 +1924,6 @@ public class ServerConfig {
* <p>
* Note that if this is not set then the DbPlatform may already have a
* DbEncrypt set (H2, MySql, Postgres and Oracle platforms have a DbEncrypt)
* </p>
*/
public void setDbEncrypt(DbEncrypt dbEncrypt) {
this.dbEncrypt = dbEncrypt;
@@ -2053,7 +1998,6 @@ public class ServerConfig {
* Set to true if LocalTime should be persisted with nanos precision.
* <p>
* Otherwise it is persisted using java.sql.Time which is seconds precision.
* </p>
*/
public void setLocalTimeWithNanos(boolean localTimeWithNanos) {
this.localTimeWithNanos = localTimeWithNanos;
@@ -2063,7 +2007,6 @@ public class ServerConfig {
* Return true if Duration should be persisted with nanos precision (SQL DECIMAL).
* <p>
* Otherwise it is persisted with second precision (SQL INTEGER).
* </p>
*/
public boolean isDurationWithNanos() {
return durationWithNanos;
@@ -2073,7 +2016,6 @@ public class ServerConfig {
* Set to true if Duration should be persisted with nanos precision (SQL DECIMAL).
* <p>
* Otherwise it is persisted with second precision (SQL INTEGER).
* </p>
*/
public void setDurationWithNanos(boolean durationWithNanos) {
this.durationWithNanos = durationWithNanos;
@@ -2144,7 +2086,6 @@ public class ServerConfig {
* <p>
* Typically this is a sql script that inserts test seed data when running tests.
* Place a sql script in src/test/resources that inserts test seed data.
* </p>
*/
public String getDdlSeedSql() {
return ddlSeedSql;
@@ -2155,7 +2096,6 @@ public class ServerConfig {
* <p>
* Typically this is a sql script that inserts test seed data when running tests.
* Place a sql script in src/test/resources that inserts test seed data.
* </p>
*/
public void setDdlSeedSql(String ddlSeedSql) {
this.ddlSeedSql = ddlSeedSql;
@@ -2232,14 +2172,11 @@ public class ServerConfig {
* <p>
* The class can be an Entity, Embedded type, ScalarType, BeanPersistListener,
* BeanFinder or BeanPersistController.
* </p>
* <p>
* If no classes are specified then the classes are found automatically via
* searching the class path.
* </p>
* <p>
* Alternatively the classes can be added via {@link #setClasses(List)}.
* </p>
*
* @param cls the entity type (or other type) that should be registered by this
* database.
@@ -2261,7 +2198,6 @@ public class ServerConfig {
* Add a package to search for entities via class path search.
* <p>
* This is only used if classes have not been explicitly specified.
* </p>
*/
public void addPackage(String packageName) {
packages.add(packageName);
@@ -2271,7 +2207,6 @@ public class ServerConfig {
* Return packages to search for entities via class path search.
* <p>
* This is only used if classes have not been explicitly specified.
* </p>
*/
public List<String> getPackages() {
return packages;
@@ -2281,7 +2216,6 @@ public class ServerConfig {
* Set packages to search for entities via class path search.
* <p>
* This is only used if classes have not been explicitly specified.
* </p>
*/
public void setPackages(List<String> packages) {
this.packages = packages;
@@ -2293,10 +2227,8 @@ public class ServerConfig {
* <p>
* If no classes are specified then the classes are found automatically via
* searching the class path.
* </p>
* <p>
* Alternatively the classes can contain added via {@link #addClass(Class)}.
* </p>
*/
public void setClasses(List<Class<?>> classes) {
this.classes = classes;
@@ -2316,7 +2248,6 @@ public class ServerConfig {
* This defaults to true and means that for "find by id" and "find by natural key"
* queries that normally hit L2 bean cache automatically will not do so after a write/persist
* on the transaction.
* </p>
* <p>
* <pre>{@code
*
@@ -2377,7 +2308,6 @@ public class ServerConfig {
* Set to false if by default updates in JDBC batch should not include all properties.
* <p>
* This mode can be explicitly set per transaction.
* </p>
*
* @see Transaction#setUpdateAllLoadedProperties(boolean)
*/
@@ -2445,7 +2375,6 @@ public class ServerConfig {
* <p>
* Note alternatively you can use {@link #setQueryAdapters(List)} to set all
* the BeanQueryAdapter instances.
* </p>
*/
public void add(BeanQueryAdapter beanQueryAdapter) {
queryAdapters.add(beanQueryAdapter);
@@ -2463,7 +2392,6 @@ public class ServerConfig {
* <p>
* Note alternatively you can use {@link #add(BeanQueryAdapter)} to add
* BeanQueryAdapter instances one at a time.
* </p>
*/
public void setQueryAdapters(List<BeanQueryAdapter> queryAdapters) {
this.queryAdapters = queryAdapters;
@@ -2495,7 +2423,6 @@ public class ServerConfig {
* <p>
* Note alternatively you can use {@link #setPersistControllers(List)} to set
* all the BeanPersistController instances.
* </p>
*/
public void add(BeanPersistController beanPersistController) {
persistControllers.add(beanPersistController);
@@ -2506,7 +2433,6 @@ public class ServerConfig {
* <p>
* Note alternatively you can use {@link #setPostLoaders(List)} to set
* all the BeanPostLoad instances.
* </p>
*/
public void add(BeanPostLoad postLoad) {
postLoaders.add(postLoad);
@@ -2517,7 +2443,6 @@ public class ServerConfig {
* <p>
* Note alternatively you can use {@link #setPostConstructListeners(List)} to set
* all the BeanPostConstructListener instances.
* </p>
*/
public void add(BeanPostConstructListener listener) {
postConstructListeners.add(listener);
@@ -2577,7 +2502,6 @@ public class ServerConfig {
* <p>
* Note alternatively you can use {@link #add(BeanPersistController)} to add
* BeanPersistController instances one at a time.
* </p>
*/
public void setPersistControllers(List<BeanPersistController> persistControllers) {
this.persistControllers = persistControllers;
@@ -2588,7 +2512,6 @@ public class ServerConfig {
* <p>
* Note alternatively you can use {@link #setPersistListeners(List)} to set
* all the BeanPersistListener instances.
* </p>
*/
public void add(BeanPersistListener beanPersistListener) {
persistListeners.add(beanPersistListener);
@@ -2634,7 +2557,6 @@ public class ServerConfig {
* <p>
* Note alternatively you can use {@link #add(BeanPersistListener)} to add
* BeanPersistListener instances one at a time.
* </p>
*/
public void setPersistListeners(List<BeanPersistListener> persistListeners) {
this.persistListeners = persistListeners;
@@ -2929,14 +2851,12 @@ public class ServerConfig {
* @return the classes
*/
private List<Class<?>> getClasses(PropertiesWrapper properties) {
String classNames = properties.get("classes", null);
if (classNames == null) {
return classes;
}
List<Class<?>> classList = new ArrayList<>();
String[] split = StringHelper.splitNames(classNames);
for (String cn : split) {
if (!"class".equalsIgnoreCase(cn)) {
@@ -2952,13 +2872,10 @@ public class ServerConfig {
}
private List<String> getSearchList(String searchNames, List<String> defaultValue) {
if (searchNames != null) {
String[] entries = StringHelper.splitNames(searchNames);
List<String> hitList = new ArrayList<>(entries.length);
Collections.addAll(hitList, entries);
return hitList;
} else {
return defaultValue;
@@ -2970,7 +2887,6 @@ public class ServerConfig {
* platform supports getGeneratedKeys in batch mode.
*/
public PersistBatch appliedPersistBatchOnCascade() {
if (persistBatchOnCascade == PersistBatch.INHERIT) {
// use the platform default (ALL except SQL Server which has NONE)
return databasePlatform.getPersistBatchOnCascade();
@@ -2982,7 +2898,6 @@ public class ServerConfig {
* Return the Jackson ObjectMapper.
* <p>
* Note that this is not strongly typed as Jackson ObjectMapper is an optional dependency.
* </p>
*/
public Object getObjectMapper() {
return objectMapper;
@@ -2992,7 +2907,6 @@ public class ServerConfig {
* Set the Jackson ObjectMapper.
* <p>
* Note that this is not strongly typed as Jackson ObjectMapper is an optional dependency.
* </p>
*/
public void setObjectMapper(Object objectMapper) {
this.objectMapper = objectMapper;
@@ -3011,7 +2925,6 @@ public class ServerConfig {
* Setting this to true has the effect that eq(propertyName, value), ieq(propertyName, value) and
* ne(propertyName, value) have no effect when the value is null. The expression factory adds a NoopExpression
* which will add "1=1" into the SQL rather than "is null".
* </p>
*/
public void setExpressionEqualsWithNullAsNoop(boolean expressionEqualsWithNullAsNoop) {
this.expressionEqualsWithNullAsNoop = expressionEqualsWithNullAsNoop;
@@ -3105,7 +3018,6 @@ public class ServerConfig {
* In general we don't want to do that as when we use a distributed cache (like Ignite, Hazelcast etc)
* we are making network calls and we prefer to do this in background and not impact the response time
* of the executing transaction.
* </p>
*/
public void setNotifyL2CacheInForeground(boolean notifyL2CacheInForeground) {
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
@@ -3142,7 +3054,6 @@ public class ServerConfig {
* <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
@@ -3179,7 +3090,6 @@ public class ServerConfig {
* 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;
@@ -3265,7 +3175,6 @@ public class ServerConfig {
* <p>
* When false we either register entity classes via application code or use classpath
* scanning to find and register entity classes.
* </p>
*/
public boolean isAutoLoadModuleInfo() {
return loadModuleInfo && classes.isEmpty();
@@ -0,0 +1,38 @@
package io.ebean.config.dbplatform.mariadb;
import io.ebean.config.dbplatform.DbStandardHistorySupport;
/**
* History support for MariaDB.
*/
public class MariaDbHistorySupport extends DbStandardHistorySupport {
/**
* Return the ' as of timestamp ?' clause appended after the table name.
*/
@Override
public String getAsOfViewSuffix(String asOfViewSuffix) {
return " for system_time as of ?";
}
@Override
public String getVersionsBetweenSuffix(String asOfViewSuffix) {
return " for system_time between ? and ?";
}
/**
* Returns the SQL Server specific effective start column.
*/
@Override
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
return tableAlias + ".row_start";
}
/**
* Returns the SQL Server specific effective end column.
*/
@Override
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
return tableAlias + ".row_end";
}
}
@@ -0,0 +1,16 @@
package io.ebean.config.dbplatform.mariadb;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.mysql.BaseMySqlPlatform;
/**
* MariaDB platform.
*/
public class MariaDbPlatform extends BaseMySqlPlatform {
public MariaDbPlatform() {
super();
this.platform = Platform.MARIADB;
this.historySupport = new MariaDbHistorySupport();
}
}
@@ -0,0 +1,68 @@
package io.ebean.config.dbplatform.mysql;
import io.ebean.Query;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.SqlErrorCodes;
import java.sql.Types;
/**
* Base platform for both MySql and MariaDB.
*/
public abstract class BaseMySqlPlatform extends DatabasePlatform {
public BaseMySqlPlatform() {
super();
this.useExtraTransactionOnIterateSecondaryQueries = true;
this.selectCountWithAlias = true;
this.supportsSavepointId = false;
this.inlineSqlUpdateLimit = true;
this.dbEncrypt = new MySqlDbEncrypt();
this.historySupport = new MySqlHistorySupport();
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsIdentity(true);
this.dbIdentity.setSupportsSequence(false);
this.dbDefaultValue.setNow("now(6)"); // must have same precision as TIMESTAMP
this.dbDefaultValue.setFalse("0");
this.dbDefaultValue.setTrue("1");
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("1205")
.addDuplicateKey("1062", "1169")
.addDataIntegrity("630", "839", "840", "893", "1215", "1216", "1217", "1364", "1451", "1452", "1557")
.build();
this.openQuote = "`";
this.closeQuote = "`";
// use pipe for escaping as it depends if mysql runs in no_backslash_escapes or not.
this.likeClauseRaw = "like ? escape''";
this.likeClauseEscaped = "like ? escape'|'";
this.forwardOnlyHintOnFindIterate = true;
this.booleanDbType = Types.BIT;
dbTypeMap.put(DbType.BIT, new DbPlatformType("tinyint(1)"));
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("tinyint(1)"));
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("datetime(6)"));
dbTypeMap.put(DbType.CLOB, new MySqlClob());
dbTypeMap.put(DbType.BLOB, new MySqlBlob());
dbTypeMap.put(DbType.BINARY, new DbPlatformType("binary", 255));
dbTypeMap.put(DbType.VARBINARY, new DbPlatformType("varbinary", 255));
dbTypeMap.put(DbType.JSON, new DbPlatformType("json", false));
dbTypeMap.put(DbType.JSONB, new DbPlatformType("json", false));
}
@Override
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
// NOWAIT and SKIP LOCKED currently not supported with MySQL
return sql + " for update";
}
}
@@ -4,7 +4,7 @@ import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
public class MySql55Platform extends MySqlPlatform {
public class MySql55Platform extends BaseMySqlPlatform {
public MySql55Platform() {
super();
@@ -1,77 +1,15 @@
package io.ebean.config.dbplatform.mysql;
import io.ebean.Query;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.SqlErrorCodes;
import java.sql.Types;
/**
* MySQL specific platform.
* <p>
* <ul>
* <li>supportsGetGeneratedKeys = true</li>
* <li>Uses LIMIT OFFSET clause</li>
* <li>Uses ` for quoted identifiers</li>
* </ul>
* </p>
*/
public class MySqlPlatform extends DatabasePlatform {
public class MySqlPlatform extends BaseMySqlPlatform {
public MySqlPlatform() {
super();
this.platform = Platform.MYSQL;
this.useExtraTransactionOnIterateSecondaryQueries = true;
this.selectCountWithAlias = true;
this.supportsSavepointId = false;
this.inlineSqlUpdateLimit = true;
this.dbEncrypt = new MySqlDbEncrypt();
this.historySupport = new MySqlHistorySupport();
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsIdentity(true);
this.dbIdentity.setSupportsSequence(false);
this.dbDefaultValue.setNow("now(6)"); // must have same precision as TIMESTAMP
this.dbDefaultValue.setFalse("0");
this.dbDefaultValue.setTrue("1");
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("1205")
.addDuplicateKey("1062", "1169")
.addDataIntegrity("630", "839", "840", "893", "1215", "1216", "1217", "1364", "1451", "1452", "1557")
.build();
this.openQuote = "`";
this.closeQuote = "`";
// use pipe for escaping as it depends if mysql runs in no_backslash_escapes or not.
this.likeClauseRaw = "like ? escape''";
this.likeClauseEscaped = "like ? escape'|'";
this.forwardOnlyHintOnFindIterate = true;
this.booleanDbType = Types.BIT;
dbTypeMap.put(DbType.BIT, new DbPlatformType("tinyint(1)"));
dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("tinyint(1)"));
dbTypeMap.put(DbType.TIMESTAMP, new DbPlatformType("datetime(6)"));
dbTypeMap.put(DbType.CLOB, new MySqlClob());
dbTypeMap.put(DbType.BLOB, new MySqlBlob());
dbTypeMap.put(DbType.BINARY, new DbPlatformType("binary", 255));
dbTypeMap.put(DbType.VARBINARY, new DbPlatformType("varbinary", 255));
dbTypeMap.put(DbType.JSON, new DbPlatformType("json", false));
dbTypeMap.put(DbType.JSONB, new DbPlatformType("json", false));
}
@Override
protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) {
// NOWAIT and SKIP LOCKED currently not supported with MySQL
return sql + " for update";
}
}
@@ -1,9 +1,15 @@
package io.ebeaninternal.api;
import io.ebean.bean.BeanCollection;
/**
* Controls the loading of ManyToOne and OneToOne relationships.
*/
public interface LoadBeanContext extends LoadSecondaryQuery {
/**
* Register a BeanCollection into the load context.
*/
void register(String manyProperty, BeanCollection<?> collection);
}
@@ -788,7 +788,7 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
/**
* Set to true when we only include the Id property if it is explicitly included in the select().
*/
void setManualId(boolean manualId);
void setManualId();
/**
* Set default select clauses where none have been explicitly defined.
@@ -313,4 +313,9 @@ public interface SpiTransaction extends Transaction {
* Return true when nested transactions should create Savepoints.
*/
boolean isNestedUseSavepoint();
/**
* Return true if explicitly set to skip cache (ignores skipOnWrite).
*/
boolean isSkipCacheExplicit();
}
@@ -158,6 +158,11 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
transaction.setSkipCache(skipCache);
}
@Override
public boolean isSkipCacheExplicit() {
return transaction.isSkipCacheExplicit();
}
@Override
public boolean isSkipCache() {
return transaction.isSkipCache();
@@ -15,6 +15,7 @@ import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.mariadb.MariaDbPlatform;
import io.ebean.config.dbplatform.mysql.MySql55Platform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.nuodb.NuoDbPlatform;
@@ -820,6 +821,8 @@ public class DefaultDbMigration implements DbMigration {
return new Postgres9Platform();
case POSTGRES:
return new PostgresPlatform();
case MARIADB:
return new MariaDbPlatform();
case MYSQL55:
return new MySql55Platform();
case MYSQL:
@@ -111,11 +111,9 @@ public class BaseTableDdl implements TableDdl {
this.columnName = column.getName();
this.defaultValue = platformDdl.convertDefaultValue(column.getDefaultValue());
boolean alterNotNull = Boolean.TRUE.equals(column.isNotnull());
if (column.getBefore().isEmpty() && alterNotNull && defaultValue == null) {
handleStrictError(tableName, columnName);
}
before = getScriptsForPlatform(column.getBefore());
after = getScriptsForPlatform(column.getAfter());
this.withHistory = withHistory;
@@ -127,10 +125,8 @@ public class BaseTableDdl implements TableDdl {
DdlMigrationHelp(AlterColumn alter) {
this.tableName = alter.getTableName();
this.columnName = alter.getColumnName();
String tmp = alter.getDefaultValue() != null ? alter.getDefaultValue() : alter.getCurrentDefaultValue();
this.defaultValue = platformDdl.convertDefaultValue(tmp);
boolean alterNotNull = Boolean.TRUE.equals(alter.isNotnull());
// here we add the platform's default update script
withHistory = isTrue(alter.isWithHistory());
@@ -201,7 +197,6 @@ public class BaseTableDdl implements TableDdl {
public String getDefaultValue() {
return defaultValue;
}
}
/**
@@ -241,7 +236,6 @@ public class BaseTableDdl implements TableDdl {
*/
@Override
public void generate(DdlWrite writer, CreateTable createTable) throws IOException {
reset();
String tableName = lowerTableName(createTable.getName());
@@ -275,7 +269,6 @@ public class BaseTableDdl implements TableDdl {
if (platformDdl.isInlineForeignKeys()) {
writeInlineForeignKeys(writer, createTable);
}
apply.newLine().append(")");
addTableStorageEngine(apply, createTable);
addTableCommentInline(apply, createTable);
@@ -285,9 +278,7 @@ public class BaseTableDdl implements TableDdl {
apply.endOfStatement();
addComments(apply, createTable);
writeUniqueOneToOneConstraints(writer, createTable);
if (isTrue(createTable.isWithHistory())) {
// create history with rollback before the
// associated drop table is written to rollback
@@ -305,7 +296,6 @@ public class BaseTableDdl implements TableDdl {
// add blank line for a bit of whitespace between tables
apply.end();
writer.dropAll().end();
if (!platformDdl.isInlineForeignKeys()) {
writeAddForeignKeys(writer, createTable);
}
@@ -324,7 +314,6 @@ public class BaseTableDdl implements TableDdl {
if (hasValue(tableComment)) {
platformDdl.addTableComment(apply, createTable.getName(), tableComment);
}
for (Column column : createTable.getColumn()) {
if (!StringHelper.isNull(column.getComment())) {
platformDdl.addColumnComment(apply, createTable.getName(), column.getName(), column.getComment());
@@ -427,7 +416,6 @@ public class BaseTableDdl implements TableDdl {
writeForeignKey(write, createTable.getName(), column);
}
}
writeAddCompoundForeignKeys(write, createTable);
}
@@ -448,7 +436,6 @@ public class BaseTableDdl implements TableDdl {
// no matching unique constraint so add the index
fkeyBuffer.appendStatement(platformDdl.createIndex(new WriteCreateIndex(request.indexName(), tableName, request.cols(), false)));
}
alterTableAddForeignKey(write.getOptions(), fkeyBuffer, request);
fkeyBuffer.end();
@@ -456,7 +443,6 @@ public class BaseTableDdl implements TableDdl {
if (hasValue(request.indexName())) {
write.dropAllForeignKeys().appendStatement(platformDdl.dropIndex(request.indexName(), tableName));
}
write.dropAllForeignKeys().end();
}
@@ -513,9 +499,7 @@ public class BaseTableDdl implements TableDdl {
* Write the unique constraints inline with the create table statement.
*/
protected void writeUniqueConstraints(DdlBuffer apply, CreateTable createTable) throws IOException {
boolean inlineUniqueWhenNullable = platformDdl.isInlineUniqueWhenNullable();
List<Column> columns = new WriteUniqueConstraint(createTable.getColumn()).uniqueKeys();
for (Column column : columns) {
if (Boolean.TRUE.equals(column.isNotnull()) || inlineUniqueWhenNullable) {
@@ -532,12 +516,10 @@ public class BaseTableDdl implements TableDdl {
* Write the unique constraint inline with the create table statement.
*/
protected void inlineUniqueConstraintSingle(DdlBuffer buffer, Column column) throws IOException {
String uqName = column.getUnique();
if (uqName == null) {
uqName = column.getUniqueOneToOne();
}
buffer.append(",").newLine();
buffer.append(" constraint ").append(uqName).append(" unique ");
buffer.append("(");
@@ -549,7 +531,6 @@ public class BaseTableDdl implements TableDdl {
* Write the primary key constraint inline with the create table statement.
*/
protected void writePrimaryKeyConstraint(DdlBuffer buffer, String pkName, String[] pkColumns) throws IOException {
buffer.append(",").newLine();
buffer.append(" constraint ").append(pkName).append(" primary key");
appendColumns(pkColumns, buffer);
@@ -559,7 +540,6 @@ public class BaseTableDdl implements TableDdl {
* Return as an array of string column names.
*/
protected String[] toColumnNames(List<Column> columns) {
String[] cols = new String[columns.size()];
for (int i = 0; i < cols.length; i++) {
cols[i] = columns.get(i).getName();
@@ -682,13 +662,11 @@ public class BaseTableDdl implements TableDdl {
*/
@Override
public void generate(DdlWrite writer, AddColumn addColumn) throws IOException {
String tableName = addColumn.getTableName();
List<Column> columns = addColumn.getColumn();
for (Column column : columns) {
alterTableAddColumn(writer.apply(), tableName, column, false, isTrue(addColumn.isWithHistory()));
}
if (isTrue(addColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
// make same changes to the history table
String historyTable = historyTable(tableName);
@@ -697,14 +675,11 @@ public class BaseTableDdl implements TableDdl {
alterTableAddColumn(writer.apply(), historyTable, column, true, true);
}
}
for (Column column : columns) {
if (hasValue(column.getReferences())) {
writeForeignKey(writer, tableName, column);
}
}
// add a bit of whitespace
writer.apply().end();
}
@@ -713,9 +688,7 @@ public class BaseTableDdl implements TableDdl {
*/
@Override
public void generate(DdlWrite writer, DropTable dropTable) throws IOException {
dropTable(writer.apply(), dropTable.getName());
if (hasValue(dropTable.getSequenceCol())
&& platformDdl.getPlatform().getDbIdentity().isSupportsSequence()) {
String sequenceName = dropTable.getSequenceName();
@@ -731,9 +704,7 @@ public class BaseTableDdl implements TableDdl {
*/
@Override
public void generate(DdlWrite writer, DropColumn dropColumn) throws IOException {
String tableName = dropColumn.getTableName();
alterTableDropColumn(writer.apply(), tableName, dropColumn.getColumnName());
if (isTrue(dropColumn.isWithHistory()) && historySupport == HistorySupport.TRIGGER_BASED) {
@@ -741,7 +712,6 @@ public class BaseTableDdl implements TableDdl {
regenerateHistoryTriggers(tableName, HistoryTableUpdate.Change.DROP, dropColumn.getColumnName());
alterTableDropColumn(writer.apply(), historyTable(tableName), dropColumn.getColumnName());
}
writer.apply().end();
}
@@ -826,7 +796,6 @@ public class BaseTableDdl implements TableDdl {
* Register the base table that we need to regenerate the history triggers on.
*/
protected void regenerateHistoryTriggers(String baseTableName, HistoryTableUpdate.Change change, String column) {
HistoryTableUpdate update = regenerateHistoryTriggers.computeIfAbsent(baseTableName, HistoryTableUpdate::new);
update.add(change, column);
}
@@ -836,7 +805,6 @@ public class BaseTableDdl implements TableDdl {
* Will be called, if there is a type, dbdefault or notnull change.
*/
protected void alterColumnBaseAttributes(DdlWrite writer, AlterColumn alter) throws IOException {
String ddl = platformDdl.alterColumnBaseAttributes(alter);
if (hasValue(ddl)) {
writer.apply().appendStatement(ddl);
@@ -857,27 +825,22 @@ public class BaseTableDdl implements TableDdl {
}
protected void alterColumnDefaultValue(DdlWrite writer, AlterColumn alter) throws IOException {
writer.apply().appendStatement(platformDdl.alterColumnDefaultValue(alter.getTableName(), alter.getColumnName(), alter.getDefaultValue()));
}
protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter, String constraintName) throws IOException {
writer.apply().appendStatement(platformDdl.alterTableDropConstraint(alter.getTableName(), constraintName));
}
protected void addCheckConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
writer.apply().appendStatement(platformDdl.alterTableAddCheckConstraint(alter.getTableName(), alter.getCheckConstraintName(), alter.getCheckConstraint()));
}
protected void alterColumnNotnull(DdlWrite writer, AlterColumn alter) throws IOException {
writer.apply().appendStatement(platformDdl.alterColumnNotnull(alter.getTableName(), alter.getColumnName(), alter.isNotnull()));
}
protected void alterColumnType(DdlWrite writer, AlterColumn alter) throws IOException {
String ddl = platformDdl.alterColumnType(alter.getTableName(), alter.getColumnName(), alter.getType());
if (hasValue(ddl)) {
writer.apply().appendStatement(ddl);
@@ -891,33 +854,26 @@ public class BaseTableDdl implements TableDdl {
}
protected void alterColumnAddForeignKey(DdlWrite writer, AlterColumn alterColumn) throws IOException {
alterTableAddForeignKey(writer.getOptions(), writer.apply(), new WriteForeignKey(alterColumn));
}
protected void alterColumnDropForeignKey(DdlWrite writer, AlterColumn alter) throws IOException {
writer.apply().appendStatement(platformDdl.alterTableDropForeignKey(alter.getTableName(), alter.getDropForeignKey()));
}
protected void alterColumnDropUniqueConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
writer.apply().appendStatement(platformDdl.alterTableDropUniqueConstraint(alter.getTableName(), alter.getDropUnique()));
}
protected void alterColumnAddUniqueOneToOneConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
addUniqueConstraint(writer, alter, alter.getUniqueOneToOne());
}
protected void alterColumnAddUniqueConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
addUniqueConstraint(writer, alter, alter.getUnique());
}
protected void addUniqueConstraint(DdlWrite writer, AlterColumn alter, String uqName) throws IOException {
String[] cols = {alter.getColumnName()};
boolean notNull = alter.isNotnull() != null ? alter.isNotnull() : Boolean.TRUE.equals(alter.isNotnull());
writer.apply().appendStatement(platformDdl.alterTableAddUniqueConstraint(alter.getTableName(), uqName, cols, notNull ? null : cols));
@@ -965,11 +921,4 @@ public class BaseTableDdl implements TableDdl {
return Boolean.TRUE.equals(value);
}
/**
* Return as an int value with 0 when it is null.
*/
protected int toInt(BigInteger value) {
return (value == null) ? 0 : value.intValue();
}
}
@@ -0,0 +1,14 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.dbplatform.DatabasePlatform;
/**
* MariaDB platform DDL.
*/
public class MariaDbDdl extends MySqlDdl {
public MariaDbDdl(DatabasePlatform platform) {
super(platform);
this.historyDdl = new MariaDbHistoryDdl();
}
}
@@ -0,0 +1,53 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.model.MTable;
import java.io.IOException;
/**
* History DDL for MariaDB.
*/
public class MariaDbHistoryDdl implements PlatformHistoryDdl {
@Override
public void configure(ServerConfig serverConfig, PlatformDdl platformDdl) {
// do nothing
}
@Override
public void createWithHistory(DdlWrite writer, MTable table) throws IOException {
String baseTable = table.getName();
enableSystemVersioning(writer, baseTable);
}
private void enableSystemVersioning(DdlWrite writer, String baseTable) throws IOException {
DdlBuffer apply = writer.applyHistoryView();
apply.append("alter table ").append(baseTable).append(" add system versioning").endOfStatement();
DdlBuffer drop = writer.dropAll();
drop.append("alter table ").append(baseTable).append(" drop system versioning").endOfStatement();
}
@Override
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
String baseTable = dropHistoryTable.getBaseTable();
DdlBuffer apply = writer.applyHistoryView();
apply.append("alter table ").append(baseTable).append(" drop system versioning").endOfStatement();
}
@Override
public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException {
String baseTable = addHistoryTable.getBaseTable();
enableSystemVersioning(writer, baseTable);
}
@Override
public void updateTriggers(DdlWrite writer, HistoryTableUpdate baseTable) {
// do nothing
}
}
@@ -54,4 +54,9 @@ public class PostgresDdl extends PlatformDdl {
public String asIdentityColumn(String columnDefn, DdlIdentity identity) {
return asIdentityStandardOptions(columnDefn, identity);
}
@Override
public String alterColumnType(String tableName, String columnName, String type) {
return super.alterColumnType(tableName, columnName, type) + " using " + columnName + "::" + convert(type);
}
}
@@ -15,7 +15,7 @@ import java.util.Objects;
*/
public class MColumn {
private final String name;
private String name;
private String type;
private String checkConstraint;
private String checkConstraintName;
@@ -88,7 +88,6 @@ public class MColumn {
* Return a copy of this column used for creating the associated draft table.
*/
public MColumn copyForDraft() {
MColumn copy = new MColumn(name, type);
copy.draftOnly = draftOnly;
copy.checkConstraint = checkConstraint;
@@ -470,6 +469,14 @@ public class MColumn {
this.dbMigrationInfos = dbMigrationInfos;
}
/**
* Rename the column.
*/
public MColumn rename(String newName) {
this.name = newName;
return this;
}
/**
* Apply changes based on the AlterColumn request.
*/
@@ -11,6 +11,7 @@ import io.ebeaninternal.dbmigration.migration.DropColumn;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.migration.DropTable;
import io.ebeaninternal.dbmigration.migration.ForeignKey;
import io.ebeaninternal.dbmigration.migration.RenameColumn;
import io.ebeaninternal.dbmigration.migration.UniqueConstraint;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
@@ -415,6 +416,15 @@ public class MTable {
}
}
public void apply(RenameColumn renameColumn) {
checkTableName(renameColumn.getTableName());
MColumn column = columns.remove(renameColumn.getOldName());
if (column == null) {
throw new IllegalStateException("Column [" + renameColumn.getOldName() + "] does not exist for RenameColumn change on table [" + renameColumn.getTableName() + "]?");
}
addColumn(column.rename(renameColumn.getNewName()));
}
public String getName() {
return name;
}
@@ -17,6 +17,7 @@ import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.migration.DropIndex;
import io.ebeaninternal.dbmigration.migration.DropTable;
import io.ebeaninternal.dbmigration.migration.Migration;
import io.ebeaninternal.dbmigration.migration.RenameColumn;
import io.ebeaninternal.dbmigration.migration.Sql;
import java.util.ArrayList;
@@ -163,6 +164,8 @@ public class ModelContainer {
applyChange((AddColumn) change);
} else if (change instanceof DropColumn) {
applyChange((DropColumn) change);
} else if (change instanceof RenameColumn) {
applyChange((RenameColumn) change);
} else if (change instanceof CreateIndex) {
applyChange((CreateIndex) change);
} else if (change instanceof DropIndex) {
@@ -314,6 +317,14 @@ public class ModelContainer {
table.apply(dropColumn);
}
protected void applyChange(RenameColumn renameColumn) {
MTable table = tables.get(renameColumn.getTableName());
if (table == null) {
throw new IllegalStateException("Table [" + renameColumn.getTableName() + "] does not exist in model?");
}
table.apply(renameColumn);
}
/**
* Add a table (typically from reading EbeanServer meta data).
*/
@@ -8,6 +8,7 @@ import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.mariadb.MariaDbPlatform;
import io.ebean.config.dbplatform.mysql.MySql55Platform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.nuodb.NuoDbPlatform;
@@ -76,11 +77,13 @@ public class DatabasePlatformFactory {
* Lookup the platform by name.
*/
private DatabasePlatform byDatabaseName(String dbName) {
dbName = dbName.toLowerCase();
if (dbName.equals("h2")) {
return new H2Platform();
}
if (dbName.equals("mariadb")) {
return new MariaDbPlatform();
}
if (dbName.equals("mysql")) {
return new MySqlPlatform();
}
@@ -123,7 +126,6 @@ public class DatabasePlatformFactory {
if (dbName.equals("hana")) {
return new HanaPlatform();
}
throw new RuntimeException("database platform " + dbName + " is not known?");
}
@@ -159,15 +161,17 @@ public class DatabasePlatformFactory {
if (dbProductName.contains("oracle")) {
return new OraclePlatform();
} else if (dbProductName.contains("microsoft")) {
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 mysqlVersion(majorVersion, minorVersion);
throw new IllegalArgumentException("For SqlServer please explicitly choose either sqlserver16 or sqlserver17 as the platform via DatabaseConfig.setDatabasePlatformName. Refer to issue #1340 for more details");
} else if (dbProductName.contains("h2")) {
return new H2Platform();
} else if (dbProductName.contains("hsql database engine")) {
return new HsqldbPlatform();
} else if (dbProductName.contains("postgres")) {
return readPostgres(connection, majorVersion);
} else if (dbProductName.contains("mariadb")) {
return new MariaDbPlatform();
} else if (dbProductName.contains("mysql")) {
return mysqlVersion(majorVersion, minorVersion);
} else if (dbProductName.contains("nuo")) {
return new NuoDbPlatform();
} else if (dbProductName.contains("sqlite")) {
@@ -33,7 +33,6 @@ public class DefaultBackgroundExecutor implements SpiBackgroundExecutor {
@Override
public void execute(Runnable r) {
final Map<String, String> map = MDC.getCopyOfContextMap();
if (map == null) {
pool.execute(r);
} else {
@@ -50,10 +49,14 @@ public class DefaultBackgroundExecutor implements SpiBackgroundExecutor {
@Override
public void executePeriodically(Runnable r, long delay, TimeUnit unit) {
final Map<String, String> map = MDC.getCopyOfContextMap();
executePeriodically(r, delay, delay, unit);
}
@Override
public void executePeriodically(Runnable r, long initialDelay, long delay, TimeUnit unit) {
final Map<String, String> map = MDC.getCopyOfContextMap();
if (map == null) {
schedulePool.scheduleWithFixedDelay(r, delay, delay, unit);
schedulePool.scheduleWithFixedDelay(r, initialDelay, delay, unit);
} else {
schedulePool.scheduleWithFixedDelay(() -> {
MDC.setContextMap(map);
@@ -62,14 +65,13 @@ public class DefaultBackgroundExecutor implements SpiBackgroundExecutor {
} finally {
MDC.clear();
}
}, delay, delay, unit);
}, initialDelay, delay, unit);
}
}
@Override
public ScheduledFuture<?> schedule(Runnable r, long delay, TimeUnit unit) {
final Map<String, String> map = MDC.getCopyOfContextMap();
if (map == null) {
return schedulePool.schedule(r, delay, unit);
} else {
@@ -87,7 +89,6 @@ public class DefaultBackgroundExecutor implements SpiBackgroundExecutor {
@Override
public <V> ScheduledFuture<V> schedule(Callable<V> c, long delay, TimeUnit unit) {
final Map<String, String> map = MDC.getCopyOfContextMap();
if (map == null) {
return schedulePool.schedule(c, delay, unit);
} else {
@@ -33,6 +33,7 @@ import io.ebean.Update;
import io.ebean.UpdateQuery;
import io.ebean.ValuePair;
import io.ebean.Version;
import io.ebean.annotation.Platform;
import io.ebean.annotation.TxIsolation;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.CallOrigin;
@@ -311,9 +312,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
private void configureServerPlugins() {
autoTuneService.startup();
for (Plugin plugin : serverPlugins) {
plugin.configure(this);
}
@@ -323,7 +322,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Execute all the plugins with an online flag indicating the DB is up or not.
*/
public void executePlugins(boolean online) {
if (!serverConfig.isDocStoreOnly()) {
ddlGenerator.execute(online);
}
@@ -386,6 +384,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return metaInfoManager;
}
@Override
public Platform getPlatform() {
return databasePlatform.getPlatform();
}
@Override
public SpiServer getPluginApi() {
return this;
@@ -471,14 +474,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Shutdown the services like threads and DataSource.
*/
private void shutdownInternal(boolean shutdownDataSource, boolean deregisterDriver) {
logger.debug("Shutting down instance:{}", serverName);
if (shutdown) {
// already shutdown
return;
}
shutdownPlugins();
autoTuneService.shutdown();
// shutdown background threads
backgroundExecutor.shutdown();
@@ -492,11 +493,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
private void shutdownPlugins() {
if (serverConfig.isDumpMetricsOnShutdown()) {
new DumpMetrics(this, serverConfig.getDumpMetricsOptions()).dump();
}
for (Plugin plugin : serverPlugins) {
try {
plugin.shutdown();
@@ -601,7 +600,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (a == null) {
return null;
}
BeanDescriptor<?> desc = getBeanDescriptor(a.getClass());
return DiffHelp.diff(a, b, desc);
}
@@ -626,10 +624,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void externalModification(String tableName, boolean inserts, boolean updates, boolean deletes) {
TransactionEventTable evt = new TransactionEventTable();
evt.add(tableName, inserts, updates, deletes);
externalModification(evt);
}
@@ -697,14 +693,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> T getReference(Class<T> type, Object id) {
if (id == null) {
throw new NullPointerException("The id is null");
}
BeanDescriptor desc = getBeanDescriptor(type);
id = desc.convertId(id);
PersistenceContext pc = null;
SpiTransaction t = transactionManager.getActive();
if (t != null) {
@@ -719,12 +712,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (inheritInfo == null || inheritInfo.isConcrete()) {
return (T) desc.contextRef(pc, null, false, id);
}
BeanProperty idProp = desc.getIdProperty();
if (idProp == null) {
throw new PersistenceException("No ID properties for this type? " + desc);
}
// we actually need to do a query because we don't know the type without the discriminator
// value, just select the id property and discriminator column (auto added)
return find(type).select(idProp.getName()).setId(id).findOne();
@@ -769,13 +760,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
ScopedTransaction scopeTrans = transactionManager.beginScopedTransaction(scope);
try {
return c.call();
} catch (Error e) {
throw scopeTrans.caughtError(e);
} catch (Exception e) {
throw new PersistenceException(scopeTrans.caughtThrowable(e));
} finally {
scopeTrans.complete();
}
@@ -791,13 +779,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
ScopedTransaction t = transactionManager.beginScopedTransaction(scope);
try {
r.run();
} catch (Error e) {
throw t.caughtError(e);
} catch (Exception e) {
throw new PersistenceException(t.caughtThrowable(e));
} finally {
t.complete();
}
@@ -879,7 +864,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
@SuppressWarnings("unchecked")
public <T> void sort(List<T> list, String sortByClause) {
if (list == null) {
throw new NullPointerException("list is null");
}
@@ -901,7 +885,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> Set<String> validateQuery(Query<T> query) {
BeanDescriptor<T> beanDescriptor = getBeanDescriptor(query.getBeanType());
if (beanDescriptor == null) {
throw new PersistenceException("BeanDescriptor not found, is [" + query.getBeanType() + "] an entity bean?");
@@ -1010,13 +993,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
String m = beanType.getName() + " is NOT an Entity Bean registered with this server?";
throw new PersistenceException(m);
}
return new DefaultOrmUpdate<>(beanType, this, desc.getBaseTable(), ormUpdate);
}
@Override
public <T> DtoQuery<T> findDto(Class<T> dtoType, String sql) {
DtoBeanDescriptor<T> descriptor = dtoBeanManager.getDescriptor(dtoType);
return new DefaultDtoQuery<>(this, descriptor, sql.trim());
}
@@ -1033,14 +1014,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> DtoQuery<T> findDto(Class<T> dtoType, SpiQuery<?> ormQuery) {
DtoBeanDescriptor<T> descriptor = dtoBeanManager.getDescriptor(dtoType);
return new DefaultDtoQuery<>(this, descriptor, ormQuery);
}
@Override
public SpiResultSet findResultSet(SpiQuery<?> ormQuery, SpiTransaction transaction) {
SpiOrmQueryRequest<?> request = createQueryRequest(ormQuery.getType(), ormQuery, transaction);
request.initTransIfRequired();
return request.findResultSet();
@@ -1073,7 +1052,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> T find(Class<T> beanType, Object uid) {
return find(beanType, uid, null);
}
@@ -1082,33 +1060,27 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public <T> T find(Class<T> beanType, Object id, Transaction t) {
if (id == null) {
throw new NullPointerException("The id is null");
}
Query<T> query = createQuery(beanType).setId(id);
return findId(query, t);
}
<T> SpiOrmQueryRequest<T> createQueryRequest(Type type, Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = buildQueryRequest(type, query, t);
request.prepareQuery();
return request;
}
<T> SpiOrmQueryRequest<T> buildQueryRequest(Type type, Query<T> query, Transaction t) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(type);
spiQuery.checkNamedParameters();
return buildQueryRequest(spiQuery, t);
}
private <T> SpiOrmQueryRequest<T> buildQueryRequest(SpiQuery<T> query, Transaction t) {
if (t == null) {
t = currentServerTransaction();
}
@@ -1118,9 +1090,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
// to define the 'default' select clause
query.setDefaultSelectClause();
}
query.selectAllForLazyLoadProperty();
ProfileLocation profileLocation = query.getProfileLocation();
if (profileLocation != null) {
profileLocation.obtain();
@@ -1129,7 +1099,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (query.getParentNode() == null) {
query.setOrigin(createCallOrigin());
}
return new OrmQueryRequest<>(this, queryEngine, query, (SpiTransaction) t);
}
@@ -1138,15 +1107,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@SuppressWarnings("unchecked")
private <T> T findIdCheckPersistenceContextAndCache(Transaction transaction, SpiQuery<T> query, Object id) {
SpiTransaction t = (SpiTransaction) transaction;
if (t == null) {
t = currentServerTransaction();
}
BeanDescriptor<T> desc = query.getBeanDescriptor();
id = desc.convertId(id);
PersistenceContext pc = null;
if (t != null && useTransactionPersistenceContext(query)) {
// first look in the transaction scoped persistence context
@@ -1162,11 +1128,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
}
if (!query.isBeanCacheGet() || (t != null && t.isSkipCache())) {
return null;
}
// Hit the L2 bean cache
return desc.cacheBeanGet(id, query.isReadOnly(), pc);
}
@@ -1189,7 +1153,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@SuppressWarnings("unchecked")
private <T> T findId(Query<T> query, Transaction t) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(Type.BEAN);
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isForceHitDatabase()) {
@@ -1200,7 +1163,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return bean;
}
}
SpiOrmQueryRequest<T> request = buildQueryRequest(spiQuery, t);
request.prepareQuery();
if (request.isUseDocStore()) {
@@ -1209,7 +1171,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
try {
request.initTransIfRequired();
return (T) request.findId();
} finally {
request.endTransIfRequired();
}
@@ -1222,17 +1183,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> T findOne(Query<T> query, Transaction transaction) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
if (spiQuery.isFindById()) {
// actually a find by Id query
return findId(query, transaction);
}
if (transaction == null) {
transaction = currentServerTransaction();
}
// a query that is expected to return either 0 or 1 beans
List<T> list = findList(query, transaction, true);
return extractUnique(list);
@@ -1241,10 +1199,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private <T> T extractUnique(List<T> list) {
if (list.isEmpty()) {
return null;
} else if (list.size() > 1) {
throw new NonUniqueResultException("Unique expecting 0 or 1 results but got [" + list.size() + "]");
} else {
return list.get(0);
}
@@ -1253,7 +1209,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <T> Set<T> findSet(Query<T> query, Transaction t) {
SpiOrmQueryRequest request = createQueryRequest(Type.SET, query, t);
Object result = request.getFromQueryCache();
if (result != null) {
@@ -1270,7 +1225,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public <K, T> Map<K, T> findMap(Query<T> query, Transaction t) {
SpiOrmQueryRequest request = createQueryRequest(Type.MAP, query, t);
request.resetBeanCacheAutoMode(false);
if ((t == null || !t.isSkipCache()) && request.getFromBeanCache()) {
@@ -1292,7 +1246,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
@SuppressWarnings("unchecked")
public <A, T> List<A> findSingleAttributeList(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ATTRIBUTE, query, t);
Object result = request.getFromQueryCache();
if (result != null) {
@@ -1301,7 +1254,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
try {
request.initTransIfRequired();
return (List<A>) request.findSingleAttributeList();
} finally {
request.endTransIfRequired();
}
@@ -1309,7 +1261,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> int findCount(Query<T> query, Transaction t) {
SpiQuery<T> spiQuery = ((SpiQuery<T>) query);
if (!spiQuery.isDistinct()) {
spiQuery = spiQuery.copy();
@@ -1319,7 +1270,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> int findCountWithCopy(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.COUNT, query, t);
Integer result = request.getFromQueryCache();
if (result != null) {
@@ -1328,7 +1278,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
try {
request.initTransIfRequired();
return request.findCount();
} finally {
request.endTransIfRequired();
}
@@ -1342,7 +1291,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> boolean exists(Query<?> ormQuery, Transaction transaction) {
Query<?> ormQueryCopy = ormQuery.copy();
ormQueryCopy.setMaxRows(1);
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, ormQueryCopy, transaction);
@@ -1350,7 +1298,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
request.initTransIfRequired();
List<Object> ids = request.findIds();
return !ids.isEmpty();
} finally {
request.endTransIfRequired();
}
@@ -1358,14 +1305,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <A, T> List<A> findIds(Query<T> query, Transaction t) {
return findIdsWithCopy(((SpiQuery<T>) query).copy(), t);
}
@SuppressWarnings("unchecked")
@Override
public <A, T> List<A> findIdsWithCopy(Query<T> query, Transaction t) {
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, query, t);
Object result = request.getFromQueryCache();
if (result != null) {
@@ -1378,7 +1323,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
try {
request.initTransIfRequired();
return request.findIds();
} finally {
request.endTransIfRequired();
}
@@ -1386,7 +1330,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> int delete(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.DELETE, query, t);
try {
request.initTransIfRequired();
@@ -1410,7 +1353,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> int update(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.UPDATE, query, t);
try {
request.initTransIfRequired();
@@ -1423,82 +1365,60 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> FutureRowCount<T> findFutureCount(Query<T> q, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) q).copy();
copy.setFutureFetch(true);
Transaction newTxn = createTransaction();
CallableQueryCount<T> call = new CallableQueryCount<>(this, copy, newTxn);
QueryFutureRowCount<T> queryFuture = new QueryFutureRowCount<>(call);
QueryFutureRowCount<T> queryFuture = new QueryFutureRowCount<>(new CallableQueryCount<T>(this, copy, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
@Override
public <T> FutureIds<T> findFutureIds(Query<T> query, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) query).copy();
copy.setFutureFetch(true);
Transaction newTxn = createTransaction();
CallableQueryIds<T> call = new CallableQueryIds<>(this, copy, newTxn);
QueryFutureIds<T> queryFuture = new QueryFutureIds<>(call);
QueryFutureIds<T> queryFuture = new QueryFutureIds<>(new CallableQueryIds<T>(this, copy, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
@Override
public <T> FutureList<T> findFutureList(Query<T> query, Transaction t) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setFutureFetch(true);
// FutureList query always run in it's own persistence content
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
if (!spiQuery.isDisableReadAudit()) {
BeanDescriptor<T> desc = beanDescriptorManager.getBeanDescriptor(spiQuery.getBeanType());
desc.readAuditFutureList(spiQuery);
}
// Create a new transaction solely to execute the findList() at some future time
Transaction newTxn = createTransaction();
CallableQueryList<T> call = new CallableQueryList<>(this, spiQuery, newTxn);
QueryFutureList<T> queryFuture = new QueryFutureList<>(call);
QueryFutureList<T> queryFuture = new QueryFutureList<>(new CallableQueryList<T>(this, spiQuery, newTxn));
backgroundExecutor.execute(queryFuture.getFutureTask());
return queryFuture;
}
@Override
public <T> PagedList<T> findPagedList(Query<T> query, Transaction transaction) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
int maxRows = spiQuery.getMaxRows();
if (maxRows == 0) {
throw new PersistenceException("maxRows must be specified for findPagedList() query");
}
if (spiQuery.isUseDocStore()) {
return docStore().findPagedList(createQueryRequest(Type.LIST, query, transaction));
}
return new LimitOffsetPagedList<>(this, spiQuery);
}
@Override
public <T> QueryIterator<T> findIterate(Query<T> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
try {
request.initTransIfRequired();
return request.findIterate();
} catch (RuntimeException ex) {
request.endTransIfRequired();
throw ex;
@@ -1507,19 +1427,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> Stream<T> findLargeStream(Query<T> query, Transaction transaction) {
return findStreamWithSingleContext(false, query, transaction);
return findStream(query, transaction);
}
@Override
public <T> Stream<T> findStream(Query<T> query, Transaction transaction) {
return findStreamWithSingleContext(true, query, transaction);
}
private <T> Stream<T> findStreamWithSingleContext(boolean singleContext, Query<T> query, Transaction transaction) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, transaction);
if (singleContext) {
request.setIterateSingleContext();
}
try {
request.initTransIfRequired();
return toStream(request.findIterate());
@@ -1549,14 +1462,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> void findEach(Query<T> query, Consumer<T> consumer, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
if (request.isUseDocStore()) {
docStore().findEach(request, consumer);
return;
}
request.initTransIfRequired();
request.findEach(consumer);
// no try finally - findEach guarantee's cleanup of the transaction if required
@@ -1564,13 +1474,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ITERATE, query, t);
if (request.isUseDocStore()) {
docStore().findEachWhile(request, consumer);
return;
}
request.initTransIfRequired();
request.findEachWhile(consumer);
// no try finally - findEachWhile guarantee's cleanup of the transaction if required
@@ -1578,12 +1486,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> List<Version<T>> findVersions(Query<T> query, Transaction transaction) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.LIST, query, transaction);
try {
request.initTransIfRequired();
return request.findVersions();
} finally {
request.endTransIfRequired();
}
@@ -1596,14 +1502,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@SuppressWarnings("unchecked")
private <T> List<T> findList(Query<T> query, Transaction t, boolean findOne) {
SpiOrmQueryRequest<T> request = buildQueryRequest(Type.LIST, query, t);
request.resetBeanCacheAutoMode(findOne);
if ((t == null || !t.isSkipCache()) && request.getFromBeanCache()) {
// hit bean cache and got all results from cache
return request.getBeanCacheHits();
}
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
@@ -1612,11 +1516,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (request.isUseDocStore()) {
return docStore().findList(request);
}
try {
request.initTransIfRequired();
return request.findList();
} finally {
request.endTransIfRequired();
}
@@ -1624,7 +1526,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public SqlRow findOne(SqlQuery query, Transaction t) {
// no findId() method for SqlQuery...
// a query that is expected to return either 0 or 1 rows
List<SqlRow> list = findList(query, t);
@@ -1633,7 +1534,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction) {
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, transaction);
try {
request.initTransIfRequired();
@@ -1645,7 +1545,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction) {
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, transaction);
try {
request.initTransIfRequired();
@@ -1657,12 +1556,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public List<SqlRow> findList(SqlQuery query, Transaction t) {
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, t);
try {
request.initTransIfRequired();
return request.findList();
} finally {
request.endTransIfRequired();
}
@@ -1709,7 +1606,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
try {
request.initTransIfRequired();
request.findEach(consumer);
} finally {
request.endTransIfRequired();
}
@@ -1721,7 +1617,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
try {
request.initTransIfRequired();
request.findEachWhile(consumer);
} finally {
request.endTransIfRequired();
}
@@ -1733,7 +1628,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
try {
request.initTransIfRequired();
return request.findList();
} finally {
request.endTransIfRequired();
}
@@ -1745,7 +1639,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
try {
request.initTransIfRequired();
return extractUnique(request.findList());
} finally {
request.endTransIfRequired();
}
@@ -1796,7 +1689,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void updateAll(Collection<?> beans, Transaction transaction) {
if (beans == null || beans.isEmpty()) {
return;
}
@@ -1837,7 +1729,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public void insertAll(Collection<?> beans, Transaction transaction) {
if (beans == null || beans.isEmpty()) {
return;
}
@@ -1851,7 +1742,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> List<T> publish(Query<T> query, Transaction transaction) {
return executeInTrans((txn) -> persister.publish(query, txn), transaction);
}
@@ -1867,7 +1757,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> T publish(Class<T> beanType, Object id, Transaction transaction) {
Query<T> query = find(beanType).setId(id);
List<T> liveBeans = publish(query, transaction);
return (liveBeans.size() == 1) ? liveBeans.get(0) : null;
@@ -1875,13 +1764,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> List<T> draftRestore(Query<T> query, Transaction transaction) {
return executeInTrans((txn) -> persister.draftRestore(query, txn), transaction);
}
@Override
public <T> T draftRestore(Class<T> beanType, Object id, Transaction transaction) {
Query<T> query = find(beanType).setId(id);
List<T> beans = draftRestore(query, transaction);
return (beans.size() == 1) ? beans.get(0) : null;
@@ -1926,7 +1813,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Save all beans in the iterator with an explicit transaction.
*/
private int saveAllInternal(Collection<?> beans, Transaction transaction) {
if (beans == null || beans.isEmpty()) {
return 0;
}
@@ -1937,7 +1823,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
persister.save(checkEntityBean(bean), txn);
saveCount++;
}
txn.flushBatchOnCollection();
return saveCount;
}, transaction);
@@ -1964,7 +1849,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
private int delete(Class<?> beanType, Object id, Transaction transaction, boolean permanent) {
return executeInTrans((txn) -> persister.delete(beanType, id, txn, permanent), transaction);
}
@@ -2050,19 +1934,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Delete all the beans in the iterator with an explicit transaction.
*/
private int deleteAllInternal(Collection<?> beans, Transaction transaction, boolean permanent) {
if (beans == null || beans.isEmpty()) {
return 0;
}
return executeInTrans((txn) -> {
txn.checkBatchEscalationOnCollection();
int deleteCount = 0;
for (Object bean : beans) {
persister.delete(checkEntityBean(bean), txn, permanent);
deleteCount++;
}
txn.flushBatchOnCollection();
return deleteCount;
}, transaction);
@@ -2163,7 +2044,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public boolean isSupportedType(java.lang.reflect.Type genericType) {
TypeInfo typeInfo = ParamTypeHelper.getTypeInfo(genericType);
return typeInfo != null && getBeanDescriptor(typeInfo.getBeanType()) != null;
}
@@ -2286,7 +2166,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
P result = fun.apply(wrap.transaction());
wrap.commitIfCreated();
return result;
} catch (RuntimeException e) {
wrap.endIfCreated();
throw e;
@@ -2299,7 +2178,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Create a transaction if one is not currently active.
*/
ObtainedTransaction initTransIfRequired(Transaction t) {
if (t != null) {
return new ObtainedTransaction((SpiTransaction) t);
}
@@ -2368,16 +2246,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public Set<Property> checkUniqueness(Object bean, Transaction transaction) {
EntityBean entityBean = checkEntityBean(bean);
BeanDescriptor<?> beanDesc = getBeanDescriptor(entityBean.getClass());
BeanProperty idProperty = beanDesc.getIdProperty();
// if the ID of the Property is null we are unable to check uniqueness
if (idProperty == null) {
return Collections.emptySet();
}
Object id = idProperty.getVal(entityBean);
if (entityBean._ebean_getIntercept().isNew() && id != null) {
// Primary Key is changeable only on new models - so skip check if we are not
@@ -2390,7 +2265,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return ret;
}
}
for (BeanProperty[] props : beanDesc.getUniqueProps()) {
Set<Property> ret = checkUniqueness(entityBean, beanDesc, props, transaction);
if (ret != null) {
@@ -2407,12 +2281,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
BeanProperty idProperty = beanDesc.getIdProperty();
Query<?> query = new DefaultOrmQuery<>(beanDesc, this, expressionFactory);
ExpressionList<?> exprList = query.where();
if (!entityBean._ebean_getIntercept().isNew()) {
// if model is not new, exclude ourself.
exprList.ne(idProperty.getName(), idProperty.getVal(entityBean));
}
for (Property prop : props) {
Object value = prop.getVal(entityBean);
if (value == null) {
@@ -2420,7 +2292,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
exprList.eq(prop.getName(), value);
}
if (findCount(query, transaction) > 0) {
Set<Property> ret = new LinkedHashSet<>();
Collections.addAll(ret, props);
@@ -47,7 +47,7 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
SpiQuery<?> ormQuery = query.getOrmQuery();
if (ormQuery != null) {
ormQuery.setType(type);
ormQuery.setManualId(true);
ormQuery.setManualId();
// execute the underlying ORM query returning the ResultSet
SpiResultSet result = server.findResultSet(ormQuery, trans);
@@ -117,12 +117,8 @@ class InitDataSource {
}
private DataSource create(DataSourceConfig dsConfig, boolean readOnly) {
DataSourceFactory factory = DataSourceFactory.get();
if (factory == null) {
throw new IllegalStateException("No DataSourceFactory service implementation found in class path. Missing dependency to ebean-datasource?");
}
String poolName = config.getName() + (readOnly ? "-ro" : "");
return factory.createPool(poolName, dsConfig);
return DataSourceFactory.create(poolName, dsConfig);
}
/**
@@ -503,7 +503,6 @@ public class InternalConfiguration {
* Create the DataTimeZone implementation to use.
*/
private DataTimeZone initDataTimeZone() {
String tz = serverConfig.getDataTimeZone();
if (tz == null) {
if (isMySql(getPlatform())) {
@@ -57,14 +57,14 @@ public class MultiTenantDbCatalogSupplier implements DataSourceSupplier {
@Override
public void shutdown(boolean deregisterDriver) {
if (readOnlyDataSource instanceof DataSourcePool) {
((DataSourcePool) readOnlyDataSource).shutdown(false);
((DataSourcePool) readOnlyDataSource).shutdown();
}
if (dataSource instanceof DataSourcePool) {
((DataSourcePool) dataSource).shutdown(deregisterDriver);
((DataSourcePool) dataSource).shutdown();
}
}
private class CatalogDataSource implements DataSource {
private static class CatalogDataSource implements DataSource {
final DataSource dataSource;
final CurrentTenantProvider tenantProvider;
@@ -57,10 +57,10 @@ class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
@Override
public void shutdown(boolean deregisterDriver) {
if (readOnlyDataSource instanceof DataSourcePool) {
((DataSourcePool) readOnlyDataSource).shutdown(false);
((DataSourcePool) readOnlyDataSource).shutdown();
}
if (dataSource instanceof DataSourcePool) {
((DataSourcePool) dataSource).shutdown(deregisterDriver);
((DataSourcePool) dataSource).shutdown();
}
}
@@ -8,7 +8,9 @@ import io.ebean.bean.CallStack;
*/
class NoopCallOriginFactory implements CallOriginFactory {
private final CallOrigin COMMON = new CallStack(Thread.currentThread().getStackTrace(), 0, 0);
private static final StackTraceElement E0 = new StackTraceElement("none", "none", "none", 0);
private final CallOrigin COMMON = new CallStack(new StackTraceElement[]{E0}, 0, 0);
@Override
public CallOrigin createCallOrigin() {
@@ -89,8 +89,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private Set<String> dependentTables;
private boolean iterateSingleContext;
/**
* Create the InternalQueryRequest.
*/
@@ -108,15 +106,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return queryEngine.translate(this, bindLog, sql, e);
}
@Override
public void setIterateSingleContext() {
this.iterateSingleContext = true;
}
public boolean isIterateSingleContext() {
return iterateSingleContext;
}
@Override
public boolean isDeleteByStatement() {
if (!transaction.isPersistCascade() || beanDescriptor.isDeleteByStatement()) {
@@ -316,7 +305,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
*/
@Override
public JsonReadOptions createJsonReadOptions() {
persistenceContext = getPersistenceContext(query, transaction);
if (query.getPersistenceContext() == null) {
query.setPersistenceContext(persistenceContext);
@@ -327,7 +315,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
loadContext = new DLoadContext(this, secondaryQueries);
jsonRead.setLoadContext(loadContext);
}
return jsonRead;
}
@@ -335,8 +322,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
* For iterate queries reset the persistenceContext and loadContext.
*/
public void flushPersistenceContextOnIterate() {
if (!iterateSingleContext) {
persistenceContext = new DefaultPersistenceContext();
if (persistenceContext.resetLimit()) {
persistenceContext = persistenceContext.forIterateReset();
loadContext.resetPersistenceContext(persistenceContext);
if (jsonRead != null) {
jsonRead.setPersistenceContext(persistenceContext);
@@ -350,7 +337,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
* transaction scoped.
*/
private PersistenceContext getPersistenceContext(SpiQuery<?> query, SpiTransaction t) {
// check if there is already a persistence context set which is the case
// when lazy loading or query joins are executed
PersistenceContext ctx = query.getPersistenceContext();
@@ -358,7 +344,14 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
// determine the scope (from the query and then server)
PersistenceContextScope scope = ebeanServer.getPersistenceContextScope(query);
return (scope == PersistenceContextScope.QUERY || t == null) ? new DefaultPersistenceContext() : t.getPersistenceContext();
if (scope == PersistenceContextScope.QUERY || t == null) {
return new DefaultPersistenceContext();
}
if (Type.ITERATE == query.getType()) {
return t.getPersistenceContext().forIterate();
} else {
return t.getPersistenceContext();
}
}
/**
@@ -578,6 +571,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return cacheKey != null && query.getUseQueryCache().isPut();
}
public boolean isBeanCachePutMany() {
return !transaction.isSkipCacheExplicit() && query.isBeanCachePut();
}
public boolean isBeanCachePut() {
return !transaction.isSkipCache() && query.isBeanCachePut();
}
@@ -586,7 +583,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
* Merge in prior L2 bean cache hits with the query result.
*/
public void mergeCacheHits(BeanCollection<T> result) {
if (cacheBeans != null && !cacheBeans.isEmpty()) {
if (query.getType() == Type.MAP) {
mergeCacheHitsToMap(result);
@@ -188,7 +188,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
super(server, t, persistExecute);
this.entityBean = (EntityBean) bean;
this.intercept = entityBean._ebean_getIntercept();
@@ -204,7 +203,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
if (Flags.isRecurse(flags)) {
this.persistCascade = t.isPersistCascade();
}
if (this.type == Type.UPDATE) {
if (intercept.isNew()) {
// 'stateless update' - set loaded properties as dirty
@@ -279,7 +277,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Init the transaction and also check for batch on cascade escalation.
*/
public void initTransIfRequiredWithBatchCascade() {
if (createImplicitTransIfRequired()) {
docStoreMode = calcDocStoreMode(transaction, type);
}
@@ -317,9 +314,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
private void onUpdateGeneratedProperties() {
for (BeanProperty prop : beanDescriptor.propertiesGenUpdate()) {
GeneratedProperty generatedProperty = prop.getGeneratedProperty();
if (prop.isVersion()) {
if (isLoadedProperty(prop)) {
@@ -451,7 +446,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Return true if any of the given properties are dirty.
*/
public boolean hasDirtyProperty(int[] propertyPositions) {
for (int propertyPosition : propertyPositions) {
if (dirtyProperties[propertyPosition]) {
return true;
@@ -517,7 +511,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
@Override
public void docStoreUpdate(DocStoreUpdateContext txn) throws IOException {
switch (type) {
case INSERT:
beanDescriptor.docStoreInsert(idValue, this, txn);
@@ -554,7 +547,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
public void addToPersistMap(BeanPersistIdMap beanPersistMap) {
beanPersistMap.add(beanDescriptor, type, idValue);
}
@@ -564,19 +556,15 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
case INSERT:
beanPersistListener.inserted(bean);
break;
case UPDATE:
beanPersistListener.updated(bean, updatedProperties);
break;
case DELETE:
beanPersistListener.deleted(bean);
break;
case DELETE_SOFT:
beanPersistListener.softDeleted(bean);
break;
default:
}
}
@@ -785,7 +773,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
case INSERT:
executeInsert();
return -1;
case UPDATE:
if (beanPersistListener != null) {
// store the updated properties for sending later
@@ -793,15 +780,12 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
executeUpdate();
return -1;
case DELETE_SOFT:
prepareForSoftDelete();
executeSoftDelete();
return -1;
case DELETE:
return executeDelete();
default:
throw new RuntimeException("Invalid type " + type);
}
@@ -811,13 +795,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Soft delete is executed as update so we want to set deleted=true property.
*/
private void prepareForSoftDelete() {
beanDescriptor.setSoftDeleteValue(entityBean);
}
@Override
public int executeOrQueue() {
boolean batch = isBatchThisRequest();
try {
BatchControl control = transaction.getBatchControl();
@@ -827,7 +809,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
if (batch) {
control = persistExecute.createBatchControl(transaction);
return control.executeOrQueue(this, true);
} else {
return executeNoBatch();
}
@@ -926,7 +907,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
controllerPost();
}
setNotifyCache();
boolean isChangeLog = beanDescriptor.isChangeLog();
if (type == Type.UPDATE && (isChangeLog || notifyCache || docStoreMode == DocStoreMode.UPDATE)) {
// get the dirty properties for update notification to the doc store
@@ -935,16 +915,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
if (isChangeLog) {
changeLog();
}
// if bean persisted again then should result in an update
intercept.setLoaded();
if (isInsert()) {
postInsert();
}
addPostCommitListeners();
notifyCacheOnPostExecute();
if (isLogSummary()) {
logSummary();
}
@@ -994,7 +971,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
private void logSummary() {
String draft = (beanDescriptor.isDraftable() && !publish) ? " draft[true]" : "";
String name = beanDescriptor.getName();
switch (type) {
@@ -1136,7 +1112,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* of queue events or list of update events.
*/
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
if (type == Type.UPDATE) {
beanDescriptor.docStoreUpdateEmbedded(this, docStoreUpdates);
}
@@ -1165,7 +1140,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* </p>
*/
private boolean determineUpdateAllLoadedProperties() {
Boolean txnUpdateAll = transaction.isUpdateAllLoadedProperties();
if (txnUpdateAll != null) {
// use the setting explicitly set on the transaction
@@ -1174,7 +1148,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
// if using batch use the server default setting
requestUpdateAllLoadedProps = isBatchThisRequest() && ebeanServer.isUpdateAllPropertiesInBatch();
}
return requestUpdateAllLoadedProps;
}
@@ -1196,25 +1169,21 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Return the key for an update persist request.
*/
public String getUpdatePlanHash() {
StringBuilder key;
if (determineUpdateAllLoadedProperties()) {
key = intercept.getLoadedPropertyKey();
} else {
key = intercept.getDirtyPropertyKey();
}
BeanProperty versionProperty = beanDescriptor.getVersionProperty();
if (versionProperty != null) {
if (intercept.isLoadedProperty(versionProperty.getPropertyIndex())) {
key.append('v');
}
}
if (publish) {
key.append('p');
}
return key.toString();
}
@@ -1380,13 +1349,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Build the bean update for the L2 cache.
*/
public void addBeanUpdate(CacheChangeSet changeSet) {
if (!updatedManysOnly || collectionChanges != null) {
boolean updateNaturalKey = false;
String key = beanDescriptor.cacheKey(idValue);
Map<String, Object> changes = new LinkedHashMap<>();
EntityBean bean = getEntityBean();
boolean[] dirtyProperties = getDirtyProperties();
@@ -1410,7 +1376,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
// add element collection update
changes.putAll(collectionChanges);
}
changeSet.addBeanUpdate(beanDescriptor, key, changes, updateNaturalKey, getVersion());
}
}
@@ -7,6 +7,7 @@ import io.ebeaninternal.dbmigration.ddlgeneration.platform.DB2Ddl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.H2Ddl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HanaColumnStoreDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HsqldbDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.MariaDbDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.MySqlDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.NuoDbDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.OracleDdl;
@@ -31,6 +32,8 @@ public class PlatformDdlBuilder {
return new H2Ddl(platform);
case DB2:
return new DB2Ddl(platform);
case MARIADB:
return new MariaDbDdl(platform);
case MYSQL55:
case MYSQL:
return new MySqlDdl(platform);
@@ -44,10 +44,10 @@ class SimpleDataSourceProvider implements DataSourceSupplier {
@Override
public void shutdown(boolean deregisterDriver) {
if (readOnlyDataSource instanceof DataSourcePool){
((DataSourcePool) readOnlyDataSource).shutdown(false);
((DataSourcePool) readOnlyDataSource).shutdown();
}
if (dataSource instanceof DataSourcePool){
((DataSourcePool) dataSource).shutdown(deregisterDriver);
((DataSourcePool) dataSource).shutdown();
}
}
}
@@ -177,9 +177,4 @@ public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T>, DocQueryRequ
*/
boolean isDeleteByStatement();
/**
* Set when we want to use a single persistence context for all beans returned
* in the query (so all beans are held in memory)
*/
void setIterateSingleContext();
}
@@ -39,6 +39,7 @@ import io.ebean.util.SplitName;
import io.ebeaninternal.api.BeanCacheResult;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.api.LoadBeanContext;
import io.ebeaninternal.api.LoadContext;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
@@ -105,7 +106,6 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -219,9 +219,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
private final BeanProperty draftDirty;
/**
* Map of BeanProperty Linked so as to preserve order.
*/
private final LinkedHashMap<String, BeanProperty> propMap;
/**
@@ -234,44 +231,17 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
private final Map<String, BeanPropertyAssoc<?>> tablePath = new HashMap<>();
/**
* The type of bean this describes.
*/
final Class<T> beanType;
final Class<?> rootBeanType;
/**
* This is not sent to a remote client.
*/
private final BeanDescriptorMap owner;
final String[] properties;
/**
* Intercept pre post on insert,update, and delete .
*/
private volatile BeanPersistController persistController;
private final BeanPostLoad beanPostLoad;
private final BeanPostConstructListener beanPostConstructListener;
/**
* Listens for post commit insert update and delete events.
*/
private volatile BeanPersistController persistController;
private volatile BeanPersistListener persistListener;
private final BeanQueryAdapter queryAdapter;
/**
* If set overrides the find implementation. Server side only.
*/
private final BeanFindController beanFinder;
/**
* Used for fine grain filtering for the change log.
*/
private final ChangeLogFilter changeLogFilter;
/**
@@ -281,22 +251,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
private final boolean abstractType;
/**
* Derived list of properties that make up the unique id.
*/
private final BeanProperty idProperty;
private final int idPropertyIndex;
/**
* Derived list of properties that are used for version concurrency checking.
*/
private final BeanProperty versionProperty;
private final int versionPropertyIndex;
private final BeanProperty whenModifiedProperty;
private final BeanProperty whenCreatedProperty;
/**
@@ -313,14 +273,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Scalar mutable properties (need to dirty check on update).
*/
private final BeanProperty[] propertiesMutable;
private final BeanPropertyAssocOne<?> unidirectional;
private final BeanProperty orderColumn;
/**
* list of properties that are Lists/Sets/Maps (Derived).
*/
private final BeanProperty[] propertiesNonMany;
private final BeanProperty[] propertiesAggregate;
private final BeanPropertyAssocMany<?>[] propertiesMany;
@@ -332,25 +287,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* list of properties that are associated beans and not embedded (Derived).
*/
private final BeanPropertyAssocOne<?>[] propertiesOne;
private final BeanPropertyAssocOne<?>[] propertiesOneImported;
private final BeanPropertyAssocOne<?>[] propertiesOneImportedSave;
private final BeanPropertyAssocOne<?>[] propertiesOneImportedDelete;
//private final BeanPropertyAssocOne<?>[] propertiesOneExported;
private final BeanPropertyAssocOne<?>[] propertiesOneExportedSave;
private final BeanPropertyAssocOne<?>[] propertiesOneExportedDelete;
/**
* list of properties that are embedded beans.
*/
private final BeanPropertyAssocOne<?>[] propertiesEmbedded;
/**
* List of the scalar properties excluding id and secondary table properties.
*/
private final BeanProperty[] propertiesBaseScalar;
private final BeanProperty[] propertiesTransient;
/**
@@ -382,11 +326,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
private final EntityBean prototypeEntityBean;
private final IdBinder idBinder;
private String idBinderInLHSSql;
private String idBinderIdSql;
private String deleteByIdSql;
private String deleteByIdInSql;
private String whereIdInSql;
@@ -394,29 +335,19 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
private String softDeleteByIdInSql;
private final String name;
private final String baseTableAlias;
private final boolean cacheSharableBeans;
private final String docStoreQueueId;
private final BeanDescriptorDraftHelp<T> draftHelp;
private final BeanDescriptorCacheHelp<T> cacheHelp;
private final BeanDescriptorJsonHelp<T> jsonHelp;
private DocStoreBeanAdapter<T> docStoreAdapter;
private DocumentMapping docMapping;
private boolean docStoreEmbeddedInvalidation;
private final String defaultSelectClause;
private SpiEbeanServer ebeanServer;
/**
* Construct the BeanDescriptor.
*/
public BeanDescriptor(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy) {
this.owner = owner;
this.multiValueSupported = owner.isMultiValueSupported();
this.entityType = deploy.getEntityType();
@@ -431,7 +362,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.namedQuery = deploy.getNamedQuery();
this.namedRawSql = deploy.getNamedRawSql();
this.inheritInfo = deploy.getInheritInfo();
this.beanFinder = deploy.getBeanFinder();
this.persistController = deploy.getPersistController();
this.persistListener = deploy.getPersistListener();
@@ -439,7 +369,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.beanPostLoad = deploy.getPostLoad();
this.queryAdapter = deploy.getQueryAdapter();
this.changeLogFilter = deploy.getChangeLogFilter();
this.defaultSelectClause = deploy.getDefaultSelectClause();
this.identityMode = deploy.buildIdentityMode();
this.idType = identityMode.getIdType();
@@ -449,7 +378,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.selectLastInsertedIdDraft = deploy.getSelectLastInsertedIdDraft();
this.concurrencyMode = deploy.getConcurrencyMode();
this.indexDefinitions = deploy.getIndexDefinitions();
this.readAuditing = deploy.isReadAuditing();
this.draftable = deploy.isDraftable();
this.draftableElement = deploy.isDraftableElement();
@@ -464,10 +392,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.partitionMeta = deploy.getPartitionMeta();
this.storageEngine = deploy.getStorageEngine();
this.autoTunable = beanFinder == null && (entityType == EntityType.ORM || entityType == EntityType.VIEW);
// helper object used to derive lists of properties
DeployBeanPropertyLists listHelper = new DeployBeanPropertyLists(owner, this, deploy);
this.softDeleteProperty = listHelper.getSoftDeleteProperty();
// if formula is set, the property is virtual only (there is no column in db) the formula must evaluate to true,
// if there is a join to a deleted bean. Example: '@Formula(select = "${ta}.user_id is null")'
@@ -503,22 +429,17 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.propertiesGenInsert = listHelper.getGeneratedInsert();
this.propertiesGenUpdate = listHelper.getGeneratedUpdate();
this.idOnlyReference = isIdOnlyReference(propertiesBaseScalar);
boolean noRelationships = propertiesOne.length + propertiesMany.length == 0;
this.cacheSharableBeans = noRelationships && deploy.getCacheOptions().isReadOnly();
this.cacheHelp = new BeanDescriptorCacheHelp<>(this, owner.getCacheManager(), deploy.getCacheOptions(), cacheSharableBeans, propertiesOneImported);
this.jsonHelp = new BeanDescriptorJsonHelp<>(this);
this.draftHelp = new BeanDescriptorDraftHelp<>(this);
this.docStoreAdapter = owner.createDocStoreBeanAdapter(this, deploy);
this.docStoreQueueId = docStoreAdapter.getQueueId();
// Check if there are no cascade save associated beans ( subject to change
// in initialiseOther()). Note that if we are in an inheritance hierarchy
// then we also need to check every BeanDescriptors in the InheritInfo as
// well. We do that later in initialiseOther().
saveRecurseSkippable = (0 == (propertiesOneExportedSave.length + propertiesOneImportedSave.length + propertiesManySave.length));
// Check if there are no cascade delete associated beans (also subject to
@@ -529,7 +450,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.idBinder = owner.createIdBinder(idProperty);
this.whenModifiedProperty = findWhenModifiedProperty();
this.whenCreatedProperty = findWhenCreatedProperty();
// derive the index position of the Id and Version properties
this.abstractType = Modifier.isAbstract(beanType.getModifiers());
if (abstractType) {
@@ -537,7 +457,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.versionPropertyIndex = -1;
this.unloadProperties = new int[0];
this.propertiesIndex = new BeanProperty[0];
} else {
EntityBeanIntercept ebi = prototypeEntityBean._ebean_getIntercept();
this.idPropertyIndex = (idProperty == null) ? -1 : ebi.findProperty(idProperty.getName());
@@ -568,23 +487,19 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* These properties need to be unloaded when populating beans for queries.
*/
private int[] derivePropertiesToUnload(EntityBean prototypeEntityBean) {
boolean[] loaded = prototypeEntityBean._ebean_getIntercept().getLoaded();
int[] props = new int[loaded.length];
int pos = 0;
// collect the positions of the properties initialised in the default constructor.
for (int i = 0; i < loaded.length; i++) {
if (loaded[i]) {
props[pos++] = i;
}
}
if (pos == 0) {
// nothing set in the constructor
return new int[0];
}
// populate a smaller/minimal array
int[] unload = new int[pos];
System.arraycopy(props, 0, unload, 0, pos);
@@ -665,14 +580,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* <p>
* These properties need to be initialised prior to the association properties
* as they are used to get the imported and exported properties.
* </p>
*/
void initialiseId(BeanDescriptorInitContext initContext) {
if (logger.isTraceEnabled()) {
logger.trace("BeanDescriptor initialise " + fullName);
}
if (draftable) {
initContext.addDraft(baseTable, draftTable);
}
@@ -680,7 +592,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
// add mapping (used to swap out baseTable for asOf queries)
initContext.addHistory(baseTable, baseTableAsOf);
}
if (inheritInfo != null) {
inheritInfo.setDescriptor(this);
}
@@ -695,19 +606,17 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Initialise the exported and imported parts for associated properties.
*/
public void initialiseOther(BeanDescriptorInitContext initContext) {
for (BeanPropertyAssocMany<?> many : propertiesManyToMany) {
// register associated draft table for M2M intersection
many.registerDraftIntersectionTable(initContext);
}
if (historySupport) {
// history support on this bean so check all associated intersection tables
// and if they are not excluded register the associated 'with history' table
for (BeanPropertyAssocMany<?> aPropertiesManyToMany : propertiesManyToMany) {
for (BeanPropertyAssocMany<?> manyToMany : propertiesManyToMany) {
// register associated history table for M2M intersection
if (!aPropertiesManyToMany.isExcludedFromHistory()) {
TableJoin intersectionTableJoin = aPropertiesManyToMany.getIntersectionTableJoin();
if (!manyToMany.isExcludedFromHistory()) {
TableJoin intersectionTableJoin = manyToMany.getIntersectionTableJoin();
initContext.addHistoryIntersection(intersectionTableJoin.getTable());
}
}
@@ -730,11 +639,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
idBinderIdSql = idBinder.getBindIdSql(baseTableAlias);
String idBinderInLHSSqlNoAlias = idBinder.getBindIdInSql(null);
String idEqualsSql = idBinder.getBindIdSql(null);
deleteByIdSql = "delete from " + baseTable + " where " + idEqualsSql;
whereIdInSql = " where " + idBinderInLHSSqlNoAlias + " ";
deleteByIdInSql = "delete from " + baseTable + whereIdInSql;
if (softDelete) {
softDeleteByIdSql = "update " + baseTable + " set " + getSoftDeleteDbSet() + " where " + idEqualsSql;
softDeleteByIdInSql = "update " + baseTable + " set " + getSoftDeleteDbSet() + " where " + idBinderInLHSSqlNoAlias + " ";
@@ -765,7 +672,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return false;
}
boolean hasCircularImportedIdTo(BeanDescriptor sourceDesc) {
boolean hasCircularImportedIdTo(BeanDescriptor<?> sourceDesc) {
for (BeanPropertyAssocOne<?> assocOne : propertiesOneImportedSave) {
if (assocOne.getTargetDescriptor() == sourceDesc) {
return true;
@@ -792,7 +699,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Perform last initialisation for the descriptor.
*/
void initLast() {
for (BeanProperty prop : propertiesNonTransient) {
if (prop.isUnique()) {
propertiesUnique.add(new BeanProperty[]{prop});
@@ -895,13 +801,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
public void merge(EntityBean bean, EntityBean existing) {
EntityBeanIntercept fromEbi = bean._ebean_getIntercept();
EntityBeanIntercept toEbi = existing._ebean_getIntercept();
int propertyLength = toEbi.getPropertyLength();
String[] names = getProperties();
for (int i = 0; i < propertyLength; i++) {
if (fromEbi.isLoadedProperty(i)) {
@@ -977,14 +880,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return the bean change for an update generating 'new values' and 'old values' in JSON form.
*/
private BeanChange updateBeanChange(PersistRequestBean<T> request) {
try {
BeanChangeJson changeJson = new BeanChangeJson(this, request.isStatelessUpdate());
request.getEntityBeanIntercept().addDirtyPropertyValues(changeJson);
changeJson.flush();
return beanChange(ChangeType.UPDATE, request.getBeanId(), changeJson.newJson(), changeJson.oldJson());
} catch (RuntimeException e) {
logger.error("Failed to write ChangeLog entry for update", e);
return null;
@@ -995,16 +895,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return the bean change for an insert.
*/
private BeanChange insertBeanChange(PersistRequestBean<T> request) {
try {
StringWriter writer = new StringWriter(200);
SpiJsonWriter jsonWriter = createJsonWriter(writer);
jsonWriteForInsert(jsonWriter, request.getEntityBean());
jsonWriter.flush();
return beanChange(ChangeType.INSERT, request.getBeanId(), writer.toString(), null);
} catch (IOException e) {
logger.error("Failed to write ChangeLog entry for insert", e);
return null;
@@ -1063,12 +959,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* concurrency checking.
*/
private SqlUpdate deleteByIdList(List<Object> idList, DeleteMode mode) {
String baseSql = mode.isHard() ? deleteByIdInSql : softDeleteByIdInSql;
StringBuilder sb = new StringBuilder(baseSql);
String inClause = idBinder.getIdInValueExprDelete(idList.size());
sb.append(inClause);
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
idBinder.addIdInBindValues(delete, idList);
return delete;
@@ -1079,15 +973,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* concurrency checking.
*/
private SqlUpdate deleteById(Object id, DeleteMode mode) {
String baseSql = mode.isHard() ? deleteByIdSql : softDeleteByIdSql;
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(baseSql);
Object[] bindValues = idBinder.getBindValues(id);
for (Object bindValue : bindValues) {
sqlDelete.setParameter(bindValue);
}
return sqlDelete;
}
@@ -1100,9 +991,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
void initialiseFkeys() {
for (BeanPropertyAssocOne<?> aPropertiesOneImported : propertiesOneImported) {
if (!aPropertiesOneImported.isFormula()) {
aPropertiesOneImported.addFkey();
for (BeanPropertyAssocOne<?> oneImported : propertiesOneImported) {
if (!oneImported.isFormula()) {
oneImported.addFkey();
}
}
}
@@ -1210,12 +1101,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Build the Document mapping recursively with the given prefix relative to the root of the document.
*/
public void docStoreMapping(final DocMappingBuilder mapping, final String prefix) {
if (prefix != null && idProperty != null) {
// id property not included in the
idProperty.docStoreMapping(mapping, prefix);
}
if (inheritInfo != null) {
String discCol = inheritInfo.getDiscriminatorColumn();
if (Types.VARCHAR == inheritInfo.getDiscriminatorType()) {
@@ -1702,13 +1591,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Trim query plans not used since the passed in epoch time.
*/
void trimQueryPlans(long unusedSince) {
Iterator<CQueryPlan> it = queryPlanCache.values().iterator();
while (it.hasNext()) {
CQueryPlan queryPlan = it.next();
if (queryPlan.getLastQueryTime() < unusedSince) {
it.remove();
}
}
queryPlanCache.values().removeIf(queryPlan -> queryPlan.getLastQueryTime() < unusedSince);
}
/**
@@ -1804,9 +1687,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Find a property annotated with @WhenCreated or @CreatedTimestamp.
*/
private BeanProperty findWhenCreatedProperty() {
for (BeanProperty aPropertiesBaseScalar : propertiesBaseScalar) {
if (aPropertiesBaseScalar.isGeneratedWhenCreated()) {
return aPropertiesBaseScalar;
for (BeanProperty baseScalar : propertiesBaseScalar) {
if (baseScalar.isGeneratedWhenCreated()) {
return baseScalar;
}
}
return null;
@@ -1816,10 +1699,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Find a property annotated with @WhenModified or @UpdatedTimestamp.
*/
private BeanProperty findWhenModifiedProperty() {
for (BeanProperty aPropertiesBaseScalar : propertiesBaseScalar) {
if (aPropertiesBaseScalar.isGeneratedWhenModified()) {
return aPropertiesBaseScalar;
for (BeanProperty baseScalar : propertiesBaseScalar) {
if (baseScalar.isGeneratedWhenModified()) {
return baseScalar;
}
}
return null;
@@ -1829,14 +1711,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return the many property included in the query or null if one is not.
*/
public BeanPropertyAssocMany<?> getManyProperty(SpiQuery<?> query) {
OrmQueryDetail detail = query.getDetail();
for (BeanPropertyAssocMany<?> aPropertiesMany : propertiesMany) {
if (detail.includesPath(aPropertiesMany.getName())) {
return aPropertiesMany;
for (BeanPropertyAssocMany<?> many : propertiesMany) {
if (detail.includesPath(many.getName())) {
return many;
}
}
return null;
}
@@ -1905,7 +1785,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Bind the idValue to the preparedStatement.
* <p>
* This takes care of the various id types such as embedded beans etc.
* </p>
*/
public void bindId(DataBind dataBind, Object idValue) throws SQLException {
idBinder.bindId(dataBind, idValue);
@@ -1915,7 +1794,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return the id as an array of scalar bindable values.
* <p>
* This 'flattens' any EmbeddedId or multiple Id property cases.
* </p>
*/
public Object[] getBindIdValues(Object idValue) {
return idBinder.getBindValues(idValue);
@@ -1940,7 +1818,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
try {
EntityBean bean = (EntityBean) prototypeEntityBean._ebean_newInstance();
if (beanPostConstructListener != null) {
beanPostConstructListener.autowire(bean); // calls all registered listeners
beanPostConstructListener.postConstruct(bean); // calls first the @PostConstruct method and then the listeners
@@ -1959,7 +1836,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
}
return bean;
} catch (Exception ex) {
throw new PersistenceException(ex);
}
@@ -1999,7 +1875,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
@SuppressWarnings("unchecked")
public T createReference(Boolean readOnly, boolean disableLazyLoad, Object id, PersistenceContext pc) {
if (cacheSharableBeans && !disableLazyLoad && !Boolean.FALSE.equals(readOnly)) {
CachedBeanData d = cacheHelp.beanCacheGetData(cacheKey(id));
if (d != null) {
@@ -2016,10 +1891,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
return findReferenceBean(id, pc);
}
EntityBean eb = createEntityBean();
id = convertSetId(id, eb);
EntityBeanIntercept ebi = eb._ebean_getIntercept();
if (disableLazyLoad) {
ebi.setDisableLazyLoad(true);
@@ -2034,9 +1907,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
contextPut(pc, id, eb);
ebi.setPersistenceContext(pc);
}
return (T) eb;
} catch (Exception ex) {
throw new PersistenceException(ex);
}
@@ -2086,14 +1957,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
public BeanProperty findPropertyFromPath(String path) {
BeanDescriptor<?> other = this;
while (true) {
String[] split = SplitName.splitBegin(path);
if (split[1] == null) {
return other._findBeanProperty(split[0]);
}
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) other._findBeanProperty(split[0]);
BeanDescriptor<?> targetDesc = assocProp.getTargetDescriptor();
path = split[1];
other = targetDesc;
}
@@ -2114,13 +1983,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return result;
}
String[] splitBegin = SplitName.splitBegin(path);
BeanProperty beanProperty = result.findProperty(splitBegin[0]);
if (beanProperty instanceof BeanPropertyAssoc<?>) {
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProperty;
path = splitBegin[1];
result = assocProp.getTargetDescriptor();
} else {
throw new PersistenceException("Invalid path " + path + " from " + result.getFullName());
}
@@ -2147,7 +2014,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* For bidirectional this is a real property on the bean. For unidirectional
* relationships we have this 'shadow' property which is not externally
* visible.
* </p>
*/
public BeanPropertyAssocOne<?> getUnidirectional() {
BeanDescriptor<?> other = this;
@@ -2168,7 +2034,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* <p>
* If this is false and the Id is null it is assumed that a database auto
* increment feature is being used to populate the id.
* </p>
*/
public boolean isUseIdGenerator() {
return idGenerator != null;
@@ -2195,7 +2060,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* <p>
* If this BeanDescriptor is for a table then this returns the table name
* instead.
* </p>
*/
@Override
@Nonnull
@@ -2315,7 +2179,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return the Id value for the bean with embeddedId beans converted into maps.
* <p>
* The usage is to provide simple id types for JSON processing (for embeddedId's).
* </p>
*/
public Object getIdForJson(Object bean) {
return idBinder.getIdForJson((EntityBean) bean);
@@ -2325,7 +2188,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Convert the idValue assuming embeddedId values are Maps.
* <p>
* The usage is to provide simple id types for JSON processing (for embeddedId's).
* </p>
*/
Object convertIdFromJson(Object idValue) {
return idBinder.convertIdFromJson(idValue);
@@ -2359,7 +2221,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* <p>
* If the bean is not null, the id value is set to the id property of the bean
* after it has been converted to the correct type.
* </p>
*/
public Object convertSetId(Object idValue, EntityBean bean) {
return idBinder.convertSetId(idValue, bean);
@@ -2385,9 +2246,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
public void sort(List<T> list, String sortByClause) {
ElComparator<T> comparator = getElComparator(sortByClause);
list.sort(comparator);
list.sort(getElComparator(sortByClause));
}
public ElComparator<T> getElComparator(String propNameOrSortBy) {
@@ -2398,10 +2257,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Register all the assoc many properties on this bean that are not populated with the load context.
* <p>
* This provides further lazy loading via the load context.
* </p>
*/
public void lazyLoadRegister(String prefix, EntityBeanIntercept ebi, EntityBean bean, LoadContext loadContext) {
// load the List/Set/Map proxy objects (deferred fetching of lists)
BeanPropertyAssocMany<?>[] manys = propertiesMany();
for (BeanPropertyAssocMany<?> many : manys) {
@@ -2420,16 +2277,18 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* define a Reference for the collection and not invoke a query.
*/
public boolean lazyLoadMany(EntityBeanIntercept ebi) {
return lazyLoadMany(ebi, null);
}
public boolean lazyLoadMany(EntityBeanIntercept ebi, LoadBeanContext parent) {
int lazyLoadProperty = ebi.getLazyLoadPropertyIndex();
if (lazyLoadProperty == -1) {
return false;
}
if (inheritInfo != null) {
return descOf(ebi.getOwner().getClass()).lazyLoadMany(ebi, lazyLoadProperty);
return descOf(ebi.getOwner().getClass()).lazyLoadMany(ebi, lazyLoadProperty, parent);
}
return lazyLoadMany(ebi, lazyLoadProperty);
return lazyLoadMany(ebi, lazyLoadProperty, parent);
}
/**
@@ -2442,13 +2301,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Check for lazy loading of many property.
*/
private boolean lazyLoadMany(EntityBeanIntercept ebi, int lazyLoadProperty) {
private boolean lazyLoadMany(EntityBeanIntercept ebi, int lazyLoadProperty, LoadBeanContext loadBeanContext) {
BeanProperty lazyLoadBeanProp = propertiesIndex[lazyLoadProperty];
if (lazyLoadBeanProp instanceof BeanPropertyAssocMany<?>) {
BeanPropertyAssocMany<?> manyProp = (BeanPropertyAssocMany<?>) lazyLoadBeanProp;
manyProp.createReference(ebi.getOwner());
final BeanCollection<?> collection = manyProp.createReference(ebi.getOwner());
ebi.setLoadedLazy();
if (loadBeanContext != null) {
loadBeanContext.register(manyProp.getName(), collection);
}
return true;
}
return false;
@@ -2468,7 +2329,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
@SuppressWarnings("unchecked")
private ElComparator<T> createComparator(String sortByClause) {
SortByClause sortBy = SortByClauseParser.parse(sortByClause);
if (sortBy.size() == 1) {
// simple comparator for a single property
@@ -2477,24 +2337,20 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
// create a compound comparator based on the list of properties
ElComparator<T>[] comparators = new ElComparator[sortBy.size()];
List<SortByClause.Property> sortProps = sortBy.getProperties();
for (int i = 0; i < sortProps.size(); i++) {
SortByClause.Property sortProperty = sortProps.get(i);
comparators[i] = createPropertyComparator(sortProperty);
}
return new ElComparatorCompound<>(comparators);
}
private ElComparator<T> createPropertyComparator(SortByClause.Property sortProp) {
ElPropertyValue elGetValue = getElGetValue(sortProp.getName());
if (elGetValue == null) {
logger.error("Sort property [" + sortProp + "] not found in " + beanType + ". Cannot sort.");
return new ElComparatorNoop<>();
}
if (elGetValue.isAssocMany()) {
logger.error("Sort property [" + sortProp + "] in " + beanType + " is a many-property. Cannot sort.");
return new ElComparatorNoop<>();
@@ -2539,7 +2395,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Similar to ElPropertyValue but also uses foreign key shortcuts.
* <p>
* The foreign key shortcuts means we can avoid unnecessary joins.
* </p>
*/
public ElPropertyDeploy getElPropertyDeploy(String propName) {
ElPropertyDeploy elProp = elDeployCache.get(propName);
@@ -2560,7 +2415,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
ElPropertyValue buildElGetValue(String propName, ElPropertyChainBuilder chain, boolean propertyDeploy) {
if (propertyDeploy && chain != null) {
ElPropertyDeploy fk = elDeployCache.get(propName);
if (fk instanceof BeanFkeyProperty) {
@@ -2568,17 +2422,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return ((BeanFkeyProperty) fk).create(chain.getExpression(), chain.isContainsMany());
}
}
int basePos = propName.indexOf('.');
if (basePos > -1) {
// nested or embedded property
String baseName = propName.substring(0, basePos);
BeanProperty assocProp = _findBeanProperty(baseName);
if (assocProp == null) {
return null;
}
String remainder = propName.substring(basePos + 1);
return assocProp.buildElPropertyValue(propName, remainder, chain, propertyDeploy);
}
@@ -2639,7 +2490,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* <p>
* This searches this BeanDescriptor and then searches further down the
* inheritance tree (not up).
* </p>
*/
@Override
public BeanProperty findProperty(String propName) {
@@ -2666,11 +2516,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Reset the many properties to empty state ready for reloading.
*/
public void resetManyProperties(Object dbBean) {
EntityBean bean = (EntityBean) dbBean;
for (BeanPropertyAssocMany<?> aPropertiesMany : propertiesMany) {
if (aPropertiesMany.isCascadeRefresh()) {
aPropertiesMany.resetMany(bean);
for (BeanPropertyAssocMany<?> many : propertiesMany) {
if (many.isCascadeRefresh()) {
many.resetMany(bean);
}
}
}
@@ -2680,7 +2529,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* <p>
* This means is has no relationships and has readOnly=true in its cache
* options.
* </p>
*/
public boolean isCacheSharableBeans() {
return cacheSharableBeans;
@@ -2781,8 +2629,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* De-register the BeanPersistListener.
*/
public void deregister(BeanPersistListener listener) {
// volatile read...
BeanPersistListener currentListener = persistListener;
if (currentListener != null) {
if (currentListener instanceof ChainedBeanPersistListener) {
@@ -2798,8 +2644,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* De-register the BeanPersistController.
*/
public void deregister(BeanPersistController controller) {
// volatile read...
BeanPersistController currentController = persistController;
if (currentController != null) {
if (currentController instanceof ChainedBeanPersistController) {
@@ -2815,9 +2659,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Register the new BeanPersistController.
*/
public void register(BeanPersistListener newPersistListener) {
if (newPersistListener.isRegisterFor(beanType)) {
// volatile read...
BeanPersistListener currentListener = persistListener;
if (currentListener == null) {
persistListener = newPersistListener;
@@ -2837,18 +2679,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Register the new BeanPersistController.
*/
public void register(BeanPersistController newController) {
if (newController.isRegisterFor(beanType)) {
// volatile read...
BeanPersistController currentController = persistController;
if (currentController == null) {
persistController = newController;
} else {
if (currentController instanceof ChainedBeanPersistController) {
// add it to the existing chain
persistController = ((ChainedBeanPersistController) currentController).register(newController);
} else {
// build new chain of the 2
persistController = new ChainedBeanPersistController(currentController, newController);
}
}
@@ -2901,17 +2739,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Return the dependent tables for a view based entity.
* <p>
* These tables
* </p>
*/
public String[] getDependentTables() {
return dependentTables;
}
/**
* Return the base table. Only properties mapped to the base table are by
* default persisted.
* Return the base table. Only properties mapped to the base table are by default persisted.
*/
@Override
public String getBaseTable() {
@@ -3012,7 +2846,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
@Override
public ExtraJoin extraJoin(String propertyPath) {
ElPropertyValue elGetValue = getElGetValue(propertyPath);
if (elGetValue != null) {
BeanProperty beanProperty = elGetValue.getBeanProperty();
@@ -3150,7 +2983,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* <p>
* This is only used with Identity columns and getGeneratedKeys is not
* supported.
* </p>
*/
public String getSelectLastInsertedId(boolean publish) {
return publish ? selectLastInsertedId : selectLastInsertedIdDraft;
@@ -3198,8 +3030,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
/**
* Return the beans that are embedded. These share the base table with the
* owner bean.
* Return the beans that are embedded. These share the base table with the owner bean.
*/
public BeanPropertyAssocOne<?>[] propertiesEmbedded() {
return propertiesEmbedded;
@@ -3252,12 +3083,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* @param insertMode true if the 'root request' was an insert rather than an update
*/
public boolean isInsertMode(EntityBeanIntercept ebi, boolean insertMode) {
if (ebi.isLoaded()) {
// must be an update as the bean is loaded
return false;
}
if (idProperty.isEmbedded()) {
// not using Id generator so just base on isLoaded()
return !ebi.isLoaded();
@@ -3329,7 +3158,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
public ConcurrencyMode getConcurrencyMode(EntityBeanIntercept ebi) {
if (!hasVersionProperty(ebi)) {
return ConcurrencyMode.NONE;
} else {
@@ -3350,15 +3178,14 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Populate the diff for updates with flattened non-null property values.
*/
public void diff(String prefix, Map<String, ValuePair> map, EntityBean newBean, EntityBean oldBean) {
for (BeanProperty aPropertiesBaseScalar : propertiesBaseScalar) {
aPropertiesBaseScalar.diff(prefix, map, newBean, oldBean);
for (BeanProperty baseScalar : propertiesBaseScalar) {
baseScalar.diff(prefix, map, newBean, oldBean);
}
for (BeanPropertyAssocOne<?> aPropertiesOne : propertiesOne) {
aPropertiesOne.diff(prefix, map, newBean, oldBean);
for (BeanPropertyAssocOne<?> one : propertiesOne) {
one.diff(prefix, map, newBean, oldBean);
}
for (BeanPropertyAssocOne<?> aPropertiesEmbedded : propertiesEmbedded) {
aPropertiesEmbedded.diff(prefix, map, newBean, oldBean);
for (BeanPropertyAssocOne<?> embedded : propertiesEmbedded) {
embedded.diff(prefix, map, newBean, oldBean);
}
}
@@ -3368,10 +3195,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* <p>
* This is primarily used for paging queries to ensure that an order by clause is provided and that the order by
* provides unique ordering of the rows (so that the paging is predicable).
* </p>
*/
public void appendOrderById(SpiQuery<T> query) {
if (idProperty != null && !idProperty.isEmbedded() && !query.order().containsProperty(idProperty.getName())) {
query.order().asc(idProperty.getName());
}
@@ -3409,7 +3234,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Returns ManyToOnes and OneToOnes on the imported owning side.
* <p>
* Excludes OneToOnes on the exported side.
* </p>
*/
public BeanPropertyAssocOne<?>[] propertiesOneImported() {
return propertiesOneImported;
@@ -3483,7 +3307,6 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* no version property exists on the bean.
* <p>
* Note that this DOES NOT find a version property on an embedded bean.
* </p>
*/
public BeanProperty getVersionProperty() {
return versionProperty;
@@ -245,7 +245,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* Run periodic trim of query plans.
*/
public void scheduleBackgroundTrim() {
backgroundExecutor.executePeriodically(this::trimQueryPlans, 60L, TimeUnit.SECONDS);
backgroundExecutor.executePeriodically(this::trimQueryPlans, 117L, 60L, TimeUnit.SECONDS);
}
private void trimQueryPlans() {
@@ -50,4 +50,45 @@ public class DtoMappingRequest {
public QueryPlanMetric createMetric() {
return MetricFactory.get().createQueryPlanMetric(type, label, profileLocation, sql);
}
/**
* Map all DB columns to setters.
*/
DtoReadSet[] mapSetters(DtoMeta meta) {
DtoReadSet[] setterProps = new DtoReadSet[columnMeta.length];
for (int i = 0; i < columnMeta.length; i++) {
setterProps[i] = mapColumn(i, meta);
}
return setterProps;
}
/**
* Map DB columns after constructor to setters.
*/
DtoReadSet[] mapArgPlusSetters(DtoMeta meta, int firstOnes) {
DtoReadSet[] setterProps = new DtoReadSet[columnMeta.length - firstOnes];
int pos = 0;
for (int i = firstOnes; i < columnMeta.length; i++) {
setterProps[pos++] = mapColumn(i, meta);
}
return setterProps;
}
private DtoReadSet mapColumn(int pos, DtoMeta meta) {
String label = columnMeta[pos].getLabel();
DtoReadSet property = meta.findProperty(label);
if (property == null || property.isReadOnly()) {
if (isRelaxedMode()) {
property = DtoReadSetColumnSkip.INSTANCE;
} else {
throw new IllegalStateException(unableToMapColumnMessage(columnMeta[pos], meta));
}
}
return property;
}
private String unableToMapColumnMessage(DtoColumn col, DtoMeta meta) {
return "Unable to map DB column " + col + " to a property with a setter method on " + meta.dtoType()+". Consider query.setRelaxedMode() to skip mapping this column.";
}
}
@@ -72,55 +72,16 @@ class DtoMeta {
}
private DtoQueryPlanConPlus matchMaxArgPlusSetters(DtoMappingRequest request) {
int firstOnes = maxArgConstructor.getArgCount();
DtoColumn[] cols = request.getColumnMeta();
DtoReadSet[] setterProps = new DtoReadSet[cols.length - firstOnes];
int pos = 0;
for (int i = firstOnes; i < cols.length; i++) {
String label = cols[i].getLabel();
DtoReadSet property = findProperty(label);
if (property == null || property.isReadOnly()) {
if (request.isRelaxedMode()) {
property = DtoReadSetColumnSkip.INSTANCE;
} else {
throw new IllegalStateException("Unable to map DB column " + cols[i] + " to a property with a setter method on " + dtoType);
}
}
setterProps[pos++] = property;
}
DtoReadSet[] setterProps = request.mapArgPlusSetters(this, maxArgConstructor.getArgCount());
return new DtoQueryPlanConPlus(request, maxArgConstructor, setterProps);
}
private DtoQueryPlan matchSetters(DtoMappingRequest request) {
DtoColumn[] cols = request.getColumnMeta();
DtoReadSet[] setterProps = new DtoReadSet[cols.length];
for (int i = 0; i < cols.length; i++) {
String label = cols[i].getLabel();
DtoReadSet property = findProperty(label);
if (property == null || property.isReadOnly()) {
if (request.isRelaxedMode()) {
property = DtoReadSetColumnSkip.INSTANCE;
} else {
throw new IllegalStateException("Unable to map DB column " + cols[i] + " to a property with a setter method on " + dtoType);
}
}
setterProps[i] = property;
}
DtoReadSet[] setterProps = request.mapSetters(this);
return new DtoQueryPlanConSetter(request, defaultConstructor, setterProps);
}
private DtoReadSet findProperty(String label) {
DtoReadSet findProperty(String label) {
String upperLabel = label.toUpperCase();
DtoMetaProperty property = propMap.get(upperLabel);
if (property == null && upperLabel.startsWith("IS_")) {
@@ -131,4 +92,8 @@ class DtoMeta {
}
return property;
}
Class<?> dtoType() {
return dtoType;
}
}
@@ -346,8 +346,9 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
@Override
public Query<T> orderBy(String orderBy) {
return query.order(orderBy);
public ExpressionList<T> orderBy(String orderBy) {
query.order(orderBy);
return this;
}
@Override
@@ -145,8 +145,9 @@ public class FilterExpressionList<T> extends DefaultExpressionList<T> {
}
@Override
public Query<T> orderBy(String orderBy) {
return rootQuery.orderBy(orderBy);
public ExpressionList<T> orderBy(String orderByClause) {
this.orderByClause = orderByClause;
return this;
}
@Override
@@ -809,7 +809,7 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
}
@Override
public Query<T> orderBy(String orderBy) {
public ExpressionList<T> orderBy(String orderBy) {
return exprList.orderBy(orderBy);
}
@@ -18,6 +18,8 @@ public class DbExpressionHandlerFactory {
case POSTGRES:
case POSTGRES9:
return new PostgresDbExpression();
case MARIADB:
return new MariaDbExpression();
case MYSQL55:
case MYSQL:
return new MySqlDbExpression();
@@ -0,0 +1,16 @@
package io.ebeaninternal.server.expression.platform;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.expression.Op;
/**
* MariaDB specific handling of platform specific expressions.
*/
class MariaDbExpression extends BasicDbExpression {
@Override
public void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
request.append("(").append(propName).append(" ->> '$.").append(path).append("')");
request.append(operator.bind());
}
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.loadcontext;
import io.ebean.CacheMode;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanLoader;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
@@ -32,7 +33,13 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
// bufferList only required when using query joins (queryFetch)
this.bufferList = (!queryFetch) ? null : new ArrayList<>();
this.currentBuffer = createBuffer(firstBatchSize);
this.cache = (queryProps == null) ? false : queryProps.isCache();
this.cache = (queryProps != null) && queryProps.isCache();
}
@Override
public void register(String manyProperty, BeanCollection<?> collection) {
String path = fullPath + "." + manyProperty;
parent.register(path, collection);
}
/**
@@ -175,15 +182,13 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
@Override
public void loadBean(EntityBeanIntercept ebi) {
// A synchronized (this) is effectively held by EntityBeanIntercept.loadBean()
if (context.desc.lazyLoadMany(ebi)) {
if (context.desc.lazyLoadMany(ebi, context)) {
// lazy load property was a Many
return;
}
if (context.hitCache) {
Set<EntityBeanIntercept> hits = context.desc.cacheBeanLoadAll(list, persistenceContext, ebi.getLazyLoadPropertyIndex(), ebi.getLazyLoadProperty());
list.removeAll(hits);
if (list.isEmpty() || hits.contains(ebi)) {
// successfully hit the L2 cache so don't invoke DB lazy loading
@@ -193,6 +198,7 @@ class DLoadBeanContext extends DLoadBaseContext implements LoadBeanContext {
LoadBeanRequest req = new LoadBeanRequest(this, ebi, context.hitCache);
context.desc.getEbeanServer().loadBean(req);
list.clear();
}
}
@@ -73,7 +73,6 @@ public class DLoadContext implements LoadContext {
* Construct for use with JSON marshalling (doc store).
*/
public DLoadContext(BeanDescriptor<?> rootDescriptor, PersistenceContext persistenceContext) {
this.useDocStore = true;
this.rootDescriptor = rootDescriptor;
this.ebeanServer = rootDescriptor.getEbeanServer();
@@ -100,7 +99,6 @@ public class DLoadContext implements LoadContext {
}
public DLoadContext(OrmQueryRequest<?> request, SpiQuerySecondary secondaryQueries) {
this.tenantId = request.getTenantId();
this.persistenceContext = request.getPersistenceContext();
this.ebeanServer = request.getServer();
@@ -131,7 +129,6 @@ public class DLoadContext implements LoadContext {
// initialise rootBeanContext after origin and relativePath have been set
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, defaultBatchSize, null);
registerSecondaryQueries(secondaryQueries);
}
@@ -153,14 +150,12 @@ public class DLoadContext implements LoadContext {
* Register the +query and +lazy secondary queries with their appropriate LoadBeanContext or LoadManyContext.
*/
private void registerSecondaryQueries(SpiQuerySecondary secondaryQueries) {
this.secQuery = secondaryQueries.getQueryJoins();
if (secQuery != null) {
for (OrmQueryProperties pathProperties : secQuery) {
registerSecondaryQuery(pathProperties);
}
}
List<OrmQueryProperties> lazyJoins = secondaryQueries.getLazyJoins();
if (lazyJoins != null) {
for (OrmQueryProperties lazyJoin : lazyJoins) {
@@ -174,9 +169,7 @@ public class DLoadContext implements LoadContext {
* used to build the appropriate query for +query or +lazy loading.
*/
private void registerSecondaryQuery(OrmQueryProperties props) {
ElPropertyValue elGetValue = rootDescriptor.getElGetValue(props.getPath());
boolean many = elGetValue.getBeanProperty().containsMany();
registerSecondaryNode(many, props);
}
@@ -190,11 +183,9 @@ public class DLoadContext implements LoadContext {
*/
@Override
public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) {
if (secQuery == null) {
return -1;
}
int maxBatch = 0;
for (OrmQueryProperties aSecQuery : secQuery) {
int batchSize = aSecQuery.getQueryFetchBatch();
@@ -211,7 +202,6 @@ public class DLoadContext implements LoadContext {
*/
@Override
public void executeSecondaryQueries(OrmQueryRequest<?> parentRequest, boolean forEach) {
if (secQuery != null) {
for (OrmQueryProperties aSecQuery : secQuery) {
LoadSecondaryQuery load = getLoadSecondaryQuery(aSecQuery.getPath());
@@ -234,7 +224,6 @@ public class DLoadContext implements LoadContext {
}
private ObjectGraphNode createObjectGraphNode(String path) {
if (relativePath != null) {
if (path == null) {
path = relativePath;
@@ -311,7 +300,6 @@ public class DLoadContext implements LoadContext {
}
private void registerSecondaryNode(boolean many, OrmQueryProperties props) {
int batchSize;
if (props.isQueryFetch()) {
batchSize = 100;
@@ -319,7 +307,6 @@ public class DLoadContext implements LoadContext {
int lazyJoinBatch = props.getLazyFetchBatch();
batchSize = lazyJoinBatch > 0 ? lazyJoinBatch : defaultBatchSize;
}
String path = props.getPath();
if (many) {
manyMap.put(path, createManyContext(path, batchSize, props));
@@ -336,7 +323,6 @@ public class DLoadContext implements LoadContext {
}
private DLoadManyContext createManyContext(String path, int batchSize, OrmQueryProperties queryProps) {
BeanPropertyAssocMany<?> p = (BeanPropertyAssocMany<?>) getBeanProperty(rootDescriptor, path);
return new DLoadManyContext(this, p, path, batchSize, queryProps);
}
@@ -13,6 +13,9 @@ import io.ebeaninternal.server.persist.dmlbind.BindableList;
import java.sql.SQLException;
import static io.ebean.annotation.Platform.MARIADB;
import static io.ebean.annotation.Platform.MYSQL;
/**
* Meta data for insert handler. The meta data is for a particular bean type. It
* is considered immutable and is thread safe.
@@ -166,12 +169,7 @@ final class InsertMeta {
request.append("insert into ").append(table);
if (nullId && noColumnsForInsert(draftTable)) {
if (this.platform.base() == Platform.MYSQL) {
request.append(" values (default)");
} else {
request.append(" default values");
}
return request.toString();
return request.append(defaultValues()).toString();
}
request.append(" (");
@@ -199,6 +197,10 @@ final class InsertMeta {
return request.toString();
}
private String defaultValues() {
return platform.base() == MYSQL || platform.base() == MARIADB ? " values (default)" : " default values";
}
/**
* Return true if the insert actually contains no columns.
*/
@@ -608,12 +608,10 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
}
QueryIterator<T> readIterate(int bufferSize, OrmQueryRequest<T> request) {
if (bufferSize > 0) {
return new CQueryIteratorWithBuffer<>(this, request, bufferSize);
} else {
if (bufferSize == 1) {
return new CQueryIteratorSimple<>(this, request);
} else {
return new CQueryIteratorWithBuffer<>(this, request, bufferSize);
}
}
@@ -201,26 +201,20 @@ public class CQueryEngine {
* when you have finished).
*/
public <T> QueryIterator<T> findIterate(OrmQueryRequest<T> request) {
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
if (defaultFetchSizeFindEach > 0) {
request.setDefaultFetchBuffer(defaultFetchSizeFindEach);
}
if (request.isIterateSingleContext()) {
// expected relatively small number of results, single persistence context
cquery.prepareBindExecuteQuery();
} else if (!cquery.prepareBindExecuteQueryForwardOnly(forwardOnlyHintOnFindIterate)) {
if (!cquery.prepareBindExecuteQueryForwardOnly(forwardOnlyHintOnFindIterate)) {
// query has been cancelled already
logger.trace("Future fetch already cancelled");
return null;
}
if (request.isLogSql()) {
logSql(cquery);
}
// first check batch sizes set on query joins
int iterateBufferSize = request.getSecondaryQueriesMinBatchSize(defaultSecondaryQueryBatchSize);
if (iterateBufferSize < 1) {
@@ -228,20 +222,19 @@ public class CQueryEngine {
int queryBatch = request.getQuery().getLazyLoadBatchSize();
if (queryBatch > 0) {
iterateBufferSize = queryBatch;
} else {
iterateBufferSize = 100;
}
}
QueryIterator<T> readIterate = cquery.readIterate(iterateBufferSize, request);
if (request.isLogSummary()) {
logFindManySummary(cquery);
}
if (request.isAuditReads()) {
// indicates we need to audit as the iterator progresses
cquery.auditFindIterate();
}
return readIterate;
} catch (SQLException e) {
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.core.OrmQueryRequest;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.NoSuchElementException;
/**
* A QueryIterator that uses a buffer to execute secondary queries periodically.
@@ -17,6 +18,7 @@ class CQueryIteratorWithBuffer<T> implements QueryIterator<T> {
private final OrmQueryRequest<T> request;
private final ArrayList<T> buffer;
private boolean closed;
private boolean moreToLoad = true;
CQueryIteratorWithBuffer(CQuery<T> cquery, OrmQueryRequest<T> request, int bufferSize) {
@@ -29,6 +31,7 @@ class CQueryIteratorWithBuffer<T> implements QueryIterator<T> {
@Override
@SuppressWarnings("unchecked")
public boolean hasNext() {
boolean ret = false;
try {
if (buffer.isEmpty() && moreToLoad) {
// load buffer
@@ -44,23 +47,33 @@ class CQueryIteratorWithBuffer<T> implements QueryIterator<T> {
}
request.executeSecondaryQueries(true);
}
return !buffer.isEmpty();
ret = !buffer.isEmpty();
return ret;
} catch (SQLException e) {
throw cquery.createPersistenceException(e);
} finally {
if (!ret) {
close();
}
}
}
@Override
public T next() {
if (buffer.isEmpty()) {
throw new NoSuchElementException();
}
return buffer.remove(0);
}
@Override
public void close() {
cquery.updateExecutionStatisticsIterator();
cquery.close();
request.endTransIfRequired();
if (!closed) {
closed = true;
cquery.updateExecutionStatisticsIterator();
cquery.close();
request.endTransIfRequired();
}
}
@Override
@@ -133,7 +133,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
SpiQuery<T> query = request.getQuery();
if (request.isBeanCachePut()) {
if (request.isBeanCachePutMany()) {
// load the individual beans into the bean cache
BeanDescriptor<T> descriptor = request.getBeanDescriptor();
Collection<T> c = result.getActualDetails();
@@ -299,7 +299,7 @@ public final class SqlTreeBuilder {
Collection<PropertyJoin> includes = manyWhereJoins.getPropertyJoins();
for (PropertyJoin joinProp : includes) {
STreePropertyAssoc beanProperty = (STreePropertyAssoc) desc.findPropertyFromPath(joinProp.getProperty());
SqlTreeNodeManyWhereJoin nodeJoin = new SqlTreeNodeManyWhereJoin(joinProp.getProperty(), beanProperty, joinProp.getSqlJoinType());
SqlTreeNodeManyWhereJoin nodeJoin = new SqlTreeNodeManyWhereJoin(joinProp.getProperty(), beanProperty, joinProp.getSqlJoinType(), temporalMode);
myJoinList.add(nodeJoin);
}
if (manyWhereJoins.isFormulaWithJoin()) {
@@ -107,7 +107,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
private SqlTreeNodeBean(String prefix, STreePropertyAssoc beanProp, STreeType desc, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId, STreePropertyAssocMany lazyLoadParent,
SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
this.lazyLoadParent = lazyLoadParent;
this.lazyLoadParentIdBinder = (lazyLoadParent == null) ? null : lazyLoadParent.getIdBinder();
this.prefix = prefix;
@@ -116,21 +115,16 @@ class SqlTreeNodeBean implements SqlTreeNode {
this.idBinder = desc.getIdBinder();
this.temporalMode = temporalMode;
this.temporalVersions = temporalMode == SpiQuery.TemporalMode.VERSIONS;
this.nodeBeanProp = beanProp;
this.extraWhere = (beanProp == null) ? null : beanProp.getExtraWhere();
this.aggregation = props.isAggregation();
boolean aggregationRoot = props.isAggregationRoot();
// the bean has an Id property and we want to use it
this.readId = !aggregationRoot && withId && desc.hasId();
this.disableLazyLoad = disableLazyLoad || !readId || desc.isRawSqlBased() || temporalVersions;
this.partialObject = props.isPartialObject();
this.properties = props.getProps();
this.children = myChildren == null ? NO_CHILDREN : myChildren.toArray(new SqlTreeNode[0]);
pathMap = createPathMap(prefix, desc);
}
@@ -163,7 +157,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
private Map<String, String> createPathMap(String prefix, STreeType desc) {
HashMap<String, String> m = new HashMap<>();
for (STreePropertyAssocMany many : desc.propsMany()) {
String name = many.getName();
@@ -206,13 +199,11 @@ class SqlTreeNodeBean implements SqlTreeNode {
@Override
@SuppressWarnings("unchecked")
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
// read the sys period lower and upper bounds
// these are always the first 2 columns in the resultSet
Timestamp start = ctx.getDataReader().getTimestamp();
Timestamp end = ctx.getDataReader().getTimestamp();
T bean = (T) load(ctx, null, null);
return new Version<>(bean, start, end);
}
@@ -326,7 +317,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
private void readIdNullBean() {
// bean must be null...
localBean = null;
// ... but there may exist as reference bean in parent which has to be marked as deleted.
if (parentBean != null && nodeBeanProp instanceof STreePropertyAssocOne) {
contextBean = ((STreePropertyAssocOne)nodeBeanProp).getValueAsEntityBean(parentBean);
@@ -355,7 +345,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
localBean = contextBean;
lazyLoadMany = true;
}
// recursively continue reading...
for (SqlTreeNode aChildren : children) {
// read each child... and let them set their
@@ -476,7 +465,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
*/
@Override
public EntityBean load(DbReadContext ctx, EntityBean parentBean, EntityBean contextParent) throws SQLException {
Load load = (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean);
load.initialise();
if (load.isLazyLoadManyRoot()) {
@@ -489,7 +477,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
@Override
public void appendGroupBy(DbSqlContext ctx, boolean subQuery) {
ctx.pushJoin(prefix);
ctx.pushTableAlias(prefix);
if (lazyLoadParent != null) {
@@ -525,19 +512,15 @@ class SqlTreeNodeBean implements SqlTreeNode {
*/
@Override
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
ctx.pushJoin(prefix);
ctx.pushTableAlias(prefix);
if (temporalVersions) {
// select sys_period lower and upper columns
ctx.appendHistorySysPeriod();
}
if (lazyLoadParent != null) {
lazyLoadParent.addSelectExported(ctx, prefix);
}
if (readId) {
if (!subQuery && inheritInfo != null) {
ctx.appendColumn(inheritInfo.getDiscriminatorColumn());
@@ -551,7 +534,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
// values back to this localBean
aChildren.appendSelect(ctx, subQuery);
}
ctx.popTableAlias();
ctx.popJoin();
}
@@ -586,7 +568,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
@Override
public void appendWhere(DbSqlContext ctx) {
// Only apply inheritance to root node as any join will already have the inheritance join include - see TableJoin
if (inheritInfo != null && nodeBeanProp == null) {
if (!inheritInfo.isRoot()) {
@@ -598,9 +579,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
ctx.append(inheritInfo.getWhere());
}
}
appendExtraWhere(ctx);
for (SqlTreeNode aChildren : children) {
// recursively add to the where clause any
// fixed predicates (extraWhere etc)
@@ -651,7 +630,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
@Override
public void addSoftDeletePredicate(SpiQuery<?> query) {
if (desc.isSoftDelete()) {
query.addSoftDeletePredicate(desc.getSoftDeletePredicate(baseTableAlias));
}
@@ -688,17 +666,14 @@ class SqlTreeNodeBean implements SqlTreeNode {
* table if this is a ManyToMany node.
*/
SqlJoinType appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) {
SqlJoinType sqlJoinType = appendFromAsJoin(ctx, joinType);
if (temporalMode != SpiQuery.TemporalMode.SOFT_DELETED && desc.isSoftDelete()) {
// add the soft delete predicate to the join clause
if (desc.isSoftDelete() && temporalMode != SpiQuery.TemporalMode.SOFT_DELETED) {
ctx.append("and ").append(desc.getSoftDeletePredicate(ctx.getTableAlias(prefix))).append(" ");
}
return sqlJoinType;
}
SqlJoinType appendFromAsJoin(DbSqlContext ctx, SqlJoinType joinType) {
if (nodeBeanProp instanceof STreePropertyAssocMany) {
STreePropertyAssocMany manyProp = (STreePropertyAssocMany) nodeBeanProp;
if (manyProp.hasJoinTable()) {
@@ -714,11 +689,9 @@ class SqlTreeNodeBean implements SqlTreeNode {
if (!manyProp.isExcludedFromHistory()) {
intersectionAsOfTableAlias = true;
}
return nodeBeanProp.addJoin(joinType, alias2, alias, ctx);
}
}
return nodeBeanProp.addJoin(joinType, prefix, ctx);
}
@@ -735,14 +708,12 @@ class SqlTreeNodeBean implements SqlTreeNode {
if (queryMode.isLoadContextBean()) {
return true;
}
// reload if contextBean is partial object
return !contextBean._ebean_getIntercept().isFullyLoadedBean();
}
@Override
public boolean hasMany() {
for (SqlTreeNode child : children) {
if (child.hasMany()) {
return true;
@@ -23,16 +23,21 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
private final STreePropertyAssoc nodeBeanProp;
private final STreeType target;
/**
* The many where join which is either INNER or OUTER.
*/
private final SqlJoinType manyJoinType;
SqlTreeNodeManyWhereJoin(String prefix, STreePropertyAssoc prop, SqlJoinType manyJoinType) {
private final boolean softDelete;
SqlTreeNodeManyWhereJoin(String prefix, STreePropertyAssoc prop, SqlJoinType manyJoinType, SpiQuery.TemporalMode temporalMode) {
this.nodeBeanProp = prop;
this.prefix = prefix;
this.manyJoinType = manyJoinType;
this.target = nodeBeanProp.target();
this.softDelete = (temporalMode != SpiQuery.TemporalMode.SOFT_DELETED && target.isSoftDelete());
String[] split = SplitName.split(prefix);
this.parentPrefix = split[0];
}
@@ -94,12 +99,16 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
if (nodeBeanProp instanceof STreePropertyAssocOne) {
nodeBeanProp.addJoin(joinType, parentAlias, alias, ctx);
if (softDelete) {
ctx.append("and ").append(target.getSoftDeletePredicate(alias)).append(" ");
}
} else {
STreePropertyAssocMany manyProp = (STreePropertyAssocMany) nodeBeanProp;
if (!manyProp.hasJoinTable()) {
manyProp.addJoin(joinType, parentAlias, alias, ctx);
if (softDelete) {
ctx.append("and ").append(target.getSoftDeletePredicate(alias)).append(" ");
}
} else {
String alias2 = alias + "z_";
@@ -1715,8 +1715,10 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public void setManualId(boolean manualId) {
this.manualId = manualId;
public void setManualId() {
if (detail != null && detail.hasSelectClause()) {
this.manualId = true;
}
}
/**
@@ -34,18 +34,62 @@ public final class DefaultPersistenceContext implements PersistenceContext {
private final Monitor monitor = new Monitor();
private int putCount;
/**
* Create a new PersistenceContext.
*/
public DefaultPersistenceContext() {
}
/**
* Create as a shallow copy with initial or types that have not been added to.
*/
private DefaultPersistenceContext(DefaultPersistenceContext parent, boolean initial) {
for (Map.Entry<Class<?>, ClassContext> entry : parent.typeCache.entrySet()) {
typeCache.put(entry.getKey(), entry.getValue().copy(initial));
}
}
/**
* Return the initial shallow copy with each ClassContext noting it's initialSize (to detect additions).
*/
@Override
public PersistenceContext forIterate() {
return new DefaultPersistenceContext(this, true);
}
/**
* Return a shallow copy including each ClassContext that has had no additions (still at initialSize).
*/
@Override
public PersistenceContext forIterateReset() {
return new DefaultPersistenceContext(this, false);
}
public boolean resetLimit() {
synchronized (monitor) {
if (putCount < 100) {
return false;
}
putCount = 0;
for (ClassContext value : typeCache.values()) {
if (value.resetLimit()) {
return true;
}
}
// checking after another 100 puts
return false;
}
}
/**
* Set an object into the PersistenceContext.
*/
@Override
public void put(Class<?> rootType, Object id, Object bean) {
synchronized (monitor) {
putCount++;
getClassContext(rootType).put(id, bean);
}
}
@@ -53,6 +97,7 @@ public final class DefaultPersistenceContext implements PersistenceContext {
@Override
public Object putIfAbsent(Class<?> rootType, Object id, Object bean) {
synchronized (monitor) {
putCount++;
return getClassContext(rootType).putIfAbsent(id, bean);
}
}
@@ -133,7 +178,6 @@ public final class DefaultPersistenceContext implements PersistenceContext {
}
private ClassContext getClassContext(Class<?> rootType) {
return typeCache.computeIfAbsent(rootType, k -> new ClassContext());
}
@@ -143,29 +187,72 @@ public final class DefaultPersistenceContext implements PersistenceContext {
private Set<Object> deleteSet;
private int initialSize;
private ClassContext parent;
private ClassContext() {
}
/**
* Create as a shallow copy.
*/
private ClassContext(ClassContext source, boolean initial) {
if (initial || source.isTransfer()) {
parent = source.transferParent();
initialSize = parent.size();
if (source.deleteSet != null) {
deleteSet = new HashSet<>(source.deleteSet);
}
}
}
/**
* True if this should be transferred to a new iterator persistence context.
*/
private boolean isTransfer() {
// map not added to and has some original/parent beans
return map.isEmpty() && initialSize > 0;
}
private ClassContext transferParent() {
return (parent != null) ? parent : this;
}
/**
* Return a shallow copy if initial copy or it has not grown (still at initialSize).
*/
private ClassContext copy(boolean initial) {
return new ClassContext(this, initial);
}
/**
* Return true if grown above the reset limit size of 1000.
*/
private boolean resetLimit() {
return map.size() > 1000;
}
@Override
public String toString() {
return "size:" + map.size();
}
private Object get(Object id) {
Object bean = (parent == null) ? null : parent.get(id);
return bean != null ? bean : map.get(id);
}
private WithOption getWithOption(Object id) {
if (deleteSet != null && deleteSet.contains(id)) {
return WithOption.DELETED;
}
Object bean = map.get(id);
Object bean = get(id);
return (bean == null) ? null : new WithOption(bean);
}
private Object get(Object id) {
return map.get(id);
}
private Object putIfAbsent(Object id, Object bean) {
Object existingValue = map.get(id);
Object existingValue = get(id);
if (existingValue != null) {
// it is not absent
return existingValue;
@@ -180,7 +267,7 @@ public final class DefaultPersistenceContext implements PersistenceContext {
}
private int size() {
return map.size();
return map.size() + initialSize;
}
private void clear() {
@@ -140,6 +140,11 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
return false;
}
@Override
public boolean isSkipCacheExplicit() {
return false;
}
@Override
public void setSkipCache(boolean skipCache) {
}
@@ -293,6 +293,11 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
return sb.toString();
}
@Override
public boolean isSkipCacheExplicit() {
return (skipCache != null && !skipCache);
}
@Override
public boolean isSkipCache() {
if (skipCache != null) return skipCache;
@@ -200,6 +200,11 @@ class NoTransaction implements SpiTransaction {
public void setSkipCache(boolean skipCache) {
}
@Override
public boolean isSkipCacheExplicit() {
return false;
}
@Override
public boolean isSkipCache() {
return false;
@@ -191,6 +191,7 @@ public class TransactionManager implements SpiTransactionManager {
this.txnMain = metricFactory.createTimedMetric("txn.main");
this.txnReadOnly = metricFactory.createTimedMetric("txn.readonly");
this.txnNamed = metricFactory.createTimedMetricMap("txn.named.");
// Add gauges for db pool size
scopeManager.register(this);
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4
View File
@@ -206,6 +206,10 @@ public abstract class BaseTestCase {
return Platform.MYSQL == platform();
}
public boolean isMariaDB() {
return Platform.MARIADB == platform();
}
public boolean isHana() {
return Platform.HANA == platform();
}
@@ -37,7 +37,7 @@ public class ConditionalTestRunner extends BlockJUnit4ClassRunner {
}
private boolean platformMath(Platform[] platforms) {
Platform basePlatform = DB.getDefault().getPluginApi().getDatabasePlatform().getPlatform().base();
Platform basePlatform = DB.getDefault().getPlatform().base();
for (Platform platform : platforms) {
if (platform.equals(basePlatform)) {
return true;
@@ -140,6 +140,32 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
}
@Test
public void asDto_withoutSelectClause() {
ResetBasicData.reset();
LoggedSqlCollector.start();
DtoQuery<ContactDto2> query = DB.find(Contact.class)
.where().isNotNull("email").order().asc("lastName")
.asDto(ContactDto2.class)
.setRelaxedMode();
List<ContactDto2> dtos = query.findList();
assertThat(dtos).isNotEmpty();
for (ContactDto2 dto : dtos) {
assertThat(dto.getEmail()).isNotNull();
assertThat(dto.getFirstName()).isNotNull();
assertThat(dto.getLastName()).isNotNull();
assertThat(dto.getId()).isGreaterThan(0);
}
List<String> sql = LoggedSqlCollector.stop();
assertSql(sql.get(0)).contains("select t0.id, t0.first_name, t0.last_name");
}
@Test
public void asDto_withoutExplicitId() {
@@ -326,6 +352,46 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
}
}
public static class ContactDto2 {
int id;
String firstName;
String lastName;
String email;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
public static class ContactDto {
String email;
+3 -3
View File
@@ -168,7 +168,7 @@ public class UpdateQueryTest extends BaseTestCase {
assertSql(query).contains("update o_customer cust set status=?, updtime=? where id > ?");
}
@IgnorePlatform(Platform.MYSQL)
@IgnorePlatform({Platform.MYSQL, Platform.MARIADB})
@Test
public void withJoin() {
@@ -214,7 +214,7 @@ public class UpdateQueryTest extends BaseTestCase {
assertSql(sql.get(0)).contains("update o_customer set status=? where id in (select t0.id from o_customer t0 left join o_address t1 on t1.id = t0.billing_address_id where t1.country_code = ? and t0.id > ? limit 100)");
}
@ForPlatform({Platform.H2, Platform.POSTGRES, Platform.MYSQL})
@ForPlatform({Platform.H2, Platform.POSTGRES, Platform.MYSQL, Platform.MARIADB})
@Test
public void simpleWithLimit() {
@@ -230,7 +230,7 @@ public class UpdateQueryTest extends BaseTestCase {
.update();
final List<String> sql = LoggedSqlCollector.stop();
if (isMySql() || isH2()) {
if (isMySql() || isH2() || isMariaDB()) {
assertSql(sql.get(0)).contains("update o_customer set status=? where id > ? limit 100");
} else {
assertSql(sql.get(0)).contains("update o_customer set status=? where id in (select t0.id from o_customer t0 where t0.id > ? limit 100)");
@@ -1,7 +1,8 @@
package io.ebean.bean;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.DB;
import org.tests.compositekeys.db.AuditInfo;
import org.tests.model.basic.Customer;
import org.tests.model.basic.EBasic;
import org.tests.model.basic.ResetBasicData;
@@ -19,12 +20,11 @@ import static org.junit.Assert.assertTrue;
public class EntityBeanInterceptTest extends BaseTestCase {
@Test
public void testHasDirtyProperty() throws Exception {
public void testHasDirtyProperty() {
ResetBasicData.reset();
List<Customer> list = Ebean.find(Customer.class).findList();
List<Customer> list = DB.find(Customer.class).findList();
Set<String> propertyNames = new HashSet<>();
propertyNames.add("name");
@@ -32,7 +32,7 @@ public class EntityBeanInterceptTest extends BaseTestCase {
Customer customer = list.get(0);
EntityBeanIntercept ebi = ((EntityBean) customer)._ebean_getIntercept();
EntityBeanIntercept ebi = ebi(customer);
assertFalse(ebi.hasDirtyProperty(propertyNames));
@@ -48,7 +48,7 @@ public class EntityBeanInterceptTest extends BaseTestCase {
public void isPartial_when_new() {
EBasic basic = new EBasic();
EntityBeanIntercept ebi = ((EntityBean) basic)._ebean_getIntercept();
EntityBeanIntercept ebi = ebi(basic);
assertThat(ebi.isPartial()).isTrue();
}
@@ -58,7 +58,7 @@ public class EntityBeanInterceptTest extends BaseTestCase {
EBasic basic = new EBasic();
basic.setId(42);
basic.setName("some");
EntityBeanIntercept ebi = ((EntityBean) basic)._ebean_getIntercept();
EntityBeanIntercept ebi = ebi(basic);
assertThat(ebi.isPartial()).isTrue();
}
@@ -72,8 +72,64 @@ public class EntityBeanInterceptTest extends BaseTestCase {
basic.setSomeDate(null);
basic.setStatus(EBasic.Status.ACTIVE);
EntityBeanIntercept ebi = ((EntityBean) basic)._ebean_getIntercept();
EntityBeanIntercept ebi = ebi(basic);
assertThat(ebi.isPartial()).isFalse();
}
@Test
public void isEmbeddedNewOrDirty() {
AuditInfo auditInfo = new AuditInfo();
EntityBeanIntercept ebi = ebi(auditInfo);
assertTrue(ebi.isNew());
assertTrue(ebi.isEmbeddedNewOrDirty(auditInfo));
auditInfo.setUpdatedBy("initial");
ebi.setLoaded();
assertTrue(ebi.isLoaded());
assertFalse(ebi.isEmbeddedNewOrDirty(auditInfo));
auditInfo.setUpdatedBy("nowDirty");
assertTrue(ebi.isDirty());
assertTrue(ebi.isEmbeddedNewOrDirty(auditInfo));
assertFalse(ebi.isEmbeddedNewOrDirty(null));
}
@Test
public void setEmbeddedLoaded() {
AuditInfo auditInfo = new AuditInfo();
EntityBeanIntercept ebi = ebi(auditInfo);
assertFalse(ebi.isLoaded());
ebi.setEmbeddedLoaded(auditInfo);
assertTrue(ebi.isLoaded());
}
@Test
public void initialisedMany() {
Customer customer = new Customer();
EntityBeanIntercept ebi = ebi(customer);
final int contactsPos = findProperty("contacts", ebi);
assertFalse(ebi.isLoadedProperty(contactsPos));
ebi.initialisedMany(contactsPos);
assertTrue(ebi.isLoadedProperty(contactsPos));
}
private int findProperty(String name, EntityBeanIntercept eb) {
final String[] names = eb.getOwner()._ebean_getPropertyNames();
for (int i = 0; i < names.length; i++) {
if (names[i].equals(name)) {
return i;
}
}
throw new RuntimeException("property not found");
}
@SuppressWarnings("unchecked")
private EntityBeanIntercept ebi(Object bean) {
return ((EntityBean)bean)._ebean_getIntercept();
}
}
@@ -1,5 +1,8 @@
package io.ebean.config.dbplatform;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.Platform;
import io.ebean.config.MatchingNamingConvention;
import io.ebean.config.PlatformConfig;
@@ -9,9 +12,45 @@ import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
public class DatabasePlatformTest {
public class DatabasePlatformTest extends BaseTestCase {
@Test
@ForPlatform(Platform.H2)
public void h2_platform() {
final Platform platform = DB.getDefault().getPlatform();
assertThat(platform).isSameAs(Platform.H2);
}
@Test
@ForPlatform(Platform.POSTGRES)
public void postgres_platform() {
final Platform platform = DB.getDefault().getPlatform().base();
assertThat(platform).isSameAs(Platform.POSTGRES);
}
@Test
@ForPlatform(Platform.MYSQL)
public void mysql_platform() {
final Platform platform = DB.getDefault().getPlatform().base();
assertThat(platform).isSameAs(Platform.MYSQL);
}
@Test
@ForPlatform(Platform.MARIADB)
public void mariadb_platform() {
final Platform platform = DB.getDefault().getPlatform().base();
assertThat(platform).isSameAs(Platform.MARIADB);
}
@Test
@ForPlatform(Platform.SQLSERVER)
public void sqlserver_platform() {
final Platform platform = DB.getDefault().getPlatform().base();
assertThat(platform).isSameAs(Platform.SQLSERVER);
}
@Test
public void convertQuotedIdentifiers_when_allQuotedIdentifier_sqlServer() {
@@ -30,10 +30,10 @@ import io.ebean.Update;
import io.ebean.UpdateQuery;
import io.ebean.ValuePair;
import io.ebean.Version;
import io.ebean.annotation.Platform;
import io.ebean.annotation.TxIsolation;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
@@ -137,6 +137,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
@Override
public Platform getPlatform() {
return Platform.GENERIC;
}
@Override
public SpiServer getPluginApi() {
return null;
@@ -24,7 +24,7 @@ public class DbMigrationTest extends BaseTestCase {
server().script().runScript(scriptName, content, useAutoCommit);
}
@IgnorePlatform({Platform.ORACLE, Platform.NUODB})
@IgnorePlatform({Platform.ORACLE, Platform.NUODB, Platform.MARIADB})
@Test
public void testRunMigration() throws IOException {
// first clean up previously created objects
@@ -20,12 +20,12 @@ import static org.junit.Assert.assertTrue;
public class PlatformDdl_AlterColumnTest {
private PlatformDdl h2Ddl = PlatformDdlBuilder.create(new H2Platform());
private PlatformDdl pgDdl = PlatformDdlBuilder.create(new PostgresPlatform());
private PlatformDdl mysqlDdl = PlatformDdlBuilder.create(new MySqlPlatform());
private PlatformDdl oraDdl = PlatformDdlBuilder.create(new OraclePlatform());
private PlatformDdl sqlServerDdl = PlatformDdlBuilder.create(new SqlServer17Platform());
private PlatformDdl hanaDdl = PlatformDdlBuilder.create(new HanaPlatform());
private final PlatformDdl h2Ddl = PlatformDdlBuilder.create(new H2Platform());
private final PlatformDdl pgDdl = PlatformDdlBuilder.create(new PostgresPlatform());
private final PlatformDdl mysqlDdl = PlatformDdlBuilder.create(new MySqlPlatform());
private final PlatformDdl oraDdl = PlatformDdlBuilder.create(new OraclePlatform());
private final PlatformDdl sqlServerDdl = PlatformDdlBuilder.create(new SqlServer17Platform());
private final PlatformDdl hanaDdl = PlatformDdlBuilder.create(new HanaPlatform());
{
ServerConfig serverConfig = Ebean.getDefaultServer().getPluginApi().getServerConfig();
@@ -122,7 +122,9 @@ public class PlatformDdl_AlterColumnTest {
assertEquals("alter table mytab alter column acol varchar(20)", sql);
sql = pgDdl.alterColumnType("mytab", "acol", "varchar(20)");
assertEquals("alter table mytab alter column acol type varchar(20)", sql);
assertEquals("alter table mytab alter column acol type varchar(20) using acol::varchar(20)", sql);
sql = pgDdl.alterColumnType("mytab", "acol", "bigint");
assertEquals("alter table mytab alter column acol type bigint using acol::bigint", sql);
sql = oraDdl.alterColumnType("mytab", "acol", "varchar(20)");
assertEquals("alter table mytab modify acol varchar2(20)", sql);
@@ -154,6 +154,16 @@ public class ModelContainerTest {
container.applyChange(dropHistoryTable);
}
@Test
public void apply_renameColumn() {
ModelContainer container = new ModelContainer();
container.apply(mig("6.0__renameColumn.model.xml"), ver("6.0"));
final MTable table = container.getTable("document");
assertThat(table.getColumn("title")).isNotNull();
assertThat(table.getColumn("short_title")).isNull();
}
@Test
public void getSchemas() {
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServerFactory;
import io.ebean.config.ServerConfig;
import io.ebean.DatabaseFactory;
import io.ebean.config.DatabaseConfig;
import io.ebean.event.AbstractBeanPersistListener;
import io.ebean.event.BeanPersistAdapter;
import io.ebean.event.BeanPersistListener;
@@ -14,9 +14,9 @@ import static org.junit.Assert.*;
public class BeanDescriptor_registerTest {
@Test
public void testRegisterDeregister() throws Exception {
public void testRegisterDeregister() {
ServerConfig config = new ServerConfig();
DatabaseConfig config = new DatabaseConfig();
config.setName("h2other");
config.loadFromProperties();
@@ -25,7 +25,7 @@ public class BeanDescriptor_registerTest {
config.setDefaultServer(false);
config.getClasses().add(EBasic.class);
SpiEbeanServer ebeanServer = (SpiEbeanServer) EbeanServerFactory.create(config);
SpiEbeanServer ebeanServer = (SpiEbeanServer)DatabaseFactory.create(config);
BeanDescriptor<EBasic> desc = ebeanServer.getBeanDescriptor(EBasic.class);
persistListenerRegistrationTests(desc);
@@ -308,12 +308,19 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
}
@Test
public void equals_when_manualId() {
public void equals_when_manualId_andSelectClause() {
DefaultOrmQuery<Customer> q1 = query().select("name");
q1.setManualId();
assertDifferent(q1, query().select("name"));
}
@Test
public void equals_when_manualId_andNoSelectClause() {
DefaultOrmQuery<Customer> q1 = query();
q1.setManualId(true);
q1.setManualId();
assertDifferent(q1, query());
assertSame(q1, query());
}
private CQueryPlanKey planKey(ExpressionList<Customer> id) {
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.rawsql;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.DB;
import io.ebean.Query;
import io.ebean.RawSql;
import io.ebean.RawSqlBuilder;
@@ -52,7 +52,7 @@ public class TestRawSqlParsing extends BaseTestCase {
.parse(sql)
.create();
List<Customer> list = Ebean.createQuery(Customer.class)
List<Customer> list = DB.find(Customer.class)
.setRawSql(rawSql)
.setParameter("name", "Rob")
.findList();
@@ -67,7 +67,7 @@ public class TestRawSqlParsing extends BaseTestCase {
RawSql sql = RawSqlBuilder.parse("SELECT id, name FROM o_customer ${where}").create();
List<Customer> customers = Ebean.createQuery(Customer.class)
List<Customer> customers = DB.find(Customer.class)
.setRawSql(sql)
.where().gt("id", 1)
.findList();
@@ -91,7 +91,7 @@ public class TestRawSqlParsing extends BaseTestCase {
RawSql rawSql = RawSqlBuilder.parse(sql).create();
Query<Customer> query = Ebean.createQuery(Customer.class)
Query<Customer> query = DB.find(Customer.class)
.setRawSql(rawSql)
.setFirstRow(1)
.setMaxRows(5);
@@ -103,6 +103,70 @@ public class TestRawSqlParsing extends BaseTestCase {
}
@ForPlatform({Platform.H2, Platform.POSTGRES})
@Test
public void testUnion_explicitWhere() {
ResetBasicData.reset();
String sql =
"select id, name from (" +
" select id, name from o_customer where name <= 'f' " +
" union all " +
" select id, name from o_customer where name > 'f' " +
") all_split ${where}";
RawSql rawSql = RawSqlBuilder.parse(sql).create();
Query<Customer> query = DB.find(Customer.class)
.setRawSql(rawSql)
.setFirstRow(1)
.setMaxRows(5);
query.findList();
assertThat(sqlOf(query)).contains(") all_split limit 5 offset 1");
Query<Customer> query1 = DB.find(Customer.class)
.setRawSql(rawSql)
.where().gt("id", 2)
.setFirstRow(1)
.setMaxRows(5)
.query();
query1.findList();
assertThat(sqlOf(query1)).contains("all_split where id > ? limit 5 offset 1");
}
@ForPlatform({Platform.H2, Platform.POSTGRES})
@Test
public void testUnion_explicitAndhere() {
ResetBasicData.reset();
String sql =
"select id, name from (" +
" select id, name from o_customer where name <= 'f' " +
" union all " +
" select id, name from o_customer where name > 'f' " +
") all_split where 1=1 ${andWhere}";
RawSql rawSql = RawSqlBuilder.parse(sql).create();
Query<Customer> query = DB.find(Customer.class)
.setRawSql(rawSql)
.setFirstRow(1)
.setMaxRows(5);
query.findList();
assertThat(sqlOf(query)).contains(") all_split where 1=1 limit 5 offset 1");
Query<Customer> query1 = DB.find(Customer.class)
.setRawSql(rawSql)
.where().gt("id", 2)
.setFirstRow(1)
.setMaxRows(5)
.query();
query1.findList();
assertThat(sqlOf(query1)).contains("all_split where 1=1 and id > ? limit 5 offset 1");
}
@ForPlatform({Platform.H2, Platform.POSTGRES})
@Test
public void testColumnName2() {
@@ -113,7 +177,7 @@ public class TestRawSqlParsing extends BaseTestCase {
RawSql rawSql = RawSqlBuilder.parse(sql).create();
Query<A2Customer> query = Ebean.createQuery(A2Customer.class)
Query<A2Customer> query = DB.find(A2Customer.class)
.setRawSql(rawSql);
final List<A2Customer> list = query.findList();
@@ -135,7 +199,7 @@ public class TestRawSqlParsing extends BaseTestCase {
RawSql rawSql = RawSqlBuilder.parse(sql).create();
Query<ACustomer> query = Ebean.createQuery(ACustomer.class)
Query<ACustomer> query = DB.find(ACustomer.class)
.setRawSql(rawSql);
final List<ACustomer> list = query.findList();
@@ -159,7 +223,7 @@ public class TestRawSqlParsing extends BaseTestCase {
.columnMapping("v.customerName", "custName")
.create();
Query<ACustomer> query = Ebean.createQuery(ACustomer.class)
Query<ACustomer> query = DB.find(ACustomer.class)
.setRawSql(rawSql);
final List<ACustomer> list = query.findList();
@@ -2,33 +2,36 @@ package io.ebeaninternal.server.transaction;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.server.deploy.PersistenceContextUtil;
import org.tests.model.basic.Car;
import org.tests.model.basic.Customer;
import org.tests.model.basic.Vehicle;
import org.junit.Test;
import org.tests.model.basic.Car;
import org.tests.model.basic.Contact;
import org.tests.model.basic.Customer;
import org.tests.model.basic.Product;
import org.tests.model.basic.Vehicle;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class DefaultPersistenceContextTest {
Customer customer42;
private final Customer customer42;
Car car1;
private final Car car1;
public DefaultPersistenceContextTest() {
customer42 = new Customer();
customer42.setId(42);
car1 = new Car();
car1.setId(1);
}
PersistenceContext pc() {
private DefaultPersistenceContext pc() {
return new DefaultPersistenceContext();
}
PersistenceContext pcWith42() {
PersistenceContext pc = pc();
private DefaultPersistenceContext pcWith42() {
DefaultPersistenceContext pc = pc();
pc.put(Customer.class, 42, customer42);
return pc;
}
@@ -38,7 +41,7 @@ public class DefaultPersistenceContextTest {
}
@Test
public void put_get_withInheritance() throws Exception {
public void put_get_withInheritance() {
PersistenceContext pc = pc();
pc.put(root(Vehicle.class), 1, car1);
@@ -48,7 +51,7 @@ public class DefaultPersistenceContextTest {
}
@Test
public void put_get() throws Exception {
public void put_get() {
PersistenceContext pc = pc();
pc.put(Customer.class, customer42.getId(), customer42);
@@ -58,7 +61,7 @@ public class DefaultPersistenceContextTest {
}
@Test
public void putIfAbsent_when_absent() throws Exception {
public void putIfAbsent_when_absent() {
PersistenceContext pc = pc();
Object existing = pc.putIfAbsent(Customer.class, customer42.getId(), customer42);
@@ -67,7 +70,7 @@ public class DefaultPersistenceContextTest {
}
@Test
public void putIfAbsent_when_notAbsent() throws Exception {
public void putIfAbsent_when_notAbsent() {
PersistenceContext pc = pcWith42();
Object existing = pc.putIfAbsent(Customer.class, customer42.getId(), new Customer());
@@ -76,21 +79,21 @@ public class DefaultPersistenceContextTest {
}
@Test
public void get_when_empty() throws Exception {
public void get_when_empty() {
PersistenceContext pc = pc();
Object found = pc.get(Customer.class, 42);
assertThat(found).isNull();
}
@Test
public void get_when_there() throws Exception {
public void get_when_there() {
PersistenceContext pc = pcWith42();
Object found = pc.get(Customer.class, 42);
assertThat(found).isSameAs(customer42);
}
@Test
public void getWithOption_when_empty() throws Exception {
public void getWithOption_when_empty() {
PersistenceContext pc = pc();
PersistenceContext.WithOption withOption = pc.getWithOption(Customer.class, 42);
@@ -98,7 +101,7 @@ public class DefaultPersistenceContextTest {
}
@Test
public void getWithOption_when_there() throws Exception {
public void getWithOption_when_there() {
PersistenceContext pc = pcWith42();
@@ -107,7 +110,7 @@ public class DefaultPersistenceContextTest {
}
@Test
public void getWithOption_when_deleted() throws Exception {
public void getWithOption_when_deleted() {
PersistenceContext pc = pcWith42();
pc.deleted(Customer.class, 42);
@@ -118,21 +121,21 @@ public class DefaultPersistenceContextTest {
}
@Test
public void size_when_empty() throws Exception {
public void size_when_empty() {
PersistenceContext pc = pc();
assertThat(pc.size(Customer.class)).isEqualTo(0);
}
@Test
public void size_when_some() throws Exception {
public void size_when_some() {
PersistenceContext pc = pcWith42();
assertThat(pc.size(Customer.class)).isEqualTo(1);
}
@Test
public void clear() throws Exception {
public void clear() {
PersistenceContext pc = pcWith42();
pc.clear();
@@ -140,7 +143,7 @@ public class DefaultPersistenceContextTest {
}
@Test
public void clearClass() throws Exception {
public void clearClass() {
PersistenceContext pc = pcWith42();
pc.clear(Customer.class);
@@ -148,7 +151,7 @@ public class DefaultPersistenceContextTest {
}
@Test
public void clearClassAndId() throws Exception {
public void clearClassAndId() {
PersistenceContext pc = pcWith42();
pc.put(Customer.class, 43, new Customer());
@@ -159,4 +162,102 @@ public class DefaultPersistenceContextTest {
pc.clear(Customer.class, 43);
assertThat(pc.size(Customer.class)).isEqualTo(0);
}
@Test
public void forIterate() {
final DefaultPersistenceContext pc = pcWith42();
final Object origCustomer42 = pc.get(Customer.class, 42);
// act
final PersistenceContext pcIterate = pc.forIterate();
assertThat(pc).isNotSameAs(pcIterate);
assertThat(pcIterate.size(Customer.class)).isEqualTo(1);
// assert same instance (bean effectively transferred to iterator persistence context
final Object customer42 = pcIterate.get(Customer.class, 42);
assertThat(customer42).isSameAs(origCustomer42);
final PersistenceContext.WithOption option = pcIterate.getWithOption(Customer.class, 42);
assertThat(option.getBean()).isSameAs(origCustomer42);
}
@Test
public void forIterate_many() {
DefaultPersistenceContext pc = new DefaultPersistenceContext();
addCustomers(pc, 1, 100);
addContacts(pc, 1, 1010);
assertThat(pc.size(Customer.class)).isEqualTo(100);
assertThat(pc.size(Contact.class)).isEqualTo(1010);
// act
final PersistenceContext pcIterate = pc.forIterate();
assertThat(pcIterate.size(Customer.class)).isEqualTo(100);
assertThat(pcIterate.size(Contact.class)).isEqualTo(1010);
}
@Test
public void forIterate_resetLimit_forIterateReset() {
DefaultPersistenceContext initialPc = new DefaultPersistenceContext();
addCustomers(initialPc, 1, 100);
addContacts(initialPc, 1, 1010);
final PersistenceContext pcIterate = initialPc.forIterate();
assertFalse(pcIterate.resetLimit());
// added 900 NEW contact beans
addContacts(pcIterate, 2000, 900);
assertThat(pcIterate.size(Contact.class)).isEqualTo(1910);
assertFalse(pcIterate.resetLimit());
// boundary, added 1000 NEW contact beans (still false)
addContacts(pcIterate, 3000, 100);
assertFalse(pcIterate.resetLimit());
addContacts(pcIterate, 4000, 1);
addProducts(pcIterate, 1, 100);
// ACT - over 1000 added beans boundary for contacts so returns true
assertTrue(pcIterate.resetLimit());
assertThat(pcIterate.size(Contact.class)).isEqualTo(2011);
assertThat(pcIterate.size(Customer.class)).isEqualTo(100);
assertThat(pcIterate.size(Product.class)).isEqualTo(100);
// ACT - obtain new PC forIterateReset
PersistenceContext pcReset = pcIterate.forIterateReset();
// keeps original customer beans as no new added beans there
assertThat(pcReset.size(Customer.class)).isEqualTo(100); // customers didn't change
// added beans to contacts and products so those where reset
assertThat(pcReset.size(Contact.class)).isEqualTo(0);
assertThat(pcReset.size(Product.class)).isEqualTo(0);
}
@Test
public void toString_sillyTest() {
DefaultPersistenceContext pc = pcWith42();
assertThat(pc.toString()).contains("org.tests.model.basic.Customer");
}
private void addCustomers(PersistenceContext pc, int start, int loop) {
for (int i = start; i < start + loop; i++) {
Customer bean = new Customer();
bean.setId(i);
pc.put(Customer.class, i, bean);
}
}
private void addContacts(PersistenceContext pc, int start, int loop) {
for (int i = start; i < start + loop; i++) {
Contact bean = new Contact();
bean.setId(i);
pc.put(Contact.class, i, bean);
}
}
private void addProducts(PersistenceContext pc, int start, int loop) {
for (int i = start; i < start + loop; i++) {
Product bean = new Product();
bean.setId(i);
pc.put(Product.class, i, bean);
}
}
}
+18
View File
@@ -0,0 +1,18 @@
package main;
import io.ebean.docker.commands.MariaDBConfig;
import io.ebean.docker.commands.MariaDBContainer;
public class StartMariaDb {
public static void main(String[] args) {
MariaDBConfig config = new MariaDBConfig("10.5");
config.setDbName("unit");
config.setUser("unit");
config.setPassword("unit");
MariaDBContainer container = new MariaDBContainer(config);
container.startWithDropCreate();
}
}
@@ -19,7 +19,7 @@ import static org.junit.Assert.assertNotNull;
public class TestAggregateFormula extends BaseTestCase {
@IgnorePlatform({Platform.SQLSERVER, Platform.MYSQL, Platform.NUODB})
@IgnorePlatform({Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.NUODB})
@Test
public void minDistinctOrderByNulls() {
@@ -49,7 +49,7 @@ public class TestMetaAnnotation extends BaseTestCase {
* This test writes 101 spaces to "line1" which is annotated with &#64;Size(max=100).
*/
@Test
@IgnorePlatform({Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL}) // pg & mssql does not fail if string is too long.
@IgnorePlatform({Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB}) // pg & mssql does not fail if string is too long.
public void testWrite101SpacesToLine1() {
Address address = new Address();
@@ -66,7 +66,7 @@ public class TestMetaAnnotation extends BaseTestCase {
* This test writes 101 spaces to "line1" which is meta-annotated with {@link SizeMedium}.
*/
@Test
@IgnorePlatform({Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL})
@IgnorePlatform({Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
public void testWrite101SpacesToLine2() {
Address address = new Address();
@@ -23,7 +23,7 @@ public class TestQueryForUpdate extends BaseTestCase {
@Test
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL})
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
public void testForUpdate() {
ResetBasicData.reset();
@@ -41,7 +41,7 @@ public class TestQueryForUpdate extends BaseTestCase {
}
@Test
@ForPlatform({ Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL})
@ForPlatform({ Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
public void testForUpdate_when_alreadyInPC() {
EBasic basic = new EBasic("test PC cache");
+45 -17
View File
@@ -2,7 +2,7 @@ package org.tests.cache;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebean.Ebean;
import io.ebean.Transaction;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheStatistics;
import org.ebeantest.LoggedSqlCollector;
@@ -27,37 +27,66 @@ public class TestBeanCache extends BaseTestCase {
private static final Logger log = LoggerFactory.getLogger(TestBeanCache.class);
private ServerCache beanCache = DB.getDefault().getServerCacheManager().getBeanCache(OCachedBean.class);
private final ServerCache beanCache = DB.getDefault().getServerCacheManager().getBeanCache(OCachedBean.class);
@Test
public void findById_when_idTypeConverted() {
OCachedBean bean = new OCachedBean();
bean.setName("findById");
Ebean.save(bean);
DB.save(bean);
OCachedBean bean0 = Ebean.find(OCachedBean.class, bean.getId());
OCachedBean bean0 = DB.find(OCachedBean.class, bean.getId());
assertNotNull(bean0);
// expect to hit the cache, no SQL
LoggedSqlCollector.start();
OCachedBean bean1 = Ebean.find(OCachedBean.class, bean.getId());
OCachedBean bean1 = DB.find(OCachedBean.class, bean.getId());
List<String> sql = LoggedSqlCollector.stop();
assertNotNull(bean1);
assertThat(sql).isEmpty();
// expect to hit the cache, no SQL
LoggedSqlCollector.start();
OCachedBean bean2 = Ebean.find(OCachedBean.class).setReadOnly(true).setId(String.valueOf(bean.getId())).findOne();
OCachedBean bean2 = DB.find(OCachedBean.class).setReadOnly(true).setId(String.valueOf(bean.getId())).findOne();
sql = LoggedSqlCollector.stop();
assertNotNull(bean2);
assertThat(sql).isEmpty();
}
@Test
public void idsIn_explicitCache_expect_cachePut() {
List<OCachedBean> beans = createBeans(Arrays.asList("k0","k1"));
List<Long> ids = beans.stream().map(OCachedBean::getId).collect(Collectors.toList());
beanCache.clear();
beanCache.getStatistics(true);
try (Transaction transaction = DB.beginTransaction()) {
// skipCacheAfterWrite set after this write ...
final OCachedBean junk = createBean("junk");
DB.save(junk);
List<OCachedBean> list = DB.find(OCachedBean.class)
.where().idIn(ids)
.setUseCache(true)
.findList();
assertThat(list).hasSize(2);
transaction.commit();
}
ServerCacheStatistics statistics = beanCache.getStatistics(true);
assertThat(statistics.getHitCount()).isEqualTo(0);
assertThat(statistics.getMissCount()).isEqualTo(2);
assertThat(statistics.getPutCount()).isEqualTo(2);
}
@Test
public void idsInExpression() {
List<OCachedBean> beans = createBeans();
List<OCachedBean> beans = createBeans(Arrays.asList("z0", "z1", "z2"));
List<Long> ids = beans.stream().map(OCachedBean::getId).collect(Collectors.toList());
beanCache.clear();
@@ -134,26 +163,25 @@ public class TestBeanCache extends BaseTestCase {
assertThat(statistics.getMissCount()).isEqualTo(missCount);
}
private List<OCachedBean> createBeans() {
List<String> names = Arrays.asList("z0", "z1", "z2");
private List<OCachedBean> createBeans(List<String> names) {
List<OCachedBean> beans = new ArrayList<>();
for (String name : names) {
OCachedBean bean = new OCachedBean();
bean.setName(name);
beans.add(bean);
beans.add(createBean(name));
}
DB.saveAll(beans);
return beans;
}
private OCachedBean createBean(String name) {
OCachedBean bean = new OCachedBean();
bean.setName(name);
return bean;
}
@Test
public void find_whenNotExits() {
Country country = Ebean.find(Country.class)
Country country = DB.find(Country.class)
.where()
.eq("name","NotValid")
.findOne();
@@ -26,7 +26,7 @@ public class TestDefaults extends BaseTestCase {
final List<String> current = LoggedSqlCollector.current();
assertThat(current).isNotEmpty();
if (isMySql()) {
if (isMySql() || isMariaDB()) {
assertThat(current.get(0)).contains("insert into defaults_model_draft values (default);");
} else if (isSqlServer()) {
assertThat(current.get(0)).contains("insert into defaults_model_draft (id) values (?)");
@@ -27,7 +27,7 @@ public class TestDeleteByQuery extends BaseTestCase {
}
@Test
@IgnorePlatform(Platform.MYSQL)
@IgnorePlatform({Platform.MYSQL, Platform.MARIADB})
public void deleteWithLimit() {
createUser("deleteWithLimit1");
createUser("deleteWithLimit2");
@@ -23,6 +23,61 @@ public class TestHistoryInsert extends BaseTestCase {
private final Logger logger = LoggerFactory.getLogger(TestHistoryInsert.class);
/**
* Looks like we MUST use useLegacyDatetimeCode=false ... in order for
* the correct server timezone to be honored by the MariaDB JDBC driver.
*/
@Test
@ForPlatform({Platform.MARIADB})
public void mariadb_simple_history() {
Timestamp t0 = new Timestamp(System.currentTimeMillis());
littleSleep();
User user = new User();
user.setName("Jim");
user.setEmail("one@email.com");
user.setPasswordHash("someHash");
DB.save(user);
Timestamp t1 = new Timestamp(System.currentTimeMillis());
littleSleep();
user.setName("NotJim");
user.save();
Timestamp t2 = new Timestamp(System.currentTimeMillis());
littleSleep();
user.setName("NotJimV2");
user.setEmail("two@email.com");
user.save();
Timestamp t3 = new Timestamp(System.currentTimeMillis());
List<Version<User>> versions = DB.find(User.class).setId(user.getId()).findVersionsBetween(t0, t3);
assertThat(versions).hasSize(3);
// use useLegacyDatetimeCode=false with MariaDB JDBC driver
final User user0 = DB.find(User.class).setId(user.getId()).asOf(t0).findOne();
final User user1 = DB.find(User.class).setId(user.getId()).asOf(t1).findOne();
final User user2 = DB.find(User.class).setId(user.getId()).asOf(t2).findOne();
final User user3 = DB.find(User.class).setId(user.getId()).asOf(t3).findOne();
assertThat(user1.getName()).isEqualTo("Jim");
assertThat(user1.getEmail()).isEqualTo("one@email.com");
assertThat(user2.getName()).isEqualTo("NotJim");
assertThat(user2.getEmail()).isEqualTo("one@email.com");
assertThat(user3.getName()).isEqualTo("NotJimV2");
assertThat(user3.getEmail()).isEqualTo("two@email.com");
assertThat(user0).isNull();
}
private void littleSleep() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Test
@ForPlatform({Platform.H2, Platform.POSTGRES})
public void test() throws InterruptedException {
@@ -1,40 +1,80 @@
package org.tests.query;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.DB;
import io.ebean.FetchConfig;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.annotation.Transactional;
import io.ebean.bean.PersistenceContext;
import io.ebeaninternal.api.SpiTransaction;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.tests.model.basic.Contact;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.junit.Assert;
import org.junit.Test;
import org.tests.o2m.OmBasicChild;
import org.tests.o2m.OmBasicParent;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public class TestQueryFindEach extends BaseTestCase {
private final Random random = new Random();
@Test
public void test() {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
Query<Customer> query
= server.find(Customer.class)
.setAutoTune(false)
.fetch("contacts", new FetchConfig().query(2)).where().gt("id", 0).order("id")
Query<Customer> query = DB.find(Customer.class)
.fetch("contacts", new FetchConfig().query(2))
.where().gt("id", 0).order("id")
.setMaxRows(2).query();
final AtomicInteger counter = new AtomicInteger(0);
query.findEach(customer -> {
counter.incrementAndGet();
customer.getName();
assertNotNull(customer.getName());
});
Assert.assertEquals(2, counter.get());
assertEquals(2, counter.get());
}
@Test
public void persistenceContext_scope() {
ResetBasicData.reset();
Query<Contact> query = DB.find(Contact.class);
try (final Transaction transaction = DB.beginTransaction()) {
// effectively loads customers into persistence context
final List<Customer> customerList = DB.find(Customer.class)
.select("name")
.findList();
SpiTransaction spiTxn = (SpiTransaction) transaction;
PersistenceContext pc = spiTxn.getPersistenceContext();
assertThat(pc.size(Customer.class)).isEqualTo(customerList.size());
LoggedSqlCollector.start();
query.findEach(contact -> {
// use customer from persistence context (otherwise would invoke lazy loading)
assertNotNull(contact.getCustomer().getName());
});
final List<String> sql = LoggedSqlCollector.stop();
assertThat(sql).hasSize(1);
}
}
/**
@@ -45,10 +85,9 @@ public class TestQueryFindEach extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
Query<Customer> query = server.find(Customer.class).setAutoTune(false)
.fetch("contacts", new FetchConfig().query(2)).where().gt("id", 0).order("id")
Query<Customer> query = DB.find(Customer.class)
.fetch("contacts", new FetchConfig().query(2))
.where().gt("id", 0).order("id")
.setMaxRows(2).query();
final AtomicInteger counter = new AtomicInteger(0);
@@ -60,6 +99,47 @@ public class TestQueryFindEach extends BaseTestCase {
}
});
Assert.assertFalse("Never get here - exception thrown", true);
fail("Never get here - exception thrown");
}
@Test
public void iterateResetLimit() {
DB.find(OmBasicChild.class).delete();
DB.find(OmBasicParent.class).delete();
insertData();
LoggedSqlCollector.start();
try (final Transaction transaction = DB.beginTransaction()) {
// DB.find(OmBasicParent.class).findList();
DB.find(OmBasicChild.class)
.setLazyLoadBatchSize(100)
//.fetchQuery("parent","name")
//.fetch("parent","name")
.findEach(child -> {
assertNotNull(child.getParent().getName());
});
}
final List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.size()).isLessThan(50);
}
@Transactional(batchSize = 40)
private void insertData() {
for (int i = 0; i < 150; i++) {
insertData2(i);
}
}
private void insertData2(int i) {
OmBasicParent p = new OmBasicParent("bl_" + i);
DB.save(p);
int children = 20 + random.nextInt(10);
for (int j = 0; j < children; j++) {
OmBasicChild c = new OmBasicChild("bl_" + i + "_" + j, p);
DB.save(c);
}
}
}
@@ -1,17 +1,19 @@
package org.tests.query;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.DB;
import io.ebean.FetchConfig;
import io.ebean.Query;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.junit.Assert;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
public class TestQueryFindEachWhile extends BaseTestCase {
@Test
@@ -19,10 +21,8 @@ public class TestQueryFindEachWhile extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
Query<Customer> query
= server.find(Customer.class)
= DB.find(Customer.class)
.setAutoTune(false)
.fetch("contacts", new FetchConfig().query(2)).where().gt("id", 0).order("id")
.setMaxRows(2).query();
@@ -31,11 +31,11 @@ public class TestQueryFindEachWhile extends BaseTestCase {
query.findEachWhile(customer -> {
counter.incrementAndGet();
customer.getName();
assertNotNull(customer.getName());
return true;
});
Assert.assertEquals(2, counter.get());
assertEquals(2, counter.get());
}
/**
@@ -46,9 +46,7 @@ public class TestQueryFindEachWhile extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
Query<Customer> query = server.find(Customer.class).setAutoTune(false)
Query<Customer> query = DB.find(Customer.class).setAutoTune(false)
.fetch("contacts", new FetchConfig().query(2)).where().gt("id", 0).order("id")
.setMaxRows(2).query();
@@ -62,6 +60,6 @@ public class TestQueryFindEachWhile extends BaseTestCase {
return true;
});
Assert.assertFalse("Never get here - exception thrown", true);
fail("Never get here - exception thrown");
}
}
@@ -1,8 +1,7 @@
package org.tests.query;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.DB;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.datasource.DataSourcePool;
@@ -32,9 +31,7 @@ public class TestQueryFindIterate extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
Query<Customer> query = server.find(Customer.class)
Query<Customer> query = DB.find(Customer.class)
.setMaxRows(2);
final AtomicInteger count = new AtomicInteger();
@@ -54,9 +51,7 @@ public class TestQueryFindIterate extends BaseTestCase {
public void test_hasNext_hasNext() {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
QueryIterator<Customer> queryIterator = server.find(Customer.class)
QueryIterator<Customer> queryIterator = DB.find(Customer.class)
.where()
.isNotNull("name")
.setMaxRows(3)
@@ -89,9 +84,7 @@ public class TestQueryFindIterate extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
Query<Customer> query = server.find(Customer.class)
Query<Customer> query = DB.find(Customer.class)
.setAutoTune(false)
//.fetch("contacts", new FetchConfig().query(2)).where().gt("id", 0).orderBy("id")
.setMaxRows(2);
@@ -108,7 +101,7 @@ public class TestQueryFindIterate extends BaseTestCase {
ResetBasicData.reset();
Ebean.find(Order.class)
DB.find(Order.class)
//.select("orderDate")
.where().gt("id", 0).le("id", 10)
.findEach(order -> {
@@ -129,7 +122,7 @@ public class TestQueryFindIterate extends BaseTestCase {
LoggedSqlCollector.start();
Ebean.find(Order.class)
DB.find(Order.class)
.setLazyLoadBatchSize(10)
.select("status, orderDate")
.fetch("customer", "name")
@@ -159,10 +152,10 @@ public class TestQueryFindIterate extends BaseTestCase {
LoggedSqlCollector.start();
// make sure we don't hit the L2 cache for order shipments
Ebean.getServerCacheManager().clear(Order.class);
Ebean.getServerCacheManager().clear(OrderShipment.class);
DB.getServerCacheManager().clear(Order.class);
DB.getServerCacheManager().clear(OrderShipment.class);
Ebean.find(Order.class)
DB.find(Order.class)
.setLazyLoadBatchSize(10)
.setUseCache(false)
.select("status, orderDate")
@@ -190,10 +183,8 @@ public class TestQueryFindIterate extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
// intentionally a query with incorrect type binding
Query<Customer> query = server.find(Customer.class)
Query<Customer> query = DB.find(Customer.class)
.setAutoTune(false)
.where().gt("id", "JUNK_NOT_A_LONG")
.setMaxRows(2).query();
@@ -203,11 +194,11 @@ public class TestQueryFindIterate extends BaseTestCase {
});
if (!server.getName().equals("h2")) {
if (!DB.getDefault().getName().equals("h2")) {
// MySql allows the query with type conversion?
throw new PersistenceException("H2 does expected thing but MySql does not");
}
assertTrue("Never get here as exception thrown", false);
fail("Never get here as exception thrown");
}
@@ -216,9 +207,7 @@ public class TestQueryFindIterate extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
Query<Customer> query = server.find(Customer.class)
Query<Customer> query = DB.find(Customer.class)
.setAutoTune(false)
.where().gt("id", 0)
.setMaxRows(2).query();
@@ -231,7 +220,16 @@ public class TestQueryFindIterate extends BaseTestCase {
}
@Test
public void testCloseConnection() {
public void testCloseConnection_findIterate() {
findIterateCloseConnection(false);
}
@Test
public void testCloseConnection_findIterate_withBatchLoad() {
findIterateCloseConnection(true);
}
public void findIterateCloseConnection(boolean withLoadBatch) {
ResetBasicData.reset();
SpiServer pluginApi = server().getPluginApi();
@@ -241,12 +239,17 @@ public class TestQueryFindIterate extends BaseTestCase {
}
int startConns = dsPool.getStatus(false).getBusy();
QueryIterator<Customer> queryIterator = server().find(Customer.class)
.where()
.isNotNull("name")
.setMaxRows(3)
.order().asc("id")
.findIterate();
final Query<Customer> query = server().find(Customer.class)
.where()
.isNotNull("name")
.setMaxRows(3)
.order().asc("id");
if (withLoadBatch) {
query.setLazyLoadBatchSize(1);
}
QueryIterator<Customer> queryIterator = query.findIterate();
assertThat(dsPool.getStatus(false).getBusy()).isEqualTo(startConns + 1);
@@ -267,7 +270,8 @@ public class TestQueryFindIterate extends BaseTestCase {
try {
queryIterator.next();
fail("noSuchElementException expected");
} catch (NoSuchElementException e) {}
} catch (NoSuchElementException e) {
logger.debug("Expected NoSuchElementException");
}
}
}
@@ -1,61 +0,0 @@
package org.tests.query;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.FetchConfig;
import io.ebean.Query;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.junit.Assert;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicInteger;
public class TestQueryFindVisit extends BaseTestCase {
@Test
public void test() {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
Query<Customer> query = server.find(Customer.class).setAutoTune(false)
.fetch("contacts", new FetchConfig().query(2)).where().gt("id", 0)
.order("id")
.setMaxRows(2).query();
final AtomicInteger counter = new AtomicInteger(0);
query.findEach(bean -> counter.incrementAndGet());
Assert.assertEquals(2, counter.get());
}
/**
* Test the behaviour when an exception is thrown inside the findVisit().
*/
@Test(expected = IllegalStateException.class)
public void testVisitThrowingException() {
ResetBasicData.reset();
EbeanServer server = Ebean.getServer(null);
Query<Customer> query = server.find(Customer.class).setAutoTune(false)
.fetch("contacts", new FetchConfig().query(2)).where().gt("id", 0).order("id")
.setMaxRows(2).query();
final AtomicInteger counter = new AtomicInteger(0);
query.findEach(bean -> {
counter.incrementAndGet();
if (counter.intValue() > 0) {
throw new IllegalStateException("cause a failure");
}
});
Assert.assertFalse("Never get here - exception thrown", true);
}
}
@@ -1,7 +1,7 @@
package org.tests.query;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.DB;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
@@ -17,7 +17,7 @@ public class TestQueryInIdTypeConversion extends BaseTestCase {
ResetBasicData.reset();
List<Customer> list = Ebean.find(Customer.class).where().idIn("1", "2").findList();
List<Customer> list = DB.find(Customer.class).where().idIn("1", "2").findList();
assertThat(list).isNotEmpty();
}
@@ -26,7 +26,13 @@ public class TestQueryInIdTypeConversion extends BaseTestCase {
ResetBasicData.reset();
List<Customer> list = Ebean.find(Customer.class).where().idIn().findList();
List<Customer> list = DB.find(Customer.class).where().idIn().findList();
assertThat(list).isEmpty();
}
@Test
public void test_emptyList_idIn() {
List<Customer> list = DB.find(Customer.class).where().idIn(new Object[] {}).findList();
assertThat(list).isEmpty();
}
}
@@ -1,6 +1,7 @@
package org.tests.query;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebean.Ebean;
import io.ebean.Expr;
import io.ebean.Query;
@@ -57,7 +58,7 @@ public class TestWhereRawClause extends BaseTestCase {
ResetBasicData.reset();
Query<Customer> query = Ebean.find(Customer.class)
Query<Customer> query = DB.find(Customer.class)
.where()
.raw("id in (select c.id from o_customer c where c.name in (?1))", asList("Rob", "Fiona", "Jack"))
.query();
@@ -41,7 +41,7 @@ public class TestOrderByWithDistinct extends BaseTestCase {
}
@Test
@IgnorePlatform({Platform.MYSQL, Platform.SQLSERVER, Platform.NUODB}) // do not support nulls first/last
@IgnorePlatform({Platform.MYSQL, Platform.MARIADB, Platform.SQLSERVER, Platform.NUODB}) // do not support nulls first/last
public void testDistinctOn() {
MRole role = Ebean.getReference(MRole.class, 1);
@@ -1,10 +1,9 @@
package org.tests.query.other;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.DB;
import io.ebean.Query;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Assert;
import org.junit.Test;
import org.tests.model.basic.Order;
import org.tests.model.basic.ResetBasicData;
@@ -12,6 +11,7 @@ import org.tests.model.basic.ResetBasicData;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
public class TestQueryRowCountWithMany extends BaseTestCase {
@@ -24,7 +24,7 @@ public class TestQueryRowCountWithMany extends BaseTestCase {
Long productId = 1L;
Query<Order> query = Ebean.find(Order.class)
Query<Order> query = DB.find(Order.class)
.fetch("details")
.where().eq("details.product.id", productId)
.order("cretime asc").query();
@@ -66,8 +66,8 @@ public class TestQueryRowCountWithMany extends BaseTestCase {
List<String> sqlLogged = LoggedSqlCollector.stop();
Assert.assertEquals(list.size(), rowCount);
Assert.assertEquals(2, sqlLogged.size());
assertEquals(list.size(), rowCount);
assertEquals(2, sqlLogged.size());
assertThat(trimSql(sqlLogged.get(1), 1)).contains(
"select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id where u1.product_id = ?)");
@@ -81,19 +81,20 @@ public class TestQueryRowCountWithMany extends BaseTestCase {
Long productId = 1L;
Query<Order> query = Ebean.find(Order.class)
Query<Order> query = DB.find(Order.class)
.fetch("details")
.where().eq("details.product.id", productId)
.setFirstRow(2)
.setMaxRows(20)
.orderBy("cretime asc");
.orderBy("cretime asc")
.query();
LoggedSqlCollector.start();
query.findCount();
List<String> sqlLogged = LoggedSqlCollector.stop();
Assert.assertEquals(1, sqlLogged.size());
assertEquals(1, sqlLogged.size());
assertThat(trimSql(sqlLogged.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id where u1.product_id = ?)");
query.findList();

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