Compare commits

...
Author SHA1 Message Date
rob bygrave 2b3e485beb [maven-release-plugin] prepare release ebean-11.19.2 2018-08-06 21:42:41 +12:00
rob bygrave d4be0718a8 #1460 - ENH: Add FetchGroup ... as a mechanism to specify select() + fetch() separately from Query
Change to add generics for FetchGroup
2018-08-06 21:30:36 +12:00
rob bygrave 6d0556a120 #1460 - ENH: Add FetchGroup ... as a mechanism to specify select() + fetch() separately from Query
Change to add generics for FetchGroup
2018-08-02 23:18:44 +12:00
rob bygrave ec7e4e4935 #1461 - @OneToMany with @JoinTable without table name does not default join table name 2018-08-02 22:25:48 +12:00
rob bygrave 471605619a #1460 - ENH: Add FetchGroup ... as a mechanism to specify select() + fetch() separately from Query 2018-07-30 23:51:52 +12:00
rob bygrave 02fe956056 #1459 - findNative with join to a @OneToMany ... does not map properly 2018-07-27 12:31:56 +12:00
rob bygrave db13034fec No effective change - update code style in test 2018-07-27 12:31:27 +12:00
rob bygrave c2489beb82 [maven-release-plugin] prepare for next development iteration 2018-07-18 22:25:00 +12:00
rob bygrave 9a424d7d6d [maven-release-plugin] prepare release ebean-11.19.1 2018-07-18 22:24:45 +12:00
rob bygrave 9b74602617 Bump pom to 11.19.1-SNAPSHOT 2018-07-18 22:22:48 +12:00
Roland PramlandRob Bygrave 5c99986476 Migration with procedures (#1451)
DB Migration with helper stored procedures (for MySql and SQL Server)
2018-07-18 22:21:47 +12:00
rob bygrave 7d38933e9d Refactor remove dead code - BindableRequest registerGeneratedVersion()
No effective change
2018-07-18 22:16:15 +12:00
rob bygrave 7668fb11a9 [maven-release-plugin] prepare for next development iteration 2018-07-18 22:05:02 +12:00
rob bygrave eac68166d9 [maven-release-plugin] prepare release ebean-11.18.6 2018-07-18 22:04:48 +12:00
rob bygrave cb0023345f Refactor scalar types for Varchar, LongVarchar and Clob - ScalarTypeStringBase 2018-07-18 21:59:11 +12:00
rob bygrave f0f7feb125 No effective change - code format 2018-07-18 21:49:19 +12:00
Roland PramlandRob Bygrave c8e32253a5 FIX: support for ScalarTypeConverter and @Lob (#1454) 2018-07-18 21:41:46 +12:00
rob bygrave 01541bb208 #1457 - Issue with @ManyToOne with @EmbeddedId where only some columns are insertable or updatable 2018-07-18 21:29:08 +12:00
rob bygrave 2d44d06f36 #1456 - io.ebean.annotation.NotNull not honored by @ManyToOne property 2018-07-18 19:36:41 +12:00
rob bygrave fda930a162 No effective change - fix javadoc title to Ebean 11 2018-07-18 17:01:14 +12:00
Roland PramlandRob Bygrave 5a6cdb9a86 FIX: null handling in ScalarType.toBeanType (#1455) 2018-07-18 16:33:36 +12:00
rob bygrave 5ef7fe8504 [maven-release-plugin] prepare for next development iteration 2018-07-15 16:09:15 +12:00
rob bygrave 11586fb635 [maven-release-plugin] prepare release ebean-11.18.5 2018-07-15 16:09:00 +12:00
rob bygrave d555122067 #1453 - @Formula property included in DDL when on @Inheritance bean and without @Transient 2018-07-15 15:36:29 +12:00
rob bygrave b6d3dc3516 Fix test code only - Tune entity bean needs at least 1 non Id property 2018-07-15 15:01:17 +12:00
rob bygrave bbf54d6196 git push origin masterMerge branch 'FOCONIS-cache-bug-delete' 2018-07-15 14:13:48 +12:00
rob bygrave 74c88ce511 #1452 Fix - Deleting a bean does not invalidate the bean cache (Regression from 11.18.1 - introduced by changes in #1427) 2018-07-15 14:13:29 +12:00
rob bygrave 309bf6a11a Merge branch 'cache-bug-delete' of https://github.com/FOCONIS/ebean into FOCONIS-cache-bug-delete 2018-07-15 13:56:37 +12:00
rob bygrave 02b6ab0f74 No effective change, move null check on orderBy to prior to parse 2018-07-15 13:55:51 +12:00
rob bygrave 0c41993ce7 No effective change, whitespace 2018-07-15 13:55:15 +12:00
rob bygrave d949c48245 git push origin masterMerge branch 'tobias--bugfix/lazy_fetch_with_id_column_name' 2018-07-15 13:52:14 +12:00
rob bygrave 93e603ca25 #1449 Fix for failing lazy fetch when logical property name starts with underscore 2018-07-15 13:49:05 +12:00
rob bygrave 117d22fd67 Merge branch 'bugfix/lazy_fetch_with_id_column_name' of https://github.com/tobias-/ebean into tobias--bugfix/lazy_fetch_with_id_column_name 2018-07-15 13:11:33 +12:00
Roland Praml f13de2c8a6 ADD: Testcase for delete with cache 2018-07-13 19:57:56 +02:00
Mitsch164andRob Bygrave 5b8ef8154a Fix Transaction Flushing when ID Property is Loaded (#1450)
* Fix for unintentianal flush when ID is loaded

* fix duplicateKey Exception

* higher id
2018-07-12 15:10:17 +12:00
Tobias df892f4509 Failing testcase with lazy fetch
For some reason, all entities with an id starting with _ fails when
lazy-fetching due to sort not being properly renamed.
2018-07-11 15:52:56 +02:00
rob bygrave 2755e6d1ec [maven-release-plugin] prepare for next development iteration 2018-07-05 23:40:12 +12:00
rob bygrave 2024193da9 [maven-release-plugin] prepare release ebean-11.18.4 2018-07-05 23:39:56 +12:00
rob bygrave 72788c7428 No effective change - tidy test code 2018-07-05 22:41:28 +12:00
rob bygrave 8789709fb8 No effective change - tidy test code 2018-07-05 22:40:09 +12:00
rob bygrave 588aaf9361 #1447 - Delete query ... should honour cascading delete if defined
See also PR #1445 with the failing test case
2018-07-05 22:37:54 +12:00
Michael BenzandRob Bygrave 68649d0e98 Failing unit test to demonstrate the bug that deleting an entity via … (#1445)
* Failing unit test to demonstrate the bug that deleting an entity via an Ebean query does not respect the cascade setting.
(here a mapped optional OneToOne entity is not deleted using an Ebean query even if the CascadeType.ALL was used)

* moved init and cleanup code to @Before and @After JUnit methods and added another test that shows Ebean.delete() works as expected
2018-07-05 19:52:40 +12:00
rob bygrave f6047d3b73 [maven-release-plugin] prepare for next development iteration 2018-07-04 22:09:27 +12:00
rob bygrave 629e096e28 [maven-release-plugin] prepare release ebean-11.18.3 2018-07-04 22:09:12 +12:00
rob bygrave c981944c8e #1444 - JSON parsing with inheritance - java.lang.UnsupportedOperationException: cannot create entity bean for abstract entity ... 2018-07-04 21:26:17 +12:00
rob bygrave 1ba9f4e9de No effective change - Add simple profile query main 2018-07-04 21:25:36 +12:00
rob bygrave ce02e7ce70 No effective change - tidy tests 2018-07-04 21:25:10 +12:00
rob bygrave 1d9a2ed43a No effective change - tidy DJsonContext 2018-07-02 21:33:59 +12:00
rob bygrave ae81ce5f47 #1443 - Bump ebean-migration to 11.7.1 (which adds JdbcMigration support and "I" run first repeatable migrations) 2018-07-01 20:30:54 +12:00
rob bygrave 9e42f98902 [maven-release-plugin] prepare for next development iteration 2018-06-28 10:27:06 +12:00
rob bygrave ada21729ec [maven-release-plugin] prepare release ebean-11.18.2 2018-06-28 10:26:53 +12:00
rob bygrave 8b7d882b8a #1442 - Add support to set ebean.dbSchema ... (~ default DB schema) with create-all.sql and DB migrations (so it all just works) 2018-06-28 10:23:34 +12:00
Michael BenzandRob Bygrave cf2d9df256 Migration enum drop check constraints (#1435)
* Fix: apply drop checkConstraints properly which were not applied for Enum to String

* Use IF EXISTS to drop constraints

* added TODO and comment about MariaDB supporting CHECK constraints from version 10.2.1 forward compared to MySQL 5.7/8.0 parsing but ignoring CHECK constraints

* updated reference scripts for dropping constraints on enums
2018-06-27 10:38:56 +12:00
rob bygrave 48b05eb358 No effective change - modify logback-test.xml to put the test example logging files into target directory 2018-06-27 10:37:00 +12:00
rob bygrave 0796fc0d88 [maven-release-plugin] prepare for next development iteration 2018-06-27 00:30:37 +12:00
rob bygrave df585644e9 [maven-release-plugin] prepare release ebean-11.18.1 2018-06-27 00:30:23 +12:00
rob bygrave ff3c5b3d82 #1441 - Update ebean-dbmigration.xsd adding missing alterForeignKey element 2018-06-27 00:23:36 +12:00
rob bygrave 1a03e84f0e #1439 - IllegalArgumentException: No rule for io.ebeaninternal.dbmigration.migration.Sql@315df4bb 2018-06-26 22:40:08 +12:00
rob bygrave 49ab17cd34 #1438 - Mapping of @ElementCollection with an Enum that is otherwise not used produces error 2018-06-26 22:11:40 +12:00
rob bygrave 9cd189034c #1437 - or(expr, expr) ... where expr has a ToMany should produce a LEFT JOIN (and instead we only get JOIN) 2018-06-26 21:33:44 +12:00
rob bygrave ed566ca153 #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Refactor on PostCommitProcessing with fixes for dependent table invalidation
2018-06-26 00:25:33 +12:00
rob bygrave 9159cec36c No effective change - remove invalid javadoc on Query due to #1434 2018-06-19 16:35:42 +12:00
rob bygrave b58fc2df04 #1436 - PersistenceException: Query threw SQLException:No ScalarType registered for class java.util.Arrays$ArrayList Query was:select id, name from o_customer where id = any(?)
Add test for SqlUpdate
2018-06-19 16:30:21 +12:00
rob bygrave dd8607b072 #1436 - PersistenceException: Query threw SQLException:No ScalarType registered for class java.util.Arrays$ArrayList Query was:select id, name from o_customer where id = any(?) 2018-06-19 15:30:19 +12:00
rob bygrave a5a8765bd0 Update tests only - non native boolean (MySql) and ignore non-named savepoint nested transaction tests with MySql. 2018-06-19 15:24:35 +12:00
rob bygrave ae55b58438 No effective change - remove unused imports 2018-06-19 01:48:45 +12:00
rob bygrave 671d082ad3 #1427 - Map all deleteByIds into a single CacheChangeBeanRemove and use bulk removeAll api to remove from cache 2018-06-19 01:34:28 +12:00
rob bygrave 43e95d81f6 #1427 - RemoteTransactionEvent process dependent tables 2018-06-19 00:50:14 +12:00
rob bygrave 3a64cf6952 #1434 - Remove deprecated API from EbeanServer - finder methods that take explicit transaction. Migrate to use ebeanServer.extended() 2018-06-18 21:34:51 +12:00
rob bygrave 8d976f83a9 #1417 - Breaking API - Remove PersistBatch.INSERT ... migrate to PersistBatch.ALL 2018-06-18 21:15:10 +12:00
rob bygrave 117737c790 #1433 - Remove io.ebeaninternal.api.SpiContainerBootup ... which was replaced by io.ebean.config.AutoConfigure 2018-06-18 20:02:14 +12:00
rob bygrave 085843339f #1432 - Refactor internals for RemoteTransactionEvent / BeanPersistIds ... to not send inserted ids 2018-06-17 23:34:27 +12:00
rob bygrave bf33b3f96f #1431 - Refactor internals for RemoteTransactionEvent binary serialisation 2018-06-17 23:29:11 +12:00
rob bygrave e1d4159453 #1431 - Refactor internals for RemoteTransactionEvent binary serialisation 2018-06-17 22:25:45 +12:00
rob bygrave 4f1f3cadf9 Merge branch 'feature/query-cache-1427' 2018-06-16 16:02:35 +12:00
rob bygrave 01e1a4642b [maven-release-plugin] prepare for next development iteration 2018-06-16 15:26:11 +12:00
rob bygrave 9c73309dfd #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Update RemoteTransactionEvent, add (back) common binary message handling (Common to TCP, K8s and upcoming binary pubsub)
2018-06-16 15:15:54 +12:00
rob bygrave bd3c99e67f #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Wrap Clock as ClockService to use in TransactionManager and DefaultServer (and allow for changing the Clock implementation for testing).
2018-06-16 00:22:44 +12:00
rob bygrave 5adbebaf6e Merge branch 'master' into feature/query-cache-1427 2018-06-16 00:09:25 +12:00
rob bygrave 6a9ab49ef0 #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Propagation across cluster.
2018-06-16 00:09:03 +12:00
rob bygrave 96cc8d7605 #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Initial work, does not include propagation across cluster.
2018-06-15 17:36:23 +12:00
271 changed files with 5683 additions and 2226 deletions
+6 -6
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.17.5</version>
<version>11.19.2</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-11.17.5</tag>
<tag>ebean-11.19.2</tag>
</scm>
<profiles>
@@ -117,7 +117,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>3.11</version>
<version>4.1</version>
</dependency>
<dependency>
@@ -135,7 +135,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.5.4</version>
<version>11.7.1</version>
</dependency>
<dependency>
@@ -192,7 +192,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4.1212</version>
<version>42.2.2</version>
<scope>provided</scope>
</dependency>
@@ -360,7 +360,7 @@
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.9.1</version>
<configuration>
<doctitle>Ebean 10</doctitle>
<doctitle>Ebean 11</doctitle>
<overview>src/main/java/io/ebean/overview.html</overview>
<source>1.8</source>
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
-451
View File
@@ -821,457 +821,6 @@ public interface EbeanServer {
*/
ExtendedServer extended();
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Return the number of 'top level' or 'root' entities this query should return.
*
* @see Query#findCount()
* @see Query#findFutureCount()
*/
@Deprecated
<T> int findCount(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Return the Id values of the query as a List.
*
* @see Query#findIds()
*/
@Nonnull
@Deprecated
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Return a QueryIterator for the query.
* <p>
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
* {@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)
* @see Query#findEachWhile(Predicate)
*/
@Nonnull
@Deprecated
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute the query visiting the each bean one at a time.
* <p>
* Unlike findList() this is suitable for processing a query that will return
* a very large resultSet. The reason is that not all the result beans need to be
* held in memory at the same time and instead processed one at a time.
* </p>
* <p>
* Internally this query using a PersistenceContext scoped to each bean (and the
* beans associated object graph).
* </p>
* <p>
* <pre>{@code
*
* ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findEach((Order order) -> {
*
* // do something with the order bean
* System.out.println(" -- processing order ... " + order);
* });
*
* }</pre>
*
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
@Deprecated
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute the query visiting the each bean one at a time.
* <p>
* Compared to findEach() this provides the ability to stop processing the query
* results early by returning false for the Predicate.
* </p>
* <p>
* Unlike findList() this is suitable for processing a query that will return
* a very large resultSet. The reason is that not all the result beans need to be
* held in memory at the same time and instead processed one at a time.
* </p>
* <p>
* Internally this query using a PersistenceContext scoped to each bean (and the
* beans associated object graph).
* </p>
* <p>
* <pre>{@code
*
* ebeanServer.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findEachWhile((Order order) -> {
*
* // do something with the order bean
* System.out.println(" -- processing order ... " + order);
*
* boolean carryOnProcessing = ...
* return carryOnProcessing;
* });
*
* }</pre>
*
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
@Deprecated
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Return versions of a @History entity bean.
* <p>
* Generally this query is expected to be a find by id or unique predicates query.
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
@Deprecated
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute a query returning a list of beans.
* <p>
* Generally you are able to use {@link Query#findList()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
* <p>
* <pre>{@code
*
* List<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findList();
*
* }</pre>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched beans.
* @see Query#findList()
*/
@Nonnull
@Deprecated
<T> List<T> findList(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute find row count query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @param query the query to execute the row count on
* @param transaction the transaction (can be null).
* @return a Future object for the row count query
* @see Query#findFutureCount()
*/
@Nonnull
@Deprecated
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute find Id's query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @param query the query to execute the fetch Id's on
* @param transaction the transaction (can be null).
* @return a Future object for the list of Id's
* @see Query#findFutureIds()
*/
@Nonnull
@Deprecated
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute find list query in a background thread returning a FutureList object.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a timeout).
* <p>
* This query will execute in it's own PersistenceContext and using its own transaction.
* What that means is that it will not share any bean instances with other queries.
*
* @param query the query to execute in the background
* @param transaction the transaction (can be null).
* @return a Future object for the list result of the query
* @see Query#findFutureList()
*/
@Nonnull
@Deprecated
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Return a PagedList for this query using firstRow and maxRows.
* <p>
* The benefit of using this over findList() is that it provides functionality to get the
* total row count etc.
* </p>
* <p>
* If maxRows is not set on the query prior to calling findPagedList() then a
* PersistenceException is thrown.
* </p>
* <p>
* <pre>{@code
*
* PagedList<Order> pagedList = Ebean.find(Order.class)
* .setFirstRow(50)
* .setMaxRows(20)
* .findPagedList();
*
* // fetch the total row count in the background
* pagedList.loadRowCount();
*
* List<Order> orders = pagedList.getList();
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* @return The PagedList
* @see Query#findPagedList()
*/
@Nonnull
@Deprecated
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute the query returning a set of entity beans.
* <p>
* Generally you are able to use {@link Query#findSet()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
* <p>
* <pre>{@code
*
* Set<Customer> customers =
* ebeanServer.find(Customer.class)
* .where().ilike("name", "rob%")
* .findSet();
*
* }</pre>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute
* @param transaction the transaction to use (can be null).
* @return the set of fetched beans.
* @see Query#findSet()
*/
@Nonnull
@Deprecated
<T> Set<T> findSet(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute the query returning the entity beans in a Map.
* <p>
* Generally you are able to use {@link Query#findMap()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the map of fetched beans.
* @see Query#findMap()
*/
@Nonnull
@Deprecated
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute the query returning a list of values for a single property.
* <p>
* <h3>Example 1:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .select("name")
* .orderBy().asc("name")
* .findSingleAttributeList();
*
* }</pre>
* <h3>Example 2:</h3>
* <pre>{@code
*
* List<String> names =
* Ebean.find(Customer.class)
* .setDistinct(true)
* .select("name")
* .where().eq("status", Customer.Status.NEW)
* .orderBy().asc("name")
* .setMaxRows(100)
* .findSingleAttributeList();
*
* }</pre>
*
* @return the list of values for the selected property
* @see Query#findSingleAttributeList()
*/
@Nonnull
@Deprecated
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute the query returning at most one entity bean or null (if no matching
* bean is found).
* <p>
* This will throw a NonUniqueResultException if the query finds more than one result.
* </p>
* <p>
* Generally you are able to use {@link Query#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched beans.
* @throws NonUniqueResultException if more than one result was found
* @see Query#findOne()
*/
@Nullable
@Deprecated
<T> T findOne(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Similar to findOne() but returns an Optional (rather than nullable).
*/
@Nonnull
@Deprecated
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @param query the query used for the delete
* @param transaction the transaction to use (can be null)
* @param <T> the type of entity bean to fetch.
* @return the number of beans/rows that were deleted
*/
@Deprecated
<T> int delete(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute the update query returning the number of rows updated.
* <p>
* The update query must be created using {@link #update(Class)}.
* </p>
*
* @param query the update query to execute
* @param transaction the optional transaction to use for the update (can be null)
* @param <T> the type of entity bean
* @return The number of rows updated
*/
@Deprecated
<T> int update(Query<T> query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute the sql query returning a list of MapBean.
* <p>
* Generally you are able to use {@link SqlQuery#findList()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched MapBean.
* @see SqlQuery#findList()
*/
@Nonnull
@Deprecated
List<SqlRow> findList(SqlQuery query, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute the SqlQuery iterating a row at a time.
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
@Deprecated
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
* <p>
* Returning false after processing a row stops the iteration through the query results.
* </p>
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
@Deprecated
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
/**
* Deprecated - Moved to the ExtendedServer API, please use via extended().
* Execute the sql query returning a single MapBean or null.
* <p>
* This will throw a PersistenceException if the query found more than one
* result.
* </p>
* <p>
* Generally you are able to use {@link SqlQuery#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the fetched MapBean or null if none was found.
* @see SqlQuery#findOne()
*/
@Nullable
@Deprecated
SqlRow findOne(SqlQuery query, Transaction transaction);
/**
* Either Insert or Update the bean depending on its state.
* <p>
@@ -412,6 +412,11 @@ public interface ExpressionList<T> {
*/
Query<T> select(String properties);
/**
* Apply the fetchGroup which defines what part of the object graph to load.
*/
Query<T> select(FetchGroup<T> fetchGroup);
/**
* Set whether this query uses DISTINCT.
* <p>
+114
View File
@@ -0,0 +1,114 @@
package io.ebean;
import javax.annotation.Nonnull;
/**
* Defines what part of the object graph to load (select and fetch clauses).
* <p>
* Using a FetchGroup effectively sets the select() and fetch() clauses for a query. It is alternative
* to specifying the select() and fetch() clauses on the query allowing for more re-use of "what to load"
* that can be defined separately from the query and combined with other FetchGroups.
* </p>
*
* <h3>Select example</h3>*
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* <h3>Select and fetch example</h3>
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName, lastName, email")
* .build();
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* <h3>Combining FetchGroups</h3>
* <p>
* FetchGroups can be combined together to form another FetchGroup.
* </p>
* <pre>{@code
*
* FetchGroup<Address> FG_ADDRESS = FetchGroup.of(Address.class)
* .select("line1, line2, city")
* .fetch("country", "name")
* .build();
*
* FetchGroup<Customer> FG_CUSTOMER = FetchGroup.of(Customer.class)
* .select("name, version")
* .fetch("billingAddress", FG_ADDRESS)
* .build();
*
*
* Customer.query()
* .select(FG_CUSTOMER)
* .findList();
*
* }</pre>
*
* @param <T> The bean type the Fetch group can be applied to
*/
public interface FetchGroup<T> {
/**
* Return the FetchGroup with the given select clause.
* <p>
* We use this for simple FetchGroup that only select() properties and do not have additional fetch() clause.
* </p>
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* @param select The select clause of the FetchGroup
*
* @return The FetchGroup with the given select clause
*/
@Nonnull
static <T> FetchGroup<T> of(Class<T> cls, String select) {
return XServiceProvider.fetchGroupOf(cls, select);
}
/**
* Return the FetchGroupBuilder with the given select clause that we can add fetch clauses to.
* <p>
* We chain select() with one or more fetch() clauses to define the object graph to load.
* </p>
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName, lastName, email")
* .build();
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* @return The FetchGroupBuilder with the given select clause which we will add fetch clauses to
*/
@Nonnull
static <T> FetchGroupBuilder<T> of(Class<T> cls) {
return XServiceProvider.fetchGroupOf(cls);
}
}
@@ -0,0 +1,93 @@
package io.ebean;
import javax.annotation.Nonnull;
/**
* Builds a FetchGroup by adding fetch clauses.
* <p>
* We add select() and fetch() clauses to define the object graph we want to load.
* </p>
*
* <pre>{@code
*
* FetchGroup fetchGroup = FetchGroup
* .select("name, status")
* .fetch("contacts", "firstName, lastName, email")
* .build();
*
* Customer.query()
* .select(fetchGroup)
* .where()
* ...
* .findList();
*
* }</pre>
*/
public interface FetchGroupBuilder<T> {
/**
* Specify specific properties to select (top level properties).
*/
@Nonnull
FetchGroupBuilder<T> select(String select);
/**
* Fetch all the properties at the given path.
*/
@Nonnull
FetchGroupBuilder<T> fetch(String path);
/**
* Fetch the path with the nested fetch group.
*/
@Nonnull
FetchGroupBuilder<T> fetch(String path, FetchGroup<?> nestedGroup);
/**
* Fetch the path using a query join with the nested fetch group.
*/
@Nonnull
FetchGroupBuilder<T> fetchQuery(String path, FetchGroup<?> nestedGroup);
/**
* Fetch the path lazily with the nested fetch group.
*/
@Nonnull
FetchGroupBuilder<T> fetchLazy(String path, FetchGroup<?> nestedGroup);
/**
* Fetch the path including specified properties.
*/
@Nonnull
FetchGroupBuilder<T> fetch(String path, String properties);
/**
* Fetch the path including all its properties using a query join.
*/
@Nonnull
FetchGroupBuilder<T> fetchQuery(String path);
/**
* Fetch the path including specified properties using a query join.
*/
@Nonnull
FetchGroupBuilder<T> fetchQuery(String path, String properties);
/**
* Fetch the path including all its properties lazily.
*/
@Nonnull
FetchGroupBuilder<T> fetchLazy(String path);
/**
* Fetch the path including specified properties lazily.
*/
@Nonnull
FetchGroupBuilder<T> fetchLazy(String path, String properties);
/**
* Build and return the FetchGroup.
*/
@Nonnull
FetchGroup<T> build();
}
+5 -8
View File
@@ -397,6 +397,11 @@ public interface Query<T> {
*/
Query<T> select(String fetchProperties);
/**
* Apply the fetchGroup which defines what part of the object graph to load.
*/
Query<T> select(FetchGroup<T> fetchGroup);
/**
* Specify a path to fetch eagerly including specific properties.
* <p>
@@ -602,8 +607,6 @@ public interface Query<T> {
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
*
* @see EbeanServer#findIds(Query, Transaction)
*/
@Nonnull
<A> List<A> findIds();
@@ -740,8 +743,6 @@ public interface Query<T> {
* .findList();
*
* }</pre>
*
* @see EbeanServer#findList(Query, Transaction)
*/
@Nonnull
List<T> findList();
@@ -759,8 +760,6 @@ public interface Query<T> {
* .findSet();
*
* }</pre>
*
* @see EbeanServer#findSet(Query, Transaction)
*/
@Nonnull
Set<T> findSet();
@@ -782,8 +781,6 @@ public interface Query<T> {
* .findMap();
*
* }</pre>
*
* @see EbeanServer#findMap(Query, Transaction)
*/
@Nonnull
<K> Map<K, T> findMap();
+34 -4
View File
@@ -359,7 +359,10 @@ public interface Transaction extends AutoCloseable {
void setBatchMode(boolean useBatch);
/**
* The JDBC batch mode to use for this transaction.
* Deprecated - migrate to {@link #setBatchMode(boolean)}.
* <p>
* Set the JDBC batch mode to use for this transaction.
* </p>
* <p>
* If this is NONE then JDBC batch can still be used for each request - save(), insert(), update() or delete()
* and this would be useful if the request cascades to detail beans.
@@ -368,15 +371,23 @@ public interface Transaction extends AutoCloseable {
* @param persistBatchMode the batch mode to use for this transaction
* @see io.ebean.config.ServerConfig#setPersistBatch(PersistBatch)
*/
@Deprecated
void setBatch(PersistBatch persistBatchMode);
/**
* Deprecated - migrate to {@link #isBatchMode()}.
* Return the batch mode at the transaction level.
*/
@Deprecated
PersistBatch getBatch();
/**
* Return the batch mode at the transaction level.
*/
PersistBatch getBatch();
boolean isBatchMode();
/**
* Set the JDBC batch mode to use for a save() or delete() request.
* Set the JDBC batch mode to use for a save() or delete() when cascading to children.
* <p>
* This only takes effect when batch mode on the transaction has not already meant that
* JDBC batch mode is being used.
@@ -385,17 +396,36 @@ public interface Transaction extends AutoCloseable {
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
* </p>
* <p>
* This is effectively already turned on for all platforms apart from older Sql Server.
* </p>
*
* @param batchOnCascadeMode the batch mode to use per save(), insert(), update() or delete()
* @param batchMode the batch mode to use per save(), insert(), update() or delete()
* @see io.ebean.config.ServerConfig#setPersistBatchOnCascade(PersistBatch)
*/
void setBatchOnCascade(boolean batchMode);
/**
* Set the batch mode when cascading.
* <p>
* Deprecated in favour of {@link #setBatchOnCascade(boolean)}
* </p>
*/
@Deprecated
void setBatchOnCascade(PersistBatch batchOnCascadeMode);
/**
* Deprecated - migrate to {@link #isBatchMode()}.
* Return the batch mode at the request level (for each save(), insert(), update() or delete()).
*/
@Deprecated
PersistBatch getBatchOnCascade();
/**
* Return the batch mode at the request level.
*/
boolean isBatchOnCascade();
/**
* Specify the number of statements before a batch is flushed automatically.
*/
@@ -1,5 +1,6 @@
package io.ebean;
import io.ebean.service.SpiFetchGroupService;
import io.ebean.service.SpiProfileLocationFactory;
import io.ebean.service.SpiRawSqlService;
@@ -15,6 +16,16 @@ class XServiceProvider {
private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation();
private static SpiFetchGroupService fetchGroupService = initSpiFetchGroupService();
private static SpiFetchGroupService initSpiFetchGroupService() {
Iterator<SpiFetchGroupService> loader = ServiceLoader.load(SpiFetchGroupService.class).iterator();
if (loader.hasNext()) {
return loader.next();
}
throw new IllegalStateException("No service implementation found for SpiFetchGroupService?");
}
private static SpiRawSqlService initRawSql() {
Iterator<SpiRawSqlService> loader = ServiceLoader.load(SpiRawSqlService.class).iterator();
@@ -47,4 +58,17 @@ class XServiceProvider {
return profileLocationFactory;
}
/**
* Return the FetchGroup with the given select clause.
*/
static <T> FetchGroup<T> fetchGroupOf(Class<T> cls, String select) {
return fetchGroupService.of(cls, select);
}
/**
* Return the FetchGroupBuilder with the given select clause.
*/
static <T> FetchGroupBuilder<T> fetchGroupOf(Class<T> cls) {
return fetchGroupService.of(cls);
}
}
+54
View File
@@ -0,0 +1,54 @@
package io.ebean.cache;
import java.util.Set;
/**
* For query cache entries we additionally hold the dependent tables and timestamp for the query result.
* <p>
* We use the dependent tables and timestamp to validate that tables the query joins to have not been
* modified since the query cache entry was cached. If any dependent tables have since been modified
* the query cache entry is treated as invalid.
* </p>
*/
public class QueryCacheEntry {
private final Object value;
private final Set<String> dependentTables;
private final long timestamp;
/**
* Create with dependent tables and timestamp.
*
* @param value The query result being cached
* @param dependentTables The extra tables the query is dependent on (joins to)
* @param timestamp The timestamp that the query uses to check for modifications
*/
public QueryCacheEntry(Object value, Set<String> dependentTables, long timestamp) {
this.value = value;
this.dependentTables = dependentTables;
this.timestamp = timestamp;
}
/**
* Return the actual query result.
*/
public Object getValue() {
return value;
}
/**
* Return the tables the query result is dependent on.
*/
public Set<String> getDependentTables() {
return dependentTables;
}
/**
* Return the timestamp used to check for modifications on the dependent tables.
*/
public long getTimestamp() {
return timestamp;
}
}
@@ -0,0 +1,12 @@
package io.ebean.cache;
/**
* Used to validate that a query cache entry is still valid based on dependent tables.
*/
public interface QueryCacheEntryValidate {
/**
* Return true if the entry is still valid based on dependent tables.
*/
boolean isValid(QueryCacheEntry queryCacheEntry);
}
+65
View File
@@ -0,0 +1,65 @@
package io.ebean.cache;
import io.ebean.config.CurrentTenantProvider;
/**
* Configuration used to create ServerCache instances.
*/
public class ServerCacheConfig {
private final ServerCacheType type;
private final String cacheKey;
private final ServerCacheOptions cacheOptions;
private final CurrentTenantProvider tenantProvider;
private final QueryCacheEntryValidate queryCacheEntryValidate;
public ServerCacheConfig(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions, CurrentTenantProvider tenantProvider, QueryCacheEntryValidate queryCacheEntryValidate) {
this.type = type;
this.cacheKey = cacheKey;
this.cacheOptions = cacheOptions;
this.tenantProvider = tenantProvider;
this.queryCacheEntryValidate = queryCacheEntryValidate;
}
/**
* Return the cache type.
*/
public ServerCacheType getType() {
return type;
}
/**
* Return the name of the cache.
*/
public String getCacheKey() {
return cacheKey;
}
/**
* Return the tuning options.
*/
public ServerCacheOptions getCacheOptions() {
return cacheOptions;
}
/**
* Return the current tenant provider.
*/
public CurrentTenantProvider getTenantProvider() {
return tenantProvider;
}
/**
* Return the service that provides validation for query cache entries.
*/
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
return queryCacheEntryValidate;
}
/**
* Return true if the cache is a query cache.
*/
public boolean isQueryCache() {
return type == ServerCacheType.QUERY;
}
}
+10 -3
View File
@@ -1,7 +1,5 @@
package io.ebean.cache;
import io.ebean.config.CurrentTenantProvider;
/**
* Defines method for constructing caches for beans and queries.
*/
@@ -10,6 +8,15 @@ public interface ServerCacheFactory {
/**
* Create the cache for the given type with options.
*/
ServerCache createCache(ServerCacheType type, String cacheKey, CurrentTenantProvider tenantProvider, ServerCacheOptions cacheOptions);
ServerCache createCache(ServerCacheConfig config);
/**
* Return a ServerCacheNotify that we will send ServerCacheNotification events to.
* <p>
* This is used if a ServerCacheNotifyPlugin is not supplied.
* </p>
*
* @param listener The listener that should be used to process the notification events.
*/
ServerCacheNotify createCacheNotify(ServerCacheNotify listener);
}
@@ -0,0 +1,29 @@
package io.ebean.cache;
import java.util.Set;
/**
* Notification event that dependent tables have been modified.
* <p>
* This is sent to other interested servers (in the cluster).
* </p>
*/
public class ServerCacheNotification {
private final long modifyTimestamp;
private final Set<String> dependentTables;
public ServerCacheNotification(long modifyTimestamp, Set<String> dependentTables) {
this.modifyTimestamp = modifyTimestamp;
this.dependentTables = dependentTables;
}
public long getModifyTimestamp() {
return modifyTimestamp;
}
public Set<String> getDependentTables() {
return dependentTables;
}
}
+12
View File
@@ -0,0 +1,12 @@
package io.ebean.cache;
/**
* Interface for both listening to notification changes and sending them to other members of the cluster.
*/
public interface ServerCacheNotify {
/**
* Notify other server cache members of the table modifications or process the notifications.
*/
void notify(ServerCacheNotification notification);
}
@@ -0,0 +1,17 @@
package io.ebean.cache;
import io.ebean.config.ServerConfig;
/**
* Plugin that provides a ServerCacheNotify implementation.
* <p>
* Is supplied this will be used to send the ServerCacheNotification event to other cluster members.
* </p>
*/
public interface ServerCacheNotifyPlugin {
/**
* Create a ServerCacheNotify implementation given the server configuration.
*/
ServerCacheNotify create(ServerConfig serverConfig);
}
@@ -102,6 +102,11 @@ public class DbMigrationConfig {
*/
protected Map<String, String> runPlaceholderMap;
/**
* DB schema used for the migration (and testing).
*/
protected String dbSchema;
/**
* DB user used to run the DB migration.
*/
@@ -373,6 +378,33 @@ public class DbMigrationConfig {
this.dbPassword = dbPassword;
}
/**
* Return the DB schema to use (for migration, testing etc).
*/
public String getDbSchema() {
String schema = readEnvironment("ddl.migration.schema");
if (schema != null) {
return schema;
}
return dbSchema;
}
/**
* Set the Db schema to use.
*/
public void setDbSchema(String dbSchema) {
this.dbSchema = dbSchema;
}
/**
* Set the Db schema if it hasn't already been defined.
*/
public void setDefaultDbSchema(String dbSchema) {
if (this.dbSchema == null) {
this.dbSchema = dbSchema;
}
}
/**
* Return migration versions that should be added to history without running.
*/
@@ -475,6 +507,7 @@ public class DbMigrationConfig {
runMigration = properties.getBoolean("migration.run", runMigration);
metaTable = properties.get("migration.metaTable", metaTable);
runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
dbSchema = properties.get("migration.dbSchema", dbSchema);
//Do not set user and pass from "datasource.db.username"
//There is a null test in MigrationRunner::getConnection to handle this
@@ -557,6 +590,7 @@ public class DbMigrationConfig {
runnerConfig.setRunPlaceholders(runPlaceholders);
runnerConfig.setDbUsername(getDbUsername());
runnerConfig.setDbPassword(getDbPassword());
runnerConfig.setDbSchema(getDbSchema());
runnerConfig.setClassLoader(classLoader);
if (patchInsertOn != null) {
runnerConfig.setPatchInsertOn(patchInsertOn);
@@ -296,6 +296,11 @@ public class ServerConfig {
*/
private DataSourceConfig readOnlyDataSourceConfig = new DataSourceConfig();
/**
* Optional - the database schema that should be used to own the tables etc.
*/
private String dbSchema;
/**
* The db migration config (migration resource path etc).
*/
@@ -1120,6 +1125,25 @@ public class ServerConfig {
this.profilingConfig = profilingConfig;
}
/**
* Return the DB schema to use.
*/
public String getDbSchema() {
return dbSchema;
}
/**
* Set the DB schema to use. This specifies to use this schema for:
* <ul>
* <li>Running Database migrations - Create and use the DB schema</li>
* <li>Testing DDL - Create-all.sql DDL execution creates and uses schema</li>
* <li>Testing Docker - Set default schema on connection URL</li>
* </ul>
*/
public void setDbSchema(String dbSchema) {
this.dbSchema = dbSchema;
}
/**
* Return the DB migration configuration.
*/
@@ -2764,6 +2788,10 @@ public class ServerConfig {
*/
protected void loadSettings(PropertiesWrapper p) {
dbSchema = p.get("dbSchema", dbSchema);
if (dbSchema != null) {
migrationConfig.setDefaultDbSchema(dbSchema);
}
profilingConfig.loadSettings(p, name);
migrationConfig.loadSettings(p, name);
platformConfig.loadSettings(p);
@@ -16,6 +16,7 @@ import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
/**
@@ -658,6 +659,39 @@ public class DatabasePlatform {
return persistBatchOnCascade;
}
/**
* Create the DB schema if it does not exist.
*/
public void createSchemaIfNotExists(String dbSchema, Connection connection) throws SQLException {
if (!schemaExists(dbSchema, connection)) {
Statement query = connection.createStatement();
try {
logger.info("create schema:{}", dbSchema);
query.executeUpdate("create schema " + dbSchema);
} finally {
JdbcClose.close(query);
}
}
}
/**
* Return true if the schema exists.
*/
public boolean schemaExists(String dbSchema, Connection connection) throws SQLException {
ResultSet schemas = connection.getMetaData().getSchemas();
try {
while (schemas.next()) {
String schema = schemas.getString(1);
if (schema.equalsIgnoreCase(dbSchema)) {
return true;
}
}
} finally {
JdbcClose.close(schemas);
}
return false;
}
/**
* Return true if the table exists.
*/
@@ -0,0 +1,26 @@
package io.ebean.service;
import io.ebean.FetchGroup;
import io.ebean.FetchGroupBuilder;
/**
* Service that parses FetchGroup expressions.
*/
public interface SpiFetchGroupService {
/**
* Return the FetchGroup with the given select clause.
*
* @param beanType The type of entity bean the fetch group is for
* @param select The properties to select (top level properties)
*/
<T> FetchGroup<T> of(Class<T> beanType, String select);
/**
* Create and return a FetchGroupBuilder starting with a select() clause.
*
* @param beanType The type of entity bean the fetch group is for
* @return The FetchGroupBuilder to add additional select and fetch clauses
*/
<T> FetchGroupBuilder<T> of(Class<T> beanType);
}
@@ -0,0 +1,47 @@
package io.ebeaninternal.api;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
/**
* Context used to read binary format messages.
*/
public class BinaryReadContext {
private final DataInputStream in;
/**
* Create with protocol 0 and byte data.
*/
public BinaryReadContext(byte[] byteData) {
this(new DataInputStream(new ByteArrayInputStream(byteData)));
}
/**
* Create with protocol version and DataInputStream data.
*/
public BinaryReadContext(DataInputStream in) {
this.in = in;
}
public DataInputStream in() {
return in;
}
public boolean readBoolean() throws IOException {
return in.readBoolean();
}
public int readInt() throws IOException {
return in.readInt();
}
public String readUTF() throws IOException {
return in.readUTF();
}
public long readLong() throws IOException {
return in.readLong();
}
}
@@ -0,0 +1,23 @@
package io.ebeaninternal.api;
import java.io.IOException;
/**
* Messages that can be sent in binary form.
* <p>
* Mainly RemoteTransactionEvent which is sent to cluster members.
* </p>
*/
public interface BinaryWritable {
int TYPE_BEANIUD = 1;
int TYPE_TABLEIUD = 2;
int TYPE_CACHE = 3;
int TYPE_TABLEMOD = 4;
/**
* Write message in binary format.
*/
void writeBinary(BinaryWriteContext out) throws IOException;
}
@@ -0,0 +1,49 @@
package io.ebeaninternal.api;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* Context used to write binary message (like RemoteTransactionEvent).
*/
public class BinaryWriteContext {
private final DataOutputStream out;
private long counter;
public BinaryWriteContext(DataOutputStream out) {
this.out = out;
}
/**
* Return the number of message parts that have been written.
*/
public long counter() {
return counter;
}
/**
* Return the output stream to write to.
*/
public DataOutputStream os() {
return out;
}
/**
* Start a message part with a given type code.
*/
public DataOutputStream start(int type) throws IOException {
counter++;
out.writeBoolean(true);
out.writeInt(type);
return out;
}
/**
* End of message parts.
*/
public void end() throws IOException {
out.writeBoolean(false);
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import java.io.Serializable;
@@ -154,6 +155,10 @@ public class BindParams implements Serializable {
public void setParameter(int position, Object value) {
Param p = getParam(position);
if (value instanceof Collection) {
// use of postgres ANY with positioned parameter
value = new MultiValueWrapper((Collection)value);
}
p.setInValue(value);
}
@@ -1,18 +0,0 @@
package io.ebeaninternal.api;
/**
* Plugin API available to invoke something prior to container bootup.
* <p>
* The initial intent is to provide a hook for 'docker-run' such that we can automatically ensure
* we have a test DB docker container running and setup ready to go.
* </p>
*/
public interface SpiContainerBootup {
/**
* Run something at bootup prior to the container starting.
*
* For example, start DB docker container(s).
*/
void bootup();
}
@@ -101,6 +101,11 @@ public interface SpiTransaction extends Transaction {
*/
String getId();
/**
* Return the start timestamp for the transaction (JVM side).
*/
long getStartMillis();
/**
* Return true if this transaction has updateAllLoadedProperties set.
* If null is returned the server default is used (set on ServerConfig).
@@ -179,7 +184,7 @@ public interface SpiTransaction extends Transaction {
* Return true if this request should be batched. Conversely returns false
* if this request should be executed immediately.
*/
boolean isBatchThisRequest(PersistRequest.Type type);
boolean isBatchThisRequest();
/**
* Return the BatchControl used to batch up persist requests.
@@ -30,6 +30,11 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.translate(message, cause);
}
@Override
public long getStartMillis() {
return transaction.getStartMillis();
}
@Override
public void setLabel(String label) {
transaction.setLabel(label);
@@ -245,6 +250,11 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
transaction.setBatchMode(useBatch);
}
@Override
public boolean isBatchMode() {
return transaction.isBatchMode();
}
@Override
public void setBatch(PersistBatch persistBatchMode) {
transaction.setBatch(persistBatchMode);
@@ -255,11 +265,21 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
return transaction.getBatch();
}
@Override
public void setBatchOnCascade(boolean batchMode) {
transaction.setBatchOnCascade(batchMode);
}
@Override
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
transaction.setBatchOnCascade(batchOnCascadeMode);
}
@Override
public boolean isBatchOnCascade() {
return transaction.isBatchOnCascade();
}
@Override
public PersistBatch getBatchOnCascade() {
return transaction.getBatchOnCascade();
@@ -356,8 +376,8 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
}
@Override
public boolean isBatchThisRequest(PersistRequest.Type type) {
return transaction.isBatchThisRequest(type);
public boolean isBatchThisRequest() {
return transaction.isBatchThisRequest();
}
@Override
@@ -3,7 +3,9 @@ package io.ebeaninternal.api;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.transaction.DeleteByIdMap;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import java.io.Serializable;
@@ -108,8 +110,20 @@ public class TransactionEvent implements Serializable {
/**
* Build and return the cache changeSet.
*/
public CacheChangeSet buildCacheChanges(boolean viewInvalidation) {
CacheChangeSet changeSet = new CacheChangeSet(viewInvalidation);
public CacheChangeSet buildCacheChanges(TransactionManager manager) {
if (eventBeans == null && deleteByIdMap == null && eventTables == null) {
return null;
}
CacheChangeSet changeSet = new CacheChangeSet(manager.clockNowMillis());
if (eventTables != null && !eventTables.isEmpty()) {
// notify cache with table based changes
BeanDescriptorManager dm = manager.getBeanDescriptorManager();
for (TransactionEventTable.TableIUD tableIUD : eventTables.values()) {
dm.cacheNotify(tableIUD, changeSet);
}
}
if (eventBeans != null) {
eventBeans.notifyCache(changeSet);
}
@@ -1,10 +1,7 @@
package io.ebeaninternal.api;
import io.ebean.event.BulkTableEvent;
import io.ebeaninternal.server.cluster.BinaryMessage;
import io.ebeaninternal.server.cluster.BinaryMessageList;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
@@ -12,7 +9,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public final class TransactionEventTable implements Serializable {
public final class TransactionEventTable implements Serializable, BinaryWritable {
private static final long serialVersionUID = 2236555729767483264L;
@@ -23,20 +20,13 @@ public final class TransactionEventTable implements Serializable {
return "TransactionEventTable " + map.values();
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
@Override
public void writeBinary(BinaryWriteContext out) throws IOException {
for (TableIUD tableIud : map.values()) {
tableIud.writeBinaryMessage(msgList);
tableIud.writeBinary(out);
}
}
public void readBinaryMessage(DataInput dataInput) throws IOException {
TableIUD tableIud = TableIUD.readBinaryMessage(dataInput);
map.put(tableIud.getTableName(), tableIud);
}
public void add(TransactionEventTable table) {
for (TableIUD iud : table.values()) {
@@ -47,7 +37,6 @@ public final class TransactionEventTable implements Serializable {
public void add(String table, boolean insert, boolean update, boolean delete) {
table = table.toUpperCase();
add(new TableIUD(table, insert, update, delete));
}
@@ -67,7 +56,7 @@ public final class TransactionEventTable implements Serializable {
return map.values();
}
public static class TableIUD implements Serializable, BulkTableEvent {
public static class TableIUD implements Serializable, BulkTableEvent, BinaryWritable {
private static final long serialVersionUID = -1958317571064162089L;
@@ -83,7 +72,7 @@ public final class TransactionEventTable implements Serializable {
this.delete = delete;
}
public static TableIUD readBinaryMessage(DataInput dataInput) throws IOException {
public static TableIUD readBinaryMessage(BinaryReadContext dataInput) throws IOException {
String table = dataInput.readUTF();
boolean insert = dataInput.readBoolean();
@@ -93,17 +82,13 @@ public final class TransactionEventTable implements Serializable {
return new TableIUD(table, insert, update, delete);
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
BinaryMessage msg = new BinaryMessage(table.length() + 10);
DataOutputStream os = msg.getOs();
os.writeInt(BinaryMessage.TYPE_TABLEIUD);
@Override
public void writeBinary(BinaryWriteContext out) throws IOException {
DataOutputStream os = out.start(TYPE_TABLEIUD);
os.writeUTF(table);
os.writeBoolean(insert);
os.writeBoolean(update);
os.writeBoolean(delete);
msgList.add(msg);
}
@Override
@@ -1,8 +1,8 @@
package io.ebeaninternal.dbmigration;
import io.ebean.Transaction;
import io.ebean.config.ServerConfig;
import io.ebean.migration.ddl.DdlRunner;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.model.CurrentModel;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
@@ -38,6 +38,7 @@ public class DdlGenerator {
private final boolean createOnly;
private final boolean jaxbPresent;
private final boolean ddlCommitOnCreateIndex;
private final String dbSchema;
private CurrentModel currentModel;
private String dropAllContent;
@@ -48,6 +49,7 @@ public class DdlGenerator {
this.jaxbPresent = serverConfig.getClassLoadConfig().isJavaxJAXBPresent();
this.generateDdl = serverConfig.isDdlGenerate();
this.createOnly = serverConfig.isDdlCreateOnly();
this.dbSchema = serverConfig.getDbSchema();
if (!serverConfig.getTenantMode().isDdlEnabled() && serverConfig.isDdlRun()) {
log.warn("DDL can't be run on startup with TenantMode " + serverConfig.getTenantMode());
this.runDdl = false;
@@ -85,30 +87,54 @@ public class DdlGenerator {
* Run the DDL drop and DDL create scripts if properties have been set.
*/
protected void runDdl() {
if (runDdl) {
Connection connection = null;
try {
runInitSql();
runDropSql();
runCreateSql();
runSeedSql();
} catch (IOException e) {
String msg = "Error reading drop/create script from file system";
throw new RuntimeException(msg, e);
connection = obtainConnection();
runDdlWith(connection);
} finally {
JdbcClose.rollback(connection);
JdbcClose.close(connection);
}
}
}
private void runDdlWith(Connection connection) {
try {
if (dbSchema != null) {
createSchemaIfRequired(connection);
}
runInitSql(connection);
runDropSql(connection);
runCreateSql(connection);
runSeedSql(connection);
} catch (IOException e) {
throw new RuntimeException("Error reading drop/create script from file system", e);
}
}
private Connection obtainConnection() {
try {
return server.getPluginApi().getDataSource().getConnection();
} catch (SQLException e) {
throw new PersistenceException("Failed to obtain connection to run DDL", e);
}
}
private void createSchemaIfRequired(Connection connection) {
try {
server.getDatabasePlatform().createSchemaIfNotExists(dbSchema, connection);
} catch (SQLException e) {
throw new PersistenceException("Failed to create DB Schema", e);
}
}
/**
* Execute all the DDL statements in the script.
*/
public int runScript(boolean expectErrors, String content, String scriptName) {
public int runScript(Connection connection, boolean expectErrors, String content, String scriptName) {
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
Transaction transaction = server.createTransaction();
Connection connection = transaction.getConnection();
try {
if (expectErrors) {
connection.setAutoCommit(true);
@@ -119,64 +145,63 @@ public class DdlGenerator {
if (expectErrors) {
connection.setAutoCommit(false);
}
transaction.commit();
connection.commit();
return count;
} catch (SQLException e) {
throw new PersistenceException("Failed to run script", e);
} finally {
transaction.end();
JdbcClose.rollback(connection);
}
}
protected void runDropSql() throws IOException {
protected void runDropSql(Connection connection) throws IOException {
if (!createOnly) {
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), true);
if (extraApply != null) {
runScript(false, extraApply, "extra-dll");
runScript(connection, false, extraApply, "extra-dll");
}
}
if (dropAllContent == null) {
dropAllContent = readFile(getDropFileName());
}
runScript(true, dropAllContent, getDropFileName());
runScript(connection, true, dropAllContent, getDropFileName());
}
}
protected void runCreateSql() throws IOException {
protected void runCreateSql(Connection connection) throws IOException {
if (createAllContent == null) {
createAllContent = readFile(getCreateFileName());
}
runScript(false, createAllContent, getCreateFileName());
runScript(connection, false, createAllContent, getCreateFileName());
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), false);
if (extraApply != null) {
runScript(false, extraApply, "extra-dll");
runScript(connection, false, extraApply, "extra-dll");
}
}
}
protected void runInitSql() throws IOException {
runResourceScript(server.getServerConfig().getDdlInitSql());
protected void runInitSql(Connection connection) throws IOException {
runResourceScript(connection, server.getServerConfig().getDdlInitSql());
}
protected void runSeedSql() throws IOException {
runResourceScript(server.getServerConfig().getDdlSeedSql());
protected void runSeedSql(Connection connection) throws IOException {
runResourceScript(connection, server.getServerConfig().getDdlSeedSql());
}
protected void runResourceScript(String sqlScript) throws IOException {
protected void runResourceScript(Connection connection, String sqlScript) throws IOException {
if (sqlScript != null) {
try (InputStream is = getClassLoader().getResourceAsStream(sqlScript)) {
if (is != null) {
String content = readContent(new InputStreamReader(is));
runScript(false, content, sqlScript);
runScript(connection, false, content, sqlScript);
}
}
}
@@ -314,24 +314,28 @@ public class DefaultDbMigration implements DbMigration {
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform) throws IOException {
if (dbPlatform != null) {
ExtraDdl extraDdl = ExtraDdlXmlReader.read("/extra-ddl.xml");
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
generateExtraDdl(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltin());
generateExtraDdl(migrationDir, dbPlatform, ExtraDdlXmlReader.read());
}
}
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl) throws IOException {
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
writeExtraDdl(migrationDir, script);
}
}
}
}
}
/**
* Write (or override) the "repeatable" migration script.
*/
private void writeExtraDdl(File migrationDir, DdlScript script) throws IOException {
String fullName = repeatableMigrationName(script.getName());
String fullName = repeatableMigrationName(script.isInit(), script.getName());
logger.info("writing repeatable script {}", fullName);
@@ -342,8 +346,18 @@ public class DefaultDbMigration implements DbMigration {
}
}
private String repeatableMigrationName(String scriptName) {
return "R__" + scriptName.replace(' ', '_') + migrationConfig.getApplySuffix();
private String repeatableMigrationName(boolean init, String scriptName) {
StringBuilder sb = new StringBuilder();
if (init) {
sb.append("I__");
} else {
sb.append("R__");
}
sb.append(scriptName.replace(' ', '_'));
sb.append(migrationConfig.getApplySuffix());
return sb.toString();
}
/**
@@ -840,12 +840,15 @@ public class BaseTableDdl implements TableDdl {
if (hasValue(alterColumn.getComment())) {
alterColumnComment(writer, alterColumn);
}
if (hasValue(alterColumn.getDropCheckConstraint())) {
dropCheckConstraint(writer, alterColumn, alterColumn.getDropCheckConstraint());
}
boolean alterCheckConstraint = hasValue(alterColumn.getCheckConstraint());
if (alterCheckConstraint) {
// drop constraint before altering type etc
dropCheckConstraint(writer, alterColumn);
dropCheckConstraint(writer, alterColumn, alterColumn.getCheckConstraintName());
}
boolean alterBaseAttributes = false;
if (hasValue(alterColumn.getType())) {
@@ -922,9 +925,9 @@ public class BaseTableDdl implements TableDdl {
}
}
protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter) throws IOException {
protected void dropCheckConstraint(DdlWrite writer, AlterColumn alter, String constraintName) throws IOException {
String ddl = platformDdl.alterTableDropConstraint(alter.getTableName(), alter.getCheckConstraintName());
String ddl = platformDdl.alterTableDropConstraint(alter.getTableName(), constraintName);
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
@@ -38,9 +38,20 @@ public class MySqlDdl extends PlatformDdl {
return "alter table " + tableName + " drop foreign key " + maxConstraintName(fkName);
}
/**
* It is rather complex to delete a column on MySql as there must not exist any foreign keys.
* That's why we call a user stored procedure here
*/
@Override
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')").endOfStatement();
}
@Override
public String alterTableDropConstraint(String tableName, String constraintName) {
// drop constraint not supported
// drop constraint not supported in MySQL 5.7 and 8.0 but starting with MariaDB 10.2.1 CHECK is evaluated
// TODO: Implement for MariaDB >= 10.2.1
return null;
}
@@ -60,7 +71,7 @@ public class MySqlDdl extends PlatformDdl {
@Override
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + defaultValue;
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + convertDefaultValue(defaultValue);
// use alter
return "alter table " + tableName + " alter " + columnName + " " + suffix;
@@ -79,8 +79,6 @@ public class PlatformDdl {
protected String alterColumn = "alter column";
protected String dropConstraint = "drop constraint";
protected String dropUniqueConstraint = "drop constraint";
protected String addConstraint = "add constraint";
@@ -248,6 +246,9 @@ public class PlatformDdl {
* Convert the standard type to the platform specific type.
*/
public String convert(String type, boolean identity) {
if (type == null) {
return null;
}
if (type.contains("[]")) {
return convertArrayType(type);
}
@@ -420,7 +421,7 @@ public class PlatformDdl {
* Drop a unique constraint from the table.
*/
public String alterTableDropConstraint(String tableName, String constraintName) {
return "alter table " + tableName + " " + dropConstraint + " " + maxConstraintName(constraintName);
return "alter table " + tableName + " " + dropConstraintIfExists + " " + maxConstraintName(constraintName);
}
/**
@@ -3,7 +3,9 @@ package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.annotation.ConstraintMode;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import java.io.IOException;
@@ -137,13 +139,7 @@ public class SqlServerDdl extends PlatformDdl {
// a rather complex statement.
StringBuilder sb = new StringBuilder();
if (DdlHelp.isDropDefault(defaultValue)) {
sb.append("delimiter $$\n");
sb.append("DECLARE @Tmp nvarchar(200);");
sb.append("select @Tmp = t1.name from sys.default_constraints t1\n");
sb.append(" join sys.columns t2 on t1.object_id = t2.default_object_id\n");
sb.append(" where t1.parent_object_id = OBJECT_ID('").append(tableName)
.append("') and t2.name = '").append(columnName).append("';\n");
sb.append("if @Tmp is not null EXEC('alter table ").append(tableName).append(" drop constraint ' + @Tmp)$$");
sb.append("EXEC usp_ebean_drop_default_constraint ").append(tableName).append(", ").append(columnName);
} else {
sb.append("alter table ").append(tableName);
sb.append(" add default ").append(convertDefaultValue(defaultValue)).append(" for ").append(columnName);
@@ -200,25 +196,50 @@ public class SqlServerDdl extends PlatformDdl {
/**
* It is rather complex to delete a column on SqlServer as there must not exist any references
* (constraints, default values, indices and foreign keys). The list is not yet complete, as
* indices over multiple columns will not yet deleted.
* (This may be changed to delete all refering objects by using the sys.* tables later)
* (constraints, default values, indices and foreign keys). That's why we call a user stored procedure here
*/
@Override
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("-- drop column ").append(tableName).append(".").append(columnName).endOfStatement();
buffer.append(alterTableDropUniqueConstraint(tableName, naming.uniqueConstraintName(tableName, columnName)));
buffer.endOfStatement();
buffer.append(alterColumnDefaultValue(tableName, columnName, DdlHelp.DROP_DEFAULT));
buffer.endOfStatement();
buffer.append(alterTableDropConstraint(tableName, naming.checkConstraintName(tableName, columnName)));
buffer.endOfStatement();
buffer.append(dropIndex(naming.indexName(tableName, columnName), tableName));
buffer.endOfStatement();
buffer.append(alterTableDropForeignKey(tableName, naming.foreignKeyConstraintName(tableName, columnName)));
buffer.endOfStatement();
super.alterTableDropColumn(buffer, tableName, columnName);
buffer.append("EXEC usp_ebean_drop_column ").append(tableName).append(", ").append(columnName).endOfStatement();
}
/**
* This writes the multi value datatypes needed for {@link MultiValueBind}
*/
@Override
public void generateProlog(DdlWrite write) throws IOException {
super.generateProlog(write);
generateTVPDefinitions(write, "bigint");
generateTVPDefinitions(write, "float");
generateTVPDefinitions(write, "bit");
generateTVPDefinitions(write, "date");
generateTVPDefinitions(write, "time");
//generateTVPDefinitions(write, "datetime2");
generateTVPDefinitions(write, "uniqueidentifier");
generateTVPDefinitions(write, "nvarchar(max)");
}
private void generateTVPDefinitions(DdlWrite write, String definition) throws IOException {
int pos = definition.indexOf('(');
String name = pos == -1 ? definition : definition.substring(0, pos);
dropTVP(write.dropAll(), name);
//TVPs are included in "I__create_procs.sql"
//createTVP(write.apply(), name, definition);
}
private void dropTVP(DdlBuffer ddl, String name) throws IOException {
ddl.append("if exists (select name from sys.types where name = 'ebean_").append(name)
.append("_tvp') drop type ebean_").append(name).append("_tvp").endOfStatement();
}
private void createTVP(DdlBuffer ddl, String name, String definition) throws IOException {
ddl.append("if not exists (select name from sys.types where name = 'ebean_").append(name)
.append("_tvp') create type ebean_").append(name).append("_tvp as table (c1 ").append(definition).append(")")
.endOfStatement();
}
}
@@ -25,9 +25,9 @@ public class AlterForeignKey {
protected String name;
@XmlAttribute(name = "columnNames", required = true)
protected String columnNames;
@XmlAttribute(name = "refColumnNames", required = true)
@XmlAttribute(name = "refColumnNames")
protected String refColumnNames;
@XmlAttribute(name = "refTableName", required = true)
@XmlAttribute(name = "refTableName")
protected String refTableName;
@XmlAttribute(name = "indexName")
protected String indexName;
@@ -9,8 +9,12 @@ import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.model.build.ModelBuildBeanVisitor;
import io.ebeaninternal.dbmigration.model.build.ModelBuildContext;
import io.ebeaninternal.dbmigration.model.visitor.VisitAllUsing;
import io.ebeaninternal.extraddl.model.DdlScript;
import io.ebeaninternal.extraddl.model.ExtraDdl;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import java.io.IOException;
import java.util.List;
/**
* Reads EbeanServer bean descriptors to build the current model.
@@ -111,6 +115,19 @@ public class CurrentModel {
if (header != null && !header.isEmpty()) {
ddl.append(header).append('\n');
}
ExtraDdl extraDdl = ExtraDdlXmlReader.readBuiltin();
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (script.isInit() && ExtraDdlXmlReader.matchPlatform(server.getDatabasePlatform().getName(), script.getPlatforms())) {
ddl.append("-- init script " + script.getName()).append('\n');
ddl.append(script.getValue());
}
}
}
ddl.append(write.apply().getBuffer());
ddl.append(write.applyForeignKeys().getBuffer());
ddl.append(write.applyHistoryView().getBuffer());
@@ -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.Sql;
import java.util.LinkedHashMap;
import java.util.List;
@@ -143,6 +144,8 @@ public class ModelContainer {
applyChange((AlterForeignKey) change);
} else if (change instanceof AddTableComment) {
applyChange((AddTableComment) change);
} else if (change instanceof Sql) {
// do nothing
} else {
throw new IllegalArgumentException("No rule for " + change);
}
@@ -258,12 +258,13 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
}
} else {
col.setDefaultValue(p.getDbColumnDefault());
col.setDbMigrationInfos(p.getDbMigrationInfos());
if (!p.isNullable() || p.isDDLNotNull()) {
col.setNotnull(true);
}
}
col.setDbMigrationInfos(p.getDbMigrationInfos());
if (p.isUnique() && !p.isId()) {
col.setUnique(determineUniqueConstraintName(col.getName()));
indexSetAdd(col.getName());
@@ -133,8 +133,7 @@ public class VisitAllUsing {
InheritInfo inheritInfo = descriptor.getInheritInfo();
if (inheritInfo != null && inheritInfo.isRoot()) {
// add all properties on the children objects
InheritChildVisitor childVisitor = new InheritChildVisitor(this, pv);
inheritInfo.visitChildren(childVisitor);
inheritInfo.visitChildren(new InheritChildVisitor(this, pv));
}
}
@@ -155,9 +154,10 @@ public class VisitAllUsing {
@Override
public void visit(InheritInfo inheritInfo) {
BeanProperty[] propertiesLocal = inheritInfo.desc().propertiesLocal();
for (BeanProperty aPropertiesLocal : propertiesLocal) {
owner.visit(pv, aPropertiesLocal);
for (BeanProperty beanProperty : inheritInfo.desc().propertiesLocal()) {
if (beanProperty.isDDLColumn()) {
owner.visit(pv, beanProperty);
}
}
}
}
@@ -39,7 +39,8 @@ public class DdlScript {
protected String platforms;
@XmlAttribute(name = "drop")
protected boolean drop;
@XmlAttribute(name = "init")
protected boolean init;
/**
* Gets the value of the value property.
*
@@ -114,4 +115,17 @@ public class DdlScript {
this.drop = drop;
}
/**
* Return if this a init script.
*/
public boolean isInit() {
return init;
}
/**
* Sets that this is a init script.
*/
public void setInit(boolean init) {
this.init = init;
}
}
@@ -83,10 +83,24 @@ public class ExtraDdlXmlReader {
}
}
/**
* Read the builtin extra ddl. (Stored procedures, tvp types etc)
*/
public static ExtraDdl readBuiltin() {
return read("/io/ebeaninternal/dbmigration/builtin-extra-ddl.xml");
}
/**
* Read the extra ddl.
*/
public static ExtraDdl read() {
return read("/extra-ddl.xml");
}
/**
* Read and return a ExtraDdl from an xml document at the given resource path.
*/
public static ExtraDdl read(String resourcePath) {
private static ExtraDdl read(String resourcePath) {
try (InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath)) {
if (is == null) {
@@ -2,6 +2,9 @@ package io.ebeaninternal.server.cache;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.ArrayList;
import java.util.Collection;
/**
* Change to remove bean from L2 cache.
*/
@@ -9,15 +12,35 @@ class CacheChangeBeanRemove implements CacheChange {
private final BeanDescriptor<?> descriptor;
private final Object id;
private final Collection<Object> ids;
CacheChangeBeanRemove(BeanDescriptor<?> descriptor, Object id) {
CacheChangeBeanRemove(Object id, BeanDescriptor<?> descriptor) {
this.descriptor = descriptor;
this.id = id;
this.ids = new ArrayList<>();
ids.add(id);
}
CacheChangeBeanRemove(BeanDescriptor<?> descriptor, Collection<Object> ids) {
this.descriptor = descriptor;
this.ids = ids;
}
@Override
public void apply() {
descriptor.cacheHandleDeleteById(id);
descriptor.cacheApplyInvalidate(ids);
}
/**
* Add more id values.
*/
public void addIds(Collection<Object> moreIds) {
ids.addAll(moreIds);
}
/**
* Add another id value.
*/
public void addId(Object id) {
ids.add(id);
}
}
@@ -25,6 +25,6 @@ class CacheChangeBeanUpdate implements CacheChange {
@Override
public void apply() {
desc.cacheBeanUpdate(id, changes, updateNaturalKey, version);
desc.cacheApplyBeanUpdate(id, changes, updateNaturalKey, version);
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.cache;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -17,22 +18,30 @@ public class CacheChangeSet {
private final List<CacheChange> entries = new ArrayList<>();
private final Set<String> touchedTables = new HashSet<>();
private final Set<BeanDescriptor<?>> queryCaches = new HashSet<>();
private final Set<BeanDescriptor<?>> beanCaches = new HashSet<>();
private final Map<BeanDescriptor<?>, CacheChangeBeanRemove> beanRemoveMap = new HashMap<>();
private final Map<ManyKey, ManyChange> manyChangeMap = new HashMap<>();
/**
* Set of "base tables" modified used to invalidate entities based on views.
*/
private final Set<String> viewInvalidation = new HashSet<>();
private final boolean viewEntityInvalidation;
private final long modificationTimestamp;
/**
* Construct specifying if we also need to process invalidation for entities based on views.
*/
public CacheChangeSet(boolean viewEntityInvalidation) {
this.viewEntityInvalidation = viewEntityInvalidation;
public CacheChangeSet(long modificationTimestamp) {
this.modificationTimestamp = modificationTimestamp;
}
/**
* Return the touched tables.
*/
public Set<String> touchedTables() {
return touchedTables;
}
/**
@@ -40,17 +49,36 @@ public class CacheChangeSet {
* <p>
* Return the set of table changes to process invalidation for entities based on views.
*/
public Set<String> apply() {
public void apply() {
for (BeanDescriptor<?> entry : queryCaches) {
entry.clearQueryCache();
}
for (BeanDescriptor<?> entry : beanCaches) {
entry.clearBeanCache();
}
for (CacheChange entry : entries) {
entry.apply();
}
for (CacheChange entry : manyChangeMap.values()) {
entry.apply();
}
return viewInvalidation;
for (CacheChange entry : beanRemoveMap.values()) {
entry.apply();
}
}
/**
* Add an entry to clear a query cache.
*/
public void addInvalidate(BeanDescriptor<?> descriptor) {
touchedTables.add(descriptor.getBaseTable());
}
/**
* Add invalidation on a set of tables.
*/
public void addInvalidate(Set<String> tables) {
touchedTables.addAll(tables);
}
/**
@@ -60,6 +88,13 @@ public class CacheChangeSet {
queryCaches.add(descriptor);
}
/**
* Add an entry to clear a bean cache.
*/
public void addClearBean(BeanDescriptor<?> descriptor) {
beanCaches.add(descriptor);
}
/**
* Add many property clear.
*/
@@ -85,18 +120,32 @@ public class CacheChangeSet {
* On bean insert register table for view based entity invalidation.
*/
public void addBeanInsert(String baseTable) {
if (viewEntityInvalidation) {
viewInvalidation.add(baseTable);
}
touchedTables.add(baseTable);
}
/**
* Remove a bean from the cache.
*/
public <T> void addBeanRemove(BeanDescriptor<T> desc, Object id) {
entries.add(new CacheChangeBeanRemove(desc, id));
if (viewEntityInvalidation) {
viewInvalidation.add(desc.getBaseTable());
CacheChangeBeanRemove entry = beanRemoveMap.get(desc);
if (entry != null) {
entry.addId(id);
} else {
beanRemoveMap.put(desc, new CacheChangeBeanRemove(id, desc));
touchedTables.add(desc.getBaseTable());
}
}
/**
* Remove a bean from the cache.
*/
public <T> void addBeanRemoveMany(BeanDescriptor<T> desc, Collection<Object> ids) {
CacheChangeBeanRemove entry = beanRemoveMap.get(desc);
if (entry != null) {
entry.addIds(ids);
} else {
beanRemoveMap.put(desc, new CacheChangeBeanRemove(desc, ids));
touchedTables.add(desc.getBaseTable());
}
}
@@ -104,10 +153,8 @@ public class CacheChangeSet {
* Update a bean entry.
*/
public <T> void addBeanUpdate(BeanDescriptor<T> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
touchedTables.add(desc.getBaseTable());
entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version));
if (viewEntityInvalidation) {
viewInvalidation.add(desc.getBaseTable());
}
}
/**
@@ -125,6 +172,13 @@ public class CacheChangeSet {
return manyChangeMap.computeIfAbsent(key, ManyChange::new);
}
/**
* Return the modification timestamp for these changes.
*/
public long modificationTimestamp() {
return modificationTimestamp;
}
/**
* Changes for a specific many property.
*/
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.cache;
import io.ebean.cache.QueryCacheEntryValidate;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.config.CurrentTenantProvider;
@@ -19,6 +20,8 @@ public class CacheManagerOptions {
private CurrentTenantProvider currentTenantProvider;
private QueryCacheEntryValidate queryCacheEntryValidate;
private ServerCacheFactory cacheFactory = new DefaultServerCacheFactory();
private ServerCacheOptions beanDefault = new ServerCacheOptions();
private ServerCacheOptions queryDefault = new ServerCacheOptions();
@@ -45,8 +48,9 @@ public class CacheManagerOptions {
return this;
}
public CacheManagerOptions with(ServerCacheFactory cacheFactory) {
public CacheManagerOptions with(ServerCacheFactory cacheFactory, QueryCacheEntryValidate queryCacheEntryValidate) {
this.cacheFactory = cacheFactory;
this.queryCacheEntryValidate = queryCacheEntryValidate;
return this;
}
@@ -82,4 +86,8 @@ public class CacheManagerOptions {
public ClusterManager getClusterManager() {
return clusterManager;
}
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
return queryCacheEntryValidate;
}
}
@@ -2,7 +2,9 @@ package io.ebeaninternal.server.cache;
import io.ebean.annotation.CacheBeanTuning;
import io.ebean.annotation.CacheQueryTuning;
import io.ebean.cache.QueryCacheEntryValidate;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheConfig;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheType;
@@ -33,22 +35,14 @@ class DefaultCacheHolder {
private final CurrentTenantProvider tenantProvider;
DefaultCacheHolder(CacheManagerOptions builder) {
this(builder.getCacheFactory(), builder.getBeanDefault(), builder.getQueryDefault(), builder.getCurrentTenantProvider());
}
private final QueryCacheEntryValidate queryCacheEntryValidate;
/**
* Create with a cache factory and default cache options.
*
* @param cacheFactory the factory for creating the cache
* @param beanDefault the default options for tuning bean caches
* @param queryDefault the default options for tuning query caches
*/
DefaultCacheHolder(ServerCacheFactory cacheFactory, ServerCacheOptions beanDefault, ServerCacheOptions queryDefault, CurrentTenantProvider tenantProvider) {
this.cacheFactory = cacheFactory;
this.beanDefault = beanDefault;
this.queryDefault = queryDefault;
this.tenantProvider = tenantProvider;
DefaultCacheHolder(CacheManagerOptions builder) {
this.cacheFactory = builder.getCacheFactory();
this.beanDefault = builder.getBeanDefault();
this.queryDefault = builder.getQueryDefault();
this.tenantProvider = builder.getCurrentTenantProvider();
this.queryCacheEntryValidate = builder.getQueryCacheEntryValidate();
}
ServerCache getCache(Class<?> beanType, String cacheKey, ServerCacheType type) {
@@ -76,7 +70,7 @@ class DefaultCacheHolder {
collectIdCaches.computeIfAbsent(beanType.getName(), s -> new ConcurrentSkipListSet<>()).add(key);
}
}
return cacheFactory.createCache(type, key, tenantProvider, options);
return cacheFactory.createCache(new ServerCacheConfig(type, key, options, tenantProvider, queryCacheEntryValidate));
}
void clearAll() {
@@ -2,10 +2,8 @@ package io.ebeaninternal.server.cache;
import io.ebean.BackgroundExecutor;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheStatistics;
import io.ebean.cache.TenantAwareKey;
import io.ebean.config.CurrentTenantProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,7 +13,6 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
@@ -65,47 +62,14 @@ public class DefaultServerCache implements ServerCache {
protected TenantAwareKey tenantAwareKey;
/**
* Construct using a ConcurrentHashMap and cache options.
*/
public DefaultServerCache(String name, CurrentTenantProvider tenantProvider, ServerCacheOptions options) {
this(name, new ConcurrentHashMap<>(), tenantProvider, options);
}
/**
* Construct passing in name, map and base eviction controls as ServerCacheOptions.
*/
public DefaultServerCache(String name, Map<Object, CacheEntry> map, CurrentTenantProvider tenantProvider, ServerCacheOptions options) {
this(name, map, tenantProvider, options.getMaxSize(), options.getMaxIdleSecs(), options.getMaxSecsToLive(), options.getTrimFrequency());
}
/**
* Construct passing in name, map and base eviction controls.
*/
public DefaultServerCache(String name, Map<Object, CacheEntry> map, CurrentTenantProvider tenantProvider, int maxSize, int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
this.name = name;
this.map = map;
this.maxSize = maxSize;
this.tenantAwareKey = new TenantAwareKey(tenantProvider);
this.maxIdleSecs = maxIdleSecs;
this.maxSecsToLive = maxSecsToLive;
this.trimFrequency = determineTrim(maxIdleSecs, maxSecsToLive, trimFrequency);
}
/**
* Determine a good trimFrequency as half of maxIdleSecs (or maxSecsToLive).
*/
int determineTrim(int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
if (trimFrequency > 0) {
return trimFrequency;
}
if (maxIdleSecs > 0) {
return maxIdleSecs / 2 - 1;
}
if (maxSecsToLive > 0) {
return maxSecsToLive / 2 - 1;
}
return 0;
public DefaultServerCache(DefaultServerCacheConfig config) {
this.name = config.getName();
this.map = config.getMap();
this.maxSize = config.getMaxSize();
this.tenantAwareKey = new TenantAwareKey(config.getTenantProvider());
this.maxIdleSecs = config.getMaxIdleSecs();
this.maxSecsToLive = config.getMaxSecsToLive();
this.trimFrequency = config.determineTrimFrequency();
}
public void periodicTrim(BackgroundExecutor executor) {
@@ -193,7 +157,7 @@ public class DefaultServerCache implements ServerCache {
/**
* Return the tenant aware key.
*/
private Object key(Object id) {
protected Object key(Object id) {
return tenantAwareKey.key(id);
}
@@ -203,7 +167,7 @@ public class DefaultServerCache implements ServerCache {
@Override
public Object get(Object id) {
CacheEntry entry = map.get(key(id));
CacheEntry entry = getCacheEntry(id);
if (entry == null) {
missCount.increment();
return null;
@@ -212,10 +176,24 @@ public class DefaultServerCache implements ServerCache {
// Important that hitCount.increment() MUST be low latency under concurrent
// use hence must use LongAdder or better here
hitCount.increment();
return entry.getValue();
return unwrapEntry(entry);
}
}
/**
* Unwrap the cache entry - override for query cache to unwrap to the query result.
*/
protected Object unwrapEntry(CacheEntry entry) {
return entry.getValue();
}
/**
* Get the cache entry - override for query cache to validate dependent tables.
*/
protected CacheEntry getCacheEntry(Object id) {
return map.get(key(id));
}
@Override
public void putAll(Map<Object, Object> keyValues) {
keyValues.forEach(this::put);
@@ -0,0 +1,80 @@
package io.ebeaninternal.server.cache;
import io.ebean.cache.QueryCacheEntryValidate;
import io.ebean.cache.ServerCacheConfig;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.config.CurrentTenantProvider;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DefaultServerCacheConfig {
private final ServerCacheConfig config;
private int maxSize;
private int maxIdleSecs;
private int maxSecsToLive;
private int trimFrequency;
private Map<Object, DefaultServerCache.CacheEntry> map;
public DefaultServerCacheConfig(ServerCacheConfig config) {
this(config, new ConcurrentHashMap<>());
}
public DefaultServerCacheConfig(ServerCacheConfig config, Map<Object, DefaultServerCache.CacheEntry> map) {
this.config = config;
this.map = map;
ServerCacheOptions options = config.getCacheOptions();
this.maxIdleSecs = options.getMaxIdleSecs();
this.maxSecsToLive = options.getMaxSecsToLive();
this.trimFrequency = options.getTrimFrequency();
this.maxSize = options.getMaxSize();
}
public CurrentTenantProvider getTenantProvider() {
return config.getTenantProvider();
}
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
return config.getQueryCacheEntryValidate();
}
public String getName() {
return config.getCacheKey();
}
public Map<Object, DefaultServerCache.CacheEntry> getMap() {
return map;
}
public int getMaxSize() {
return maxSize;
}
public int getMaxIdleSecs() {
return maxIdleSecs;
}
public int getMaxSecsToLive() {
return maxSecsToLive;
}
/**
* Determine a good trimFrequency as half of maxIdleSecs (or maxSecsToLive).
*/
public int determineTrimFrequency() {
if (trimFrequency > 0) {
return trimFrequency;
}
if (maxIdleSecs > 0) {
return maxIdleSecs / 2 - 1;
}
if (maxSecsToLive > 0) {
return maxSecsToLive / 2 - 1;
}
return 0;
}
}
@@ -2,10 +2,10 @@ package io.ebeaninternal.server.cache;
import io.ebean.BackgroundExecutor;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheConfig;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheType;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheNotify;
/**
@@ -18,25 +18,43 @@ class DefaultServerCacheFactory implements ServerCacheFactory {
/**
* Construct when l2 cache is disabled.
*/
public DefaultServerCacheFactory() {
DefaultServerCacheFactory() {
this.executor = null;
}
/**
* Construct with executor service.
*/
public DefaultServerCacheFactory(BackgroundExecutor executor) {
DefaultServerCacheFactory(BackgroundExecutor executor) {
this.executor = executor;
}
@Override
public ServerCache createCache(ServerCacheType type, String cacheKey, CurrentTenantProvider tenantProvider, ServerCacheOptions cacheOptions) {
public ServerCache createCache(ServerCacheConfig config) {
DefaultServerCache cache = new DefaultServerCache(cacheKey, tenantProvider, cacheOptions);
DefaultServerCache cache;
if (config.isQueryCache()) {
// use a server cache aware of extra validation and QueryCacheEntry
cache = new DefaultServerQueryCache(new DefaultServerCacheConfig(config));
} else {
cache = new DefaultServerCache(new DefaultServerCacheConfig(config));
}
if (executor != null) {
cache.periodicTrim(executor);
}
return cache;
}
@Override
public ServerCacheNotify createCacheNotify(ServerCacheNotify listener) {
return new NoopServerCacheNotify();
}
private static class NoopServerCacheNotify implements ServerCacheNotify {
@Override
public void notify(ServerCacheNotification notification) {
// do nothing
}
}
}
@@ -0,0 +1,41 @@
package io.ebeaninternal.server.cache;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.cache.QueryCacheEntryValidate;
/**
* Server cache for query caching.
* <p>
* Entries in this cache contain QueryCacheEntry and we need to additionally
* validate the entries when hit for changes to dependent tables.
* </p>
*/
public class DefaultServerQueryCache extends DefaultServerCache {
private final QueryCacheEntryValidate queryCacheEntryValidate;
public DefaultServerQueryCache(DefaultServerCacheConfig config) {
super(config);
this.queryCacheEntryValidate = config.getQueryCacheEntryValidate();
}
protected Object unwrapEntry(CacheEntry entry) {
return ((QueryCacheEntry) entry.getValue()).getValue();
}
@Override
protected CacheEntry getCacheEntry(Object id) {
Object key = key(id);
CacheEntry entry = map.get(key);
if (entry == null) {
return null;
}
QueryCacheEntry value = (QueryCacheEntry) entry.getValue();
if (!queryCacheEntryValidate.isValid(value)) {
map.remove(key);
removeCount.increment();
return null;
}
return entry;
}
}
@@ -1,9 +1,9 @@
package io.ebeaninternal.server.cache;
import io.ebeaninternal.server.cluster.BinaryMessage;
import io.ebeaninternal.server.cluster.BinaryMessageList;
import io.ebeaninternal.api.BinaryReadContext;
import io.ebeaninternal.api.BinaryWritable;
import io.ebeaninternal.api.BinaryWriteContext;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
@@ -12,7 +12,7 @@ import java.util.List;
/**
* Cache events broadcast across the cluster.
*/
public class RemoteCacheEvent {
public class RemoteCacheEvent implements BinaryWritable {
private boolean clearAll;
@@ -53,10 +53,10 @@ public class RemoteCacheEvent {
@Override
public String toString() {
return "clearAll:" + clearAll + " caches:" + clearCaches;
return "CacheEvent[ clearAll:" + clearAll + " caches:" + clearCaches + "]";
}
public static RemoteCacheEvent readBinaryMessage(DataInput dataInput) throws IOException {
public static RemoteCacheEvent readBinaryMessage(BinaryReadContext dataInput) throws IOException {
boolean clearAll = dataInput.readBoolean();
int size = dataInput.readInt();
@@ -72,13 +72,9 @@ public class RemoteCacheEvent {
return new RemoteCacheEvent(clearAll, clearCache);
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
int bufferSize = (clearCaches == null) ? 0 : clearCaches.size() * 30;
BinaryMessage msg = new BinaryMessage(bufferSize + 10);
DataOutputStream os = msg.getOs();
os.writeInt(BinaryMessage.TYPE_CACHE);
@Override
public void writeBinary(BinaryWriteContext out) throws IOException {
DataOutputStream os = out.start(TYPE_CACHE);
os.writeBoolean(clearAll);
if (clearCaches == null) {
os.writeInt(0);
@@ -88,7 +84,5 @@ public class RemoteCacheEvent {
os.writeUTF(cacheName);
}
}
msgList.add(msg);
}
}
@@ -1,59 +0,0 @@
package io.ebeaninternal.server.cluster;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
/**
* Represents a relatively small independent message.
* <p>
* In general terms we break up a potentially large object like
* RemoteTransactionEvent into many smaller BinaryMessages. This is so that if
* they don't all fit on a single Packet we can easily break them up and put
* them on multiple packets.
* </p>
* <p>
* Also note that for the Multicast approach a Packet will generally contain
* many messages each directed to different members of the cluster. So it would
* be common for many Ack, Resend and Control messages to all be contained in a
* single packet.
* </p>
*/
public class BinaryMessage {
public static final int TYPE_MSGCONTROL = 0;
public static final int TYPE_BEANIUD = 1;
public static final int TYPE_TABLEIUD = 2;
public static final int TYPE_CACHE = 3;
public static final int TYPE_MSGACK = 8;
public static final int TYPE_MSGRESEND = 9;
private final ByteArrayOutputStream buffer;
private final DataOutputStream os;
private byte[] bytes;
/**
* Create with an estimated buffer size.
*/
public BinaryMessage(int bufSize) {
this.buffer = new ByteArrayOutputStream(bufSize);
this.os = new DataOutputStream(buffer);
}
/**
* Return the DataOutputStream to write content to.
*/
public DataOutputStream getOs() {
return os;
}
/**
* Return all the content as a byte array.
*/
public byte[] getByteArray() {
if (bytes == null) {
bytes = buffer.toByteArray();
}
return bytes;
}
}
@@ -1,21 +0,0 @@
package io.ebeaninternal.server.cluster;
import java.util.ArrayList;
import java.util.List;
/**
* Holds a List of BinaryMessage's.
*/
public class BinaryMessageList {
final List<BinaryMessage> list = new ArrayList<>();
public void add(BinaryMessage msg) {
list.add(msg);
}
public List<BinaryMessage> getList() {
return list;
}
}
@@ -0,0 +1,41 @@
package io.ebeaninternal.server.cluster;
import io.ebeaninternal.api.BinaryReadContext;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.io.IOException;
/**
* Mechanism to convert RemoteTransactionEvent to/from byte[] content.
*/
public class BinaryTransactionEventReader {
private final ServerLookup serverLookup;
public BinaryTransactionEventReader(ServerLookup serverLookup) {
this.serverLookup = serverLookup;
}
/**
* Read Transaction from bytes.
*/
public RemoteTransactionEvent read(byte[] byteData) throws IOException {
return read(new BinaryReadContext(byteData));
}
/**
* Read Transaction using BinaryReadContext.
*/
public RemoteTransactionEvent read(BinaryReadContext dataInput) throws IOException {
String serverName = dataInput.readUTF();
SpiEbeanServer server = (SpiEbeanServer) serverLookup.getServer(serverName);
if (server == null) {
throw new IllegalStateException("EbeanServer not found for name [" + serverName + "]");
}
RemoteTransactionEvent event = new RemoteTransactionEvent(server);
event.readBinary(dataInput);
return event;
}
}
@@ -13,7 +13,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* Manages the cluster service.
*/
public class ClusterManager {
public class ClusterManager implements ServerLookup {
private static final Logger clusterLogger = LoggerFactory.getLogger("io.ebean.Cluster");
@@ -0,0 +1,14 @@
package io.ebeaninternal.server.cluster;
import io.ebean.EbeanServer;
/**
* Returns EbeanServer instances for remote message reading.
*/
public interface ServerLookup {
/**
* Return the EbeanServer instance by name.
*/
EbeanServer getServer(String name);
}
@@ -12,6 +12,9 @@ public class CacheOptions {
*/
public static final CacheOptions NO_CACHING = new CacheOptions();
public static final CacheOptions INVALIDATE_QUERY_CACHE = new CacheOptions(true);
private final boolean invalidateQueryCache;
private final boolean enableBeanCache;
private final boolean enableQueryCache;
private final boolean readOnly;
@@ -21,6 +24,18 @@ public class CacheOptions {
* Construct for no caching.
*/
private CacheOptions() {
invalidateQueryCache = false;
enableBeanCache = false;
enableQueryCache = false;
readOnly = false;
naturalKey = null;
}
/**
* Construct for invalidateQueryCache.
*/
private CacheOptions(boolean invalidateQueryCache) {
this.invalidateQueryCache = invalidateQueryCache;
enableBeanCache = false;
enableQueryCache = false;
readOnly = false;
@@ -31,12 +46,21 @@ public class CacheOptions {
* Construct with cache annotation.
*/
public CacheOptions(Cache cache, String[] naturalKey) {
invalidateQueryCache = false;
enableBeanCache = cache.enableBeanCache();
enableQueryCache = cache.enableQueryCache();
readOnly = cache.readOnly();
this.naturalKey = naturalKey;
}
/**
* Return true if this is InvalidateQueryCache. A Bean that itself isn't L2
* cached but invalidates query cache entries that join to it.
*/
public boolean isInvalidateQueryCache() {
return invalidateQueryCache;
}
/**
* Return true if bean caching is enabled.
*/
@@ -0,0 +1,29 @@
package io.ebeaninternal.server.core;
import java.time.Clock;
/**
* Wraps the Clock such that we can change the Clock for testing purposes.
*/
public class ClockService {
private Clock clock;
public ClockService(Clock clock) {
this.clock = clock;
}
/**
* Change the clock for testing purposes.
*/
public void setClock(Clock clock) {
this.clock = clock;
}
/**
* Return the Clock current timestamp in millis.
*/
public long nowMillis() {
return clock.millis();
}
}
@@ -1,9 +1,5 @@
package io.ebeaninternal.server.core;
import io.ebean.BackgroundExecutor;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCachePlugin;
import io.ebean.config.ContainerConfig;
import io.ebean.config.ServerConfig;
import io.ebean.config.ServerConfigProvider;
@@ -13,13 +9,8 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.service.SpiContainer;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiContainerBootup;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebeaninternal.server.cache.CacheManagerOptions;
import io.ebeaninternal.server.cache.DefaultServerCacheManager;
import io.ebeaninternal.server.cache.DefaultServerCachePlugin;
import io.ebeaninternal.server.cache.SpiCacheManager;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.core.bootup.BootupClassPathSearch;
import io.ebeaninternal.server.core.bootup.BootupClasses;
@@ -35,7 +26,6 @@ import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
@@ -52,8 +42,6 @@ public class DefaultContainer implements SpiContainer {
public DefaultContainer(ContainerConfig containerConfig) {
invokeBootupPlugin();
this.clusterManager = new ClusterManager(containerConfig);
this.jndiDataSourceFactory = new JndiDataSourceLookup();
@@ -62,12 +50,6 @@ public class DefaultContainer implements SpiContainer {
ShutdownManager.registerContainer(this);
}
private void invokeBootupPlugin() {
for (SpiContainerBootup boot : ServiceLoader.load(SpiContainerBootup.class)) {
boot.bootup();
}
}
@Override
public void shutdown() {
clusterManager.shutdown();
@@ -138,11 +120,10 @@ public class DefaultContainer implements SpiContainer {
// executor and l2 caching service setup early (used during server construction)
SpiBackgroundExecutor executor = createBackgroundExecutor(serverConfig);
SpiCacheManager cacheManager = getCacheManager(online, serverConfig, executor);
InternalConfiguration c = new InternalConfiguration(clusterManager, cacheManager, executor, serverConfig, bootupClasses);
InternalConfiguration c = new InternalConfiguration(online, clusterManager, executor, serverConfig, bootupClasses);
DefaultServer server = new DefaultServer(c, c.cache());
DefaultServer server = new DefaultServer(c, c.cacheManager());
// generate and run DDL if required
// if there are any other tasks requiring action in their plugins, do them as well
@@ -165,52 +146,6 @@ public class DefaultContainer implements SpiContainer {
}
}
/**
* Create and return the CacheManager.
*/
private SpiCacheManager getCacheManager(boolean online, ServerConfig serverConfig, BackgroundExecutor executor) {
if (!online || serverConfig.isDisableL2Cache()) {
// use local only L2 cache implementation as placeholder
return new DefaultServerCacheManager();
}
// reasonable default settings are for a cache per bean type
ServerCacheOptions beanOptions = new ServerCacheOptions();
beanOptions.setMaxSize(serverConfig.getCacheMaxSize());
beanOptions.setMaxIdleSecs(serverConfig.getCacheMaxIdleTime());
beanOptions.setMaxSecsToLive(serverConfig.getCacheMaxTimeToLive());
// reasonable default settings for the query cache per bean type
ServerCacheOptions queryOptions = new ServerCacheOptions();
queryOptions.setMaxSize(serverConfig.getQueryCacheMaxSize());
queryOptions.setMaxIdleSecs(serverConfig.getQueryCacheMaxIdleTime());
queryOptions.setMaxSecsToLive(serverConfig.getQueryCacheMaxTimeToLive());
boolean localL2Caching = false;
ServerCachePlugin plugin = serverConfig.getServerCachePlugin();
if (plugin == null) {
ServiceLoader<ServerCachePlugin> cacheFactories = ServiceLoader.load(ServerCachePlugin.class);
Iterator<ServerCachePlugin> iterator = cacheFactories.iterator();
if (iterator.hasNext()) {
// use the cacheFactory (via classpath service loader)
plugin = iterator.next();
logger.debug("using ServerCacheFactory {}", plugin.getClass());
} else {
// use the built in default l2 caching which is local cache based
localL2Caching = true;
plugin = new DefaultServerCachePlugin();
}
}
ServerCacheFactory factory = plugin.create(serverConfig, executor);
CacheManagerOptions builder = new CacheManagerOptions(clusterManager, serverConfig, localL2Caching)
.with(beanOptions, queryOptions)
.with(factory);
return new DefaultServerCacheManager(builder);
}
/**
* Get the entities, scalarTypes, Listeners etc combining the class registered
* ones with the already created instances.
@@ -154,7 +154,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
/**
* Clock to use for WhenModified and WhenCreated.
*/
private Clock clock;
private ClockService clockService;
private final CallStackFactory callStackFactory;
@@ -283,7 +283,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.beanLoader = new DefaultBeanLoader(this);
this.jsonContext = config.createJsonContext(this);
this.dataTimeZone = config.getDataTimeZone();
this.clock = config.getServerConfig().getClock();
this.clockService = config.getClockService();
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
@@ -515,12 +515,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public long clockNow() {
return clock.millis();
return clockService.nowMillis();
}
@Override
public void setClock(Clock clock) {
this.clock = clock;
this.clockService.setClock(clock);
}
@Override
@@ -1384,7 +1384,18 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
try {
request.initTransIfRequired();
request.markNotQueryOnly();
return request.delete();
if (request.isDeleteByStatement()) {
return request.delete();
} else {
// escalate to fetch the ids of the beans to delete due
// to cascading deletes or l2 caching etc
List<Object> ids = request.findIds();
if (ids.isEmpty()) {
return 0;
} else {
return persister.deleteByIds(request.getBeanDescriptor(), ids, request.getTransaction(), false);
}
}
} finally {
request.endTransIfRequired();
}
@@ -3,7 +3,12 @@ package io.ebeaninternal.server.core;
import com.fasterxml.jackson.core.JsonFactory;
import io.ebean.ExpressionFactory;
import io.ebean.annotation.Platform;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheManager;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.cache.ServerCacheNotifyPlugin;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCachePlugin;
import io.ebean.config.ExternalTransactionManager;
import io.ebean.config.ProfilingConfig;
import io.ebean.config.ServerConfig;
@@ -27,7 +32,10 @@ import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebeaninternal.server.autotune.AutoTuneService;
import io.ebeaninternal.server.autotune.service.AutoTuneServiceFactory;
import io.ebeaninternal.server.cache.CacheManagerOptions;
import io.ebeaninternal.server.cache.DefaultCacheAdapter;
import io.ebeaninternal.server.cache.DefaultServerCacheManager;
import io.ebeaninternal.server.cache.DefaultServerCachePlugin;
import io.ebeaninternal.server.cache.SpiCacheManager;
import io.ebeaninternal.server.changelog.DefaultChangeLogListener;
import io.ebeaninternal.server.changelog.DefaultChangeLogPrepare;
@@ -69,6 +77,7 @@ import io.ebeaninternal.server.transaction.ExplicitTransactionManager;
import io.ebeaninternal.server.transaction.ExternalTransactionScopeManager;
import io.ebeaninternal.server.transaction.JtaTransactionManager;
import io.ebeaninternal.server.transaction.NoopProfileHandler;
import io.ebeaninternal.server.transaction.TableModState;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.transaction.TransactionManagerOptions;
import io.ebeaninternal.server.transaction.TransactionScopeManager;
@@ -84,6 +93,7 @@ import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
@@ -96,6 +106,10 @@ public class InternalConfiguration {
private static final Logger logger = LoggerFactory.getLogger(InternalConfiguration.class);
private final TableModState tableModState;
private final boolean online;
private final ServerConfig serverConfig;
private final BootupClasses bootupClasses;
@@ -108,6 +122,8 @@ public class InternalConfiguration {
private final DtoBeanManager dtoBeanManager;
private final ClockService clockService;
private final DataTimeZone dataTimeZone;
private final Binder binder;
@@ -124,6 +140,12 @@ public class InternalConfiguration {
private final SpiCacheManager cacheManager;
private final ServerCachePlugin serverCachePlugin;
private ServerCacheNotify cacheNotify;
private boolean localL2Caching;
private final ExpressionFactory expressionFactory;
private final SpiBackgroundExecutor backgroundExecutor;
@@ -141,17 +163,18 @@ public class InternalConfiguration {
private final SpiLogManager logManager;
public InternalConfiguration(ClusterManager clusterManager,
SpiCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
public InternalConfiguration(boolean online, ClusterManager clusterManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses) {
this.online = online;
this.serverConfig = serverConfig;
this.clockService = new ClockService(serverConfig.getClock());
this.tableModState = new TableModState(clockService);
this.logManager = initLogManager();
this.docStoreFactory = initDocStoreFactory(serverConfig.service(DocStoreFactory.class));
this.jsonFactory = serverConfig.getJsonFactory();
this.clusterManager = clusterManager;
this.backgroundExecutor = backgroundExecutor;
this.cacheManager = cacheManager;
this.bootupClasses = bootupClasses;
this.databasePlatform = serverConfig.getDatabasePlatform();
@@ -164,6 +187,9 @@ public class InternalConfiguration {
this.deployCreateProperties = new DeployCreateProperties(typeManager);
this.deployUtil = new DeployUtil(typeManager, serverConfig);
this.serverCachePlugin = initServerCachePlugin();
this.cacheManager = initCacheManager();
InternalConfigXmlRead xmlRead = new InternalConfigXmlRead(serverConfig);
this.dtoBeanManager = new DtoBeanManager(typeManager, xmlRead.readDtoMapping());
@@ -211,6 +237,10 @@ public class InternalConfiguration {
return docStoreFactory;
}
public ClockService getClockService() {
return clockService;
}
/**
* Check if this is a SpiServerPlugin and if so 'collect' it to give the complete list
* later on the DefaultServer for late call to configure().
@@ -400,7 +430,8 @@ public class InternalConfiguration {
TransactionManagerOptions options =
new TransactionManagerOptions(notifyL2CacheInForeground, serverConfig, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager);
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
tableModState, cacheNotify, clockService);
if (serverConfig.isExplicitTransactionBeginMode()) {
return new ExplicitTransactionManager(options);
@@ -493,7 +524,7 @@ public class InternalConfiguration {
return dataTimeZone;
}
public ServerCacheManager cache() {
public ServerCacheManager cacheManager() {
return new DefaultCacheAdapter(cacheManager);
}
@@ -537,4 +568,62 @@ public class InternalConfiguration {
public SpiLogManager getLogManager() {
return logManager;
}
private ServerCachePlugin initServerCachePlugin() {
ServerCachePlugin plugin = serverConfig.getServerCachePlugin();
if (plugin == null) {
ServiceLoader<ServerCachePlugin> cacheFactories = ServiceLoader.load(ServerCachePlugin.class);
Iterator<ServerCachePlugin> iterator = cacheFactories.iterator();
if (iterator.hasNext()) {
// use the cacheFactory (via classpath service loader)
plugin = iterator.next();
logger.debug("using ServerCacheFactory {}", plugin.getClass());
} else {
// use the built in default l2 caching which is local cache based
localL2Caching = true;
plugin = new DefaultServerCachePlugin();
}
}
return plugin;
}
/**
* Create and return the CacheManager.
*/
private SpiCacheManager initCacheManager() {
if (!online || serverConfig.isDisableL2Cache()) {
// use local only L2 cache implementation as placeholder
return new DefaultServerCacheManager();
}
ServerCacheFactory factory = serverCachePlugin.create(serverConfig, backgroundExecutor);
ServerCacheNotifyPlugin notifyPlugin = serverConfig.service(ServerCacheNotifyPlugin.class);
if (notifyPlugin != null) {
// plugin supplied so use that to send notifications
cacheNotify = notifyPlugin.create(serverConfig);
} else {
cacheNotify = factory.createCacheNotify(tableModState);
}
// reasonable default settings are for a cache per bean type
ServerCacheOptions beanOptions = new ServerCacheOptions();
beanOptions.setMaxSize(serverConfig.getCacheMaxSize());
beanOptions.setMaxIdleSecs(serverConfig.getCacheMaxIdleTime());
beanOptions.setMaxSecsToLive(serverConfig.getCacheMaxTimeToLive());
// reasonable default settings for the query cache per bean type
ServerCacheOptions queryOptions = new ServerCacheOptions();
queryOptions.setMaxSize(serverConfig.getQueryCacheMaxSize());
queryOptions.setMaxIdleSecs(serverConfig.getQueryCacheMaxIdleTime());
queryOptions.setMaxSecsToLive(serverConfig.getQueryCacheMaxTimeToLive());
CacheManagerOptions builder = new CacheManagerOptions(clusterManager, serverConfig, localL2Caching)
.with(beanOptions, queryOptions)
.with(factory, tableModState);
return new DefaultServerCacheManager(builder);
}
}
@@ -8,6 +8,7 @@ import io.ebean.Version;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.common.BeanList;
import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.event.BeanFindController;
@@ -83,6 +84,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private boolean inlineCountDistinct;
private Set<String> dependentTables;
/**
* Create the InternalQueryRequest.
*/
@@ -113,6 +116,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
}
@Override
public boolean isDeleteByStatement() {
return beanDescriptor.isDeleteByStatement();
}
@Override
public boolean isMultiValueIdSupported() {
return beanDescriptor.isMultiValueIdSupported();
@@ -207,7 +215,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
/**
* Prepare the query and calculate the query plan key.
*/
public void prepareQuery() {
void prepareQuery() {
beanDescriptor.prepareQuery(query);
adapterPreQuery();
this.secondaryQueries = query.convertJoins();
@@ -381,8 +389,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
private int notifyCache(int rows, boolean update) {
if (rows > 0 && beanDescriptor.isCaching()) {
transaction.getEvent().add(beanDescriptor.getBaseTable(), false, update, !update);
if (rows > 0) {
beanDescriptor.cacheUpdateQuery(update, transaction);
}
return rows;
}
@@ -680,8 +688,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
}
public void putToQueryCache(Object queryResult) {
beanDescriptor.queryCachePut(cacheKey, queryResult);
public void putToQueryCache(Object result) {
// use transaction start where as query statement start would be better at READ_COMMITTED
long asOfTimestamp = transaction.getStartMillis();
beanDescriptor.queryCachePut(cacheKey, new QueryCacheEntry(result, dependentTables, asOfTimestamp));
}
/**
@@ -752,4 +762,13 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
public boolean isInlineCountDistinct() {
return inlineCountDistinct;
}
public void addDependentTables(Set<String> tables) {
if (tables != null && !tables.isEmpty()) {
if (dependentTables == null) {
dependentTables = new LinkedHashSet<>();
}
dependentTables.addAll(tables);
}
}
}
@@ -92,7 +92,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
* Return true if this persist request should use JDBC batch.
*/
public boolean isBatchThisRequest() {
return transaction.isBatchThisRequest(type);
return transaction.isBatchThisRequest();
}
/**
@@ -356,7 +356,15 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private boolean flushBatchOnGetter(int propertyIndex) {
// propertyIndex of -1 the Id property, no flush for get Id on UPDATE
return propertyIndex == -1 ? type == Type.INSERT : beanDescriptor.isGeneratedProperty(propertyIndex);
if (propertyIndex == -1) {
if (beanDescriptor.isIdLoaded(intercept)) {
return false;
} else {
return type == Type.INSERT;
}
} else {
return beanDescriptor.isGeneratedProperty(propertyIndex);
}
}
public void setSkipBatchForTopLevel() {
@@ -450,14 +458,14 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
if (notifyCache) {
switch (type) {
case INSERT:
beanDescriptor.cacheHandleInsert(this, changeSet);
beanDescriptor.cachePersistInsert(this, changeSet);
break;
case UPDATE:
beanDescriptor.cacheHandleUpdate(idValue, this, changeSet);
beanDescriptor.cachePersistUpdate(idValue, this, changeSet);
break;
case DELETE:
case DELETE_SOFT:
beanDescriptor.cacheHandleDelete(idValue, this, changeSet);
beanDescriptor.cachePersistDelete(idValue, this, changeSet);
break;
default:
throw new IllegalStateException("Invalid type " + type);
@@ -1194,7 +1202,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
/**
* Set the value of the Version property on the bean.
*/
public void setVersionValue(Object versionValue) {
private void setVersionValue(Object versionValue) {
version = beanDescriptor.setVersion(entityBean, versionValue);
}
@@ -63,6 +63,11 @@ public interface Persister {
*/
int deleteMany(Class<?> beanType, Collection<?> ids, Transaction transaction, boolean permanent);
/**
* Delete multiple beans when escalated from a delete query.
*/
int deleteByIds(BeanDescriptor<?> descriptor, List<Object> idList, Transaction transaction, boolean permanent);
/**
* Execute the Update.
*/
@@ -171,4 +171,9 @@ public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T>, DocQueryRequ
* Set profile location for "find all" if not set.
*/
void profileLocationAll();
/**
* Return true if delete by statement is allowed for this type given cascade rules etc.
*/
boolean isDeleteByStatement();
}
@@ -1,12 +1,12 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.query.CQueryCollectionAdd;
@@ -59,7 +59,7 @@ public interface BeanCollectionHelp<T> extends CQueryCollectionAdd<T> {
/**
* Refresh the List Set or Map.
*/
void refresh(EbeanServer server, Query<?> query, Transaction t, EntityBean parentBean);
void refresh(SpiEbeanServer server, Query<?> query, Transaction t, EntityBean parentBean);
/**
* Apply the new refreshed BeanCollection to the appropriate property of the parent bean.
@@ -11,6 +11,7 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.config.EncryptKey;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.IdType;
@@ -39,6 +40,7 @@ import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.api.LoadContext;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiUpdatePlan;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.api.json.SpiJsonReader;
@@ -289,6 +291,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
protected final InheritInfo inheritInfo;
private final boolean abstractType;
/**
* Derived list of properties that make up the unique id.
*/
@@ -544,7 +548,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.whenCreatedProperty = findWhenCreatedProperty();
// derive the index position of the Id and Version properties
if (Modifier.isAbstract(beanType.getModifiers())) {
this.abstractType = Modifier.isAbstract(beanType.getModifiers());
if (abstractType) {
this.idPropertyIndex = -1;
this.versionPropertyIndex = -1;
this.unloadProperties = new int[0];
@@ -651,6 +656,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return ebeanServer;
}
/**
* Return true if this is an abstract type.
*/
public boolean isAbstractType() {
return abstractType;
}
/**
* Return true if this is a "Doc Store only" entity bean.
*/
@@ -827,8 +839,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
@SuppressWarnings("unchecked")
public void initialiseDocMapping() {
for (BeanPropertyAssocMany<?> aPropertiesMany : propertiesMany) {
aPropertiesMany.initialisePostTarget();
for (BeanPropertyAssocMany<?> many : propertiesMany) {
many.initialisePostTarget();
}
for (BeanPropertyAssocOne<?> one : propertiesOne) {
one.initialisePostTarget();
}
if (inheritInfo != null && !inheritInfo.isRoot()) {
docStoreAdapter = (DocStoreBeanAdapter<T>) inheritInfo.getRoot().desc().docStoreAdapter();
@@ -1361,15 +1376,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Put a query result into the query cache.
*/
public void queryCachePut(Object id, Object queryResult) {
cacheHelp.queryCachePut(id, queryResult);
}
/**
* Add a query cache clear into the changeSet.
*/
public void queryCacheClear(CacheChangeSet changeSet) {
cacheHelp.queryCacheClear(changeSet);
public void queryCachePut(Object id, QueryCacheEntry entry) {
cacheHelp.queryCachePut(id, entry);
}
/**
@@ -1469,10 +1477,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
/**
* Remove a bean from the cache given its Id.
* Remove a collection of beans from the cache given the ids.
*/
public void cacheHandleDeleteById(Object id) {
cacheHelp.beanCacheRemove(id);
public void cacheApplyInvalidate(Collection<Object> ids) {
cacheHelp.beanCacheApplyInvalidate(ids);
}
/**
@@ -1503,44 +1511,51 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
/**
* Invalidate parts of cache due to SqlUpdate or external modification etc.
* Check if bulk update or delete query has a cache impact.
*/
public void cacheHandleBulkUpdate(TableIUD tableIUD) {
cacheHelp.handleBulkUpdate(tableIUD);
public void cacheUpdateQuery(boolean update, SpiTransaction transaction) {
cacheHelp.cacheUpdateQuery(update, transaction);
}
/**
* Handle a delete by id request adding an cache change into the changeSet.
* Invalidate parts of cache due to SqlUpdate or external modification etc.
*/
public void cacheHandleDeleteById(Object id, CacheChangeSet changeSet) {
cacheHelp.handleDelete(id, changeSet);
public void cachePersistTableIUD(TableIUD tableIUD, CacheChangeSet changeSet) {
cacheHelp.persistTableIUD(tableIUD, changeSet);
}
/**
* Remove a bean from the cache given its Id.
*/
public void cacheHandleDelete(Object id, PersistRequestBean<T> deleteRequest, CacheChangeSet changeSet) {
cacheHelp.handleDelete(id, deleteRequest, changeSet);
public void cachePersistDeleteByIds(Collection<Object> ids, CacheChangeSet changeSet) {
cacheHelp.persistDeleteIds(ids, changeSet);
}
/**
* Remove a bean from the cache given its Id.
*/
public void cachePersistDelete(Object id, PersistRequestBean<T> deleteRequest, CacheChangeSet changeSet) {
cacheHelp.persistDelete(id, deleteRequest, changeSet);
}
/**
* Add the insert changes to the changeSet.
*/
public void cacheHandleInsert(PersistRequestBean<T> insertRequest, CacheChangeSet changeSet) {
cacheHelp.handleInsert(insertRequest, changeSet);
public void cachePersistInsert(PersistRequestBean<T> insertRequest, CacheChangeSet changeSet) {
cacheHelp.persistInsert(insertRequest, changeSet);
}
/**
* Add the update to the changeSet.
*/
public void cacheHandleUpdate(Object id, PersistRequestBean<T> updateRequest, CacheChangeSet changeSet) {
cacheHelp.handleUpdate(id, updateRequest, changeSet);
public void cachePersistUpdate(Object id, PersistRequestBean<T> updateRequest, CacheChangeSet changeSet) {
cacheHelp.persistUpdate(id, updateRequest, changeSet);
}
/**
* Apply the update to the cache.
*/
public void cacheBeanUpdate(Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
public void cacheApplyBeanUpdate(Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
cacheHelp.cacheBeanUpdate(id, changes, updateNaturalKey, version);
}
@@ -3145,6 +3160,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
public boolean hasIdPropertyOnly(EntityBeanIntercept ebi) {
return ebi.hasIdOnly(idPropertyIndex);
}
public boolean isIdLoaded(EntityBeanIntercept ebi) {
return ebi.isLoadedProperty(idPropertyIndex);
}
public boolean hasIdValue(EntityBean bean) {
return (idProperty != null && !DmlUtil.isNullOrZero(idProperty.getValue(bean)));
@@ -3428,11 +3447,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException {
return jsonHelp.jsonRead(jsonRead, path);
return jsonHelp.jsonRead(jsonRead, path, true);
}
protected T jsonReadObject(SpiJsonReader jsonRead, String path) throws IOException {
return jsonHelp.jsonReadObject(jsonRead, path);
public T jsonReadObject(SpiJsonReader jsonRead, String path) throws IOException {
return jsonHelp.jsonRead(jsonRead, path, false);
}
public List<BeanProperty[]> getUniqueProps() {
@@ -4,8 +4,10 @@ import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.cache.ServerCache;
import io.ebeaninternal.api.BeanCacheResult;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cache.CachedBeanData;
@@ -54,6 +56,7 @@ final class BeanDescriptorCacheHelp<T> {
* Flag indicating this bean has no relationships.
*/
private final boolean cacheSharableBeans;
private final boolean invalidateQueryCache;
private final Class<?> beanType;
@@ -66,6 +69,8 @@ final class BeanDescriptorCacheHelp<T> {
private final ServerCache naturalKeyCache;
private final ServerCache queryCache;
private final boolean noCaching;
/**
* Set to true if all persist changes need to notify the cache.
*/
@@ -84,6 +89,7 @@ final class BeanDescriptorCacheHelp<T> {
this.cacheName = beanType.getSimpleName();
this.cacheManager = cacheManager;
this.cacheOptions = cacheOptions;
this.invalidateQueryCache = cacheOptions.isInvalidateQueryCache();
this.cacheSharableBeans = cacheSharableBeans;
this.propertiesOneImported = propertiesOneImported;
this.naturalKey = cacheOptions.getNaturalKey();
@@ -105,13 +111,14 @@ final class BeanDescriptorCacheHelp<T> {
this.beanCache = null;
this.naturalKeyCache = null;
}
this.noCaching = (beanCache == null && queryCache == null);
}
/**
* Derive the cache notify flags.
*/
void deriveNotifyFlags() {
cacheNotifyOnAll = (beanCache != null || queryCache != null);
cacheNotifyOnAll = (invalidateQueryCache || beanCache != null || queryCache != null);
cacheNotifyOnDelete = !cacheNotifyOnAll && isNotifyOnDeletes();
if (logger.isDebugEnabled()) {
@@ -129,7 +136,7 @@ final class BeanDescriptorCacheHelp<T> {
*/
private boolean isNotifyOnDeletes() {
for (BeanPropertyAssocOne<?> imported : propertiesOneImported) {
if (imported.isCacheNotify()) {
if (imported.isCacheNotifyRelationship()) {
return true;
}
}
@@ -225,14 +232,14 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Put a query result into the query cache.
*/
void queryCachePut(Object id, Object queryResult) {
void queryCachePut(Object id, QueryCacheEntry entry) {
if (queryCache == null) {
throw new IllegalStateException("No query cache enabled on " + desc + ". Need explicit @Cache(enableQueryCache=true)");
}
if (queryLog.isDebugEnabled()) {
queryLog.debug(" PUT {}({})", cacheName, id);
}
queryCache.put(id, queryResult);
queryCache.put(id, entry);
}
@@ -680,12 +687,12 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Remove a bean from the cache given its Id.
*/
void beanCacheRemove(Object id) {
void beanCacheApplyInvalidate(Collection<Object> ids) {
if (beanCache != null) {
if (beanLog.isDebugEnabled()) {
beanLog.debug(" REMOVE {}({})", cacheName, id);
beanLog.debug(" REMOVE {}({})", cacheName, ids);
}
beanCache.remove(id);
beanCache.removeAll(new HashSet<>(ids));
}
for (BeanPropertyAssocOne<?> imported : propertiesOneImported) {
imported.cacheClear();
@@ -719,34 +726,53 @@ final class BeanDescriptorCacheHelp<T> {
return true;
}
void cacheUpdateQuery(boolean update, SpiTransaction transaction) {
if (invalidateQueryCache || cacheNotifyOnAll || (!update && cacheNotifyOnDelete)) {
transaction.getEvent().add(desc.getBaseTable(), false, update, !update);
}
}
/**
* Add appropriate cache changes to support delete by id.
*/
void handleDelete(Object id, CacheChangeSet changeSet) {
if (beanCache != null) {
changeSet.addBeanRemove(desc, id);
void persistDeleteIds(Collection<Object> ids, CacheChangeSet changeSet) {
if (invalidateQueryCache) {
changeSet.addInvalidate(desc);
} else {
queryCacheClear(changeSet);
if (beanCache != null) {
changeSet.addBeanRemoveMany(desc, ids);
}
cacheDeleteImported(true, null, changeSet);
}
cacheDeleteImported(true, null, changeSet);
}
/**
* Add appropriate cache changes to support delete bean.
*/
void handleDelete(Object id, PersistRequestBean<T> deleteRequest, CacheChangeSet changeSet) {
queryCacheClear(changeSet);
if (beanCache != null) {
changeSet.addBeanRemove(desc, id);
void persistDelete(Object id, PersistRequestBean<T> deleteRequest, CacheChangeSet changeSet) {
if (invalidateQueryCache) {
changeSet.addInvalidate(desc);
} else {
queryCacheClear(changeSet);
if (beanCache != null) {
changeSet.addBeanRemove(desc, id);
}
cacheDeleteImported(true, deleteRequest.getEntityBean(), changeSet);
}
cacheDeleteImported(true, deleteRequest.getEntityBean(), changeSet);
}
/**
* Add appropriate cache changes to support insert.
*/
void handleInsert(PersistRequestBean<T> insertRequest, CacheChangeSet changeSet) {
queryCacheClear(changeSet);
cacheDeleteImported(false, insertRequest.getEntityBean(), changeSet);
changeSet.addBeanInsert(desc.getBaseTable());
void persistInsert(PersistRequestBean<T> insertRequest, CacheChangeSet changeSet) {
if (invalidateQueryCache) {
changeSet.addInvalidate(desc);
} else {
queryCacheClear(changeSet);
cacheDeleteImported(false, insertRequest.getEntityBean(), changeSet);
changeSet.addBeanInsert(desc.getBaseTable());
}
}
private void cacheDeleteImported(boolean clear, EntityBean entityBean, CacheChangeSet changeSet) {
@@ -758,44 +784,54 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Add appropriate changes to support update.
*/
void handleUpdate(Object id, PersistRequestBean<T> updateRequest, CacheChangeSet changeSet) {
void persistUpdate(Object id, PersistRequestBean<T> updateRequest, CacheChangeSet changeSet) {
if (invalidateQueryCache) {
changeSet.addInvalidate(desc);
queryCacheClear(changeSet);
} else {
queryCacheClear(changeSet);
if (beanCache == null) {
// query caching only
return;
}
if (beanCache == null) {
// query caching only
return;
}
List<BeanPropertyAssocMany<?>> manyCollections = updateRequest.getUpdatedManyCollections();
if (manyCollections != null) {
for (BeanPropertyAssocMany<?> many : manyCollections) {
if (!many.isElementCollection()) {
Object details = many.getValue(updateRequest.getEntityBean());
CachedManyIds entry = createManyIds(many, details);
if (entry != null) {
changeSet.addManyPut(desc, many.getName(), id, entry);
List<BeanPropertyAssocMany<?>> manyCollections = updateRequest.getUpdatedManyCollections();
if (manyCollections != null) {
for (BeanPropertyAssocMany<?> many : manyCollections) {
if (!many.isElementCollection()) {
Object details = many.getValue(updateRequest.getEntityBean());
CachedManyIds entry = createManyIds(many, details);
if (entry != null) {
changeSet.addManyPut(desc, many.getName(), id, entry);
}
}
}
}
updateRequest.addBeanUpdate(changeSet);
}
updateRequest.addBeanUpdate(changeSet);
}
/**
* Invalidate parts of cache due to SqlUpdate or external modification etc.
*/
void handleBulkUpdate(TableIUD tableIUD) {
void persistTableIUD(TableIUD tableIUD, CacheChangeSet changeSet) {
if (invalidateQueryCache) {
changeSet.addInvalidate(desc);
return;
}
if (noCaching) {
return;
}
changeSet.addInvalidate(desc);
// inserts don't invalidate the bean cache
if (tableIUD.isUpdateOrDelete()) {
beanCacheClear();
changeSet.addClearBean(desc);
}
// any change invalidates the query cache
queryCacheClear();
changeSet.addClearQuery(desc);
// any change invalidates the collection IDs cache
for (BeanPropertyAssocOne<?> imported : propertiesOneImported) {
imported.cacheClear();
imported.cacheClear(changeSet);
}
}
@@ -3,6 +3,8 @@ package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.ebean.bean.EntityBean;
import io.ebean.text.json.EJson;
import io.ebeaninternal.api.json.SpiJsonReader;
@@ -12,18 +14,18 @@ import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
public class BeanDescriptorJsonHelp<T> {
class BeanDescriptorJsonHelp<T> {
private final BeanDescriptor<T> desc;
private final InheritInfo inheritInfo;
public BeanDescriptorJsonHelp(BeanDescriptor<T> desc) {
BeanDescriptorJsonHelp(BeanDescriptor<T> desc) {
this.desc = desc;
this.inheritInfo = desc.inheritInfo;
}
public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean, String key) throws IOException {
void jsonWrite(SpiJsonWriter writeJson, EntityBean bean, String key) throws IOException {
writeJson.writeStartObject(key);
@@ -42,13 +44,11 @@ public class BeanDescriptorJsonHelp<T> {
writeJson.writeEndObject();
}
protected void jsonWriteProperties(SpiJsonWriter writeJson, EntityBean bean) throws IOException {
void jsonWriteProperties(SpiJsonWriter writeJson, EntityBean bean) {
writeJson.writeBean(desc, bean);
}
public void jsonWriteDirty(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
void jsonWriteDirty(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
if (inheritInfo == null) {
jsonWriteDirtyProperties(writeJson, bean, dirtyProps);
} else {
@@ -56,7 +56,7 @@ public class BeanDescriptorJsonHelp<T> {
}
}
protected void jsonWriteDirtyProperties(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
void jsonWriteDirtyProperties(SpiJsonWriter writeJson, EntityBean bean, boolean[] dirtyProps) throws IOException {
writeJson.writeStartObject(null);
// render the dirty properties
@@ -70,7 +70,7 @@ public class BeanDescriptorJsonHelp<T> {
}
@SuppressWarnings("unchecked")
public T jsonRead(SpiJsonReader jsonRead, String path) throws IOException {
T jsonRead(SpiJsonReader jsonRead, String path, boolean withInheritance) throws IOException {
JsonParser parser = jsonRead.getParser();
//noinspection StatementWithEmptyBody
@@ -87,43 +87,39 @@ public class BeanDescriptorJsonHelp<T> {
}
}
if (desc.inheritInfo == null) {
if (desc.inheritInfo == null || !withInheritance) {
return jsonReadObject(jsonRead, path);
}
ObjectNode node = jsonRead.getObjectMapper().readTree(parser);
if (node.isNull()) {
return null;
}
JsonParser newParser = node.traverse();
SpiJsonReader newReader = jsonRead.forJson(newParser, false);
// check for the discriminator value to determine the correct sub type
String discColumn = inheritInfo.getRoot().getDiscriminatorColumn();
if (parser.nextToken() != JsonToken.FIELD_NAME) {
String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?";
throw new JsonParseException(parser, msg, parser.getCurrentLocation());
}
String propName = parser.getCurrentName();
if (!propName.equalsIgnoreCase(discColumn)) {
// just try to assume this is the correct bean type in the inheritance
BeanProperty property = desc.getBeanProperty(propName);
if (property != null) {
EntityBean bean = desc.createEntityBean();
property.jsonRead(jsonRead, bean);
return jsonReadProperties(jsonRead, bean, path);
JsonNode discNode = node.get(discColumn);
if (discNode == null || discNode.isNull()) {
if (!desc.isAbstractType()) {
return desc.jsonReadObject(newReader, path);
}
String msg = "Error reading inheritance discriminator, expected property [" + discColumn + "] but got [" + propName + "] ?";
throw new JsonParseException(parser, msg, parser.getCurrentLocation());
String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?";
throw new JsonParseException(newParser, msg, parser.getCurrentLocation());
}
String discValue = parser.nextTextValue();
return (T) inheritInfo.readType(discValue).desc().jsonReadObject(jsonRead, path);
return (T) inheritInfo.readType(discNode.asText()).desc().jsonReadObject(newReader, path);
}
protected T jsonReadObject(SpiJsonReader readJson, String path) throws IOException {
private T jsonReadObject(SpiJsonReader readJson, String path) throws IOException {
EntityBean bean = desc.createEntityBeanForJson();
return jsonReadProperties(readJson, bean, path);
}
@SuppressWarnings("unchecked")
protected T jsonReadProperties(SpiJsonReader readJson, EntityBean bean, String path) throws IOException {
private T jsonReadProperties(SpiJsonReader readJson, EntityBean bean, String path) throws IOException {
if (path != null) {
readJson.pushPath(path);
@@ -25,6 +25,7 @@ import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.ConcurrencyMode;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.TransactionEventTable;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cache.SpiCacheManager;
import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.core.InternalConfiguration;
@@ -447,24 +448,23 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
/**
* For SQL based modifications we need to invalidate appropriate parts of the
* cache.
* For SQL based modifications we need to invalidate appropriate parts of the cache.
*/
public void cacheNotify(TransactionEventTable.TableIUD tableIUD) {
public void cacheNotify(TransactionEventTable.TableIUD tableIUD, CacheChangeSet changeSet) {
String tableName = tableIUD.getTableName().toLowerCase();
List<BeanDescriptor<?>> normalBeanTypes = tableToDescMap.get(tableName);
if (normalBeanTypes != null) {
// 'normal' entity beans based on a "base table"
for (BeanDescriptor<?> normalBeanType : normalBeanTypes) {
normalBeanType.cacheHandleBulkUpdate(tableIUD);
normalBeanType.cachePersistTableIUD(tableIUD, changeSet);
}
}
List<BeanDescriptor<?>> viewBeans = tableToViewDescMap.get(tableName);
if (viewBeans != null) {
// entity beans based on a "view"
for (BeanDescriptor<?> viewBean : viewBeans) {
viewBean.cacheHandleBulkUpdate(tableIUD);
viewBean.cachePersistTableIUD(tableIUD, changeSet);
}
}
}
@@ -1,12 +1,12 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanList;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
@@ -64,7 +64,7 @@ public class BeanListHelp<T> extends BaseCollectionHelp<T> {
}
@Override
public void refresh(EbeanServer server, Query<?> query, Transaction t, EntityBean parentBean) {
public void refresh(SpiEbeanServer server, Query<?> query, Transaction t, EntityBean parentBean) {
BeanList<?> newBeanList = (BeanList<?>) server.findList(query, t);
refresh(newBeanList, parentBean);
@@ -1,12 +1,12 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanMap;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
@@ -124,7 +124,7 @@ public class BeanMapHelp<T> extends BaseCollectionHelp<T> {
}
@Override
public void refresh(EbeanServer server, Query<?> query, Transaction t, EntityBean parentBean) {
public void refresh(SpiEbeanServer server, Query<?> query, Transaction t, EntityBean parentBean) {
BeanMap<?, ?> newBeanMap = (BeanMap<?, ?>) server.findMap(query, t);
refresh(newBeanMap, parentBean);
}
@@ -357,14 +357,34 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
* Return true if this association is updateable.
*/
public boolean isUpdateable() {
return tableJoin.columns().length <= 0 || tableJoin.columns()[0].isUpdateable();
TableJoinColumn[] columns = tableJoin.columns();
if (columns.length <= 0) {
return true;
}
for (TableJoinColumn column : columns) {
if (column.isUpdateable()) {
// at least 1 is updatable
return true;
}
}
return false;
}
/**
* Return true if this association is insertable.
*/
public boolean isInsertable() {
return tableJoin.columns().length <= 0 || tableJoin.columns()[0].isInsertable();
TableJoinColumn[] columns = tableJoin.columns();
if (columns.length <= 0) {
return true;
}
for (TableJoinColumn column : columns) {
if (column.isInsertable()) {
// at least 1 is insertable
return true;
}
}
return false;
}
/**
@@ -443,16 +463,18 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
String matchColumn = col.getForeignDbColumn();
String localColumn = col.getLocalDbColumn();
String localSqlFormula = col.getLocalSqlFormula();
boolean insertable = col.isInsertable();
boolean updateable = col.isUpdateable();
for (int j = 0; j < props.length; j++) {
if (props[j].getDbColumn().equalsIgnoreCase(matchColumn)) {
return new ImportedIdSimple(owner, localColumn, localSqlFormula, props[j], j);
return new ImportedIdSimple(owner, localColumn, localSqlFormula, props[j], j, insertable, updateable);
}
}
for (int j = 0; j < others.length; j++) {
if (others[j].getDbColumn().equalsIgnoreCase(matchColumn)) {
return new ImportedIdSimple(owner, localColumn, localSqlFormula, others[j], j + props.length);
return new ImportedIdSimple(owner, localColumn, localSqlFormula, others[j], j + props.length, insertable, updateable);
}
}
@@ -213,6 +213,12 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
// by default not including "Many" properties in document store
}
@Override
public void registerColumn(BeanDescriptor<?> desc, String prefix) {
if (targetDescriptor != null) {
desc.registerTable(targetDescriptor.getBaseTable(), this);
}
}
/**
* Return the underlying collection of beans.
*/
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
@@ -8,6 +7,7 @@ import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.api.json.SpiJsonWriter;
@@ -56,6 +56,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
private String deleteByParentIdInSql;
private BeanPropertyAssocMany<?> relationshipProperty;
private boolean cacheNotifyRelationship;
/**
* Create based on deploy information of an EmbeddedId.
@@ -127,6 +128,13 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
}
/**
* Derive late in lifecycle cache notification on this relationship.
*/
public void initialisePostTarget() {
this.cacheNotifyRelationship = isCacheNotifyRelationship();
}
/**
* Return the property value as an entity bean.
*/
@@ -141,25 +149,31 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
/**
* Return true if this relationship needs to maintain/update L2 cache.
*/
boolean isCacheNotify() {
return targetDescriptor.isBeanCaching() && relationshipProperty != null;
boolean isCacheNotifyRelationship() {
return relationshipProperty != null && targetDescriptor.isBeanCaching();
}
/**
* Clear the L2 relationship cache for this property.
*/
void cacheClear() {
if (isCacheNotify()) {
if (cacheNotifyRelationship) {
targetDescriptor.cacheManyPropClear(relationshipProperty.getName());
}
}
void cacheClear(CacheChangeSet changeSet) {
if (cacheNotifyRelationship) {
changeSet.addManyClear(targetDescriptor, relationshipProperty.getName());
}
}
/**
* Clear part of the L2 relationship cache for this property.
*/
void cacheDelete(boolean clear, EntityBean bean, CacheChangeSet changeSet) {
if (isCacheNotify()) {
if (cacheNotifyRelationship) {
if (clear) {
changeSet.addManyClear(targetDescriptor, relationshipProperty.getName());
} else {
@@ -238,7 +252,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
String rawWhere = deriveWhereParentIdSql(false);
EbeanServer server = server();
SpiEbeanServer server = server();
Query<?> q = server.find(getPropertyType());
bindParentIdEq(rawWhere, parentId, q);
return server.findIds(q, t);
@@ -250,7 +264,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
String inClause = targetIdBinder.getIdInValueExpr(false, parentIds.size());
String expr = rawWhere + inClause;
EbeanServer server = server();
SpiEbeanServer server = server();
Query<?> q = server.find(getPropertyType());
bindParentIdsIn(expr, parentIds, q);
@@ -1,12 +1,12 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanSet;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
@@ -69,7 +69,7 @@ public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
}
@Override
public void refresh(EbeanServer server, Query<?> query, Transaction t, EntityBean parentBean) {
public void refresh(SpiEbeanServer server, Query<?> query, Transaction t, EntityBean parentBean) {
BeanSet<?> newBeanSet = (BeanSet<?>) server.findSet(query, t);
refresh(newBeanSet, parentBean);
@@ -187,6 +187,6 @@ public abstract class DeployParser {
}
private boolean isWordStart(char ch) {
return Character.isLetter(ch);
return Character.isLetter(ch) || ch == UNDERSCORE;
}
}
@@ -151,7 +151,6 @@ public class InheritInfo {
* Set the descriptor for this node.
*/
public void setDescriptor(BeanDescriptor<?> descriptor) {
this.descriptor = descriptor;
}
@@ -64,8 +64,11 @@ public class ImportedIdEmbedded implements ImportedId {
@Override
public void dmlAppend(GenerateDmlRequest request) {
boolean update = request.isUpdate();
for (ImportedIdSimple anImported : imported) {
request.appendColumn(anImported.localDbColumn);
if (anImported.isInclude(update)) {
request.appendColumn(anImported.localDbColumn);
}
}
}
@@ -100,30 +103,28 @@ public class ImportedIdEmbedded implements ImportedId {
@Override
public Object bind(BindableRequest request, EntityBean bean) throws SQLException {
Object embeddedId = null;
if (bean != null) {
embeddedId = foreignAssocOne.getValue(bean);
}
Object embeddedId = (bean == null) ? null : foreignAssocOne.getValue(bean);
boolean update = request.isUpdate();
if (embeddedId == null) {
for (ImportedIdSimple anImported : imported) {
if (anImported.owner.isUpdateable()) {
if (anImported.isInclude(update)) {
request.bind(null, anImported.foreignProperty);
}
}
// return anything non-null to skip a derived relationship update
return Object.class;
} else {
EntityBean embedded = (EntityBean) embeddedId;
for (ImportedIdSimple anImported : imported) {
if (anImported.owner.isUpdateable()) {
if (anImported.isInclude(update)) {
Object scalarValue = anImported.foreignProperty.getValue(embedded);
request.bind(scalarValue, anImported.foreignProperty);
}
}
return embedded;
}
// hmmm, not worrying about this just yet
return null;
}
@Override
@@ -47,15 +47,32 @@ public final class ImportedIdSimple implements ImportedId, Comparable<ImportedId
protected final int position;
public ImportedIdSimple(BeanPropertyAssoc<?> owner, String localDbColumn, String localSqlFormula, BeanProperty foreignProperty, int position) {
/**
* If true include in insert.
*/
private final boolean insertable;
/**
* If true include in update.
*/
private final boolean updateable;
public ImportedIdSimple(BeanPropertyAssoc<?> owner, String localDbColumn, String localSqlFormula, BeanProperty foreignProperty, int position,
boolean insertable, boolean updateable) {
this.owner = owner;
this.localDbColumn = InternString.intern(localDbColumn);
this.localSqlFormula = InternString.intern(localSqlFormula);
this.foreignProperty = foreignProperty;
this.position = position;
this.insertable = insertable;
this.updateable = updateable;
this.logicalName = InternString.intern(owner.getName() + "." + foreignProperty.getName());
}
public ImportedIdSimple(BeanPropertyAssoc<?> owner, String localDbColumn, String localSqlFormula, BeanProperty foreignProperty, int position) {
this(owner, localDbColumn, localSqlFormula, foreignProperty, position, true, true);
}
/**
* Return the list as an array sorted into the same order as the Bean Properties.
*/
@@ -68,6 +85,13 @@ public final class ImportedIdSimple implements ImportedId, Comparable<ImportedId
return importedIds;
}
/**
* Return true if it should be included in the update (or insert).
*/
public boolean isInclude(boolean update) {
return (update) ? updateable : insertable;
}
@Override
public boolean equals(Object obj) {
// remove FindBugs warning
@@ -440,6 +440,13 @@ public class DeployBeanDescriptor<T> {
this.inheritInfo = inheritInfo;
}
/**
* Set that this type invalidates query caches.
*/
public void setInvalidateQueryCache() {
this.cacheOptions = CacheOptions.INVALIDATE_QUERY_CACHE;
}
/**
* Enable L2 bean and query caching based on Cache annotation.
*/
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorMap;
import io.ebeaninternal.server.deploy.BeanProperty;
@@ -11,6 +12,7 @@ import io.ebeaninternal.server.deploy.BeanPropertySimpleCollection;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.properties.BeanPropertySetter;
import io.ebeaninternal.server.type.ScalarTypeString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,6 +28,8 @@ public class DeployBeanPropertyLists {
private static final Logger logger = LoggerFactory.getLogger(DeployBeanPropertyLists.class);
private static final NoopSetter NOOP_SETTER = new NoopSetter();
private BeanProperty versionProperty;
private BeanProperty unmappedJson;
@@ -100,6 +104,7 @@ public class DeployBeanPropertyLists {
discDeployProp.setDiscriminator();
discDeployProp.setName(discriminatorColumn);
discDeployProp.setDbColumn(discriminatorColumn);
discDeployProp.setSetter(NOOP_SETTER);
// only register it in the propertyMap. This might not be used if
// an explicit property is mapped to the discriminator on the bean
@@ -491,4 +496,17 @@ public class DeployBeanPropertyLists {
return new BeanProperty(desc, deployProp);
}
private static class NoopSetter implements BeanPropertySetter {
@Override
public void set(EntityBean bean, Object value) {
// do nothing
}
@Override
public void setIntercept(EntityBean bean, Object value) {
// do nothing
}
}
}
@@ -28,6 +28,7 @@ import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.EnumType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
@@ -245,6 +246,10 @@ class AnnotationAssocManys extends AnnotationParser {
}
ScalarType<?> valueScalarType = util.getTypeManager().getScalarType(elementType);
if (valueScalarType == null && elementType.isEnum()) {
Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>)elementType;
valueScalarType = util.getTypeManager().createEnumScalarType(enumClass, EnumType.STRING);
}
boolean scalar = true;
if (valueScalarType == null) {
@@ -275,6 +280,10 @@ class AnnotationAssocManys extends AnnotationParser {
}
private void setElementProperty(DeployBeanProperty elementProp, String name, String dbColumn, int sortOrder) {
if (dbColumn == null) {
dbColumn = "value";
}
elementProp.setName(name);
elementProp.setDbColumn(dbColumn);
elementProp.setNullable(false);
@@ -295,6 +304,12 @@ class AnnotationAssocManys extends AnnotationParser {
private void readJoinTable(JoinTable joinTable, DeployBeanPropertyAssocMany<?> prop) {
String intTableName = getFullTableName(joinTable);
if (intTableName.isEmpty()) {
BeanTable localTable = factory.getBeanTable(descriptor.getBeanType());
BeanTable otherTable = factory.getBeanTable(prop.getTargetType());
intTableName = getM2MJoinTableName(localTable, otherTable);
}
// set the intersection table
DeployTableJoin intJoin = new DeployTableJoin();
intJoin.setTable(intTableName);
@@ -116,6 +116,10 @@ public class AnnotationAssocOnes extends AnnotationParser {
prop.setFetchPreference(fetchPreference.value());
}
io.ebean.annotation.NotNull nonNull = get(prop, io.ebean.annotation.NotNull.class);
if (nonNull != null) {
prop.setNullable(false);
}
if (validationAnnotations) {
NotNull notNull = get(prop, NotNull.class);
if (notNull != null && isEbeanValidationGroups(notNull.groups())) {
@@ -7,6 +7,7 @@ import io.ebean.annotation.Draftable;
import io.ebean.annotation.DraftableElement;
import io.ebean.annotation.History;
import io.ebean.annotation.Index;
import io.ebean.annotation.InvalidateQueryCache;
import io.ebean.annotation.ReadAudit;
import io.ebean.annotation.UpdateMode;
import io.ebean.annotation.View;
@@ -181,9 +182,16 @@ public class AnnotationClass extends AnnotationParser {
descriptor.setUpdateChangesOnly(updateMode.updateChangesOnly());
}
Cache cache = AnnotationUtil.findAnnotationRecursive(cls, Cache.class);
if (cache != null && !disableL2Cache) {
descriptor.setCache(cache);
if (!disableL2Cache) {
Cache cache = AnnotationUtil.findAnnotationRecursive(cls, Cache.class);
if (cache != null) {
descriptor.setCache(cache);
} else {
InvalidateQueryCache invalidateQueryCache = AnnotationUtil.findAnnotationRecursive(cls, InvalidateQueryCache.class);
if (invalidateQueryCache != null) {
descriptor.setInvalidateQueryCache();
}
}
}
Set<NamedQuery> namedQueries = AnnotationUtil.findAnnotationsRecursive(cls, NamedQuery.class);
@@ -18,6 +18,7 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.type.DataEncryptSupport;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeArray;
import io.ebeaninternal.server.type.ScalarTypeWrapper;
import io.ebeaninternal.server.type.SimpleAesEncryptor;
import io.ebeaninternal.server.type.TypeManager;
import org.slf4j.Logger;
@@ -116,8 +117,8 @@ public class DeployUtil {
throw new IllegalArgumentException("Class [" + enumType + "] is Not a Enum?");
}
try {
Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>) enumType;
EnumType type = enumerated != null ? enumerated.value() : null;
Class<? extends Enum<?>> enumClass = (Class<? extends Enum<?>>) enumType;
EnumType type = enumerated != null ? enumerated.value() : null;
ScalarType<?> scalarType = typeManager.createEnumScalarType(enumClass, type);
prop.setScalarType(scalarType);
prop.setDbType(scalarType.getJdbcType());
@@ -278,19 +279,25 @@ public class DeployUtil {
*/
public void setLobType(DeployBeanProperty prop) {
// is String or byte[] ? used to determine if its a CLOB or BLOB
Class<?> type = prop.getPropertyType();
ScalarType<?> scalarType = prop.getScalarType();
// this also sets the lob flag on DeployBeanProperty
int lobType = isClobType(type) ? dbCLOBType : dbBLOBType;
if (scalarType instanceof ScalarTypeWrapper) {
int lobType = scalarType.getJdbcType() == Types.VARCHAR ? dbCLOBType : dbBLOBType;
prop.setDbType(lobType);
} else {
// is String or byte[] ? used to determine if its a CLOB or BLOB
Class<?> type = prop.getPropertyType();
// this also sets the lob flag on DeployBeanProperty
int lobType = isClobType(type) ? dbCLOBType : dbBLOBType;
ScalarType<?> scalarType = typeManager.getScalarType(type, lobType);
if (scalarType == null) {
// this should never occur actually
throw new RuntimeException("No ScalarType for LOB type [" + type + "] [" + lobType + "]");
scalarType = typeManager.getScalarType(type, lobType);
if (scalarType == null) {
// this should never occur actually
throw new RuntimeException("No ScalarType for LOB type [" + type + "] [" + lobType + "]");
}
prop.setDbType(lobType);
prop.setScalarType(scalarType);
}
prop.setDbType(lobType);
prop.setScalarType(scalarType);
}
private boolean isClobType(Class<?> type) {
@@ -6,6 +6,7 @@ import io.ebean.DtoQuery;
import io.ebean.Expression;
import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
import io.ebean.FetchGroup;
import io.ebean.FetchPath;
import io.ebean.FutureIds;
import io.ebean.FutureList;
@@ -460,6 +461,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.select(fetchProperties);
}
@Override
public Query<T> select(FetchGroup fetchGroup) {
return query.select(fetchGroup);
}
@Override
public Query<T> setDistinct(boolean distinct) {
return query.setDistinct(distinct);
@@ -5,6 +5,7 @@ import io.ebean.CountDistinctOrder;
import io.ebean.DtoQuery;
import io.ebean.Expression;
import io.ebean.ExpressionList;
import io.ebean.FetchGroup;
import io.ebean.FetchPath;
import io.ebean.FutureIds;
import io.ebean.FutureList;
@@ -759,6 +760,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.select(properties);
}
@Override
public Query<T> select(FetchGroup fetchGroup) {
return exprList.select(fetchGroup);
}
@Override
public Query<T> setDistinct(boolean distinct) {
return exprList.setDistinct(distinct);
@@ -23,7 +23,7 @@ abstract class LogicExpression implements SpiExpression {
static class And extends LogicExpression {
And(Expression expOne, Expression expTwo) {
super(AND, expOne, expTwo);
super(true, expOne, expTwo);
}
@Override
@@ -36,7 +36,7 @@ abstract class LogicExpression implements SpiExpression {
static class Or extends LogicExpression {
Or(Expression expOne, Expression expTwo) {
super(OR, expOne, expTwo);
super(false, expOne, expTwo);
}
@Override
@@ -45,14 +45,14 @@ abstract class LogicExpression implements SpiExpression {
}
}
protected SpiExpression expOne;
SpiExpression expOne;
protected SpiExpression expTwo;
SpiExpression expTwo;
private final String joinType;
private final boolean conjunction;
LogicExpression(String joinType, Expression expOne, Expression expTwo) {
this.joinType = joinType;
LogicExpression(boolean conjunction, Expression expOne, Expression expTwo) {
this.conjunction = conjunction;
this.expOne = (SpiExpression) expOne;
this.expTwo = (SpiExpression) expTwo;
}
@@ -71,7 +71,6 @@ abstract class LogicExpression implements SpiExpression {
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
boolean conjunction = joinType.equals(AND);
context.startBool(conjunction ? Junction.Type.AND : Junction.Type.OR);
expOne.writeDocQuery(context);
expTwo.writeDocQuery(context);
@@ -107,8 +106,19 @@ abstract class LogicExpression implements SpiExpression {
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins manyWhereJoin) {
// get the current state for 'require outer joins'
boolean parentOuterJoins = manyWhereJoin.isRequireOuterJoins();
if (!conjunction) {
// turn on outer joins required for disjunction expressions
manyWhereJoin.setRequireOuterJoins(true);
}
expOne.containsMany(desc, manyWhereJoin);
expTwo.containsMany(desc, manyWhereJoin);
if (!conjunction && !parentOuterJoins) {
// restore state to not forcing outer joins
manyWhereJoin.setRequireOuterJoins(false);
}
}
@Override
@@ -128,7 +138,7 @@ abstract class LogicExpression implements SpiExpression {
request.append("(");
expOne.addSql(request);
request.append(joinType);
request.append(conjunction ? AND : OR);
expTwo.addSql(request);
request.append(") ");
}
@@ -144,7 +154,7 @@ abstract class LogicExpression implements SpiExpression {
*/
@Override
public void queryPlanHash(StringBuilder builder) {
builder.append("Logic").append(joinType).append("[");
builder.append("Logic").append(conjunction ? AND : OR).append("[");
expOne.queryPlanHash(builder);
builder.append(",");
expTwo.queryPlanHash(builder);
@@ -680,6 +680,12 @@ public final class DefaultPersister implements Persister {
return delete(descriptor, id, null, transaction, deleteMode);
}
@Override
public int deleteByIds(BeanDescriptor<?> descriptor, List<Object> idList, Transaction transaction, boolean permanent) {
DeleteMode deleteMode = (permanent || !descriptor.isSoftDelete()) ? DeleteMode.HARD : DeleteMode.SOFT;
return delete(descriptor, null, idList, transaction, deleteMode);
}
/**
* Delete by Id or a List of Id's.
*/
@@ -739,18 +745,20 @@ public final class DefaultPersister implements Persister {
// OneToMany's with delete cascade
BeanPropertyAssocMany<?>[] manys = descriptor.propertiesManyDelete();
for (BeanPropertyAssocMany<?> many : manys) {
BeanDescriptor<?> targetDesc = many.getTargetDescriptor();
// only cascade soft deletes when supported by target
if (deleteMode.isHard() || targetDesc.isSoftDelete()) {
if (deleteMode.isHard() && targetDesc.isDeleteByStatement()) {
// we can just delete children with a single statement
SqlUpdate sqlDelete = many.deleteByParentId(id, idList);
executeSqlUpdate(sqlDelete, t);
} else {
// we need to fetch the Id's to delete (recurse or notify L2 cache)
List<Object> childIds = many.findIdsByParentId(id, idList, t, null);
if (!childIds.isEmpty()) {
delete(targetDesc, null, childIds, t, deleteMode);
if (!many.isManyToMany()) {
BeanDescriptor<?> targetDesc = many.getTargetDescriptor();
// only cascade soft deletes when supported by target
if (deleteMode.isHard() || targetDesc.isSoftDelete()) {
if (deleteMode.isHard() && targetDesc.isDeleteByStatement()) {
// we can just delete children with a single statement
SqlUpdate sqlDelete = many.deleteByParentId(id, idList);
executeSqlUpdate(sqlDelete, t);
} else {
// we need to fetch the Id's to delete (recurse or notify L2 cache)
List<Object> childIds = many.findIdsByParentId(id, idList, t, null);
if (!childIds.isEmpty()) {
delete(targetDesc, null, childIds, t, deleteMode);
}
}
}
}
@@ -14,11 +14,16 @@ public class DeleteHandler extends DmlHandler {
private final DeleteMeta meta;
public DeleteHandler(PersistRequestBean<?> persist, DeleteMeta meta) {
DeleteHandler(PersistRequestBean<?> persist, DeleteMeta meta) {
super(persist, meta.isEmptyStringAsNull());
this.meta = meta;
}
@Override
public boolean isUpdate() {
return false;
}
/**
* Generate and bind the delete statement.
*/
@@ -50,11 +50,6 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
protected String sql;
/**
* The generated value for the @Version property. Must be set after where clause is bound.
*/
protected Object versionValue;
protected DmlHandler(PersistRequestBean<?> persistRequest, boolean emptyStringToNull) {
this.now = System.currentTimeMillis();
this.persistRequest = persistRequest;
@@ -231,30 +226,6 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
prop.bind(dataBind, value);
}
/**
* Register a generated value on a update. This can not be set to the bean
* until after the where clause has been bound for concurrency checking.
* <p>
* GeneratedProperty values are likely going to be used for optimistic
* concurrency checking. This includes 'counter' and 'update timestamp'
* generation.
* </p>
*/
@Override
public void registerGeneratedVersion(Object versionValue) {
this.versionValue = versionValue;
}
/**
* Set any update generated values to the bean. Must be called after where
* clause has been bound.
*/
public void setUpdateGenValues() {
if (versionValue != null) {
persistRequest.setVersionValue(versionValue);
}
}
/**
* Check with useGeneratedKeys to get appropriate PreparedStatement.
*/
@@ -29,10 +29,6 @@ public class GenerateDmlRequest {
return this;
}
public void appendColumnIsNull(String column) {
appendColumn(column, IS_NULL);
}
public void appendColumn(String column) {
//String bind = (insertMode > 0) ? "?" : "=?";
appendColumn(column, "?");
@@ -91,4 +87,7 @@ public class GenerateDmlRequest {
this.prefix2 = ", ";
}
public boolean isUpdate() {
return insertMode == 0;
}
}
@@ -50,6 +50,11 @@ public class InsertHandler extends DmlHandler {
this.concatinatedKey = meta.isConcatenatedKey();
}
@Override
public boolean isUpdate() {
return false;
}
/**
* Generate and bind the insert statement.
*/
@@ -17,11 +17,16 @@ public class UpdateHandler extends DmlHandler {
private boolean emptySetClause;
public UpdateHandler(PersistRequestBean<?> persist, UpdateMeta meta) {
UpdateHandler(PersistRequestBean<?> persist, UpdateMeta meta) {
super(persist, meta.isEmptyStringAsNull());
this.meta = meta;
}
@Override
public boolean isUpdate() {
return true;
}
/**
* Generate and bind the update statement.
*/
@@ -50,8 +55,6 @@ public class UpdateHandler extends DmlHandler {
if (persistRequest.isBatched()) {
batchedPstmt.registerInputStreams(dataBind.getInputStreams());
}
setUpdateGenValues();
logSql(sql);
}
@@ -42,12 +42,6 @@ public interface BindableRequest {
*/
void bindNoLog(Object value, BeanProperty prop) throws SQLException;
/**
* Register the value from a update GeneratedValue. This can only be set to
* the bean property after the where clause has bean built.
*/
void registerGeneratedVersion(Object value);
/**
* Return the original PersistRequest.
*/
@@ -59,4 +53,8 @@ public interface BindableRequest {
*/
long now();
/**
* Return true if this is an update request.
*/
boolean isUpdate();
}
@@ -19,9 +19,7 @@ public class FactoryAssocOnes {
*/
public void create(List<Bindable> list, BeanDescriptor<?> desc, DmlMode mode) {
BeanPropertyAssocOne<?>[] ones = desc.propertiesOneImported();
for (BeanPropertyAssocOne<?> one : ones) {
for (BeanPropertyAssocOne<?> one : desc.propertiesOneImported()) {
if (!one.isImportedPrimaryKey()) {
switch (mode) {
case INSERT:
@@ -38,6 +38,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* An object that represents a SqlSelect statement.
@@ -811,4 +812,8 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
PreparedStatement getPstmt() {
return pstmt;
}
public Set<String> getDependentTables() {
return queryPlan.getDependentTables();
}
}
@@ -25,6 +25,8 @@ import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -111,6 +113,15 @@ public class CQueryEngine {
if (request.isLogSummary()) {
request.getTransaction().logSummary(rcQuery.getSummary());
}
if (request.isQueryCachePut() && !list.isEmpty()) {
request.addDependentTables(rcQuery.getDependentTables());
list = Collections.unmodifiableList(list);
request.putToQueryCache(list);
if (Boolean.FALSE.equals(request.getQuery().isReadOnly())) {
list = new ArrayList<>(list);
}
}
return list;
} catch (SQLException e) {
@@ -173,6 +184,11 @@ public class CQueryEngine {
request.getTransaction().end();
}
if (request.isQueryCachePut()) {
request.addDependentTables(rcQuery.getDependentTables());
request.putToQueryCache(count);
}
return count;
} catch (SQLException e) {
@@ -385,6 +401,9 @@ public class CQueryEngine {
}
request.executeSecondaryQueries(false);
if (request.isQueryCachePut()) {
request.addDependentTables(cquery.getDependentTables());
}
return beanCollection;

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