mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
629e096e28 | ||
|
|
c981944c8e | ||
|
|
1ba9f4e9de | ||
|
|
ce02e7ce70 | ||
|
|
1d9a2ed43a | ||
|
|
ae81ce5f47 | ||
|
|
9e42f98902 | ||
|
|
ada21729ec | ||
|
|
8b7d882b8a | ||
|
|
cf2d9df256 | ||
|
|
48b05eb358 | ||
|
|
0796fc0d88 | ||
|
|
df585644e9 | ||
|
|
ff3c5b3d82 | ||
|
|
1a03e84f0e | ||
|
|
49ab17cd34 | ||
|
|
9cd189034c | ||
|
|
ed566ca153 | ||
|
|
9159cec36c | ||
|
|
b58fc2df04 | ||
|
|
dd8607b072 | ||
|
|
a5a8765bd0 | ||
|
|
ae55b58438 | ||
|
|
671d082ad3 | ||
|
|
43e95d81f6 | ||
|
|
3a64cf6952 | ||
|
|
8d976f83a9 | ||
|
|
117737c790 | ||
|
|
085843339f | ||
|
|
bf33b3f96f | ||
|
|
e1d4159453 | ||
|
|
4f1f3cadf9 | ||
|
|
01e1a4642b | ||
|
|
9c73309dfd | ||
|
|
bd3c99e67f | ||
|
|
5adbebaf6e | ||
|
|
6a9ab49ef0 | ||
|
|
96cc8d7605 |
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+73
-19
@@ -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;
|
||||
}
|
||||
}
|
||||
+25
-7
@@ -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"/>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
+1
-1
@@ -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
Reference in New Issue
Block a user