Compare commits

..
Author SHA1 Message Date
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
163 changed files with 2948 additions and 1455 deletions
+5 -5
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.17.5</version>
<version>11.18.3</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.18.3</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>
-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>
-8
View File
@@ -602,8 +602,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 +738,6 @@ public interface Query<T> {
* .findList();
*
* }</pre>
*
* @see EbeanServer#findList(Query, Transaction)
*/
@Nonnull
List<T> findList();
@@ -759,8 +755,6 @@ public interface Query<T> {
* .findSet();
*
* }</pre>
*
* @see EbeanServer#findSet(Query, Transaction)
*/
@Nonnull
Set<T> findSet();
@@ -782,8 +776,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.
*/
+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,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);
}
}
}
@@ -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();
}
@@ -40,7 +40,8 @@ public class MySqlDdl extends PlatformDdl {
@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;
}
@@ -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";
@@ -420,7 +418,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);
}
/**
@@ -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;
@@ -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);
}
@@ -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
@@ -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.
*/
@@ -207,7 +210,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 +384,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 +683,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 +757,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();
}
/**
@@ -450,14 +450,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);
@@ -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);
}
@@ -3428,11 +3443,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,52 @@ 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 {
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 +783,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);
}
@@ -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);
@@ -151,7 +151,6 @@ public class InheritInfo {
* Set the descriptor for this node.
*/
public void setDescriptor(BeanDescriptor<?> descriptor) {
this.descriptor = descriptor;
}
@@ -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);
@@ -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);
@@ -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);
@@ -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;
@@ -17,6 +17,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Base compiled query request for single attribute queries.
@@ -188,4 +189,8 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.getProfileId(), rowCount, query.getProfileId());
}
Set<String> getDependentTables() {
return queryPlan.getDependentTables();
}
}
@@ -24,6 +24,8 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Set;
/**
* Represents a query for a given SQL statement.
@@ -88,6 +90,8 @@ public class CQueryPlan {
*/
private volatile String auditQueryHash;
private final Set<String> dependentTables;
/**
* Create a query plan based on a OrmQueryRequest.
*/
@@ -110,6 +114,7 @@ public class CQueryPlan {
this.logWhereSql = logWhereSql;
this.encryptedProps = sqlTree.getEncryptedProps();
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
this.dependentTables = sqlTree.dependentTables();
}
/**
@@ -134,6 +139,7 @@ public class CQueryPlan {
this.logWhereSql = logWhereSql;
this.encryptedProps = sqlTree.getEncryptedProps();
this.stats = new CQueryPlanStats(this, server.isCollectQueryOrigins());
this.dependentTables = (rawSql) ? Collections.emptySet() : sqlTree.dependentTables();
}
private String location() {
@@ -154,6 +160,10 @@ public class CQueryPlan {
return beanType;
}
public Set<String> getDependentTables() {
return dependentTables;
}
public ProfileLocation getProfileLocation() {
return profileLocation;
}
@@ -12,6 +12,7 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Set;
/**
* Executes the select row count query.
@@ -154,4 +155,8 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.getProfileId(), rowCount, query.getProfileId());
}
Set<String> getDependentTables() {
return queryPlan.getDependentTables();
}
}
@@ -88,41 +88,20 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
public <T> int findCount(OrmQueryRequest<T> request) {
flushJdbcBatchOnQuery(request);
int result = queryEngine.findCount(request);
if (request.getQuery().getUseQueryCache().isPut()) {
request.putToQueryCache(result);
}
return result;
return queryEngine.findCount(request);
}
@Override
public <A> List<A> findIds(OrmQueryRequest<?> request) {
flushJdbcBatchOnQuery(request);
List<A> result = queryEngine.findIds(request);
if (request.getQuery().getUseQueryCache().isPut()) {
result = Collections.unmodifiableList(result);
request.putToQueryCache(result);
if (Boolean.FALSE.equals(request.getQuery().isReadOnly())) {
result = new ArrayList<>(result);
}
}
return result;
return queryEngine.findIds(request);
}
@Override
public <A> List<A> findSingleAttributeList(OrmQueryRequest<?> request) {
flushJdbcBatchOnQuery(request);
List<A> result = queryEngine.findSingleAttributeList(request);
if (!result.isEmpty() && request.getQuery().getUseQueryCache().isPut()) {
// load the query result into the query cache
result = Collections.unmodifiableList(result);
request.putToQueryCache(result);
if (Boolean.FALSE.equals(request.getQuery().isReadOnly())) {
result = new ArrayList<>(result);
}
}
return result;
return queryEngine.findSingleAttributeList(request);
}
@Override
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.query;
import io.ebeaninternal.api.SpiQuery;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -163,4 +164,13 @@ class SqlTree {
boolean isSingleProperty() {
return rootNode.isSingleProperty();
}
/**
* Return the tables that are joined in this query.
*/
Set<String> dependentTables() {
Set<String> tables = new LinkedHashSet<>();
rootNode.dependentTables(tables);
return tables;
}
}
@@ -9,6 +9,7 @@ import io.ebeaninternal.server.type.ScalarType;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
interface SqlTreeNode {
@@ -87,4 +88,9 @@ interface SqlTreeNode {
* Return true if the query is known to only have a single property selected.
*/
boolean isSingleProperty();
/**
* Add dependent tables to the given set.
*/
void dependentTables(Set<String> tables);
}
@@ -21,6 +21,7 @@ import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Normal bean included in the query.
@@ -578,6 +579,14 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
@Override
public void dependentTables(Set<String> tables) {
tables.add(nodeBeanProp.target().getBaseTable(temporalMode));
for (SqlTreeNode child : children) {
child.dependentTables(tables);
}
}
/**
* Join to base table for this node. This includes a join to the intersection
* table if this is a ManyToMany node.
@@ -11,6 +11,7 @@ import io.ebeaninternal.server.type.ScalarType;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* The purpose is to add an extra join to the query.
@@ -100,6 +101,16 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
children.add(child);
}
@Override
public void dependentTables(Set<String> tables) {
tables.add(assocBeanProperty.target().getBaseTable(SpiQuery.TemporalMode.CURRENT));
if (children != null) {
for (SqlTreeNode child : children) {
child.dependentTables(tables);
}
}
}
@Override
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
@@ -7,6 +7,7 @@ import io.ebeaninternal.server.deploy.DbSqlContext;
import java.sql.SQLException;
import java.util.List;
import java.util.Set;
final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
@@ -10,6 +10,7 @@ import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.type.ScalarType;
import java.util.List;
import java.util.Set;
/**
* Join to Many (or child of a many) to support where clause predicates on many properties.
@@ -109,6 +110,11 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
}
}
@Override
public void dependentTables(Set<String> tables) {
tables.add(nodeBeanProp.target().getBaseTable(SpiQuery.TemporalMode.CURRENT));
}
@Override
public void buildRawSqlSelectChain(List<String> selectChain) {
// nothing to add
@@ -5,6 +5,7 @@ import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.TableJoin;
import java.util.List;
import java.util.Set;
/**
* Represents the root node of the Sql Tree.
@@ -77,4 +78,10 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean {
return joinType;
}
@Override
public void dependentTables(Set<String> tables) {
for (SqlTreeNode child : children) {
child.dependentTables(tables);
}
}
}
@@ -47,8 +47,6 @@ public class DJsonContext implements SpiJsonContext {
private final JsonFactory jsonFactory;
private final TypeManager typeManager;
private final Object defaultObjectMapper;
private final JsonConfig.Include defaultInclude;
@@ -57,11 +55,10 @@ public class DJsonContext implements SpiJsonContext {
public DJsonContext(SpiEbeanServer server, JsonFactory jsonFactory, TypeManager typeManager) {
this.server = server;
this.typeManager = typeManager;
this.jsonFactory = (jsonFactory != null) ? jsonFactory : new JsonFactory();
this.defaultObjectMapper = this.server.getServerConfig().getObjectMapper();
this.defaultInclude = this.server.getServerConfig().getJsonInclude();
this.jsonScalar = new DJsonScalar(this.typeManager);
this.jsonScalar = new DJsonScalar(typeManager);
}
@Override
@@ -121,9 +118,8 @@ public class DJsonContext implements SpiJsonContext {
public <T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = getDescriptor(cls);
SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
try {
return desc.jsonRead(readJson, null);
return desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options)), null);
} catch (IOException e) {
throw new JsonIOException(e);
}
@@ -133,8 +129,7 @@ public class DJsonContext implements SpiJsonContext {
public <T> DJsonBeanReader<T> createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = getDescriptor(cls);
SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
return new DJsonBeanReader<>(desc, readJson);
return new DJsonBeanReader<>(desc, new ReadJson(desc, parser, options, determineObjectMapper(options)));
}
@Override
@@ -354,8 +349,7 @@ public class DJsonContext implements SpiJsonContext {
@Override
public SpiJsonWriter createJsonWriter(Writer writer) {
JsonGenerator generator = createGenerator(writer);
return createJsonWriter(generator, null);
return createJsonWriter(createGenerator(writer), null);
}
@Override
@@ -375,9 +369,7 @@ public class DJsonContext implements SpiJsonContext {
gen.writeFieldName(key);
}
gen.writeStartArray();
WriteJson writeJson = createWriteJson(gen, options);
for (T bean : collection) {
BeanDescriptor<?> d = getDescriptor(bean.getClass());
d.jsonWrite(writeJson, (EntityBean) bean, null);
@@ -34,14 +34,12 @@ public final class BeanPersistIdMap {
public void add(BeanDescriptor<?> desc, PersistRequest.Type type, Object id) {
BeanPersistIds r = getPersistIds(desc);
r.addId(type, (Serializable) id);
r.addId(type, id);
}
private BeanPersistIds getPersistIds(BeanDescriptor<?> desc) {
String beanType = desc.getFullName();
BeanPersistIds r = beanMap.computeIfAbsent(beanType, k -> new BeanPersistIds(desc));
return r;
return beanMap.computeIfAbsent(beanType, k -> new BeanPersistIds(desc));
}
}
@@ -1,8 +1,10 @@
package io.ebeaninternal.server.transaction;
import io.ebeaninternal.api.BinaryReadContext;
import io.ebeaninternal.api.BinaryWritable;
import io.ebeaninternal.api.BinaryWriteContext;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.cluster.BinaryMessage;
import io.ebeaninternal.server.cluster.BinaryMessageList;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.id.IdBinder;
@@ -10,7 +12,6 @@ import io.ebeaninternal.server.deploy.id.IdBinder;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@@ -27,15 +28,16 @@ import java.util.List;
* size of data sent around the network.
* </p>
*/
public class BeanPersistIds {
public class BeanPersistIds implements BinaryWritable {
private final BeanDescriptor<?> beanDescriptor;
private final String descriptorId;
private List<Object> insertIds;
private List<Object> updateIds;
private List<Object> deleteIds;
/**
* The ids to invalidate from the cache (updates and deletes).
*/
private List<Object> ids;
/**
* Create the payload.
@@ -45,51 +47,35 @@ public class BeanPersistIds {
this.descriptorId = desc.getDescriptorId();
}
public static BeanPersistIds readBinaryMessage(SpiEbeanServer server, DataInput dataInput) throws IOException {
public static BeanPersistIds readBinaryMessage(SpiEbeanServer server, BinaryReadContext input) throws IOException {
String descriptorId = dataInput.readUTF();
BeanDescriptor<?> desc = server.getBeanDescriptorById(descriptorId);
BeanDescriptor<?> desc = server.getBeanDescriptorById(input.readUTF());
BeanPersistIds bp = new BeanPersistIds(desc);
bp.read(dataInput);
bp.read(input);
return bp;
}
private void read(DataInput dataInput) throws IOException {
private void read(BinaryReadContext dataInput) throws IOException {
IdBinder idBinder = beanDescriptor.getIdBinder();
int iudType = dataInput.readInt();
List<Object> idList = readIdList(dataInput, idBinder);
switch (iudType) {
case 0:
insertIds = idList;
break;
case 1:
updateIds = idList;
break;
case 2:
deleteIds = idList;
break;
default:
throw new RuntimeException("Invalid iudType " + iudType);
}
dataInput.readInt(); // legacy read type
ids = readIdList(dataInput.in(), beanDescriptor.getIdBinder());
}
/**
* Write the contents into a BinaryMessage form.
* <p>
* For a RemoteBeanPersist with a large number of id's note that this is
* broken up into many BinaryMessages each with a maximum of 100 ids. This
* enables the contents of a large RemoteTransactionEvent to be split up
* across multiple Packets.
* </p>
*/
void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
@Override
public void writeBinary(BinaryWriteContext out) throws IOException {
writeIdList(beanDescriptor, 0, insertIds, msgList);
writeIdList(beanDescriptor, 1, updateIds, msgList);
writeIdList(beanDescriptor, 2, deleteIds, msgList);
DataOutputStream os = out.start(TYPE_BEANIUD);
os.writeUTF(descriptorId);
os.writeInt(1); // legacy marker for update
if (ids == null) {
os.writeInt(0);
} else {
os.writeInt(ids.size());
IdBinder idBinder = beanDescriptor.getIdBinder();
for (Object idValue : ids) {
idBinder.writeData(os, idValue);
}
}
}
private List<Object> readIdList(DataInput dataInput, IdBinder idBinder) throws IOException {
@@ -105,132 +91,46 @@ public class BeanPersistIds {
return idList;
}
/**
* Write a BinaryMessage containing the descriptorId, iudType and list of Id
* values.
* <p>
* Note that a given BinaryMessage has a maximum of 100 Ids. This is due to
* the limit of UDP packet sizes. We break up the RemoteBeanPersist into
* potentially many smaller BinaryMessages which may be put into multiple
* Packets.
* </p>
*/
private void writeIdList(BeanDescriptor<?> desc, int iudType, List<Object> idList, BinaryMessageList msgList) throws IOException {
IdBinder idBinder = desc.getIdBinder();
int count = idList == null ? 0 : idList.size();
if (count > 0) {
int loop = 0;
int i = 0;
int eof = idList.size();
do {
++loop;
int endOfLoop = Math.min(eof, loop * 100);
BinaryMessage m = new BinaryMessage(endOfLoop * 4 + 20);
DataOutputStream os = m.getOs();
os.writeInt(BinaryMessage.TYPE_BEANIUD);
os.writeUTF(descriptorId);
os.writeInt(iudType);
os.writeInt(count);
for (; i < endOfLoop; i++) {
idBinder.writeData(os, idList.get(i));
}
os.flush();
msgList.add(m);
} while (i < eof);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("BeanIds[");
if (beanDescriptor != null) {
sb.append(beanDescriptor.getFullName());
} else {
sb.append("descId:").append(descriptorId);
}
if (insertIds != null) {
sb.append(" insertIds:").append(insertIds);
}
if (updateIds != null) {
sb.append(" updateIds:").append(updateIds);
}
if (deleteIds != null) {
sb.append(" deleteIds:").append(deleteIds);
if (ids != null) {
sb.append(" ids:").append(ids);
}
sb.append("]");
return sb.toString();
}
void addId(PersistRequest.Type type, Serializable id) {
switch (type) {
case INSERT:
addInsertId(id);
break;
case UPDATE:
addUpdateId(id);
break;
case DELETE:
case DELETE_SOFT:
addDeleteId(id);
break;
default:
break;
public void addId(PersistRequest.Type type, Object id) {
if (type != PersistRequest.Type.INSERT) {
if (ids == null) {
ids = new ArrayList<>();
}
ids.add(id);
}
}
private void addInsertId(Serializable id) {
if (insertIds == null) {
insertIds = new ArrayList<>();
}
insertIds.add(id);
}
private void addUpdateId(Serializable id) {
if (updateIds == null) {
updateIds = new ArrayList<>();
}
updateIds.add(id);
}
private void addDeleteId(Serializable id) {
if (deleteIds == null) {
deleteIds = new ArrayList<>();
}
deleteIds.add(id);
}
public BeanDescriptor<?> getBeanDescriptor() {
return beanDescriptor;
}
List<Object> getDeleteIds() {
return deleteIds;
public List<Object> getIds() {
return ids;
}
/**
* Notify the cache of this event that came from another server in the cluster.
*/
void notifyCacheAndListener() {
// any change invalidates the query cache
beanDescriptor.clearQueryCache();
if (updateIds != null) {
for (Object id : updateIds) {
beanDescriptor.cacheHandleDeleteById(id);
}
}
if (deleteIds != null) {
for (Object id : deleteIds) {
beanDescriptor.cacheHandleDeleteById(id);
}
public void notifyCache(CacheChangeSet changeSet) {
changeSet.addClearQuery(beanDescriptor);
if (ids != null) {
changeSet.addBeanRemoveMany(beanDescriptor, ids);
}
}
}
@@ -7,7 +7,6 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import io.ebeanservice.docstore.api.support.DocStoreDeleteEvent;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
@@ -22,18 +21,15 @@ public final class DeleteByIdMap {
@Override
public String toString() {
return beanMap.toString();
return "DeleteById[" + beanMap.values() + "]";
}
public void notifyCache(CacheChangeSet changeSet) {
for (BeanPersistIds deleteIds : beanMap.values()) {
BeanDescriptor<?> d = deleteIds.getBeanDescriptor();
List<Object> idValues = deleteIds.getDeleteIds();
List<Object> idValues = deleteIds.getIds();
if (idValues != null) {
d.queryCacheClear(changeSet);
for (Object idValue : idValues) {
d.cacheHandleDeleteById(idValue, changeSet);
}
d.cachePersistDeleteByIds(idValues, changeSet);
}
}
}
@@ -52,7 +48,7 @@ public final class DeleteByIdMap {
public void add(BeanDescriptor<?> desc, Object id) {
BeanPersistIds r = getPersistIds(desc);
r.addId(PersistRequest.Type.DELETE, (Serializable) id);
r.addId(PersistRequest.Type.DELETE, id);
}
/**
@@ -61,15 +57,14 @@ public final class DeleteByIdMap {
public void addList(BeanDescriptor<?> desc, List<Object> idList) {
BeanPersistIds r = getPersistIds(desc);
for (Object anIdList : idList) {
r.addId(PersistRequest.Type.DELETE, (Serializable) anIdList);
for (Object idValue : idList) {
r.addId(PersistRequest.Type.DELETE, idValue);
}
}
private BeanPersistIds getPersistIds(BeanDescriptor<?> desc) {
String beanType = desc.getFullName();
BeanPersistIds r = beanMap.computeIfAbsent(beanType, k -> new BeanPersistIds(desc));
return r;
return beanMap.computeIfAbsent(beanType, k -> new BeanPersistIds(desc));
}
/**
@@ -83,7 +78,7 @@ public final class DeleteByIdMap {
// Add to queue or bulk update entries
boolean queue = (DocStoreMode.QUEUE == mode);
String queueId = desc.getDocStoreQueueId();
List<Object> idValues = deleteIds.getDeleteIds();
List<Object> idValues = deleteIds.getIds();
if (idValues != null) {
for (Object idValue : idValues) {
if (queue) {
@@ -67,6 +67,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
private Map<String, Object> userObjects;
private long startNanos;
private long startMillis;
/**
* Create without a tenantId.
@@ -79,6 +80,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
this.connection = connection;
this.persistenceContext = new DefaultPersistenceContext();
this.startNanos = System.nanoTime();
this.startMillis = manager.clockNowMillis();
}
/**
@@ -89,6 +91,12 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
this.tenantId = tenantId;
}
@Override
public long getStartMillis() {
// not used on read only transaction
return startMillis;
}
@Override
public void setLabel(String label) {
// do nothing
@@ -289,11 +297,25 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
}
@Override
public boolean isBatchMode() {
return false;
}
@Override
public boolean isBatchOnCascade() {
return false;
}
@Override
public PersistBatch getBatch() {
return null;
}
@Override
public void setBatchOnCascade(boolean batchMode) {
}
@Override
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
}
@@ -345,7 +367,7 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
* this request should be executed immediately.
*/
@Override
public boolean isBatchThisRequest(PersistRequest.Type type) {
public boolean isBatchThisRequest() {
return false;
}
@@ -118,11 +118,11 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
protected Boolean updateAllLoadedProperties;
protected PersistBatch oldBatchMode;
protected boolean oldBatchMode;
protected PersistBatch batchMode;
protected boolean batchMode;
protected PersistBatch batchOnCascadeMode;
protected boolean batchOnCascadeMode;
protected int batchSize = -1;
@@ -187,6 +187,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
protected ProfileLocation profileLocation;
protected final long startNanos;
private final long startMillis;
/**
* Create a new JdbcTransaction.
@@ -203,13 +204,15 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.startNanos = System.nanoTime();
if (manager == null) {
this.startMillis = System.currentTimeMillis();
this.logSql = false;
this.logSummary = false;
this.skipCacheAfterWrite = true;
this.batchMode = PersistBatch.NONE;
this.batchOnCascadeMode = PersistBatch.NONE;
this.batchMode = false;
this.batchOnCascadeMode = false;
this.onQueryOnly = OnQueryOnly.ROLLBACK;
} else {
this.startMillis = manager.clockNowMillis();
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.skipCacheAfterWrite = manager.isSkipCacheAfterWrite();
@@ -235,6 +238,11 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
return label;
}
@Override
public long getStartMillis() {
return startMillis;
}
@Override
public long profileOffset() {
return (profileStream == null) ? 0 : profileStream.offset();
@@ -562,14 +570,6 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
@Override
public void setBatchMode(boolean batchMode) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
this.batchMode = (batchMode) ? PersistBatch.ALL : PersistBatch.NONE;
}
@Override
public void setBatch(PersistBatch batchMode) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
@@ -577,20 +577,40 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
}
@Override
public PersistBatch getBatch() {
public void setBatch(PersistBatch batchMode) {
setBatchMode(PersistBatch.ALL == batchMode);
}
@Override
public boolean isBatchMode() {
return batchMode;
}
@Override
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
public PersistBatch getBatch() {
return batchMode ? PersistBatch.ALL : PersistBatch.NONE;
}
@Override
public void setBatchOnCascade(boolean batchMode) {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
this.batchOnCascadeMode = batchOnCascadeMode;
this.batchOnCascadeMode = batchMode;
}
@Override
public void setBatchOnCascade(PersistBatch batchMode) {
setBatchOnCascade(PersistBatch.ALL == batchMode);
}
@Override
public PersistBatch getBatchOnCascade() {
return batchOnCascadeMode ? PersistBatch.ALL : PersistBatch.NONE;
}
@Override
public boolean isBatchOnCascade() {
return batchOnCascadeMode;
}
@@ -649,36 +669,18 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
* this request should be executed immediately.
*/
@Override
public boolean isBatchThisRequest(PersistRequest.Type type) {
public boolean isBatchThisRequest() {
if (!batchOnCascadeSet && !explicit && depth <= 0) {
// implicit transaction, no gain by batching where depth <= 0
return false;
}
return isBatch(batchMode, type);
}
/**
* Return true if JDBC batch should be used on cascade persist.
*/
private boolean isBatchOnCascade(PersistRequest.Type type) {
return isBatch(batchOnCascadeMode, type);
}
private boolean isBatch(PersistBatch batch, PersistRequest.Type type) {
switch (batch) {
case ALL:
return true;
case INSERT:
return type == PersistRequest.Type.INSERT;
default:
return false;
}
return batchMode;
}
@Override
public void checkBatchEscalationOnCollection() {
if (batchMode == PersistBatch.NONE && batchOnCascadeMode != PersistBatch.NONE) {
batchMode = batchOnCascadeMode;
if (!batchMode && batchOnCascadeMode) {
batchMode = true;
batchOnCascadeSet = true;
}
}
@@ -688,7 +690,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
if (batchOnCascadeSet) {
batchFlushReset();
// restore the previous batch mode of NONE
batchMode = PersistBatch.NONE;
batchMode = false;
}
}
@@ -745,15 +747,15 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
@Override
public boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request) {
if (isBatch(batchMode, request.getType())) {
if (batchMode) {
// already batching (at top level)
return false;
}
if (isBatchOnCascade(request.getType())) {
if (batchOnCascadeMode) {
// escalate up to batch mode for this request (and cascade)
oldBatchMode = batchMode;
batchMode = PersistBatch.ALL;
oldBatchMode = false;
batchMode = true;
batchFlushReset();
// skip using jdbc batch for the top level bean (no gain there)
request.setSkipBatchForTopLevel();
@@ -47,6 +47,12 @@ class NoTransaction implements SpiTransaction {
}
@Override
public long getStartMillis() {
// not used
return System.currentTimeMillis();
}
@Override
public boolean isActive() {
// always false
@@ -230,6 +236,15 @@ class NoTransaction implements SpiTransaction {
return null;
}
@Override
public boolean isBatchMode() {
return false;
}
@Override
public void setBatchOnCascade(boolean batchMode) {
}
@Override
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
@@ -240,6 +255,11 @@ class NoTransaction implements SpiTransaction {
return null;
}
@Override
public boolean isBatchOnCascade() {
return false;
}
@Override
public void setBatchSize(int batchSize) {
@@ -331,7 +351,7 @@ class NoTransaction implements SpiTransaction {
}
@Override
public boolean isBatchThisRequest(PersistRequest.Type type) {
public boolean isBatchThisRequest() {
return false;
}
@@ -8,12 +8,12 @@ import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Set;
/**
* Performs post commit processing using a background thread.
@@ -21,7 +21,7 @@ import java.util.List;
* This includes Cluster notification, and BeanPersistListeners.
* </p>
*/
public final class PostCommitProcessing {
final class PostCommitProcessing {
private static final Logger logger = LoggerFactory.getLogger(PostCommitProcessing.class);
@@ -45,12 +45,10 @@ public final class PostCommitProcessing {
private final int txnDocStoreBatchSize;
private CacheChangeSet cacheChanges;
/**
* Create for an external modification.
*/
public PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, TransactionEvent event) {
PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, TransactionEvent event) {
this.clusterManager = clusterManager;
this.manager = manager;
@@ -67,7 +65,7 @@ public final class PostCommitProcessing {
/**
* Create for a transaction.
*/
public PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, SpiTransaction transaction) {
PostCommitProcessing(ClusterManager clusterManager, TransactionManager manager, SpiTransaction transaction) {
this.clusterManager = clusterManager;
this.manager = manager;
@@ -82,31 +80,12 @@ public final class PostCommitProcessing {
}
/**
* Notify the local part of L2 cache.
* Perform foreground cache notification if desired.
*/
void notifyLocalCache() {
processTableEvents(event.getEventTables());
if (manager.notifyL2CacheInForeground) {
// process l2 cache changes in foreground
processCacheChanges(event.buildCacheChanges(manager.viewInvalidation));
} else {
// collect l2 cache changes for delayed background processing
cacheChanges = event.buildCacheChanges(manager.viewInvalidation);
}
}
/**
* Table events are where SQL or external tools are used. In this case the
* cache is notified based on the table name (rather than bean type).
*/
private void processTableEvents(TransactionEventTable tableEvents) {
if (tableEvents != null && !tableEvents.isEmpty()) {
// notify cache with table based changes
BeanDescriptorManager dm = manager.getBeanDescriptorManager();
for (TableIUD tableIUD : tableEvents.values()) {
dm.cacheNotify(tableIUD);
}
processCacheChanges();
}
}
@@ -153,7 +132,9 @@ public final class PostCommitProcessing {
*/
Runnable backgroundNotify() {
return () -> {
processCacheChanges(cacheChanges);
if (!manager.notifyL2CacheInForeground) {
processCacheChanges();
}
localPersistListenersNotify();
notifyCluster();
processDocStoreUpdates();
@@ -163,9 +144,17 @@ public final class PostCommitProcessing {
/**
* Apply the changes to the L2 caches.
*/
private void processCacheChanges(CacheChangeSet cacheChanges) {
private void processCacheChanges() {
CacheChangeSet cacheChanges = event.buildCacheChanges(manager);
if (cacheChanges != null) {
manager.processViewInvalidation(cacheChanges.apply());
Set<String> touched = cacheChanges.touchedTables();
if (touched != null && !touched.isEmpty()) {
manager.processTouchedTables(touched, cacheChanges.modificationTimestamp());
if (remoteTransactionEvent != null) {
remoteTransactionEvent.addRemoteTableMod(new RemoteTableMod(cacheChanges.modificationTimestamp(), touched));
}
}
cacheChanges.apply();
}
}
@@ -204,7 +193,6 @@ public final class PostCommitProcessing {
}
RemoteTransactionEvent remoteTransactionEvent = new RemoteTransactionEvent(serverName);
if (beanPersistIdMap != null) {
for (BeanPersistIds beanPersist : beanPersistIdMap.values()) {
remoteTransactionEvent.addBeanPersistIds(beanPersist);
@@ -0,0 +1,58 @@
package io.ebeaninternal.server.transaction;
import io.ebeaninternal.api.BinaryReadContext;
import io.ebeaninternal.api.BinaryWritable;
import io.ebeaninternal.api.BinaryWriteContext;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
public class RemoteTableMod implements BinaryWritable {
private final long timestamp;
private final Set<String> tables;
public RemoteTableMod(long timestamp, Set<String> tables) {
this.timestamp = timestamp;
this.tables = tables;
}
@Override
public String toString() {
return "TableMod[" + timestamp + "; " + tables + "]";
}
public long getTimestamp() {
return timestamp;
}
public Set<String> getTables() {
return tables;
}
public static RemoteTableMod readBinaryMessage(BinaryReadContext dataInput) throws IOException {
long timestamp = dataInput.readLong();
int count = dataInput.readInt();
Set<String> tables = new LinkedHashSet<>();
for (int i = 0; i < count; i++) {
tables.add(dataInput.readUTF());
}
return new RemoteTableMod(timestamp, tables);
}
@Override
public void writeBinary(BinaryWriteContext out) throws IOException {
DataOutputStream os = out.start(TYPE_TABLEMOD);
os.writeLong(timestamp);
os.writeInt(tables.size());
for (String table : tables) {
os.writeUTF(table);
}
}
}
@@ -1,15 +1,20 @@
package io.ebeaninternal.server.transaction;
import io.ebeaninternal.api.BinaryReadContext;
import io.ebeaninternal.api.BinaryWritable;
import io.ebeaninternal.api.BinaryWriteContext;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.TransactionEventTable;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.server.cache.RemoteCacheEvent;
import io.ebeaninternal.server.cluster.BinaryMessageList;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class RemoteTransactionEvent implements Runnable {
public class RemoteTransactionEvent implements Runnable, BinaryWritable {
private final List<BeanPersistIds> beanPersistList = new ArrayList<>();
@@ -19,14 +24,22 @@ public class RemoteTransactionEvent implements Runnable {
private RemoteCacheEvent remoteCacheEvent;
private RemoteTableMod remoteTableMod;
private String serverName;
private transient SpiEbeanServer server;
/**
* Create for sending to other servers in the cluster.
*/
public RemoteTransactionEvent(String serverName) {
this.serverName = serverName;
}
/**
* Create from Reading and processing from remote server.
*/
public RemoteTransactionEvent(SpiEbeanServer server) {
this.server = server;
}
@@ -39,6 +52,10 @@ public class RemoteTransactionEvent implements Runnable {
@Override
public String toString() {
StringBuilder sb = new StringBuilder(100);
sb.append("TransEvent[");
if (remoteTableMod != null) {
sb.append(remoteTableMod);
}
if (!beanPersistList.isEmpty()) {
sb.append(beanPersistList);
}
@@ -46,31 +63,90 @@ public class RemoteTransactionEvent implements Runnable {
sb.append(tableList);
}
if (deleteByIdMap != null) {
sb.append(deleteByIdMap.values());
sb.append(deleteByIdMap);
}
sb.append("]");
return sb.toString();
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
/**
* Read the binary message.
*/
public void readBinary(BinaryReadContext dataInput) throws IOException {
boolean more = dataInput.readBoolean();
while (more) {
int msgType = dataInput.readInt();
readBinaryMessage(msgType, dataInput);
more = dataInput.readBoolean();
}
}
private void readBinaryMessage(int msgType, BinaryReadContext dataInput) throws IOException {
switch (msgType) {
case BinaryWritable.TYPE_BEANIUD:
addBeanPersistIds(BeanPersistIds.readBinaryMessage(server, dataInput));
break;
case BinaryWritable.TYPE_TABLEIUD:
addTableIUD(TransactionEventTable.TableIUD.readBinaryMessage(dataInput));
break;
case BinaryWritable.TYPE_CACHE:
addRemoteCacheEvent(RemoteCacheEvent.readBinaryMessage(dataInput));
break;
case BinaryWritable.TYPE_TABLEMOD:
addRemoteTableMod(RemoteTableMod.readBinaryMessage(dataInput));
break;
default:
throw new RuntimeException("Invalid Transaction msgType " + msgType);
}
}
/**
* Write a binary message to byte array given an initial buffer size.
*/
public byte[] writeBinaryAsBytes(int bufferSize) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream(bufferSize);
DataOutputStream out = new DataOutputStream(buffer);
BinaryWriteContext context = new BinaryWriteContext(out);
writeBinary(context);
out.close();
return buffer.toByteArray();
}
@Override
public void writeBinary(BinaryWriteContext out) throws IOException {
DataOutputStream os = out.os();
os.writeUTF(serverName);
if (remoteTableMod != null) {
remoteTableMod.writeBinary(out);
}
if (tableList != null) {
for (TableIUD aTableList : tableList) {
aTableList.writeBinaryMessage(msgList);
aTableList.writeBinary(out);
}
}
if (deleteByIdMap != null) {
for (BeanPersistIds deleteIds : deleteByIdMap.values()) {
deleteIds.writeBinaryMessage(msgList);
deleteIds.writeBinary(out);
}
}
for (BeanPersistIds aBeanPersistList : beanPersistList) {
aBeanPersistList.writeBinaryMessage(msgList);
aBeanPersistList.writeBinary(out);
}
if (remoteCacheEvent != null) {
remoteCacheEvent.writeBinaryMessage(msgList);
remoteCacheEvent.writeBinary(out);
}
out.end();
os.flush();
}
public boolean isEmpty() {
@@ -113,6 +189,10 @@ public class RemoteTransactionEvent implements Runnable {
tableList.add(tableIud);
}
public void addRemoteTableMod(RemoteTableMod remoteTableMod) {
this.remoteTableMod = remoteTableMod;
}
public String getServerName() {
return serverName;
}
@@ -141,4 +221,8 @@ public class RemoteTransactionEvent implements Runnable {
return remoteCacheEvent;
}
public RemoteTableMod getRemoteTableMod() {
return remoteTableMod;
}
}
@@ -0,0 +1,102 @@
package io.ebeaninternal.server.transaction;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.cache.QueryCacheEntryValidate;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheNotify;
import io.ebeaninternal.server.core.ClockService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Holds timestamp of last modification per table.
* <p>
* This information is used to validate entries in the L2 query caches.
* </p>
*/
public class TableModState implements QueryCacheEntryValidate, ServerCacheNotify {
private static final Logger log = LoggerFactory.getLogger("io.ebean.cache.TABLEMOD");
private final ClockService clockService;
private Map<String,Long> tableModStamp = new ConcurrentHashMap<>();
public TableModState(ClockService clockService) {
this.clockService = clockService;
}
/**
* Set the modified timestamp on the tables that have been touched.
*/
void touch(Set<String> touchedTables, long modTimestamp) {
for (String tableName : touchedTables) {
tableModStamp.put(tableName, modTimestamp);
}
if (log.isDebugEnabled()) {
log.debug("TableModState updated - touched:{} modTimestamp:{}", touchedTables, modTimestamp);
}
}
/**
* Return true if all the tables are valid based on timestamp comparison.
*/
boolean isValid(Set<String> tables, long sinceTimestamp) {
for (String tableName : tables) {
Long modTime = tableModStamp.get(tableName);
if (modTime != null && modTime >= sinceTimestamp ) {
if (log.isTraceEnabled()) {
log.trace("Invalidate on table:{}", tableName);
}
return false;
}
}
return true;
}
@Override
public boolean isValid(QueryCacheEntry entry) {
Set<String> dependentTables = entry.getDependentTables();
if (dependentTables != null && !dependentTables.isEmpty()) {
return isValid(dependentTables, entry.getTimestamp());
}
return true;
}
/**
* Update the table modification timestamps based on remote table modification events.
* <p>
* Generally this is used with distributed caches (Hazelcast, Ignite etc) via topic.
* </p>
*/
@Override
public void notify(ServerCacheNotification notification) {
// use local clock - for slightly more aggressive invalidation (as later)
// that removes any concern regarding clock syncing across cluster
if (log.isDebugEnabled()) {
log.debug("ServerCacheNotification:{}", notification);
}
touch(notification.getDependentTables(), clockService.nowMillis());
}
/**
* Update from Remote transaction event.
* <p>
* Generally this is used with Clustering (ebean-cluster, k8scache).
* </p>
*/
public void notify(RemoteTableMod tableMod) {
// use local clock - for slightly more aggressive invalidation (as later)
// that removes any concern regarding clock syncing across cluster
if (log.isDebugEnabled()) {
log.debug("RemoteTableMod:{}", tableMod);
}
touch(tableMod.getTables(), clockService.nowMillis());
}
}
@@ -5,6 +5,8 @@ import io.ebean.ProfileLocation;
import io.ebean.TxScope;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxType;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
@@ -26,7 +28,9 @@ import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricMap;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.core.ClockService;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.profile.TimedProfileLocation;
import io.ebeaninternal.server.profile.TimedProfileLocationRegistry;
@@ -46,7 +50,7 @@ import java.util.Set;
/**
* Manages transactions.
* <p>
* Keeps the Cache and Cluster in synch when transactions are committed.
* Keeps the Cache and Cluster in sync when transactions are committed.
* </p>
*/
public class TransactionManager implements SpiTransactionManager {
@@ -94,9 +98,9 @@ public class TransactionManager implements SpiTransactionManager {
*/
protected final DocStoreUpdateProcessor docStoreUpdateProcessor;
protected final PersistBatch persistBatch;
protected final boolean persistBatch;
protected final PersistBatch persistBatchOnCascade;
protected final boolean persistBatchOnCascade;
protected final BulkEventListenerMap bulkEventListenerMap;
@@ -136,6 +140,10 @@ public class TransactionManager implements SpiTransactionManager {
private final TimedMetricMap txnNamed;
private final TransactionScopeManager scopeManager;
private final TableModState tableModState;
private final ServerCacheNotify cacheNotify;
private final ClockService clockService;
/**
* Create the TransactionManager
*/
@@ -146,8 +154,8 @@ public class TransactionManager implements SpiTransactionManager {
this.databasePlatform = options.config.getDatabasePlatform();
this.skipCacheAfterWrite = options.config.isSkipCacheAfterWrite();
this.notifyL2CacheInForeground = options.notifyL2CacheInForeground;
this.persistBatch = options.config.getPersistBatch();
this.persistBatchOnCascade = options.config.appliedPersistBatchOnCascade();
this.persistBatch = PersistBatch.ALL == options.config.getPersistBatch();
this.persistBatchOnCascade = PersistBatch.ALL == options.config.appliedPersistBatchOnCascade();
this.rollbackOnChecked = options.config.isTransactionRollbackOnChecked();
this.beanDescriptorManager = options.descMgr;
this.viewInvalidation = options.descMgr.requiresViewEntityCacheInvalidation();
@@ -157,6 +165,9 @@ public class TransactionManager implements SpiTransactionManager {
this.clusterManager = options.clusterManager;
this.serverName = options.config.getName();
this.scopeManager = options.scopeManager;
this.tableModState = options.tableModState;
this.cacheNotify = options.cacheNotify;
this.clockService = options.clockService;
this.backgroundExecutor = options.backgroundExecutor;
this.dataSourceSupplier = options.dataSourceSupplier;
this.docStoreActive = options.config.getDocStoreConfig().isActive();
@@ -178,6 +189,13 @@ public class TransactionManager implements SpiTransactionManager {
scopeManager.register(this);
}
/**
* Return the NOW timestamp in epoch millis.
*/
public long clockNowMillis() {
return clockService.nowMillis();
}
/**
* Create a new scoped transaction.
*/
@@ -254,11 +272,11 @@ public class TransactionManager implements SpiTransactionManager {
return bulkEventListenerMap;
}
public PersistBatch getPersistBatch() {
public boolean getPersistBatch() {
return persistBatch;
}
public PersistBatch getPersistBatchOnCascade() {
public boolean getPersistBatchOnCascade() {
return persistBatchOnCascade;
}
@@ -323,7 +341,7 @@ public class TransactionManager implements SpiTransactionManager {
ExternalJdbcTransaction t = new ExternalJdbcTransaction(id, true, c, this);
// set the default batch mode
t.setBatch(persistBatch);
t.setBatchMode(persistBatch);
t.setBatchOnCascade(persistBatchOnCascade);
return t;
}
@@ -451,10 +469,17 @@ public class TransactionManager implements SpiTransactionManager {
clusterLogger.debug("processing {}", remoteEvent);
}
CacheChangeSet changeSet = new CacheChangeSet(clockNowMillis());
RemoteTableMod tableMod = remoteEvent.getRemoteTableMod();
if (tableMod != null) {
changeSet.addInvalidate(tableMod.getTables());
}
List<TableIUD> tableIUDList = remoteEvent.getTableIUDList();
if (tableIUDList != null) {
for (TableIUD tableIUD : tableIUDList) {
beanDescriptorManager.cacheNotify(tableIUD);
beanDescriptorManager.cacheNotify(tableIUD, changeSet);
}
}
@@ -462,10 +487,12 @@ public class TransactionManager implements SpiTransactionManager {
// processes both Bean IUD and DeleteById
List<BeanPersistIds> beanPersistList = remoteEvent.getBeanPersistList();
if (beanPersistList != null) {
for (BeanPersistIds aBeanPersistList : beanPersistList) {
aBeanPersistList.notifyCacheAndListener();
for (BeanPersistIds persistIds : beanPersistList) {
persistIds.notifyCache(changeSet);
}
}
changeSet.apply();
}
/**
@@ -495,10 +522,12 @@ public class TransactionManager implements SpiTransactionManager {
/**
* Invalidate the query caches for entities based on views.
*/
public void processViewInvalidation(Set<String> viewInvalidation) {
if (!viewInvalidation.isEmpty()) {
beanDescriptorManager.processViewInvalidation(viewInvalidation);
public void processTouchedTables(Set<String> touchedTables, long modTimestamp) {
tableModState.touch(touchedTables, modTimestamp);
if (viewInvalidation) {
beanDescriptorManager.processViewInvalidation(touchedTables);
}
cacheNotify.notify(new ServerCacheNotification(modTimestamp, touchedTables));
}
/**
@@ -1,10 +1,12 @@
package io.ebeaninternal.server.transaction;
import io.ebean.BackgroundExecutor;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.core.ClockService;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
@@ -24,10 +26,15 @@ public class TransactionManagerOptions {
final SpiProfileHandler profileHandler;
final TransactionScopeManager scopeManager;
final SpiLogManager logManager;
final TableModState tableModState;
final ServerCacheNotify cacheNotify;
final ClockService clockService;
public TransactionManagerOptions(boolean notifyL2CacheInForeground, ServerConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler, SpiLogManager logManager) {
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler,
SpiLogManager logManager, TableModState tableModState, ServerCacheNotify cacheNotify, ClockService clockService) {
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
this.config = config;
@@ -39,6 +46,9 @@ public class TransactionManagerOptions {
this.dataSourceSupplier = dataSourceSupplier;
this.profileHandler = profileHandler;
this.logManager = logManager;
this.tableModState = tableModState;
this.cacheNotify = cacheNotify;
this.clockService = clockService;
}
}
@@ -141,6 +141,19 @@
</xsd:complexType>
</xsd:element>
<xsd:element name="alterForeignKey">
<xsd:complexType>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="columnNames" type="xsd:string"/>
<xsd:attribute name="refColumnNames" type="xsd:string"/>
<xsd:attribute name="refTableName" type="xsd:string"/>
<xsd:attribute name="indexName" type="xsd:string"/>
<xsd:attribute name="tableName" type="xsd:string" use="required"/>
<xsd:attribute name="onDelete" type="xsd:string"/>
<xsd:attribute name="onUpdate" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="dropTable">
<xsd:complexType>
<xsd:attribute name="name" type="xsd:string" use="required"/>
@@ -300,6 +313,7 @@
<xsd:element ref="addHistoryTable" maxOccurs="unbounded"/>
<xsd:element ref="dropHistoryTable" maxOccurs="unbounded"/>
<xsd:element ref="alterForeignKey" maxOccurs="unbounded"/>
<xsd:element ref="addColumn" maxOccurs="unbounded"/>
<xsd:element ref="dropColumn" maxOccurs="unbounded"/>
+61
View File
@@ -1,5 +1,7 @@
package io.ebean;
import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.Platform;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.meta.MetaQueryMetric;
import org.ebeantest.LoggedSqlCollector;
@@ -8,6 +10,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tests.model.basic.ResetBasicData;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -98,6 +101,64 @@ public class DtoQueryTest extends BaseTestCase {
}
@ForPlatform(Platform.POSTGRES)
@Test
public void dto_bindList_usingPostrgesAnyWithPositionedParameter() {
ResetBasicData.reset();
List<Integer> ids = Arrays.asList(1, 2);
List<DCust> list = server().findDto(DCust.class, "select id, name from o_customer where id = any(?)")
.setParameter(1, ids)
.findList();
assertThat(list).isNotEmpty();
list = server().findDto(DCust.class, "select id, name from o_customer where id in (:idList)")
.setParameter("idList", ids)
.findList();
assertThat(list).isNotEmpty();
}
@ForPlatform(Platform.POSTGRES)
@Test
public void sql_bindListParam_usingPostrgesAnyWithPositionedParameter() {
ResetBasicData.reset();
List<Integer> ids = Arrays.asList(1, 2);
List<SqlRow> list = server().createSqlQuery("select id, name from o_customer where id = any(?)")
.setParameter(1, ids)
.findList();
assertThat(list).isNotEmpty();
list = server().createSqlQuery("select id, name from o_customer where id in (:idList)")
.setParameter("idList", ids)
.findList();
assertThat(list).isNotEmpty();
}
@ForPlatform(Platform.POSTGRES)
@Test
public void sqlUpdate_bindListParam_usingPostrgesAnyWithPositionedParameter() {
ResetBasicData.reset();
List<Integer> ids = Arrays.asList(999999999, 999999998);
int rows = server().createSqlUpdate("update o_customer set name = ? where id = any(?)")
.setParameter(1, "Junk")
.setParameter(2, ids)
.execute();
assertThat(rows).isEqualTo(0);
}
@Test
public void dto_queryPlanHits() {
+1 -1
View File
@@ -59,7 +59,7 @@ public class TxScopeTest {
TxScope scope = new TxScope();
scope.setBatchSize(100);
scope.setBatchOnCascade(PersistBatch.INSERT);
scope.setBatchOnCascade(PersistBatch.ALL);
scope.checkBatchMode();
assertNull(scope.getBatch());
@@ -51,8 +51,8 @@ public class ServerConfigTest {
serverConfig.setReadOnlyDataSourceConfig(new DataSourceConfig());
Properties props = new Properties();
props.setProperty("persistBatch", "INSERT");
props.setProperty("persistBatchOnCascade", "INSERT");
props.setProperty("persistBatch", "ALL");
props.setProperty("persistBatchOnCascade", "ALL");
props.setProperty("dbuuid", "binary");
props.setProperty("jdbcFetchSizeFindEach", "42");
props.setProperty("jdbcFetchSizeFindList", "43");
@@ -80,8 +80,8 @@ public class ServerConfigTest {
assertThat(serverConfig.getNamingConvention()).isInstanceOf(MatchingNamingConvention.class);
assertEquals(IdType.SEQUENCE, serverConfig.getIdType());
assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatch());
assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatchOnCascade());
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch());
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
assertEquals(PlatformConfig.DbUuid.BINARY, serverConfig.getPlatformConfig().getDbUuid());
assertEquals(JsonConfig.DateTime.ISO8601, serverConfig.getJsonDateTime());
@@ -61,6 +61,7 @@ public class DbMigrationTest extends BaseTestCase {
"migtest_ckey_detail",
"migtest_ckey_parent",
"migtest_e_basic",
"migtest_e_enum",
"migtest_e_history",
"migtest_e_history2",
"migtest_e_history3",
@@ -41,7 +41,7 @@ public class BaseTableDdlTest {
ddlGen.generate(write, alterColumn);
String ddl = write.apply().getBuffer();
assertThat(ddl).contains("alter table mytab drop constraint ck_mytab_acol");
assertThat(ddl).contains("alter table mytab drop constraint if exists ck_mytab_acol");
assertThat(ddl).contains("alter table mytab add constraint ck_mytab_acol check (acol in ('A','B'))");
}
@@ -15,7 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ModelContainerApplyTest {
@Test
public void testApply() throws Exception {
public void testApply() {
Migration migration = MigrationXmlReader.read("/container/test-create-table.xml");

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