Compare commits

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

* Use IF EXISTS to drop constraints

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

* updated reference scripts for dropping constraints on enums
2018-06-27 10:38:56 +12:00
rob bygrave 48b05eb358 No effective change - modify logback-test.xml to put the test example logging files into target directory 2018-06-27 10:37:00 +12:00
rob bygrave 0796fc0d88 [maven-release-plugin] prepare for next development iteration 2018-06-27 00:30:37 +12:00
rob bygrave df585644e9 [maven-release-plugin] prepare release ebean-11.18.1 2018-06-27 00:30:23 +12:00
rob bygrave ff3c5b3d82 #1441 - Update ebean-dbmigration.xsd adding missing alterForeignKey element 2018-06-27 00:23:36 +12:00
rob bygrave 1a03e84f0e #1439 - IllegalArgumentException: No rule for io.ebeaninternal.dbmigration.migration.Sql@315df4bb 2018-06-26 22:40:08 +12:00
rob bygrave 49ab17cd34 #1438 - Mapping of @ElementCollection with an Enum that is otherwise not used produces error 2018-06-26 22:11:40 +12:00
rob bygrave 9cd189034c #1437 - or(expr, expr) ... where expr has a ToMany should produce a LEFT JOIN (and instead we only get JOIN) 2018-06-26 21:33:44 +12:00
rob bygrave ed566ca153 #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Refactor on PostCommitProcessing with fixes for dependent table invalidation
2018-06-26 00:25:33 +12:00
rob bygrave 9159cec36c No effective change - remove invalid javadoc on Query due to #1434 2018-06-19 16:35:42 +12:00
rob bygrave b58fc2df04 #1436 - PersistenceException: Query threw SQLException:No ScalarType registered for class java.util.Arrays$ArrayList Query was:select id, name from o_customer where id = any(?)
Add test for SqlUpdate
2018-06-19 16:30:21 +12:00
rob bygrave dd8607b072 #1436 - PersistenceException: Query threw SQLException:No ScalarType registered for class java.util.Arrays$ArrayList Query was:select id, name from o_customer where id = any(?) 2018-06-19 15:30:19 +12:00
rob bygrave a5a8765bd0 Update tests only - non native boolean (MySql) and ignore non-named savepoint nested transaction tests with MySql. 2018-06-19 15:24:35 +12:00
rob bygrave ae55b58438 No effective change - remove unused imports 2018-06-19 01:48:45 +12:00
rob bygrave 671d082ad3 #1427 - Map all deleteByIds into a single CacheChangeBeanRemove and use bulk removeAll api to remove from cache 2018-06-19 01:34:28 +12:00
rob bygrave 43e95d81f6 #1427 - RemoteTransactionEvent process dependent tables 2018-06-19 00:50:14 +12:00
rob bygrave 3a64cf6952 #1434 - Remove deprecated API from EbeanServer - finder methods that take explicit transaction. Migrate to use ebeanServer.extended() 2018-06-18 21:34:51 +12:00
rob bygrave 8d976f83a9 #1417 - Breaking API - Remove PersistBatch.INSERT ... migrate to PersistBatch.ALL 2018-06-18 21:15:10 +12:00
rob bygrave 117737c790 #1433 - Remove io.ebeaninternal.api.SpiContainerBootup ... which was replaced by io.ebean.config.AutoConfigure 2018-06-18 20:02:14 +12:00
rob bygrave 085843339f #1432 - Refactor internals for RemoteTransactionEvent / BeanPersistIds ... to not send inserted ids 2018-06-17 23:34:27 +12:00
rob bygrave bf33b3f96f #1431 - Refactor internals for RemoteTransactionEvent binary serialisation 2018-06-17 23:29:11 +12:00
rob bygrave e1d4159453 #1431 - Refactor internals for RemoteTransactionEvent binary serialisation 2018-06-17 22:25:45 +12:00
rob bygrave 4f1f3cadf9 Merge branch 'feature/query-cache-1427' 2018-06-16 16:02:35 +12:00
rob bygrave 01e1a4642b [maven-release-plugin] prepare for next development iteration 2018-06-16 15:26:11 +12:00
rob bygrave 3a75e1f66c [maven-release-plugin] prepare release ebean-11.17.5 2018-06-16 15:25:56 +12:00
rob bygrave 47ac4c5c5b #1430 - Regression (from 11.10.1) - DataSource user not set ... due to PrimaryServer System.setProperty("ebean_db", defaultServerName); 2018-06-16 15:23:41 +12:00
rob bygrave 9c73309dfd #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Update RemoteTransactionEvent, add (back) common binary message handling (Common to TCP, K8s and upcoming binary pubsub)
2018-06-16 15:15:54 +12:00
rob bygrave bd3c99e67f #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Wrap Clock as ClockService to use in TransactionManager and DefaultServer (and allow for changing the Clock implementation for testing).
2018-06-16 00:22:44 +12:00
rob bygrave 5adbebaf6e Merge branch 'master' into feature/query-cache-1427 2018-06-16 00:09:25 +12:00
rob bygrave 6a9ab49ef0 #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Propagation across cluster.
2018-06-16 00:09:03 +12:00
rob bygrave 4308a98a0a #1428 - ENH: Use java.time.Clock for @WhenModified and @WhenCreated and allow it to be Replaceable for testing
Adjust such that Clock is on DefaultServer and can be set via ExtendedServer interface
2018-06-15 20:49:14 +12:00
tobias-andRob Bygrave f86f1b2647 Replaceable clock (#1428)
ENH: Use java.time.Clock for @WhenModified and @WhenCreated and allow it to be Replaceable for testing
2018-06-15 20:34:11 +12:00
rob bygrave 96cc8d7605 #1427 - QueryCache should be cleared, if one of a dependent bean is updated
Initial work, does not include propagation across cluster.
2018-06-15 17:36:23 +12:00
rob bygrave 59537227b6 #1426 - ENH: Add bitwiseNot expression ... e.g. bitwiseNot("flags", BwFlags.HAS_COLOUR) 2018-06-14 23:28:30 +12:00
rob bygrave 81c6d4601d #1425 - Stateless update with OneToMany with SoftDelete and deleteMissingChildren = true ... does a hard delete 2018-06-14 16:09:45 +12:00
rob bygrave e21839de91 #1424 - Deprecate / Move ... finder methods that take explicit transaction to ExtendedServer API 2018-06-14 11:03:29 +12:00
rob bygrave a61db2ab35 [maven-release-plugin] prepare for next development iteration 2018-06-14 00:18:14 +12:00
rob bygrave bddfa3bc5f [maven-release-plugin] prepare release ebean-11.17.4 2018-06-14 00:18:00 +12:00
Roland PramlandRob Bygrave 9ad460ecd8 FIX: applied history drops were not recognized (#1422) 2018-06-14 00:07:14 +12:00
rob bygrave 50593a7f9d #1423 - Close resources - InputStream of entity bean properties of File type 2018-06-14 00:04:43 +12:00
rob bygrave c088cfa339 #1421 - Nested programatic transaction using auto close / try with resources ... does not end/rollback the parent/outer transaction 2018-06-13 22:22:29 +12:00
rob bygrave e683e25a95 #1420 - ENH: Add a ServiceLoader mechanism to configure ServerConfig - io.ebean.config.ServerConfigProvider 2018-06-13 21:35:26 +12:00
rob bygrave c7052c5bfa #1420 - ENH: Add a ServiceLoader mechanism to configure ServerConfig - io.ebean.config.ServerConfigProvider 2018-06-13 21:30:41 +12:00
rob bygrave 698ee2198d No effective change - refactor DefaultContainer from iterator to forEach 2018-06-12 22:14:33 +12:00
rob bygrave 91c4eca528 #1419 - Refactor - saveAll() and deleteAll() ... when collections are empty skip transaction init check 2018-06-12 22:08:17 +12:00
rob bygrave 81897bd54d #1418 - Add saveAll() and deleteAll() to io.ebean.BeanRepository 2018-06-12 21:40:39 +12:00
rob bygrave 2427630c07 #1400 - Postgres @History table sys_period empty with multiple updates 2018-06-11 22:10:43 +12:00
rob bygrave 3238c0def4 [maven-release-plugin] prepare for next development iteration 2018-06-11 14:27:53 +12:00
rob bygrave 93f75151f0 [maven-release-plugin] prepare release ebean-11.17.3 2018-06-11 14:27:40 +12:00
rob bygrave 32e9b48359 #1414 - Support dynamic aggregate formula without alias e.g. query.select("date, sum(totalKms), sum(hours)") 2018-06-11 14:13:13 +12:00
rob bygrave 258e29d2f0 #1415 - Add asDto(Class<D> dtoClass) ... to ExpressionList to support fluid style 2018-06-11 13:29:13 +12:00
rob bygrave ec2f1f7243 #1414 - Support dynamic aggregate formula without alias e.g. query.select("date, sum(totalKms), sum(hours)") 2018-06-11 13:20:39 +12:00
Rob Bygrave 5775985468 [maven-release-plugin] prepare for next development iteration 2018-06-11 00:12:45 +12:00
Rob Bygrave 4788dd4ec9 [maven-release-plugin] prepare release ebean-11.17.2 2018-06-11 00:12:30 +12:00
Rob Bygrave ca7e5b79b8 #1412 - Fix Resource leaks for properties & yaml files etc - ManifestReader
Not fixing a leak but just changing to use try with resources
2018-06-11 00:04:58 +12:00
Rob Bygrave 90dfbcd7b3 #1412 - Fix Resource leaks for properties & yaml files etc - DefaultContainer.checkDataSource()
The resource was already being closed properly, changing to use try with resources and note that this now throws PersistenceException if we fail to close() on the connection (which I think is an ok change).
2018-06-11 00:02:14 +12:00
Rob Bygrave 6b39bad4da No effective change - use JdbcClose for DatabasePlatformFactory 2018-06-10 23:55:45 +12:00
Rob Bygrave 25dea0b5b0 #1412 - Fix Resource leaks for properties & yaml files etc - DefaultAutoTuneService 2018-06-10 23:48:49 +12:00
Rob Bygrave 0069d2fabc #1412 - Fix Resource leaks for properties & yaml files etc - ExtraDdlXmlReader 2018-06-10 23:46:55 +12:00
Rob Bygrave 98964638ba #1412 - Fix Resource leaks for properties & yaml files etc - MigrationXmlReader 2018-06-10 23:43:41 +12:00
Rob Bygrave 4573a995f1 #1412 - Fix Resource leaks for properties & yaml files etc
No effective change - reformat only
2018-06-10 23:37:28 +12:00
Rob Bygrave 4bc80cc724 #1412 - Fix Resource leaks for properties & yaml files etc - EbeanVersion 2018-06-10 23:36:12 +12:00
Rob Bygrave a2972abe3e #1412 - Fix Resource leaks for properties & yaml files etc - DdlGenerator 2018-06-10 23:33:21 +12:00
Rob Bygrave 54eaf561f4 #1412 - Fix Resource leaks for properties & yaml files etc - Loader 2018-06-10 23:31:16 +12:00
Rob Bygrave 3134dd3165 No effective change - reformat only 2018-06-10 23:23:48 +12:00
Roland PramlandRob Bygrave 8fc6abd480 IOUtils is no longer used, using java 1.7 StandardCharsets makes EncodeUtil also obsolete (#1410) 2018-06-10 22:30:47 +12:00
rob bygrave f031bb72b6 #1408 - Incorrect SQL when @Aggregation formula matches the property - sum(t0.sum(totalKms)) 2018-06-08 17:38:15 +12:00
rob bygrave 42f0b419e8 #1405 - ENH: add findSingleDecimal() and findSingleLong() alias methods for SqlQuery.findSingleAttribute() ... 2018-06-08 02:19:36 +12:00
rob bygrave 7cbae758fc #1407 - ENH: Add SqlQuery RowMapper and RowConsumer ... for raw JDBC ResultSet mapping and consuming 2018-06-08 02:04:38 +12:00
rob bygrave fb32cfa170 #1406 - ENH: Add SqlQuery.findSingleAttributeList() ... for returning lists of scalar values via SqlQuery 2018-06-07 22:39:19 +12:00
rob bygrave ffa8268125 #1405 - ENH: Add SqlQuery.findSingleAttribute() ... for returning single scalar values via SqlQuery 2018-06-07 22:17:01 +12:00
rob bygrave eba1003c19 #1404 - Query.findCount() with query.setDistinct(true) ... doesn't give count distinct SQL query 2018-06-07 00:32:39 +12:00
rob bygrave eca6383dba [maven-release-plugin] prepare for next development iteration 2018-06-01 15:44:03 +12:00
rob bygrave 624d08f46c [maven-release-plugin] prepare release ebean-11.17.1 2018-06-01 15:43:50 +12:00
rob bygrave 14749daf9b Bump to 11.17.1-SNAPSHOT ... as we added a SPI for io.ebeaninternal.api.SpiLogManager (for capturing the io.ebean.SQL log for testing purposes) 2018-06-01 15:42:24 +12:00
rob bygrave 2817875706 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 14:15:05 +12:00
rob bygrave 3f8d705439 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 14:10:00 +12:00
rob bygrave e9faef2692 #1401 - FOR UPDATE queries inside a transaction are not honored when beans are already cached (in the persistence context) 2018-06-01 11:33:41 +12:00
rob bygrave d459807af7 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 09:19:26 +12:00
ooknightandRob Bygrave 10ad9ef096 beautify bindNoLog format (#1399) 2018-05-31 20:23:52 +12:00
rob bygrave 11e3649b85 #1398 - Support EQ / "Equal to" predicate on client side encrypted entity property - @Encrypted(dbEncryption = false) 2018-05-31 16:02:49 +12:00
rob bygrave ddc00fedb6 #1397 - @DbForeignKey(noConstraint = true) is ignored on @ManyToMany ... foreign constraints still added 2018-05-31 10:55:58 +12:00
rob bygrave 0aa9208dcc [maven-release-plugin] prepare for next development iteration 2018-05-30 03:36:38 +12:00
278 changed files with 7183 additions and 2170 deletions
+5 -5
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.16.1</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.16.1</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>
@@ -2,6 +2,8 @@ package io.ebean;
import io.ebean.bean.EntityBean;
import java.util.Collection;
/**
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
* <p>
@@ -115,6 +117,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
db().save(bean);
}
/**
* Save all the beans in the collection.
*/
public int saveAll(Collection<T> bean) {
return db().saveAll(bean);
}
/**
* Update this entity.
*
@@ -154,6 +163,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
return db().delete(bean);
}
/**
* Delete all the beans in the collection.
*/
public int deleteAll(Collection<T> beans) {
return db().deleteAll(beans);
}
/**
* Delete a bean permanently without soft delete.
* <p>
+7 -400
View File
@@ -808,411 +808,18 @@ public interface EbeanServer {
<T> T getReference(Class<T> beanType, Object id);
/**
* Return the number of 'top level' or 'root' entities this query should return.
*
* @see Query#findCount()
* @see Query#findFutureCount()
*/
<T> int findCount(Query<T> query, Transaction transaction);
/**
* Return the Id values of the query as a List.
*
* @see Query#findIds()
*/
@Nonnull
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
/**
* Return a QueryIterator for the query.
* Return the extended API for EbeanServer.
* <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).
* The extended API has the options for executing queries that take an explicit
* transaction as an argument.
* </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
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
* 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)
*/
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
/**
* 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)
*/
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
/**
* 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.
* Typically we only need to use the extended API when we do NOT want to use the
* usual ThreadLocal based mechanism to obtain the current transaction but instead
* supply the transaction explicitly.
* </p>
*/
@Nonnull
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
/**
* 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
<T> List<T> findList(Query<T> query, Transaction transaction);
/**
* 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
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
/**
* 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
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
/**
* 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
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
/**
* 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
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
/**
* 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
<T> Set<T> findSet(Query<T> query, Transaction transaction);
/**
* 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
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
/**
* 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
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
/**
* 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
<T> T findOne(Query<T> query, Transaction transaction);
/**
* Similar to findOne() but returns an Optional (rather than nullable).
*/
@Nonnull
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
/**
* 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
*/
<T> int delete(Query<T> query, Transaction transaction);
/**
* 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
*/
<T> int update(Query<T> query, Transaction transaction);
/**
* 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
List<SqlRow> findList(SqlQuery query, Transaction transaction);
/**
* 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>
*/
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
/**
* 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>
*/
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
/**
* 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
SqlRow findOne(SqlQuery query, Transaction transaction);
ExtendedServer extended();
/**
* Either Insert or Update the bean depending on its state.
+13 -12
View File
@@ -1,11 +1,11 @@
package io.ebean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class to determine the ebean version. (
* @author Roland Praml, FOCONIS AG
@@ -13,31 +13,32 @@ import org.slf4j.LoggerFactory;
*/
public class EbeanVersion {
private EbeanVersion() {
}
private static final Logger logger = LoggerFactory.getLogger(EbeanVersion.class);
private static String version = "unknown";
static {
try {
Properties prop = new Properties();
InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties");
if (in != null) {
prop.load(in);
in.close();
version = prop.getProperty("version");
try (InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties")) {
if (in != null) {
prop.load(in);
in.close();
version = prop.getProperty("version");
}
}
logger.info("ebean version: {}", version);
} catch (IOException e) {
logger.warn("Could not determine ebean version: {}", e.getMessage());
}
}
/**
* Returns the ebean version (read from /META-INF/maven/io.ebean/ebean/pom.properties)
*/
public static String getVersion() {
return version;
}
}
@@ -120,6 +120,14 @@ public interface ExpressionList<T> {
*/
Query<T> asDraft();
/**
* Convert the query to a DTO bean query.
* <p>
* We effectively use the underlying ORM query to build the SQL and then execute
* and map it into DTO beans.
*/
<D> DtoQuery<D> asDto(Class<D> dtoClass);
/**
* Execute using "for update" clause which results in the DB locking the record.
*/
@@ -1023,6 +1031,20 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> bitwiseAll(String propertyName, long flags);
/**
* Add expression for the given bit flags to be NOT set.
* <p>
* <pre>{@code
*
* where().bitwiseNot("flags", BwFlags.HAS_COLOUR)
*
* }</pre>
*
* @param propertyName The property that holds the flags value
* @param flags The flags we are looking for
*/
ExpressionList<T> bitwiseNot(String propertyName, long flags);
/**
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
* <p>
+452
View File
@@ -0,0 +1,452 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.NonUniqueResultException;
import java.time.Clock;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* The extended API for EbeanServer.
* <p>
* This provides the finder methods that take an explicit transaction rather than obtaining
* the transaction from the usual mechanism (which is ThreadLocal based).
* </p>
* <p>
* In general we only want to use this ExtendedServer API when we want to avoid / bypass
* the use of the mechanism to get the current transaction and instead explicitly supply
* the transaction to use.
* </p>
* <p>
* Note that in all cases the transaction supplied can be null and in this case the EbeanServer
* will use the normal mechanism to obtain the transaction to use.
* </p>
*/
public interface ExtendedServer {
/**
* Return the NOW time from the Clock.
*/
long clockNow();
/**
* Set the Clock to use for <code>@WhenCreated</code> and <code>@WhenModified</code>.
* <p>
* Note that we only expect to change the Clock for testing purposes.
* </p>
*/
void setClock(Clock clock);
/**
* Return the number of 'top level' or 'root' entities this query should return.
*
* @see Query#findCount()
* @see Query#findFutureCount()
*/
<T> int findCount(Query<T> query, Transaction transaction);
/**
* Return the Id values of the query as a List.
*
* @see Query#findIds()
*/
@Nonnull
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
/**
* 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
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
* 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)
*/
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
/**
* 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)
*/
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
/**
* 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
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
/**
* 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
<T> List<T> findList(Query<T> query, Transaction transaction);
/**
* 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
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
/**
* 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
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
/**
* 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
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
/**
* 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
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
/**
* 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
<T> Set<T> findSet(Query<T> query, Transaction transaction);
/**
* 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
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
/**
* 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
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
/**
* 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
<T> T findOne(Query<T> query, Transaction transaction);
/**
* Similar to findOne() but returns an Optional (rather than nullable).
*/
@Nonnull
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
/**
* 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
*/
<T> int delete(Query<T> query, Transaction transaction);
/**
* Execute the update query returning the number of rows updated.
* <p>
* The update query must be created using {@link EbeanServer#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
*/
<T> int update(Query<T> query, Transaction transaction);
/**
* 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
List<SqlRow> findList(SqlQuery query, Transaction transaction);
/**
* 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>
*/
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
/**
* 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>
*/
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
/**
* 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
SqlRow findOne(SqlQuery query, Transaction transaction);
}
@@ -76,7 +76,6 @@ class PrimaryServer {
if (defaultServerName == null) {
defaultServerName = "db";
}
System.setProperty("ebean_db", defaultServerName);
return defaultServerName;
}
-8
View File
@@ -602,8 +602,6 @@ public interface Query<T> {
* <p>
* This query will execute against the EbeanServer that was used to create it.
* </p>
*
* @see EbeanServer#findIds(Query, Transaction)
*/
@Nonnull
<A> List<A> findIds();
@@ -740,8 +738,6 @@ public interface Query<T> {
* .findList();
*
* }</pre>
*
* @see EbeanServer#findList(Query, Transaction)
*/
@Nonnull
List<T> findList();
@@ -759,8 +755,6 @@ public interface Query<T> {
* .findSet();
*
* }</pre>
*
* @see EbeanServer#findSet(Query, Transaction)
*/
@Nonnull
Set<T> findSet();
@@ -782,8 +776,6 @@ public interface Query<T> {
* .findMap();
*
* }</pre>
*
* @see EbeanServer#findMap(Query, Transaction)
*/
@Nonnull
<K> Map<K, T> findMap();
+40
View File
@@ -0,0 +1,40 @@
package io.ebean;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Used with SqlQuery to process potentially large queries reading directly from the JDBC ResultSet.
* <p>
* This provides a low level option that reads directly from the JDBC ResultSet.
* </p>
*
* <pre>{@code
*
* String sql = "select id, name, status from o_customer order by name desc";
*
* Ebean.createSqlQuery(sql)
* .findEachRow((resultSet, rowNum) -> {
*
* // read directly from ResultSet
*
* long id = resultSet.getLong(1);
* String name = resultSet.getString(2);
*
* // do something interesting with the data
*
* });
*
* }</pre>
*/
@FunctionalInterface
public interface RowConsumer {
/**
* Read the data from the ResultSet and process it.
*
* @param resultSet The JDBC ResultSet positioned to the current row
* @param rowNum The number of the current row being mapped.
*/
void accept(ResultSet resultSet, int rowNum) throws SQLException;
}
+62
View File
@@ -0,0 +1,62 @@
package io.ebean;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Used with SqlQuery to map raw JDBC ResultSet to objects.
* <p>
* This provides a low level mapping option with direct use of JDBC ResultSet
* with the option of having logic in the mapping. For example, only map some
* columns depending on the values read from other columns.
* </p>
* <p>
* For straight mapping into beans then DtoQuery would be the first choice as
* it can automatically map the ResultSet into beans.
* </p>
*
* <pre>{@code
*
* //
* // A mapper from ResultSet into our CustomerDto bean
* //
* class CustomerMapper implements RowMapper<CustomerDto> {
*
* @Override
* public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
*
* long id = rset.getLong(1);
* String name = rset.getString(2);
* String status = rset.getString(3);
*
* return new CustomerDto(id, name, status);
* }
* }
*
*
* //
* // Then use the mapper
* //
*
* String sql = "select id, name, status from o_customer where name = ?";
*
* CustomerDto rob = Ebean.createSqlQuery(sql)
* .setParameter(1, "Rob")
* .findOne(CUSTOMER_MAPPER);
*
*
* }</pre>
*
* @param <T> The type the row data is mapped into.
*/
@FunctionalInterface
public interface RowMapper<T> {
/**
* Read the data from the ResultSet and map to the return type.
*
* @param resultSet The JDBC ResultSet positioned to the current row
* @param rowNum The number of the current row being mapped.
*/
T map(ResultSet resultSet, int rowNum) throws SQLException;
}
+107
View File
@@ -3,6 +3,7 @@ package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
@@ -77,12 +78,118 @@ public interface SqlQuery extends Serializable {
@Nullable
SqlRow findOne();
/**
* Execute the query returning a single result using the mapper.
*
* @param mapper Used to map each ResultSet row into the result object.
*/
<T> T findOne(RowMapper<T> mapper);
/**
* Execute the query returning a list using the mapper.
*
* @param mapper Used to map each ResultSet row into the result object.
*/
<T> List<T> findList(RowMapper<T> mapper);
/**
* Execute the query reading each row from ResultSet using the RowConsumer.
* <p>
* This provides a low level option that reads directly from the JDBC ResultSet
* and is good for processing very large results where (unlike findList) we don't
* hold all the results in memory but instead can process row by row.
* </p>
*
* <pre>{@code
*
* String sql = "select id, name, status from customer order by name desc";
*
* Ebean.createSqlQuery(sql)
* .findEachRow((resultSet, rowNum) -> {
*
* // read directly from ResultSet
*
* long id = resultSet.getLong(1);
* String name = resultSet.getString(2);
*
* // do something interesting with the data
*
* });
*
* }</pre>
*
* @param consumer Used to read and process each ResultSet row.
*/
void findEachRow(RowConsumer consumer);
/**
* Execute the query returning an optional row.
*/
@Nonnull
Optional<SqlRow> findOneOrEmpty();
/**
* Execute the query returning a single scalar attribute.
* <pre>@{code
*
* String sql = "select max(unit_price) from o_order_detail where order_qty > ?";
*
* BigDecimal maxPrice = Ebean.createSqlQuery(sql)
* .setParameter(1, 2)
* .findSingleAttribute(BigDecimal.class);
*
* }</pre>
*
* <p>
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
* </p>
*
* @param attributeType The type of the returned value
*/
<T> T findSingleAttribute(Class<T> attributeType);
/**
* Execute the query returning a single BigDecimal value.
* <p>
* This is an alias for <code>findSingleAttribute(BigDecimal.class)</code>
* </p>
*/
BigDecimal findSingleDecimal();
/**
* Execute the query returning a single Long value.
* <p>
* This is an alias for <code>findSingleAttribute(Long.class)</code>
* </p>
*/
Long findSingleLong();
/**
* Execute the query returning a list of scalar attribute values.
*
* <pre>{@code
*
* String sql =
* " select (unit_price * order_qty) " +
* " from o_order_detail " +
* " where unit_price > ? " +
* " order by (unit_price * order_qty) desc";
*
* //
* List<BigDecimal> lineAmounts = Ebean.createSqlQuery(sql)
* .setParameter(1, 3)
* .findSingleAttributeList(BigDecimal.class);
*
* }</pre>
*
* <p>
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
* </p>
*
* @param attributeType The type of the returned value
*/
<T> List<T> findSingleAttributeList(Class<T> attributeType);
/**
* The same as bind for named parameters.
*/
+34 -4
View File
@@ -359,7 +359,10 @@ public interface Transaction extends AutoCloseable {
void setBatchMode(boolean useBatch);
/**
* The JDBC batch mode to use for this transaction.
* Deprecated - migrate to {@link #setBatchMode(boolean)}.
* <p>
* Set the JDBC batch mode to use for this transaction.
* </p>
* <p>
* If this is NONE then JDBC batch can still be used for each request - save(), insert(), update() or delete()
* and this would be useful if the request cascades to detail beans.
@@ -368,15 +371,23 @@ public interface Transaction extends AutoCloseable {
* @param persistBatchMode the batch mode to use for this transaction
* @see io.ebean.config.ServerConfig#setPersistBatch(PersistBatch)
*/
@Deprecated
void setBatch(PersistBatch persistBatchMode);
/**
* Deprecated - migrate to {@link #isBatchMode()}.
* Return the batch mode at the transaction level.
*/
@Deprecated
PersistBatch getBatch();
/**
* Return the batch mode at the transaction level.
*/
PersistBatch getBatch();
boolean isBatchMode();
/**
* Set the JDBC batch mode to use for a save() or delete() request.
* Set the JDBC batch mode to use for a save() or delete() when cascading to children.
* <p>
* This only takes effect when batch mode on the transaction has not already meant that
* JDBC batch mode is being used.
@@ -385,17 +396,36 @@ public interface Transaction extends AutoCloseable {
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
* </p>
* <p>
* This is effectively already turned on for all platforms apart from older Sql Server.
* </p>
*
* @param batchOnCascadeMode the batch mode to use per save(), insert(), update() or delete()
* @param batchMode the batch mode to use per save(), insert(), update() or delete()
* @see io.ebean.config.ServerConfig#setPersistBatchOnCascade(PersistBatch)
*/
void setBatchOnCascade(boolean batchMode);
/**
* Set the batch mode when cascading.
* <p>
* Deprecated in favour of {@link #setBatchOnCascade(boolean)}
* </p>
*/
@Deprecated
void setBatchOnCascade(PersistBatch batchOnCascadeMode);
/**
* Deprecated - migrate to {@link #isBatchMode()}.
* Return the batch mode at the request level (for each save(), insert(), update() or delete()).
*/
@Deprecated
PersistBatch getBatchOnCascade();
/**
* Return the batch mode at the request level.
*/
boolean isBatchOnCascade();
/**
* Specify the number of statements before a batch is flushed automatically.
*/
+54
View File
@@ -0,0 +1,54 @@
package io.ebean.cache;
import java.util.Set;
/**
* For query cache entries we additionally hold the dependent tables and timestamp for the query result.
* <p>
* We use the dependent tables and timestamp to validate that tables the query joins to have not been
* modified since the query cache entry was cached. If any dependent tables have since been modified
* the query cache entry is treated as invalid.
* </p>
*/
public class QueryCacheEntry {
private final Object value;
private final Set<String> dependentTables;
private final long timestamp;
/**
* Create with dependent tables and timestamp.
*
* @param value The query result being cached
* @param dependentTables The extra tables the query is dependent on (joins to)
* @param timestamp The timestamp that the query uses to check for modifications
*/
public QueryCacheEntry(Object value, Set<String> dependentTables, long timestamp) {
this.value = value;
this.dependentTables = dependentTables;
this.timestamp = timestamp;
}
/**
* Return the actual query result.
*/
public Object getValue() {
return value;
}
/**
* Return the tables the query result is dependent on.
*/
public Set<String> getDependentTables() {
return dependentTables;
}
/**
* Return the timestamp used to check for modifications on the dependent tables.
*/
public long getTimestamp() {
return timestamp;
}
}
@@ -0,0 +1,12 @@
package io.ebean.cache;
/**
* Used to validate that a query cache entry is still valid based on dependent tables.
*/
public interface QueryCacheEntryValidate {
/**
* Return true if the entry is still valid based on dependent tables.
*/
boolean isValid(QueryCacheEntry queryCacheEntry);
}
+65
View File
@@ -0,0 +1,65 @@
package io.ebean.cache;
import io.ebean.config.CurrentTenantProvider;
/**
* Configuration used to create ServerCache instances.
*/
public class ServerCacheConfig {
private final ServerCacheType type;
private final String cacheKey;
private final ServerCacheOptions cacheOptions;
private final CurrentTenantProvider tenantProvider;
private final QueryCacheEntryValidate queryCacheEntryValidate;
public ServerCacheConfig(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions, CurrentTenantProvider tenantProvider, QueryCacheEntryValidate queryCacheEntryValidate) {
this.type = type;
this.cacheKey = cacheKey;
this.cacheOptions = cacheOptions;
this.tenantProvider = tenantProvider;
this.queryCacheEntryValidate = queryCacheEntryValidate;
}
/**
* Return the cache type.
*/
public ServerCacheType getType() {
return type;
}
/**
* Return the name of the cache.
*/
public String getCacheKey() {
return cacheKey;
}
/**
* Return the tuning options.
*/
public ServerCacheOptions getCacheOptions() {
return cacheOptions;
}
/**
* Return the current tenant provider.
*/
public CurrentTenantProvider getTenantProvider() {
return tenantProvider;
}
/**
* Return the service that provides validation for query cache entries.
*/
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
return queryCacheEntryValidate;
}
/**
* Return true if the cache is a query cache.
*/
public boolean isQueryCache() {
return type == ServerCacheType.QUERY;
}
}
+10 -3
View File
@@ -1,7 +1,5 @@
package io.ebean.cache;
import io.ebean.config.CurrentTenantProvider;
/**
* Defines method for constructing caches for beans and queries.
*/
@@ -10,6 +8,15 @@ public interface ServerCacheFactory {
/**
* Create the cache for the given type with options.
*/
ServerCache createCache(ServerCacheType type, String cacheKey, CurrentTenantProvider tenantProvider, ServerCacheOptions cacheOptions);
ServerCache createCache(ServerCacheConfig config);
/**
* Return a ServerCacheNotify that we will send ServerCacheNotification events to.
* <p>
* This is used if a ServerCacheNotifyPlugin is not supplied.
* </p>
*
* @param listener The listener that should be used to process the notification events.
*/
ServerCacheNotify createCacheNotify(ServerCacheNotify listener);
}
@@ -0,0 +1,29 @@
package io.ebean.cache;
import java.util.Set;
/**
* Notification event that dependent tables have been modified.
* <p>
* This is sent to other interested servers (in the cluster).
* </p>
*/
public class ServerCacheNotification {
private final long modifyTimestamp;
private final Set<String> dependentTables;
public ServerCacheNotification(long modifyTimestamp, Set<String> dependentTables) {
this.modifyTimestamp = modifyTimestamp;
this.dependentTables = dependentTables;
}
public long getModifyTimestamp() {
return modifyTimestamp;
}
public Set<String> getDependentTables() {
return dependentTables;
}
}
+12
View File
@@ -0,0 +1,12 @@
package io.ebean.cache;
/**
* Interface for both listening to notification changes and sending them to other members of the cluster.
*/
public interface ServerCacheNotify {
/**
* Notify other server cache members of the table modifications or process the notifications.
*/
void notify(ServerCacheNotification notification);
}
@@ -0,0 +1,17 @@
package io.ebean.cache;
import io.ebean.config.ServerConfig;
/**
* Plugin that provides a ServerCacheNotify implementation.
* <p>
* Is supplied this will be used to send the ServerCacheNotification event to other cluster members.
* </p>
*/
public interface ServerCacheNotifyPlugin {
/**
* Create a ServerCacheNotify implementation given the server configuration.
*/
ServerCacheNotify create(ServerConfig serverConfig);
}
@@ -102,6 +102,11 @@ public class DbMigrationConfig {
*/
protected Map<String, String> runPlaceholderMap;
/**
* DB schema used for the migration (and testing).
*/
protected String dbSchema;
/**
* DB user used to run the DB migration.
*/
@@ -373,6 +378,33 @@ public class DbMigrationConfig {
this.dbPassword = dbPassword;
}
/**
* Return the DB schema to use (for migration, testing etc).
*/
public String getDbSchema() {
String schema = readEnvironment("ddl.migration.schema");
if (schema != null) {
return schema;
}
return dbSchema;
}
/**
* Set the Db schema to use.
*/
public void setDbSchema(String dbSchema) {
this.dbSchema = dbSchema;
}
/**
* Set the Db schema if it hasn't already been defined.
*/
public void setDefaultDbSchema(String dbSchema) {
if (this.dbSchema == null) {
this.dbSchema = dbSchema;
}
}
/**
* Return migration versions that should be added to history without running.
*/
@@ -475,6 +507,7 @@ public class DbMigrationConfig {
runMigration = properties.getBoolean("migration.run", runMigration);
metaTable = properties.get("migration.metaTable", metaTable);
runPlaceholders = properties.get("migration.placeholders", runPlaceholders);
dbSchema = properties.get("migration.dbSchema", dbSchema);
//Do not set user and pass from "datasource.db.username"
//There is a null test in MigrationRunner::getConnection to handle this
@@ -557,6 +590,7 @@ public class DbMigrationConfig {
runnerConfig.setRunPlaceholders(runPlaceholders);
runnerConfig.setDbUsername(getDbUsername());
runnerConfig.setDbPassword(getDbPassword());
runnerConfig.setDbSchema(getDbSchema());
runnerConfig.setClassLoader(classLoader);
if (patchInsertOn != null) {
runnerConfig.setPatchInsertOn(patchInsertOn);
@@ -33,6 +33,7 @@ import io.ebean.util.StringHelper;
import org.avaje.datasource.DataSourceConfig;
import javax.sql.DataSource;
import java.time.Clock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -57,22 +58,24 @@ import java.util.ServiceLoader;
* <pre>{@code
*
* ServerConfig c = new ServerConfig();
* c.setName("db");
*
* // read the ebean.properties and load
* // those settings into this serverConfig object
* c.loadFromProperties();
*
* // add any classes found in the app.data package
* c.addPackage("com.myapp.domain");
*
* // register as the 'Default' server
* c.setDefaultServer(true);
* // explicitly register the entity beans to avoid classpath scanning
* c.addClass(Customer.class);
* c.addClass(User.class);
*
* EbeanServer server = EbeanServerFactory.create(c);
*
* }</pre>
*
* <p>
* Note that ServerConfigProvider provides a standard Java ServiceLoader mechanism that can
* be used to apply configuration to the ServerConfig.
* </p>
*
* @author emcgreal
* @author rbygrave
* @see EbeanServerFactory
@@ -293,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).
*/
@@ -360,6 +368,11 @@ public class ServerConfig {
*/
private String uuidStateFile = "ebean-uuid.state";
/**
* The clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
*/
private Clock clock = Clock.systemUTC();
private List<IdGenerator> idGenerators = new ArrayList<>();
private List<BeanFindController> findControllers = new ArrayList<>();
private List<BeanPersistController> persistControllers = new ArrayList<>();
@@ -505,6 +518,20 @@ public class ServerConfig {
}
/**
* Get the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
*/
public Clock getClock() {
return clock;
}
/**
* Set the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
*/
public void setClock(final Clock clock) {
this.clock = clock;
}
/**
* Return the slow query time in millis.
*/
@@ -1098,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.
*/
@@ -2742,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);
@@ -0,0 +1,39 @@
package io.ebean.config;
/**
* Provides a ServiceLoader based mechanism to configure a ServerConfig.
* <p>
* Provide an implementation and register it via the standard Java ServiceLoader mechanism
* via a file at <code>META-INF/services/io.ebean.config.ServerConfigProvider</code>.
* </p>
* <p>
* If you are using a DI container like Spring or Guice you are unlikely to use this but instead use a
* spring specific configuration. When we are not using a DI container we may use this mechanism to
* explicitly register the entity beans and avoid classpath scanning.
* </p>
* <pre>{@code
*
* public class EbeanConfigProvider implements ServerConfigProvider {
*
* @Override
* public void apply(ServerConfig config) {
*
* // register the entity bean classes explicitly
* config.addClass(Customer.class);
* config.addClass(User.class);
* ...
* }
* }
*
* }</pre>
*/
public interface ServerConfigProvider {
/**
* Apply the configuration to the ServerConfig.
* <p>
* Typically we explicitly register entity bean classes and thus avoid classpath scanning.
* </p>
*/
void apply(ServerConfig config);
}
@@ -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.
*/
@@ -18,22 +18,22 @@ import java.util.Set;
*/
class LoadContext {
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
/**
* Map we are loading the properties into.
*/
private final Map<String, String> map = new LinkedHashMap<>();
private final Map<String, String> map = new LinkedHashMap<>();
/**
* Names of resources/files that were loaded.
*/
private final Set<String> loadedResources = new LinkedHashSet<>();
private final Set<String> loadedResources = new LinkedHashSet<>();
/**
* Return the input stream (maybe null) for the given source.
*/
InputStream resource(String resourcePath, Loader.Source source) {
InputStream resource(String resourcePath, Loader.Source source) {
InputStream is = null;
if (source == Loader.Source.RESOURCE) {
@@ -46,21 +46,21 @@ class LoadContext {
if (file.exists()) {
try {
is = new FileInputStream(file);
loadedResources.add("file:"+resourcePath);
loadedResources.add("file:" + resourcePath);
} catch (FileNotFoundException e) {
throw new IllegalStateException(e);
}
}
}
return is;
}
return is;
}
private InputStream resourceStream(String resourcePath) {
InputStream is = getClass().getResourceAsStream("/" + resourcePath);
if (is == null) {
// search the module path for top level resource
is = ClassLoader.getSystemResourceAsStream(resourcePath);
is = ClassLoader.getSystemResourceAsStream(resourcePath);
}
return is;
}
@@ -68,30 +68,30 @@ class LoadContext {
/**
* Add a property entry.
*/
void put(String key, String val) {
if (val != null) {
val = val.trim();
void put(String key, String val) {
if (val != null) {
val = val.trim();
}
map.put(key, val);
}
map.put(key, val);
}
/**
* Evaluate all the expressions and return as a Properties object.
*/
Properties eval() {
Properties eval() {
log.info("loaded properties from {}", loadedResources);
log.info("loaded properties from {}", loadedResources);
Properties properties = new Properties();
Properties properties = new Properties();
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = PropertyEval.eval(entry.getValue());
properties.setProperty(key, value);
}
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = PropertyEval.eval(entry.getValue());
properties.setProperty(key, value);
}
return properties;
}
return properties;
}
/**
* Read the special properties that can point to an external properties source.
@@ -16,10 +16,10 @@ import java.util.Properties;
*/
class Loader {
private static final Logger log = LoggerFactory.getLogger(Loader.class);
private static final Logger log = LoggerFactory.getLogger(Loader.class);
enum Source {
RESOURCE,
enum Source {
RESOURCE,
FILE
}
@@ -27,7 +27,7 @@ class Loader {
private YamlLoader yamlLoader;
Loader() {
Loader() {
String skipYaml = System.getProperty("ebeanSkipYaml");
if (!"true".equals(skipYaml)) {
try {
@@ -44,19 +44,19 @@ class Loader {
/**
* Load the configuration with the expected ordering.
*/
void load() {
void load() {
loadMain(Source.RESOURCE);
// external file configuration overrides the resources configuration
loadMain(Source.FILE);
loadViaSystemProperty();
loadViaIndirection();
loadViaSystemProperty();
loadViaIndirection();
// test configuration (if found) overrides main configuration
// test configuration (if found) overrides main configuration
// we should only find these resources when running tests
loadTest();
}
loadTest();
}
/**
* Load test configuration.
@@ -89,7 +89,7 @@ class Loader {
}
private void loadViaSystemProperty() {
String fileName = System.getenv("EBEAN_PROPS_FILE");
String fileName = System.getenv("EBEAN_PROPS_FILE");
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName != null) {
@@ -112,59 +112,66 @@ class Loader {
* Evaluate all the configuration entries and return as properties.
*/
Properties eval() {
return loadContext.eval();
}
return loadContext.eval();
}
void loadYaml(String resourcePath, Source source) {
if (yamlLoader != null) {
try {
yamlLoader.load(resource(resourcePath, source));
void loadYaml(String resourcePath, Source source) {
if (yamlLoader != null) {
try {
try (InputStream is = resource(resourcePath, source)) {
yamlLoader.load(is);
}
} catch (Exception e) {
log.warn("Failed to read yml from:" + resourcePath, e);
}
}
}
}
}
void loadProperties(String resourcePath, Source source) {
InputStream is = resource(resourcePath, source);
if (is != null) {
loadProperties(is);
}
}
void loadProperties(String resourcePath, Source source) {
try {
try (InputStream is = resource(resourcePath, source)) {
if (is != null) {
loadProperties(is);
}
}
} catch (Exception e) {
log.warn("Failed to read properties from:" + resourcePath, e);
}
}
private InputStream resource(String resourcePath, Source source) {
return loadContext.resource(resourcePath, source);
}
private InputStream resource(String resourcePath, Source source) {
return loadContext.resource(resourcePath, source);
}
private void loadProperties(InputStream is) {
private void loadProperties(InputStream is) {
if (is != null) {
try {
Properties properties = new Properties();
properties.load(is);
put(properties);
} catch (IOException e) {
throw new RuntimeException("Failed to load properties?", e);
} finally {
close(is);
}
}
}
if (is != null) {
try {
Properties properties = new Properties();
properties.load(is);
put(properties);
} catch (IOException e) {
throw new RuntimeException("Failed to load properties?", e);
} finally {
close(is);
}
}
}
private void put(Properties properties) {
Enumeration<?> enumeration = properties.propertyNames();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
String property = properties.getProperty(key);
loadContext.put(key, property);
}
}
private void put(Properties properties) {
Enumeration<?> enumeration = properties.propertyNames();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
String property = properties.getProperty(key);
loadContext.put(key, property);
}
}
private void close(InputStream is) {
try {
is.close();
} catch (IOException e) {
log.warn("Error closing input stream for properties", e);
}
}
private void close(InputStream is) {
try {
is.close();
} catch (IOException e) {
log.warn("Error closing input stream for properties", e);
}
}
}
@@ -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);
}
@@ -53,6 +53,10 @@ public class ScopeTrans {
*/
private boolean rolledBack;
/**
* Flag set when nested commit has occurred.
*/
private boolean nestedCommit;
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope) {
@@ -113,7 +117,6 @@ public class ScopeTrans {
complete();
}
/**
* Complete the transaction programmatically. Try to commit.
*/
@@ -124,7 +127,7 @@ public class ScopeTrans {
}
public void end() {
if (created) {
if (created || !nestedCommit) {
transaction.end();
}
}
@@ -133,6 +136,7 @@ public class ScopeTrans {
if (created) {
transaction.commit();
} else {
nestedCommit = true;
transaction.setBatchFlushOnQuery(restoreBatchFlushOnQuery);
if (restoreBatch != null) {
transaction.setBatch(restoreBatch);
@@ -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();
}
@@ -2,8 +2,11 @@ package io.ebeaninternal.api;
import io.ebean.DtoQuery;
import io.ebean.EbeanServer;
import io.ebean.ExtendedServer;
import io.ebean.PersistenceContextScope;
import io.ebean.Query;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.Transaction;
import io.ebean.TxScope;
import io.ebean.bean.BeanCollectionLoader;
@@ -29,7 +32,12 @@ import java.util.function.Predicate;
/**
* Service Provider extension to EbeanServer.
*/
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader, BeanCollectionLoader {
/**
* Return the log manager.
*/
SpiLogManager log();
/**
* Return the server extended Json context.
@@ -229,6 +237,31 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
void scopedTransactionExit(Object returnOrThrowable, int opCode);
/**
* SqlQuery find single attribute.
*/
<T> T findSingleAttribute(SpiSqlQuery query, Class<T> cls);
/**
* SqlQuery find single attribute list.
*/
<T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls);
/**
* SqlQuery find one with mapper.
*/
<T> T findOneMapper(SpiSqlQuery query, RowMapper<T> mapper);
/**
* SqlQuery find list with mapper.
*/
<T> List<T> findListMapper(SpiSqlQuery query, RowMapper<T> mapper);
/**
* SqlQuery find each with consumer.
*/
void findEachRow(SpiSqlQuery query, RowConsumer consumer);
/**
* DTO findList query.
*/
@@ -0,0 +1,27 @@
package io.ebeaninternal.api;
/**
* Log manager for SQL, TXN and Summary logging.
* <p>
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
* the logged SQL during testing such that we can assert against the executed sql if desired.
* </p>
*/
public interface SpiLogManager {
/**
* Return the SQL logger.
*/
SpiLogger sql();
/**
* Return the TXN logger.
*/
SpiLogger txn();
/**
* Return the Summary logger.
*/
SpiLogger sum();
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.api;
/**
* Logger for SQL, TXN and Summary logging.
* <p>
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
* the logged SQL during testing such that we can assert against the executed sql if desired.
* </p>
*/
public interface SpiLogger {
/**
* Is debug logging enabled.
*/
boolean isDebug();
/**
* Is trace logging enabled.
*/
boolean isTrace();
/**
* Log a debug level message.
*/
void debug(String msg);
/**
* Log a trace level message.
*/
void trace(String msg);
}
@@ -0,0 +1,12 @@
package io.ebeaninternal.api;
/**
* API for Plugins to define the logger for io.ebean.SQL, io.ebean.TXN and io.ebean.SUM.
*/
public interface SpiLoggerFactory {
/**
* Create the logger given the name.
*/
SpiLogger create(String name);
}
@@ -644,9 +644,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
boolean isBeanCachePut();
/**
* Return true if the bean cache is being explicitly loaded via RECACHE mode.
* Return true if we must hit the DB (Cache reload or select for update).
*/
boolean isBeanCacheReload();
boolean isForceHitDatabase();
/**
* Return the cache mode for using the bean cache (Get and Put).
@@ -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
@@ -13,7 +13,7 @@ public interface TxnProfileEventCodes {
String EVT_INSERT = "i";
String EVT_UPDATE = "u";
String EVT_DELETE = "d";
String EVT_SOFT_DELETE = "ds";
String EVT_DELETE_SOFT = "ds";
String EVT_DELETE_PERMANENT = "dp";
String EVT_ORMUPDATE = "uo";
String FIND_UPDATE = "uq";
@@ -1,11 +1,11 @@
package io.ebeaninternal.dbmigration;
import io.ebean.Transaction;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.model.CurrentModel;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -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,64 @@ 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) {
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
if (is != null) {
String content = readContent(new InputStreamReader(is));
runScript(false, content, sqlScript);
try (InputStream is = getClassLoader().getResourceAsStream(sqlScript)) {
if (is != null) {
String content = readContent(new InputStreamReader(is));
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);
}
/**
@@ -79,13 +79,21 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
protected void createOrReplaceFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) throws IOException {
apply
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine()
.append("begin").newLine();
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine();
apply.append("declare").newLine()
.append(" lowerTs timestamptz;").newLine()
.append(" upperTs timestamptz;").newLine();
apply.append("begin").newLine()
.append(" lowerTs = lower(OLD.sys_period);").newLine()
.append(" upperTs = greatest(lowerTs + '1 microsecond',current_timestamp);").newLine();
apply
.append(" if (TG_OP = 'UPDATE') then").newLine();
appendInsertIntoHistory(apply, historyTable, includedColumns);
apply
.append(" NEW.").append(sysPeriod).append(" = tstzrange(").append(currentTimestamp).append(",null);").newLine()
.append(" NEW.").append(sysPeriod).append(" = tstzrange(upperTs,null);").newLine()
.append(" return new;").newLine();
apply
.append(" elsif (TG_OP = 'DELETE') then").newLine();
@@ -124,7 +132,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriod).append(",");
appendColumnNames(buffer, columns, "");
buffer.append(") values (tstzrange(lower(OLD.").append(sysPeriod).append("), ").append(currentTimestamp).append("), ");
buffer.append(") values (tstzrange(lowerTs,upperTs), ");
appendColumnNames(buffer, columns, "OLD.");
buffer.append(");").newLine();
}
@@ -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;
@@ -24,12 +24,14 @@ public class MigrationXmlReader {
*/
public static Migration read(String resourcePath) {
InputStream is = MigrationXmlReader.class.getResourceAsStream(resourcePath);
if (is == null) {
throw new IllegalArgumentException("No resource found for path [" + resourcePath + "]");
try (InputStream is = MigrationXmlReader.class.getResourceAsStream(resourcePath)) {
if (is == null) {
throw new IllegalArgumentException("No resource found for path [" + resourcePath + "]");
}
return read(is);
} catch (IOException e) {
throw new IllegalArgumentException("Failed to auto close resource " + resourcePath, e);
}
return read(is);
}
/**
@@ -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);
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.ChangeSetType;
import io.ebeaninternal.dbmigration.migration.DropColumn;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.migration.DropTable;
import io.ebeaninternal.dbmigration.migration.Migration;
@@ -207,10 +208,26 @@ public class PendingDrops {
} else if (pendingDrop instanceof DropTable && dropTableIn((DropTable) pendingDrop, appliedDrops)) {
iterator.remove();
} else if (pendingDrop instanceof DropHistoryTable && dropHistoryTableIn((DropHistoryTable) pendingDrop, appliedDrops)) {
iterator.remove();
}
}
}
/**
* Return true if the pendingDrop is contained in the appliedDrops.
*/
private boolean dropHistoryTableIn(DropHistoryTable pendingDrop, ChangeSet appliedDrops) {
for (Object o : appliedDrops.getChangeSetChildren()) {
if (o instanceof DropHistoryTable && sameHistoryTable(pendingDrop, (DropHistoryTable) o)) {
return true;
}
}
return false;
}
/**
* Return true if the pendingDrop is contained in the appliedDrops.
*/
@@ -235,6 +252,13 @@ public class PendingDrops {
return false;
}
/**
* Return true if the DropHistoryTable match by base-table name.
*/
private boolean sameHistoryTable(DropHistoryTable pendingDrop, DropHistoryTable o) {
return pendingDrop.getBaseTable().equals(o.getBaseTable());
}
/**
* Return true if the DropTable match by table name.
*/
@@ -5,6 +5,7 @@ import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.TableJoinColumn;
@@ -48,10 +49,12 @@ class ModelBuildIntersectionTable {
private void buildFkConstraints() {
ctx.fkeyBuilder(intersectionTable)
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
PropertyForeignKey foreignKey = manyProp.getForeignKey();
if (foreignKey == null || !foreignKey.isNoConstraint()) {
ctx.fkeyBuilder(intersectionTable)
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
}
intersectionTable.checkDuplicateForeignKeys();
}
@@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.IOException;
import java.io.InputStream;
/**
@@ -87,12 +88,15 @@ public class ExtraDdlXmlReader {
*/
public static ExtraDdl read(String resourcePath) {
InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath);
if (is == null) {
// we expect this and check for null
return null;
try (InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath)) {
if (is == null) {
// we expect this and check for null
return null;
}
return read(is);
} catch (IOException e) {
throw new IllegalStateException("Error on auto close of " + resourcePath, e);
}
return read(is);
}
/**
@@ -144,7 +144,7 @@ public class BaseQueryTuner {
default:
// not using autoTune when explicitly loading the l2 bean cache
// or when using Versions query
return !query.isBeanCacheReload() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
return !query.isForceHitDatabase() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
}
}
@@ -12,6 +12,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
@@ -106,11 +107,14 @@ public class DefaultAutoTuneService implements AutoTuneService {
loadAutoTuneProfiling(AutoTuneXmlReader.read(file));
} else {
// look for autotune as a resource
InputStream stream = getClass().getResourceAsStream("/" + tuningFile);
if (stream != null) {
loadAutoTuneProfiling(AutoTuneXmlReader.read(stream));
} else {
logger.warn("AutoTune file {} not found - no initial automatic query tuning", tuningFile);
try (InputStream stream = getClass().getResourceAsStream("/" + tuningFile)) {
if (stream != null) {
loadAutoTuneProfiling(AutoTuneXmlReader.read(stream));
} else {
logger.warn("AutoTune file {} not found - no initial automatic query tuning", tuningFile);
}
} catch (IOException e) {
throw new IllegalStateException("Error on auto close of " + tuningFile, e);
}
}
}
@@ -2,6 +2,9 @@ package io.ebeaninternal.server.cache;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.ArrayList;
import java.util.Collection;
/**
* Change to remove bean from L2 cache.
*/
@@ -9,15 +12,35 @@ class CacheChangeBeanRemove implements CacheChange {
private final BeanDescriptor<?> descriptor;
private final Object id;
private final Collection<Object> ids;
CacheChangeBeanRemove(BeanDescriptor<?> descriptor, Object id) {
CacheChangeBeanRemove(Object id, BeanDescriptor<?> descriptor) {
this.descriptor = descriptor;
this.id = id;
this.ids = new ArrayList<>();
ids.add(id);
}
CacheChangeBeanRemove(BeanDescriptor<?> descriptor, Collection<Object> ids) {
this.descriptor = descriptor;
this.ids = ids;
}
@Override
public void apply() {
descriptor.cacheHandleDeleteById(id);
descriptor.cacheApplyInvalidate(ids);
}
/**
* Add more id values.
*/
public void addIds(Collection<Object> moreIds) {
ids.addAll(moreIds);
}
/**
* Add another id value.
*/
public void addId(Object id) {
ids.add(id);
}
}
@@ -25,6 +25,6 @@ class CacheChangeBeanUpdate implements CacheChange {
@Override
public void apply() {
desc.cacheBeanUpdate(id, changes, updateNaturalKey, version);
desc.cacheApplyBeanUpdate(id, changes, updateNaturalKey, version);
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.cache;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -17,22 +18,30 @@ public class CacheChangeSet {
private final List<CacheChange> entries = new ArrayList<>();
private final Set<String> touchedTables = new HashSet<>();
private final Set<BeanDescriptor<?>> queryCaches = new HashSet<>();
private final Set<BeanDescriptor<?>> beanCaches = new HashSet<>();
private final Map<BeanDescriptor<?>, CacheChangeBeanRemove> beanRemoveMap = new HashMap<>();
private final Map<ManyKey, ManyChange> manyChangeMap = new HashMap<>();
/**
* Set of "base tables" modified used to invalidate entities based on views.
*/
private final Set<String> viewInvalidation = new HashSet<>();
private final boolean viewEntityInvalidation;
private final long modificationTimestamp;
/**
* Construct specifying if we also need to process invalidation for entities based on views.
*/
public CacheChangeSet(boolean viewEntityInvalidation) {
this.viewEntityInvalidation = viewEntityInvalidation;
public CacheChangeSet(long modificationTimestamp) {
this.modificationTimestamp = modificationTimestamp;
}
/**
* Return the touched tables.
*/
public Set<String> touchedTables() {
return touchedTables;
}
/**
@@ -40,17 +49,36 @@ public class CacheChangeSet {
* <p>
* Return the set of table changes to process invalidation for entities based on views.
*/
public Set<String> apply() {
public void apply() {
for (BeanDescriptor<?> entry : queryCaches) {
entry.clearQueryCache();
}
for (BeanDescriptor<?> entry : beanCaches) {
entry.clearBeanCache();
}
for (CacheChange entry : entries) {
entry.apply();
}
for (CacheChange entry : manyChangeMap.values()) {
entry.apply();
}
return viewInvalidation;
for (CacheChange entry : beanRemoveMap.values()) {
entry.apply();
}
}
/**
* Add an entry to clear a query cache.
*/
public void addInvalidate(BeanDescriptor<?> descriptor) {
touchedTables.add(descriptor.getBaseTable());
}
/**
* Add invalidation on a set of tables.
*/
public void addInvalidate(Set<String> tables) {
touchedTables.addAll(tables);
}
/**
@@ -60,6 +88,13 @@ public class CacheChangeSet {
queryCaches.add(descriptor);
}
/**
* Add an entry to clear a bean cache.
*/
public void addClearBean(BeanDescriptor<?> descriptor) {
beanCaches.add(descriptor);
}
/**
* Add many property clear.
*/
@@ -85,18 +120,32 @@ public class CacheChangeSet {
* On bean insert register table for view based entity invalidation.
*/
public void addBeanInsert(String baseTable) {
if (viewEntityInvalidation) {
viewInvalidation.add(baseTable);
}
touchedTables.add(baseTable);
}
/**
* Remove a bean from the cache.
*/
public <T> void addBeanRemove(BeanDescriptor<T> desc, Object id) {
entries.add(new CacheChangeBeanRemove(desc, id));
if (viewEntityInvalidation) {
viewInvalidation.add(desc.getBaseTable());
CacheChangeBeanRemove entry = beanRemoveMap.get(desc);
if (entry != null) {
entry.addId(id);
} else {
beanRemoveMap.put(desc, new CacheChangeBeanRemove(id, desc));
touchedTables.add(desc.getBaseTable());
}
}
/**
* Remove a bean from the cache.
*/
public <T> void addBeanRemoveMany(BeanDescriptor<T> desc, Collection<Object> ids) {
CacheChangeBeanRemove entry = beanRemoveMap.get(desc);
if (entry != null) {
entry.addIds(ids);
} else {
beanRemoveMap.put(desc, new CacheChangeBeanRemove(desc, ids));
touchedTables.add(desc.getBaseTable());
}
}
@@ -104,10 +153,8 @@ public class CacheChangeSet {
* Update a bean entry.
*/
public <T> void addBeanUpdate(BeanDescriptor<T> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
touchedTables.add(desc.getBaseTable());
entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version));
if (viewEntityInvalidation) {
viewInvalidation.add(desc.getBaseTable());
}
}
/**
@@ -125,6 +172,13 @@ public class CacheChangeSet {
return manyChangeMap.computeIfAbsent(key, ManyChange::new);
}
/**
* Return the modification timestamp for these changes.
*/
public long modificationTimestamp() {
return modificationTimestamp;
}
/**
* Changes for a specific many property.
*/
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.cache;
import io.ebean.cache.QueryCacheEntryValidate;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.config.CurrentTenantProvider;
@@ -19,6 +20,8 @@ public class CacheManagerOptions {
private CurrentTenantProvider currentTenantProvider;
private QueryCacheEntryValidate queryCacheEntryValidate;
private ServerCacheFactory cacheFactory = new DefaultServerCacheFactory();
private ServerCacheOptions beanDefault = new ServerCacheOptions();
private ServerCacheOptions queryDefault = new ServerCacheOptions();
@@ -45,8 +48,9 @@ public class CacheManagerOptions {
return this;
}
public CacheManagerOptions with(ServerCacheFactory cacheFactory) {
public CacheManagerOptions with(ServerCacheFactory cacheFactory, QueryCacheEntryValidate queryCacheEntryValidate) {
this.cacheFactory = cacheFactory;
this.queryCacheEntryValidate = queryCacheEntryValidate;
return this;
}
@@ -82,4 +86,8 @@ public class CacheManagerOptions {
public ClusterManager getClusterManager() {
return clusterManager;
}
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
return queryCacheEntryValidate;
}
}
@@ -2,7 +2,9 @@ package io.ebeaninternal.server.cache;
import io.ebean.annotation.CacheBeanTuning;
import io.ebean.annotation.CacheQueryTuning;
import io.ebean.cache.QueryCacheEntryValidate;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheConfig;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheType;
@@ -33,22 +35,14 @@ class DefaultCacheHolder {
private final CurrentTenantProvider tenantProvider;
DefaultCacheHolder(CacheManagerOptions builder) {
this(builder.getCacheFactory(), builder.getBeanDefault(), builder.getQueryDefault(), builder.getCurrentTenantProvider());
}
private final QueryCacheEntryValidate queryCacheEntryValidate;
/**
* Create with a cache factory and default cache options.
*
* @param cacheFactory the factory for creating the cache
* @param beanDefault the default options for tuning bean caches
* @param queryDefault the default options for tuning query caches
*/
DefaultCacheHolder(ServerCacheFactory cacheFactory, ServerCacheOptions beanDefault, ServerCacheOptions queryDefault, CurrentTenantProvider tenantProvider) {
this.cacheFactory = cacheFactory;
this.beanDefault = beanDefault;
this.queryDefault = queryDefault;
this.tenantProvider = tenantProvider;
DefaultCacheHolder(CacheManagerOptions builder) {
this.cacheFactory = builder.getCacheFactory();
this.beanDefault = builder.getBeanDefault();
this.queryDefault = builder.getQueryDefault();
this.tenantProvider = builder.getCurrentTenantProvider();
this.queryCacheEntryValidate = builder.getQueryCacheEntryValidate();
}
ServerCache getCache(Class<?> beanType, String cacheKey, ServerCacheType type) {
@@ -76,7 +70,7 @@ class DefaultCacheHolder {
collectIdCaches.computeIfAbsent(beanType.getName(), s -> new ConcurrentSkipListSet<>()).add(key);
}
}
return cacheFactory.createCache(type, key, tenantProvider, options);
return cacheFactory.createCache(new ServerCacheConfig(type, key, options, tenantProvider, queryCacheEntryValidate));
}
void clearAll() {
@@ -2,10 +2,8 @@ package io.ebeaninternal.server.cache;
import io.ebean.BackgroundExecutor;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheStatistics;
import io.ebean.cache.TenantAwareKey;
import io.ebean.config.CurrentTenantProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,7 +13,6 @@ import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
@@ -65,47 +62,14 @@ public class DefaultServerCache implements ServerCache {
protected TenantAwareKey tenantAwareKey;
/**
* Construct using a ConcurrentHashMap and cache options.
*/
public DefaultServerCache(String name, CurrentTenantProvider tenantProvider, ServerCacheOptions options) {
this(name, new ConcurrentHashMap<>(), tenantProvider, options);
}
/**
* Construct passing in name, map and base eviction controls as ServerCacheOptions.
*/
public DefaultServerCache(String name, Map<Object, CacheEntry> map, CurrentTenantProvider tenantProvider, ServerCacheOptions options) {
this(name, map, tenantProvider, options.getMaxSize(), options.getMaxIdleSecs(), options.getMaxSecsToLive(), options.getTrimFrequency());
}
/**
* Construct passing in name, map and base eviction controls.
*/
public DefaultServerCache(String name, Map<Object, CacheEntry> map, CurrentTenantProvider tenantProvider, int maxSize, int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
this.name = name;
this.map = map;
this.maxSize = maxSize;
this.tenantAwareKey = new TenantAwareKey(tenantProvider);
this.maxIdleSecs = maxIdleSecs;
this.maxSecsToLive = maxSecsToLive;
this.trimFrequency = determineTrim(maxIdleSecs, maxSecsToLive, trimFrequency);
}
/**
* Determine a good trimFrequency as half of maxIdleSecs (or maxSecsToLive).
*/
int determineTrim(int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
if (trimFrequency > 0) {
return trimFrequency;
}
if (maxIdleSecs > 0) {
return maxIdleSecs / 2 - 1;
}
if (maxSecsToLive > 0) {
return maxSecsToLive / 2 - 1;
}
return 0;
public DefaultServerCache(DefaultServerCacheConfig config) {
this.name = config.getName();
this.map = config.getMap();
this.maxSize = config.getMaxSize();
this.tenantAwareKey = new TenantAwareKey(config.getTenantProvider());
this.maxIdleSecs = config.getMaxIdleSecs();
this.maxSecsToLive = config.getMaxSecsToLive();
this.trimFrequency = config.determineTrimFrequency();
}
public void periodicTrim(BackgroundExecutor executor) {
@@ -193,7 +157,7 @@ public class DefaultServerCache implements ServerCache {
/**
* Return the tenant aware key.
*/
private Object key(Object id) {
protected Object key(Object id) {
return tenantAwareKey.key(id);
}
@@ -203,7 +167,7 @@ public class DefaultServerCache implements ServerCache {
@Override
public Object get(Object id) {
CacheEntry entry = map.get(key(id));
CacheEntry entry = getCacheEntry(id);
if (entry == null) {
missCount.increment();
return null;
@@ -212,10 +176,24 @@ public class DefaultServerCache implements ServerCache {
// Important that hitCount.increment() MUST be low latency under concurrent
// use hence must use LongAdder or better here
hitCount.increment();
return entry.getValue();
return unwrapEntry(entry);
}
}
/**
* Unwrap the cache entry - override for query cache to unwrap to the query result.
*/
protected Object unwrapEntry(CacheEntry entry) {
return entry.getValue();
}
/**
* Get the cache entry - override for query cache to validate dependent tables.
*/
protected CacheEntry getCacheEntry(Object id) {
return map.get(key(id));
}
@Override
public void putAll(Map<Object, Object> keyValues) {
keyValues.forEach(this::put);
@@ -0,0 +1,80 @@
package io.ebeaninternal.server.cache;
import io.ebean.cache.QueryCacheEntryValidate;
import io.ebean.cache.ServerCacheConfig;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.config.CurrentTenantProvider;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class DefaultServerCacheConfig {
private final ServerCacheConfig config;
private int maxSize;
private int maxIdleSecs;
private int maxSecsToLive;
private int trimFrequency;
private Map<Object, DefaultServerCache.CacheEntry> map;
public DefaultServerCacheConfig(ServerCacheConfig config) {
this(config, new ConcurrentHashMap<>());
}
public DefaultServerCacheConfig(ServerCacheConfig config, Map<Object, DefaultServerCache.CacheEntry> map) {
this.config = config;
this.map = map;
ServerCacheOptions options = config.getCacheOptions();
this.maxIdleSecs = options.getMaxIdleSecs();
this.maxSecsToLive = options.getMaxSecsToLive();
this.trimFrequency = options.getTrimFrequency();
this.maxSize = options.getMaxSize();
}
public CurrentTenantProvider getTenantProvider() {
return config.getTenantProvider();
}
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
return config.getQueryCacheEntryValidate();
}
public String getName() {
return config.getCacheKey();
}
public Map<Object, DefaultServerCache.CacheEntry> getMap() {
return map;
}
public int getMaxSize() {
return maxSize;
}
public int getMaxIdleSecs() {
return maxIdleSecs;
}
public int getMaxSecsToLive() {
return maxSecsToLive;
}
/**
* Determine a good trimFrequency as half of maxIdleSecs (or maxSecsToLive).
*/
public int determineTrimFrequency() {
if (trimFrequency > 0) {
return trimFrequency;
}
if (maxIdleSecs > 0) {
return maxIdleSecs / 2 - 1;
}
if (maxSecsToLive > 0) {
return maxSecsToLive / 2 - 1;
}
return 0;
}
}
@@ -2,10 +2,10 @@ package io.ebeaninternal.server.cache;
import io.ebean.BackgroundExecutor;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheConfig;
import io.ebean.cache.ServerCacheFactory;
import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCacheType;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheNotify;
/**
@@ -18,25 +18,43 @@ class DefaultServerCacheFactory implements ServerCacheFactory {
/**
* Construct when l2 cache is disabled.
*/
public DefaultServerCacheFactory() {
DefaultServerCacheFactory() {
this.executor = null;
}
/**
* Construct with executor service.
*/
public DefaultServerCacheFactory(BackgroundExecutor executor) {
DefaultServerCacheFactory(BackgroundExecutor executor) {
this.executor = executor;
}
@Override
public ServerCache createCache(ServerCacheType type, String cacheKey, CurrentTenantProvider tenantProvider, ServerCacheOptions cacheOptions) {
public ServerCache createCache(ServerCacheConfig config) {
DefaultServerCache cache = new DefaultServerCache(cacheKey, tenantProvider, cacheOptions);
DefaultServerCache cache;
if (config.isQueryCache()) {
// use a server cache aware of extra validation and QueryCacheEntry
cache = new DefaultServerQueryCache(new DefaultServerCacheConfig(config));
} else {
cache = new DefaultServerCache(new DefaultServerCacheConfig(config));
}
if (executor != null) {
cache.periodicTrim(executor);
}
return cache;
}
@Override
public ServerCacheNotify createCacheNotify(ServerCacheNotify listener) {
return new NoopServerCacheNotify();
}
private static class NoopServerCacheNotify implements ServerCacheNotify {
@Override
public void notify(ServerCacheNotification notification) {
// do nothing
}
}
}
@@ -0,0 +1,41 @@
package io.ebeaninternal.server.cache;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.cache.QueryCacheEntryValidate;
/**
* Server cache for query caching.
* <p>
* Entries in this cache contain QueryCacheEntry and we need to additionally
* validate the entries when hit for changes to dependent tables.
* </p>
*/
public class DefaultServerQueryCache extends DefaultServerCache {
private final QueryCacheEntryValidate queryCacheEntryValidate;
public DefaultServerQueryCache(DefaultServerCacheConfig config) {
super(config);
this.queryCacheEntryValidate = config.getQueryCacheEntryValidate();
}
protected Object unwrapEntry(CacheEntry entry) {
return ((QueryCacheEntry) entry.getValue()).getValue();
}
@Override
protected CacheEntry getCacheEntry(Object id) {
Object key = key(id);
CacheEntry entry = map.get(key);
if (entry == null) {
return null;
}
QueryCacheEntry value = (QueryCacheEntry) entry.getValue();
if (!queryCacheEntryValidate.isValid(value)) {
map.remove(key);
removeCount.increment();
return null;
}
return entry;
}
}
@@ -1,9 +1,9 @@
package io.ebeaninternal.server.cache;
import io.ebeaninternal.server.cluster.BinaryMessage;
import io.ebeaninternal.server.cluster.BinaryMessageList;
import io.ebeaninternal.api.BinaryReadContext;
import io.ebeaninternal.api.BinaryWritable;
import io.ebeaninternal.api.BinaryWriteContext;
import java.io.DataInput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
@@ -12,7 +12,7 @@ import java.util.List;
/**
* Cache events broadcast across the cluster.
*/
public class RemoteCacheEvent {
public class RemoteCacheEvent implements BinaryWritable {
private boolean clearAll;
@@ -53,10 +53,10 @@ public class RemoteCacheEvent {
@Override
public String toString() {
return "clearAll:" + clearAll + " caches:" + clearCaches;
return "CacheEvent[ clearAll:" + clearAll + " caches:" + clearCaches + "]";
}
public static RemoteCacheEvent readBinaryMessage(DataInput dataInput) throws IOException {
public static RemoteCacheEvent readBinaryMessage(BinaryReadContext dataInput) throws IOException {
boolean clearAll = dataInput.readBoolean();
int size = dataInput.readInt();
@@ -72,13 +72,9 @@ public class RemoteCacheEvent {
return new RemoteCacheEvent(clearAll, clearCache);
}
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
int bufferSize = (clearCaches == null) ? 0 : clearCaches.size() * 30;
BinaryMessage msg = new BinaryMessage(bufferSize + 10);
DataOutputStream os = msg.getOs();
os.writeInt(BinaryMessage.TYPE_CACHE);
@Override
public void writeBinary(BinaryWriteContext out) throws IOException {
DataOutputStream os = out.start(TYPE_CACHE);
os.writeBoolean(clearAll);
if (clearCaches == null) {
os.writeInt(0);
@@ -88,7 +84,5 @@ public class RemoteCacheEvent {
os.writeUTF(cacheName);
}
}
msgList.add(msg);
}
}
@@ -1,59 +0,0 @@
package io.ebeaninternal.server.cluster;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
/**
* Represents a relatively small independent message.
* <p>
* In general terms we break up a potentially large object like
* RemoteTransactionEvent into many smaller BinaryMessages. This is so that if
* they don't all fit on a single Packet we can easily break them up and put
* them on multiple packets.
* </p>
* <p>
* Also note that for the Multicast approach a Packet will generally contain
* many messages each directed to different members of the cluster. So it would
* be common for many Ack, Resend and Control messages to all be contained in a
* single packet.
* </p>
*/
public class BinaryMessage {
public static final int TYPE_MSGCONTROL = 0;
public static final int TYPE_BEANIUD = 1;
public static final int TYPE_TABLEIUD = 2;
public static final int TYPE_CACHE = 3;
public static final int TYPE_MSGACK = 8;
public static final int TYPE_MSGRESEND = 9;
private final ByteArrayOutputStream buffer;
private final DataOutputStream os;
private byte[] bytes;
/**
* Create with an estimated buffer size.
*/
public BinaryMessage(int bufSize) {
this.buffer = new ByteArrayOutputStream(bufSize);
this.os = new DataOutputStream(buffer);
}
/**
* Return the DataOutputStream to write content to.
*/
public DataOutputStream getOs() {
return os;
}
/**
* Return all the content as a byte array.
*/
public byte[] getByteArray() {
if (bytes == null) {
bytes = buffer.toByteArray();
}
return bytes;
}
}
@@ -1,21 +0,0 @@
package io.ebeaninternal.server.cluster;
import java.util.ArrayList;
import java.util.List;
/**
* Holds a List of BinaryMessage's.
*/
public class BinaryMessageList {
final List<BinaryMessage> list = new ArrayList<>();
public void add(BinaryMessage msg) {
list.add(msg);
}
public List<BinaryMessage> getList() {
return list;
}
}
@@ -0,0 +1,41 @@
package io.ebeaninternal.server.cluster;
import io.ebeaninternal.api.BinaryReadContext;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.io.IOException;
/**
* Mechanism to convert RemoteTransactionEvent to/from byte[] content.
*/
public class BinaryTransactionEventReader {
private final ServerLookup serverLookup;
public BinaryTransactionEventReader(ServerLookup serverLookup) {
this.serverLookup = serverLookup;
}
/**
* Read Transaction from bytes.
*/
public RemoteTransactionEvent read(byte[] byteData) throws IOException {
return read(new BinaryReadContext(byteData));
}
/**
* Read Transaction using BinaryReadContext.
*/
public RemoteTransactionEvent read(BinaryReadContext dataInput) throws IOException {
String serverName = dataInput.readUTF();
SpiEbeanServer server = (SpiEbeanServer) serverLookup.getServer(serverName);
if (server == null) {
throw new IllegalStateException("EbeanServer not found for name [" + serverName + "]");
}
RemoteTransactionEvent event = new RemoteTransactionEvent(server);
event.readBinary(dataInput);
return event;
}
}
@@ -13,7 +13,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* Manages the cluster service.
*/
public class ClusterManager {
public class ClusterManager implements ServerLookup {
private static final Logger clusterLogger = LoggerFactory.getLogger("io.ebean.Cluster");
@@ -0,0 +1,14 @@
package io.ebeaninternal.server.cluster;
import io.ebean.EbeanServer;
/**
* Returns EbeanServer instances for remote message reading.
*/
public interface ServerLookup {
/**
* Return the EbeanServer instance by name.
*/
EbeanServer getServer(String name);
}
@@ -170,11 +170,7 @@ public abstract class AbstractSqlQueryRequest {
}
if (isLogSql()) {
String logSql = TrimLogSql.trim(sql);
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
trans.logSql(logSql);
trans.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
}
setResultSet(pstmt.executeQuery(), null);
@@ -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();
}
}
@@ -14,7 +14,7 @@ import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServer16Platform;
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
import io.ebean.migration.util.JdbcClose;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.dbmigration.DbOffline;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -126,13 +126,7 @@ public class DatabasePlatformFactory {
throw new PersistenceException(ex);
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException ex) {
logger.error(null, ex);
}
JdbcClose.close(connection);
}
}
@@ -1,25 +1,16 @@
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;
import io.ebean.config.TenantMode;
import io.ebean.config.UnderscoreNamingConvention;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.properties.PropertiesLoader;
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,13 +50,6 @@ public class DefaultContainer implements SpiContainer {
ShutdownManager.registerContainer(this);
}
private void invokeBootupPlugin() {
Iterator<SpiContainerBootup> it = ServiceLoader.load(SpiContainerBootup.class).iterator();
while (it.hasNext()) {
it.next().bootup();
}
}
@Override
public void shutdown() {
clusterManager.shutdown();
@@ -104,8 +85,13 @@ public class DefaultContainer implements SpiContainer {
public SpiEbeanServer createServer(ServerConfig serverConfig) {
synchronized (this) {
setNamingConvention(serverConfig);
if (serverConfig.isDefaultServer()) {
for (ServerConfigProvider configProvider : ServiceLoader.load(ServerConfigProvider.class)) {
configProvider.apply(serverConfig);
}
}
setNamingConvention(serverConfig);
BootupClasses bootupClasses = getBootupClasses(serverConfig);
boolean online = true;
@@ -134,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
@@ -161,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.
@@ -374,25 +313,14 @@ public class DefaultContainer implements SpiContainer {
throw new RuntimeException("DataSource not set?");
}
Connection c = null;
try {
c = serverConfig.getDataSource().getConnection();
if (!serverConfig.isAutoCommitMode() && c.getAutoCommit()) {
try (Connection connection = serverConfig.getDataSource().getConnection()) {
if (!serverConfig.isAutoCommitMode() && connection.getAutoCommit()) {
logger.warn("DataSource [{}] has autoCommit defaulting to true!", serverConfig.getName());
}
return true;
} catch (SQLException ex) {
throw new PersistenceException(ex);
} finally {
if (c != null) {
try {
c.close();
} catch (SQLException ex) {
logger.error("Error closing connection", ex);
}
}
}
}
@@ -8,6 +8,7 @@ import io.ebean.DocumentStore;
import io.ebean.DtoQuery;
import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
import io.ebean.ExtendedServer;
import io.ebean.Filter;
import io.ebean.FutureIds;
import io.ebean.FutureList;
@@ -19,6 +20,8 @@ import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.SqlUpdate;
@@ -64,8 +67,10 @@ import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Type;
import io.ebeaninternal.api.SpiSqlQuery;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionManager;
@@ -114,10 +119,10 @@ import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.time.Clock;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -146,6 +151,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final DataTimeZone dataTimeZone;
/**
* Clock to use for WhenModified and WhenCreated.
*/
private ClockService clockService;
private final CallStackFactory callStackFactory;
/**
@@ -191,6 +201,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final CurrentTenantProvider currentTenantProvider;
private final SpiLogManager logManager;
/**
* The default PersistenceContextScope used if it is not explicitly set on a query.
*/
@@ -231,6 +243,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
this.logManager = config.getLogManager();
this.dtoBeanManager = config.getDtoBeanManager();
this.serverConfig = config.getServerConfig();
this.objectGraphStats = new ConcurrentHashMap<>();
@@ -270,6 +283,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.beanLoader = new DefaultBeanLoader(this);
this.jsonContext = config.createJsonContext(this);
this.dataTimeZone = config.getDataTimeZone();
this.clockService = config.getClockService();
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(docStoreComponents.updateProcessor());
@@ -317,6 +331,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public SpiLogManager log() {
return logManager;
}
@Override
public boolean isCollectQueryOrigins() {
return collectQueryOrigins;
@@ -489,6 +508,21 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return serverName;
}
@Override
public ExtendedServer extended() {
return this;
}
@Override
public long clockNow() {
return clockService.nowMillis();
}
@Override
public void setClock(Clock clock) {
this.clockService.setClock(clock);
}
@Override
public BeanState getBeanState(Object bean) {
if (bean instanceof EntityBean) {
@@ -1166,7 +1200,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(Type.BEAN);
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isBeanCacheReload()) {
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isForceHitDatabase()) {
// See if we can skip doing the fetch completely by getting the bean from the
// persistence context or the bean cache
T bean = findIdCheckPersistenceContextAndCache(t, spiQuery, spiQuery.getId());
@@ -1285,8 +1319,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> int findCount(Query<T> query, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) query).copy();
return findCountWithCopy(copy, t);
SpiQuery<T> spiQuery = ((SpiQuery<T>) query);
if (!spiQuery.isDistinct()) {
spiQuery = spiQuery.copy();
}
return findCountWithCopy(spiQuery, t);
}
@Override
@@ -1573,6 +1610,41 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
private <P> P executeSqlQuery(Function<RelationalQueryRequest, P> fun, SpiSqlQuery query) {
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, null);
try {
request.initTransIfRequired();
return fun.apply(request);
} finally {
request.endTransIfRequired();
}
}
@Override
public void findEachRow(SpiSqlQuery query, RowConsumer consumer) {
executeSqlQuery((req) -> req.findEachRow(consumer), query);
}
@Override
public <T> List<T> findListMapper(SpiSqlQuery query, RowMapper<T> mapper) {
return executeSqlQuery((req) -> req.findListMapper(mapper), query);
}
@Override
public <T> T findOneMapper(SpiSqlQuery query, RowMapper<T> mapper) {
return executeSqlQuery((req) -> req.findOneMapper(mapper), query);
}
@Override
public <T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls) {
return executeSqlQuery((req) -> req.findSingleAttributeList(cls), query);
}
@Override
public <T> T findSingleAttribute(SpiSqlQuery query, Class<T> cls) {
return executeSqlQuery((req) -> req.findSingleAttribute(cls), query);
}
@Override
public <T> void findDtoEach(SpiDtoQuery<T> query, Consumer<T> consumer) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
@@ -1684,7 +1756,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (beans == null || beans.isEmpty()) {
return;
}
executeInTrans((txn) -> {
for (Object bean : beans) {
update(checkEntityBean(bean), txn);
@@ -1726,7 +1797,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (beans == null || beans.isEmpty()) {
return;
}
executeInTrans((txn) -> {
for (Object bean : beans) {
persister.insert(checkEntityBean(bean), txn);
@@ -1795,24 +1865,27 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public int saveAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException {
return saveAllInternal(beans.iterator(), transaction);
return saveAllInternal(beans, transaction);
}
@Override
public int saveAll(Collection<?> beans) throws OptimisticLockException {
return saveAllInternal(beans.iterator(), null);
return saveAllInternal(beans, null);
}
/**
* Save all beans in the iterator with an explicit transaction.
*/
private int saveAllInternal(Iterator<?> it, Transaction transaction) {
private int saveAllInternal(Collection<?> beans, Transaction transaction) {
if (beans == null || beans.isEmpty()) {
return 0;
}
return executeInTrans((txn) -> {
txn.checkBatchEscalationOnCollection();
int saveCount = 0;
while (it.hasNext()) {
persister.save(checkEntityBean(it.next()), txn);
for (Object bean : beans) {
persister.save(checkEntityBean(bean), txn);
saveCount++;
}
@@ -1900,12 +1973,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public int deleteAllPermanent(Collection<?> beans) {
return deleteAllInternal(beans.iterator(), null, true);
return deleteAllInternal(beans, null, true);
}
@Override
public int deleteAllPermanent(Collection<?> beans, Transaction t) {
return deleteAllInternal(beans.iterator(), t, true);
return deleteAllInternal(beans, t, true);
}
/**
@@ -1913,7 +1986,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public int deleteAll(Collection<?> beans) {
return deleteAllInternal(beans.iterator(), null, false);
return deleteAllInternal(beans, null, false);
}
/**
@@ -1921,21 +1994,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public int deleteAll(Collection<?> beans, Transaction t) {
return deleteAllInternal(beans.iterator(), t, false);
return deleteAllInternal(beans, t, false);
}
/**
* Delete all the beans in the iterator with an explicit transaction.
*/
private int deleteAllInternal(Iterator<?> it, Transaction transaction, boolean permanent) {
private int deleteAllInternal(Collection<?> beans, Transaction transaction, boolean permanent) {
if (beans == null || beans.isEmpty()) {
return 0;
}
return executeInTrans((txn) -> {
txn.checkBatchEscalationOnCollection();
int deleteCount = 0;
while (it.hasNext()) {
EntityBean bean = checkEntityBean(it.next());
persister.delete(bean, txn, permanent);
for (Object bean : beans) {
persister.delete(checkEntityBean(bean), txn, permanent);
deleteCount++;
}
@@ -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;
@@ -20,11 +25,17 @@ import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiLoggerFactory;
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;
@@ -44,6 +55,8 @@ import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.expression.platform.DbExpressionHandlerFactory;
import io.ebeaninternal.server.logger.DLogManager;
import io.ebeaninternal.server.logger.DLoggerFactory;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.DefaultPersister;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
@@ -64,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;
@@ -79,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;
@@ -91,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;
@@ -103,6 +122,8 @@ public class InternalConfiguration {
private final DtoBeanManager dtoBeanManager;
private final ClockService clockService;
private final DataTimeZone dataTimeZone;
private final Binder binder;
@@ -119,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;
@@ -134,16 +161,20 @@ public class InternalConfiguration {
private final MultiValueBind multiValueBind;
public InternalConfiguration(ClusterManager clusterManager,
SpiCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
private final SpiLogManager logManager;
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.serverConfig = serverConfig;
this.bootupClasses = bootupClasses;
this.databasePlatform = serverConfig.getDatabasePlatform();
@@ -156,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());
@@ -169,6 +203,20 @@ public class InternalConfiguration {
this.cQueryEngine = new CQueryEngine(serverConfig, databasePlatform, binder, asOfTableMapping, draftTableMap);
}
private SpiLogManager initLogManager() {
// allow plugin - i.e. capture executed SQL for testing/asserts
SpiLoggerFactory loggerFactory = serverConfig.service(SpiLoggerFactory.class);
if (loggerFactory == null) {
loggerFactory = new DLoggerFactory();
}
SpiLogger sql = loggerFactory.create("io.ebean.SQL");
SpiLogger sum = loggerFactory.create("io.ebean.SUM");
SpiLogger txn = loggerFactory.create("io.ebean.TXN");
return new DLogManager(sql, sum, txn);
}
/**
* Create and return the ExpressionFactory based on configuration and database platform.
*/
@@ -189,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().
@@ -262,9 +314,9 @@ public class InternalConfiguration {
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
if (historySupport == null) {
return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
return new Binder(typeManager, logManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
}
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
return new Binder(typeManager, logManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
}
/**
@@ -378,7 +430,8 @@ public class InternalConfiguration {
TransactionManagerOptions options =
new TransactionManagerOptions(notifyL2CacheInForeground, serverConfig, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler());
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
tableModState, cacheNotify, clockService);
if (serverConfig.isExplicitTransactionBeginMode()) {
return new ExplicitTransactionManager(options);
@@ -471,7 +524,7 @@ public class InternalConfiguration {
return dataTimeZone;
}
public ServerCacheManager cache() {
public ServerCacheManager cacheManager() {
return new DefaultCacheAdapter(cacheManager);
}
@@ -511,4 +564,66 @@ public class InternalConfiguration {
public DtoBeanManager getDtoBeanManager() {
return dtoBeanManager;
}
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;
@@ -81,6 +82,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private BeanPropertyAssocMany<?> manyProperty;
private boolean inlineCountDistinct;
private Set<String> dependentTables;
/**
* Create the InternalQueryRequest.
*/
@@ -205,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();
@@ -379,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;
}
@@ -678,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));
}
/**
@@ -742,4 +749,21 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
public void slowQueryCheck(long executionTimeMicros, int rowCount) {
ebeanServer.slowQueryCheck(executionTimeMicros, rowCount, query);
}
public void setInlineCountDistinct() {
inlineCountDistinct = true;
}
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);
}
}
}
@@ -17,7 +17,7 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
INSERT(EVT_INSERT),
UPDATE(EVT_UPDATE),
DELETE(EVT_DELETE),
SOFT_DELETE(EVT_SOFT_DELETE),
DELETE_SOFT(EVT_DELETE_SOFT),
DELETE_PERMANENT(EVT_DELETE_PERMANENT),
UPDATESQL(EVT_UPDATESQL),
CALLABLESQL(EVT_CALLABLESQL);
@@ -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();
}
/**
@@ -23,6 +23,7 @@ import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.persist.BatchedSqlException;
import io.ebeaninternal.server.persist.DeleteMode;
import io.ebeaninternal.server.persist.Flags;
import io.ebeaninternal.server.persist.PersistExecute;
import io.ebeaninternal.server.transaction.BeanPersistIdMap;
@@ -271,7 +272,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
onUpdateGeneratedProperties();
}
break;
case SOFT_DELETE:
case DELETE_SOFT:
onUpdateGeneratedProperties();
break;
}
@@ -449,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 SOFT_DELETE:
beanDescriptor.cacheHandleDelete(idValue, this, changeSet);
case DELETE_SOFT:
beanDescriptor.cachePersistDelete(idValue, this, changeSet);
break;
default:
throw new IllegalStateException("Invalid type " + type);
@@ -475,7 +476,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
beanDescriptor.docStoreInsert(idValue, this, txn);
break;
case UPDATE:
case SOFT_DELETE:
case DELETE_SOFT:
beanDescriptor.docStoreUpdate(idValue, this, txn);
break;
case DELETE:
@@ -496,7 +497,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
break;
case UPDATE:
case SOFT_DELETE:
case DELETE_SOFT:
docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue);
break;
case DELETE:
@@ -527,7 +528,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
beanPersistListener.deleted(bean);
break;
case SOFT_DELETE:
case DELETE_SOFT:
beanPersistListener.softDeleted(bean);
break;
@@ -772,7 +773,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
executeUpdate();
return -1;
case SOFT_DELETE:
case DELETE_SOFT:
prepareForSoftDelete();
executeSoftDelete();
return -1;
@@ -855,7 +856,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
switch (type) {
case DELETE:
case SOFT_DELETE:
case DELETE_SOFT:
postDelete();
break;
case UPDATE:
@@ -968,7 +969,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
pendingPostUpdateNotify++;
}
break;
case SOFT_DELETE:
case DELETE_SOFT:
controller.postSoftDelete(this);
break;
case DELETE:
@@ -993,7 +994,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
case DELETE:
transaction.logSummary("Deleted [" + name + "] [" + idValue + "]" + draft);
break;
case SOFT_DELETE:
case DELETE_SOFT:
transaction.logSummary("SoftDelete [" + name + "] [" + idValue + "]" + draft);
break;
default:
@@ -1184,10 +1185,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
/**
* Return true if this is a soft delete request.
* Return the delete mode - Soft or Hard.
*/
public boolean isSoftDelete() {
return Type.SOFT_DELETE == type;
public DeleteMode deleteMode() {
return Type.DELETE_SOFT == type ? DeleteMode.SOFT : DeleteMode.HARD;
}
/**
@@ -1274,7 +1275,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
public long now() {
if (now == 0) {
now = System.currentTimeMillis();
now = ebeanServer.clockNow();
}
return now;
}
@@ -1,6 +1,8 @@
package io.ebeaninternal.server.core;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlRow;
import io.ebean.meta.MetricVisitor;
@@ -30,6 +32,31 @@ public interface RelationalQueryEngine {
*/
void findEach(RelationalQueryRequest request, Predicate<SqlRow> consumer);
/**
* Find single attribute.
*/
<T> T findSingleAttribute(RelationalQueryRequest request, Class<T> cls);
/**
* Find single attribute list.
*/
<T> List<T> findSingleAttributeList(RelationalQueryRequest request, Class<T> cls);
/**
* Find one via mapper.
*/
<T> T findOneMapper(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find list via mapper.
*/
<T> List<T> findListMapper(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find each via raw consumer.
*/
void findEachRow(RelationalQueryRequest request, RowConsumer mapper);
/**
* Collect SQL query execution statistics.
*/
@@ -1,5 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.Transaction;
@@ -53,6 +55,27 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
}
}
boolean findEachRow(RowConsumer mapper) {
queryEngine.findEachRow(this, mapper);
return true;
}
<T> List<T> findListMapper(RowMapper<T> mapper) {
return queryEngine.findListMapper(this, mapper);
}
<T> T findOneMapper(RowMapper<T> mapper) {
return queryEngine.findOneMapper(this, mapper);
}
public <T> List<T> findSingleAttributeList(Class<T> cls) {
return queryEngine.findSingleAttributeList(this, cls);
}
public <T> T findSingleAttribute(Class<T> cls) {
return queryEngine.findSingleAttribute(this, cls);
}
public void findEach(Consumer<SqlRow> consumer) {
queryEngine.findEach(this, consumer);
}
@@ -104,4 +127,35 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
}
}
public ResultSet getResultSet() {
return resultSet;
}
public void incrementRows() {
rows++;
}
public <T> List<T> mapList(RowMapper<T> mapper) throws SQLException {
List<T> list = new ArrayList<>();
while (next()) {
list.add(mapper.map(resultSet, rows++));
}
return list;
}
public <T> T mapOne(RowMapper<T> mapper) throws SQLException {
if (!next()) {
return null;
} else {
return mapper.map(resultSet, rows++);
}
}
public void mapEach(RowConsumer consumer) throws SQLException {
while (next()) {
consumer.accept(resultSet, rows++);
}
}
}
@@ -59,11 +59,8 @@ class ManifestReader {
try {
Enumeration<URL> resources = classLoader.getResources(resourcePath);
while (resources.hasMoreElements()) {
InputStream is = resources.nextElement().openStream();
try {
try (InputStream is = resources.nextElement().openStream()) {
read(new Manifest(is));
} finally {
close(is);
}
}
} catch (IOException e) {
@@ -72,14 +69,6 @@ class ManifestReader {
return packageSet;
}
private void close(InputStream is) {
try {
is.close();
} catch (IOException e) {
logger.warn("Error closing manifest InputStream", e);
}
}
/**
* Read the entity packages from the manifest.
*/
@@ -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;
@@ -62,6 +64,7 @@ import io.ebeaninternal.server.el.ElComparatorProperty;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.DeleteMode;
import io.ebeaninternal.server.persist.DmlUtil;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.ExtraJoin;
@@ -288,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.
*/
@@ -543,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];
@@ -650,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.
*/
@@ -826,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();
@@ -907,7 +923,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
case INSERT:
return changeLogFilter.includeInsert(request) ? insertBeanChange(request) : null;
case UPDATE:
case SOFT_DELETE:
case DELETE_SOFT:
return changeLogFilter.includeUpdate(request) ? updateBeanChange(request) : null;
case DELETE:
return changeLogFilter.includeDelete(request) ? deleteBeanChange(request) : null;
@@ -991,11 +1007,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
jsonWriter.writeEndObject();
}
public SqlUpdate deleteById(Object id, List<Object> idList, boolean softDelete) {
public SqlUpdate deleteById(Object id, List<Object> idList, DeleteMode mode) {
if (id != null) {
return deleteById(id, softDelete);
return deleteById(id, mode);
} else {
return deleteByIdList(idList, softDelete);
return deleteByIdList(idList, mode);
}
}
@@ -1017,9 +1033,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return SQL that can be used to delete a list of Id's without any optimistic
* concurrency checking.
*/
private SqlUpdate deleteByIdList(List<Object> idList, boolean softDelete) {
private SqlUpdate deleteByIdList(List<Object> idList, DeleteMode mode) {
String baseSql = softDelete ? softDeleteByIdInSql : deleteByIdInSql;
String baseSql = mode.isHard() ? deleteByIdInSql : softDeleteByIdInSql;
StringBuilder sb = new StringBuilder(baseSql);
String inClause = idBinder.getIdInValueExprDelete(idList.size());
sb.append(inClause);
@@ -1033,9 +1049,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return SQL that can be used to delete by Id without any optimistic
* concurrency checking.
*/
private SqlUpdate deleteById(Object id, boolean softDelete) {
private SqlUpdate deleteById(Object id, DeleteMode mode) {
String baseSql = softDelete ? softDeleteByIdSql : deleteByIdSql;
String baseSql = mode.isHard() ? deleteByIdSql : softDeleteByIdSql;
DefaultSqlUpdate sqlDelete = new DefaultSqlUpdate(baseSql);
Object[] bindValues = idBinder.getBindValues(id);
@@ -1360,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);
}
/**
@@ -1468,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);
}
/**
@@ -1502,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);
}
@@ -3427,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);
}
}
}
@@ -78,6 +78,11 @@ public final class BeanFkeyProperty implements ElPropertyValue {
return false;
}
@Override
public Object localEncrypt(Object value) {
throw new IllegalArgumentException("Should not get here?");
}
@Override
public boolean containsFormulaWithJoin() {
return false;
@@ -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);
}
@@ -28,6 +28,7 @@ import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.LocalEncryptedType;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeBoolean;
import io.ebeaninternal.server.type.ScalarTypeEnum;
@@ -1246,6 +1247,11 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
return dbBind;
}
@Override
public Object localEncrypt(Object value) {
return ((LocalEncryptedType)scalarType).localEncrypt(value);
}
/**
* Returns true if DB encrypted.
*/
@@ -65,6 +65,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
*/
final TableJoin tableJoin;
final PropertyForeignKey foreignKey;
/**
* The type of the joined bean.
*/
@@ -90,6 +92,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
*/
public BeanPropertyAssoc(BeanDescriptor<?> descriptor, DeployBeanPropertyAssoc<T> deploy) {
super(descriptor, deploy);
this.foreignKey = deploy.getForeignKey();
this.extraWhere = InternString.intern(deploy.getExtraWhere());
this.beanTable = deploy.getBeanTable();
this.mappedBy = InternString.intern(deploy.getMappedBy());
@@ -128,6 +131,13 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
return fetchPreference;
}
/**
* Return the extra configuration for the foreign key.
*/
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
/**
* Create a ElPropertyValue for a *ToOne or *ToMany.
*/
@@ -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;
@@ -44,8 +44,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
private final boolean primaryKeyExport;
private final PropertyForeignKey foreignKey;
private AssocOneHelp localHelp;
protected final BeanProperty[] embeddedProps;
@@ -58,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.
@@ -73,8 +72,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
DeployBeanPropertyAssocOne<T> deploy) {
super(descriptor, deploy);
foreignKey = deploy.getForeignKey();
primaryKeyExport = deploy.isPrimaryKeyExport();
oneToOne = deploy.isOneToOne();
oneToOneExported = deploy.isOneToOneExported();
@@ -131,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.
*/
@@ -145,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 {
@@ -242,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);
@@ -254,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);
@@ -323,10 +333,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
}
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
public boolean hasForeignKey() {
return foreignKey == null || !foreignKey.isNoConstraint();
}
@@ -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);
@@ -8,7 +8,7 @@ import java.sql.Types;
class FormulaPropertyPath {
private static final String[] AGG_FUNCTIONS = {"count", "max", "min", "avg"};
private static final String[] AGG_FUNCTIONS = {"count", "max", "min", "avg", "sum"};
private static final String DISTINCT_ = "distinct ";
@@ -118,12 +118,8 @@ class FormulaPropertyPath {
}
}
String logicalName = (alias == null) ? formula : alias;
BeanProperty targetProperty = null;
if (alias != null) {
targetProperty = descriptor._findBeanProperty(alias);
}
String logicalName = (alias == null) ? internalExpression : alias;
BeanProperty targetProperty = descriptor._findBeanProperty(logicalName);
String parsedAggregation = buildFormula(parsed);
return new DynamicPropertyAggregationFormula(logicalName, scalarType, parsedAggregation, isAggregate(), targetProperty, alias);
}
@@ -151,7 +151,6 @@ public class InheritInfo {
* Set the descriptor for this node.
*/
public void setDescriptor(BeanDescriptor<?> descriptor) {
this.descriptor = descriptor;
}
@@ -6,6 +6,7 @@ import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.expression.DefaultExpressionRequest;
import io.ebeaninternal.server.expression.IdInExpression;
import io.ebeaninternal.server.persist.DeleteMode;
import java.util.LinkedHashMap;
import java.util.List;
@@ -72,16 +73,16 @@ public class IntersectionRow {
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
}
public SqlUpdate createDelete(SpiEbeanServer server, boolean softDelete) {
public SqlUpdate createDelete(SpiEbeanServer server, DeleteMode deleteMode) {
BindParams bindParams = new BindParams();
StringBuilder sb = new StringBuilder();
if (softDelete) {
if (deleteMode.isHard()) {
sb.append("delete from ").append(tableName);
} else {
sb.append("update ").append(tableName).append(" set ");
sb.append(targetDescriptor.getSoftDeleteDbSet());
} else {
sb.append("delete from ").append(tableName);
}
sb.append(" where ");
@@ -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.
*/
@@ -647,11 +647,35 @@ public class DeployBeanProperty {
*/
public String parseAggregation() {
if (aggregation != null) {
int pos = aggregation.indexOf('(');
if (pos > -1) {
// check for recursive property name and formula
String maybePropertyName = aggregation.substring(pos + 1, aggregation.length() - 1);
if (name.equals(maybePropertyName)) {
// e.g. bean property cost mapped to sum(cost)
return aggregationJoin(pos, dbColumn);
} else {
DeployBeanProperty other = desc.getBeanProperty(maybePropertyName);
if (other != null) {
// e.g. bean property maxKms mapped to sum(totalKms) where totalKms is another property
return aggregationJoin(pos, other.getDbColumnRaw());
}
}
}
aggregationParsed = desc.parse(aggregation);
}
return aggregationParsed;
}
/**
* Simple aggregation parsing like sum(someProperty)
*/
private String aggregationJoin(int pos, String dbColumn) {
String p0 = aggregation.substring(0, pos + 1);
aggregationParsed = p0 + "${ta}." + dbColumn + aggregation.substring(aggregation.length() - 1);
return aggregationParsed;
}
public void setAggregation(String aggregation) {
this.aggregation = aggregation;
this.dbRead = true;
@@ -702,6 +726,13 @@ public class DeployBeanProperty {
return dbColumn;
}
/**
* Return the DB column without any aggregation parsing.
*/
private String getDbColumnRaw() {
return dbColumn;
}
/**
* Set the database column name this is mapped to.
*/
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy.meta;
import io.ebeaninternal.server.deploy.BeanCascadeInfo;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
/**
* Abstract base for properties mapped to an associated bean, list, set or map.
@@ -16,7 +17,7 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
/**
* Persist settings.
*/
protected final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo();
final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo();
/**
* The join table information.
@@ -42,6 +43,8 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
private int fetchPreference = 1000;
private PropertyForeignKey foreignKey;
/**
* Construct the property.
*/
@@ -117,6 +120,13 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
return cascadeInfo;
}
public void setForeignKey(PropertyForeignKey foreignKey) {
this.foreignKey = foreignKey;
}
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
/**
* Return the mappedBy deployment attribute.

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