diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 8575f4580..357f8aefb 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,9 +1,3 @@ - -GITHUB ISSUES ARE STRICTLY CONTROLLED FOR THIS PROJECT. - -Refer to http://ebean-orm.github.io/support for the policies controlling the use of github issues. -Please post issues to the Ebean group https://groups.google.com/forum/#!forum/ebean first. - ## Expected behavior ## Actual behavior diff --git a/.gitignore b/.gitignore index 1b88dd830..861a33064 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ *.autofetch -*create-all.sql -*drop-all.sql *.orig .classpath .project @@ -12,7 +10,6 @@ ebean-autotune.xml ebean-profiling*.xml /db /mydb.db -!src/test/ddl-review/*.sql profiling/ # Intellij project files @@ -20,4 +17,4 @@ profiling/ *.ipr *.iws .idea/ -*uuid.state \ No newline at end of file +*uuid.state diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 16fa6d698..46be98f0b 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -293,6 +293,6 @@ ebean.tenant.schemaProvider ebean.updateAllPropertiesInBatch ebean.updateChangesOnly ebean.updatesDeleteMissingChildren -ebean.useJavaxValidationNotNull +ebean.useValidationNotNull ebean.useJtaTransactionManager diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 472d240f9..9bb40a671 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -4,18 +4,13 @@ ebean-parent io.ebean - 12.6.2-SNAPSHOT + 12.11.3-SNAPSHOT ebean api ebean api ebean-api - - 2.11.3 - 2.11.3 - - - - javax.validation - validation-api - 1.1.0.Final - true - - javax.servlet javax.servlet-api diff --git a/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java b/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java index 9ffa4b62b..537fb7f36 100644 --- a/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java +++ b/ebean-api/src/main/java/io/ebean/BackgroundExecutor.java @@ -1,33 +1,43 @@ package io.ebean; import java.util.concurrent.Callable; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; /** - * Background thread pool service for executing of tasks asynchronously. + * Background executor service for executing of tasks asynchronously. *

- * This service is used internally by Ebean for executing background tasks such - * as the {@link Query#findFutureList()} and also for executing background tasks - * periodically. - *

+ * This service can be used to execute tasks in the background. *

- * This service has been made available so you can use it for your application - * code if you want. It can be useful for some server caching implementations - * (background population and trimming of the cache etc). - *

- * - * @author rbygrave + * This service is managed by Ebean and will perform a clean shutdown + * waiting for background tasks to complete with a default 30 second + * timeout. Shutdown occurs prior to DataSource shutdown. + *

+ * This also propagates MDC context from the current thread to the + * background task if defined. */ public interface BackgroundExecutor { /** - * Execute a task in the background. + * Execute a callable task in the background returning the Future. */ - void execute(Runnable r); + Future submit(Callable task); /** + * Execute a runnable task in the background returning the Future. + */ + Future submit(Runnable task); + + /** + * Execute a task in the background. Effectively the same as + * {@link BackgroundExecutor#submit(Runnable)} but returns void. + */ + void execute(Runnable task); + + /** + * Deprecated - migrate to scheduleWithFixedDelay(). * Execute a task periodically with a fixed delay between each execution. *

* For example, execute a runnable every minute. @@ -36,27 +46,64 @@ public interface BackgroundExecutor { * That is, this method has the same behaviour characteristics as * {@link ScheduledExecutorService#scheduleWithFixedDelay(Runnable, long, long, TimeUnit)} */ - void executePeriodically(Runnable r, long delay, TimeUnit unit); + @Deprecated + void executePeriodically(Runnable task, long delay, TimeUnit unit); /** + * Deprecated - migrate to scheduleWithFixedDelay(). * Execute a task periodically additionally with an initial delay different from delay. */ - void executePeriodically(Runnable r, long initialDelay, long delay, TimeUnit unit); + @Deprecated + void executePeriodically(Runnable task, long initialDelay, long delay, TimeUnit unit); + + /** + * Execute a task periodically with a given delay. + * + * @param task the task to execute + * @param initialDelay the time to delay first execution + * @param delay the delay between the termination of one + * execution and the commencement of the next + * @param unit the time unit of the initialDelay and delay parameters + * @return a ScheduledFuture representing pending completion of + * the series of repeated tasks. The future's {@link + * Future#get() get()} method will never return normally, + * and will throw an exception upon task cancellation or + * abnormal termination of a task execution. + */ + ScheduledFuture scheduleWithFixedDelay(Runnable task, long initialDelay, long delay, TimeUnit unit); + + /** + * Execute a task periodically with a given period. + * + *

If any execution of this task takes longer than its period, then + * subsequent executions may start late, but will not concurrently + * execute. + * + * @param task the task to execute + * @param initialDelay the time to delay first execution + * @param period the period between successive executions + * @param unit the time unit of the initialDelay and period parameters + * @return a ScheduledFuture representing pending completion of + * the series of repeated tasks. The future's {@link + * Future#get() get()} method will never return normally, + * and will throw an exception upon task cancellation or + * abnormal termination of a task execution. + */ + ScheduledFuture scheduleAtFixedRate(Runnable task, long initialDelay, long period, TimeUnit unit); /** * Schedules a Runnable for one-shot action that becomes enabled after the given delay. * * @return a ScheduledFuture representing pending completion of the task and - * whose get() method will return null upon completion + * whose get() method will return null upon completion */ - ScheduledFuture schedule(Runnable r, long delay, TimeUnit unit); + ScheduledFuture schedule(Runnable task, long delay, TimeUnit unit); /** * Schedules a Callable for one-shot action that becomes enabled after the given delay. * * @return a ScheduledFuture that can be used to extract result or cancel */ - ScheduledFuture schedule(Callable c, long delay, TimeUnit unit); - + ScheduledFuture schedule(Callable task, long delay, TimeUnit unit); } diff --git a/ebean-api/src/main/java/io/ebean/BeanFinder.java b/ebean-api/src/main/java/io/ebean/BeanFinder.java index 00aec8de5..2c8e04eec 100644 --- a/ebean-api/src/main/java/io/ebean/BeanFinder.java +++ b/ebean-api/src/main/java/io/ebean/BeanFinder.java @@ -14,7 +14,7 @@ import java.util.Optional; * * public class CustomerFinder extends BeanFinder { * - * @Inject + * @Inject * public CustomerFinder(Database database) { * super(Customer.class, database); * } diff --git a/ebean-api/src/main/java/io/ebean/BeanRepository.java b/ebean-api/src/main/java/io/ebean/BeanRepository.java index 19f390582..b89a49b39 100644 --- a/ebean-api/src/main/java/io/ebean/BeanRepository.java +++ b/ebean-api/src/main/java/io/ebean/BeanRepository.java @@ -9,10 +9,10 @@ import java.util.Collection; *

*

{@code
  *
- * @Repository
+ * @Repository
  * public class CustomerRepository extends BeanRepository {
  *
- *   @Inject
+ *   @Inject
  *   public CustomerRepository(Database server) {
  *     super(Customer.class, server);
  *   }
diff --git a/ebean-api/src/main/java/io/ebean/BeanState.java b/ebean-api/src/main/java/io/ebean/BeanState.java
index da8c24d81..daf163cc7 100644
--- a/ebean-api/src/main/java/io/ebean/BeanState.java
+++ b/ebean-api/src/main/java/io/ebean/BeanState.java
@@ -118,4 +118,9 @@ public interface BeanState {
    */
   @Nullable
   Map getLoadErrors();
+
+  /**
+   * Return the sort order value for an order column.
+   */
+  int getSortOrder();
 }
diff --git a/ebean-api/src/main/java/io/ebean/CancelableQuery.java b/ebean-api/src/main/java/io/ebean/CancelableQuery.java
new file mode 100644
index 000000000..fb3100bd2
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/CancelableQuery.java
@@ -0,0 +1,19 @@
+package io.ebean;
+
+/**
+ * Defines a cancelable query.
+ * 

+ * Typically holds a representation of the PreparedStatement to perform the + * actual cancel. + *

+ */ +public interface CancelableQuery { + + /** + * Cancel the query. + *

+ * For JDBC this translates to calling cancel on the PreparedStatement. + *

+ */ + void cancel(); +} diff --git a/ebean-api/src/main/java/io/ebean/Database.java b/ebean-api/src/main/java/io/ebean/Database.java index 5bbf29ad3..bd38253de 100644 --- a/ebean-api/src/main/java/io/ebean/Database.java +++ b/ebean-api/src/main/java/io/ebean/Database.java @@ -897,7 +897,7 @@ public interface Database { *
{@code
    *   public class Order { ...
    *
-   *     @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
+   *     @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
    * 	   List details;
    * 	   ...
    *   }
diff --git a/ebean-api/src/main/java/io/ebean/DatabaseFactory.java b/ebean-api/src/main/java/io/ebean/DatabaseFactory.java
index 13a0c4125..588f48c24 100644
--- a/ebean-api/src/main/java/io/ebean/DatabaseFactory.java
+++ b/ebean-api/src/main/java/io/ebean/DatabaseFactory.java
@@ -16,23 +16,21 @@ import java.util.concurrent.locks.ReentrantLock;
  * 

* This uses either DatabaseConfig or properties in the application.properties file to * configure and create a Database instance. - *

*

* The Database instance can either be registered with the DB singleton or * not. The DB singleton effectively holds a map of Database by a name. * If the Database is registered with the DB singleton you can retrieve it * later via {@link DB#byName(String)}. - *

*

* One Database can be nominated as the 'default/primary' Database. Many * methods on the DB singleton such as {@link DB#find(Class)} are just a * convenient way of using the 'default/primary' Database. - *

*/ public class DatabaseFactory { - private static final ReentrantLock lock = new ReentrantLock(false); + private static final ReentrantLock lock = new ReentrantLock(); private static SpiContainer container; + private static String defaultServerName; static { EbeanVersion.getVersion(); @@ -47,7 +45,7 @@ public class DatabaseFactory { public static void initialiseContainer(ContainerConfig containerConfig) { lock.lock(); try { - getContainer(containerConfig); + container(containerConfig); } finally { lock.unlock(); } @@ -59,7 +57,7 @@ public class DatabaseFactory { public static Database create(String name) { lock.lock(); try { - return getContainer(null).createServer(name); + return container(null).createServer(name); } finally { lock.unlock(); } @@ -67,6 +65,16 @@ public class DatabaseFactory { /** * Create using the DatabaseConfig object to configure the database. + * + *
{@code
+   *
+   *   DatabaseConfig config = new DatabaseConfig();
+   *   config.setName("db");
+   *   config.loadProperties();
+   *
+   *   Database database = DatabaseFactory.create(config);
+   *
+   * }
*/ public static Database create(DatabaseConfig config) { lock.lock(); @@ -76,6 +84,12 @@ public class DatabaseFactory { } Database server = createInternal(config); if (config.isRegister()) { + if (config.isDefaultServer()) { + if (defaultServerName != null && !defaultServerName.equals(config.getName())) { + throw new IllegalStateException("Registering [" + config.getName() + "] as the default server but [" + defaultServerName + "] is already registered as the default"); + } + defaultServerName = config.getName(); + } DbPrimary.setSkip(true); DbContext.getInstance().register(server, config.isDefaultServer()); } @@ -108,7 +122,6 @@ public class DatabaseFactory { * Shutdown gracefully all Database instances cleaning up any resources as required. *

* This is typically invoked via JVM shutdown hook and not explicitly called. - *

*/ public static void shutdown() { lock.lock(); @@ -120,15 +133,15 @@ public class DatabaseFactory { } private static Database createInternal(DatabaseConfig config) { - return getContainer(config.getContainerConfig()).createServer(config); + return container(config.getContainerConfig()).createServer(config); } /** - * Get the EbeanContainer initialising it if necessary. + * Return the SpiContainer initialising it if necessary. * * @param containerConfig the configuration controlling clustering communication */ - private static SpiContainer getContainer(ContainerConfig containerConfig) { + private static SpiContainer container(ContainerConfig containerConfig) { // thread safe in that all calling methods hold lock if (container != null) { return container; diff --git a/ebean-api/src/main/java/io/ebean/DbContext.java b/ebean-api/src/main/java/io/ebean/DbContext.java index cf09dc7fb..18df2169e 100644 --- a/ebean-api/src/main/java/io/ebean/DbContext.java +++ b/ebean-api/src/main/java/io/ebean/DbContext.java @@ -27,7 +27,7 @@ final class DbContext { private final HashMap syncMap = new HashMap<>(); - private final ReentrantLock lock = new ReentrantLock(false); + private final ReentrantLock lock = new ReentrantLock(); /** * The 'default' Database. diff --git a/ebean-api/src/main/java/io/ebean/DbPrimary.java b/ebean-api/src/main/java/io/ebean/DbPrimary.java index 9cbdc2eba..ffec8b114 100644 --- a/ebean-api/src/main/java/io/ebean/DbPrimary.java +++ b/ebean-api/src/main/java/io/ebean/DbPrimary.java @@ -12,7 +12,7 @@ import java.util.concurrent.locks.ReentrantLock; */ class DbPrimary { - private static final ReentrantLock lock = new ReentrantLock(false); + private static final ReentrantLock lock = new ReentrantLock(); private static String defaultServerName; private static boolean skip; diff --git a/ebean-api/src/main/java/io/ebean/DocumentStore.java b/ebean-api/src/main/java/io/ebean/DocumentStore.java index a4f75132d..0a2353ff0 100644 --- a/ebean-api/src/main/java/io/ebean/DocumentStore.java +++ b/ebean-api/src/main/java/io/ebean/DocumentStore.java @@ -149,7 +149,7 @@ public interface DocumentStore { * .setUseDocStore(true) * .where()... // perhaps add predicates * .findEachWhile(new Predicate() { - * @Override + * @Override * public void accept(Order bean) { * // process the bean * diff --git a/ebean-api/src/main/java/io/ebean/DtoQuery.java b/ebean-api/src/main/java/io/ebean/DtoQuery.java index bb2fa43ab..69ed034ef 100644 --- a/ebean-api/src/main/java/io/ebean/DtoQuery.java +++ b/ebean-api/src/main/java/io/ebean/DtoQuery.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Stream; /** * Query for performing native SQL queries that return DTO Bean's. @@ -37,7 +38,7 @@ import java.util.function.Predicate; * * }
*/ -public interface DtoQuery { +public interface DtoQuery extends CancelableQuery { /** * Execute the query returning a list. @@ -45,6 +46,26 @@ public interface DtoQuery { @Nonnull List findList(); + /** + * Execute the query iterating a row at a time. + *

+ * Note that the QueryIterator holds resources related to the underlying + * resultSet and potentially connection and MUST be closed. We should use + * QueryIterator in a try with resource block. + */ + @Nonnull + QueryIterator findIterate(); + + /** + * Execute the query returning a Stream. + *

+ * Note that the Stream holds resources related to the underlying + * resultSet and potentially connection and MUST be closed. We should use + * the Stream in a try with resource block. + */ + @Nonnull + Stream findStream(); + /** * Execute the query iterating a row at a time. *

@@ -53,6 +74,17 @@ public interface DtoQuery { */ void findEach(Consumer consumer); + /** + * Execute the query iterating the results and batching them for the consumer. + *

+ * This runs like findEach streaming results from the database but just collects the results + * into batches to pass to the consumer. + * + * @param batch The number of dto beans to collect before given them to the consumer + * @param consumer The consumer to process the batch of DTO beans + */ + void findEach(int batch, Consumer> consumer); + /** * Execute the query iterating a row at a time with the ability to stop consuming part way through. *

diff --git a/ebean-api/src/main/java/io/ebean/Ebean.java b/ebean-api/src/main/java/io/ebean/Ebean.java index f68a0666b..fb0e34db3 100644 --- a/ebean-api/src/main/java/io/ebean/Ebean.java +++ b/ebean-api/src/main/java/io/ebean/Ebean.java @@ -346,7 +346,7 @@ public final class Ebean { *

{@code
    *   public class Order { ...
    *
-   *     @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
+   *     @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
    * 	   List details;
    * 	   ...
    *   }
diff --git a/ebean-api/src/main/java/io/ebean/ExpressionFactory.java b/ebean-api/src/main/java/io/ebean/ExpressionFactory.java
index 1e5c4ebf5..3bcd5cb39 100644
--- a/ebean-api/src/main/java/io/ebean/ExpressionFactory.java
+++ b/ebean-api/src/main/java/io/ebean/ExpressionFactory.java
@@ -200,7 +200,7 @@ public interface ExpressionFactory {
   Expression gtOrNull(String propertyName, Object value);
 
   /**
-   * Greater than or Equal to OR Null  >= or null 
+   * Greater than or Equal to OR Null ({@code >= or null })
    * 

* A convenient expression combining GE and Is Null. Most often useful for range * expressions where the top range value is nullable. @@ -227,7 +227,7 @@ public interface ExpressionFactory { Expression ltOrNull(String propertyName, Object value); /** - * Less Than or Equal to OR Null <= or null + * Less Than or Equal to OR Null ({@code <= or null }) *

* A convenient expression combining LE and Is Null. Most often useful for range * expressions where the bottom range value is nullable. diff --git a/ebean-api/src/main/java/io/ebean/ExpressionList.java b/ebean-api/src/main/java/io/ebean/ExpressionList.java index 5ac3d7de8..e91846e08 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionList.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionList.java @@ -169,16 +169,33 @@ public interface ExpressionList { */ UpdateQuery asUpdate(); + /** + * Execute the query with the given lock type and WAIT. + *

+ * Note that forUpdate() is the same as + * withLock(LockType.UPDATE). + *

+ * Provides us with the ability to explicitly use Postgres + * SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks. + */ + Query withLock(Query.LockType lockType); + + /** + * Execute the query with the given lock type and lock wait. + *

+ * Note that forUpdateNoWait() is the same as + * withLock(LockType.UPDATE, LockWait.NOWAIT). + *

+ * Provides us with the ability to explicitly use Postgres + * SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks. + */ + Query withLock(Query.LockType lockType, Query.LockWait lockWait); + /** * Execute using "for update" clause which results in the DB locking the record. */ Query forUpdate(); - /** - * Execute using "for update" with given lock type (currently Postgres only). - */ - Query forUpdate(Query.LockType lockType); - /** * Execute using "for update" clause with No Wait option. *

@@ -187,11 +204,6 @@ public interface ExpressionList { */ Query forUpdateNoWait(); - /** - * Execute using "for update nowait" with given lock type (currently Postgres only). - */ - Query forUpdateNoWait(Query.LockType lockType); - /** * Execute using "for update" clause with Skip Locked option. *

@@ -200,11 +212,6 @@ public interface ExpressionList { */ Query forUpdateSkipLocked(); - /** - * Execute using "for update skip locked" with given lock type (currently Postgres only). - */ - Query forUpdateSkipLocked(Query.LockType lockType); - /** * Execute the query including soft deleted rows. */ @@ -300,6 +307,13 @@ public interface ExpressionList { */ void findEach(Consumer consumer); + /** + * Execute findEach with a batch consumer. + * + * @see Query#findEach(int, Consumer) + */ + void findEach(int batch, Consumer> consumer); + /** * Execute the query processing the beans one at a time with the ability to * stop processing before reading all the beans. @@ -902,7 +916,7 @@ public interface ExpressionList { ExpressionList gtOrNull(String propertyName, Object value); /** - * Greater Than or Equal to OR Null - >= or null . + * Greater Than or Equal to OR Null - ({@code >= or null }). */ ExpressionList geOrNull(String propertyName, Object value); @@ -923,7 +937,7 @@ public interface ExpressionList { ExpressionList ltOrNull(String propertyName, Object value); /** - * Less Than or Equal to OR Null - <= or null . + * Less Than or Equal to OR Null - ({@code <= or null }). */ ExpressionList leOrNull(String propertyName, Object value); @@ -1657,7 +1671,7 @@ public interface ExpressionList { ExpressionList endAnd(); /** - * End a AND junction - synonym for endJunction(). + * End a OR junction - synonym for endJunction(). */ ExpressionList endOr(); diff --git a/ebean-api/src/main/java/io/ebean/ExtendedServer.java b/ebean-api/src/main/java/io/ebean/ExtendedServer.java index 06ee85c2b..59b1c678e 100644 --- a/ebean-api/src/main/java/io/ebean/ExtendedServer.java +++ b/ebean-api/src/main/java/io/ebean/ExtendedServer.java @@ -67,7 +67,7 @@ public interface ExtendedServer { * * @return True if the query finds a matching row in the database */ - boolean exists(Query ormQuery, Transaction transaction); + boolean exists(Query ormQuery, Transaction transaction); /** * Return the number of 'top level' or 'root' entities this query should return. @@ -159,6 +159,13 @@ public interface ExtendedServer { */ void findEach(Query query, Consumer consumer, Transaction transaction); + /** + * Execute findEach with batch consumer. + * + * @see Query#findEach(int, Consumer) + */ + void findEach(Query query, int batch, Consumer> consumer, Transaction t); + /** * Execute the query visiting the each bean one at a time. *

diff --git a/ebean-api/src/main/java/io/ebean/FetchConfig.java b/ebean-api/src/main/java/io/ebean/FetchConfig.java index 39823b2c7..e2d4fbe35 100644 --- a/ebean-api/src/main/java/io/ebean/FetchConfig.java +++ b/ebean-api/src/main/java/io/ebean/FetchConfig.java @@ -3,30 +3,13 @@ package io.ebean; import java.io.Serializable; /** - * Defines the configuration options for a "query fetch" or a - * "lazy loading fetch". This gives you the ability to use multiple smaller - * queries to populate an object graph as opposed to a single large query. - *

- * The primary goal is to provide efficient ways of loading complex object - * graphs avoiding SQL Cartesian product and issues around populating object - * graphs that have multiple *ToMany relationships. - *

- *

- * It also provides the ability to control the lazy loading queries (batch size, - * selected properties and fetches) to avoid N+1 queries etc. - *

- * There can also be cases loading across a single OneToMany where 2 SQL queries - * using Ebean FetchConfig.query() can be more efficient than one SQL query. - * When the "One" side is wide (lots of columns) and the cardinality difference - * is high (a lot of "Many" beans per "One" bean) then this can be more - * efficient loaded as 2 SQL queries. - *

+ * Defines how a relationship is fetched via either normal SQL join, + * a eager secondary query, via lazy loading or via eagerly hitting L2 cache. *

*

{@code
  * // Normal fetch join results in a single SQL query
  * List list = DB.find(Order.class).fetch("details").findList();
  *
- * // Find Orders join details using a single SQL query
  * }
*

* Example: Using a "query join" instead of a "fetch join" we instead use 2 SQL queries @@ -37,103 +20,13 @@ import java.io.Serializable; * // This will use 2 SQL queries to build this object graph * List list = * DB.find(Order.class) - * .fetch("details", new FetchConfig().query()) + * .fetch("details", FetchConfig.ofQuery()) * .findList(); * * // query 1) find order * // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's * * }

- *

- * Example: Using 2 "query joins" - *

- *

- *

{@code
- *
- * // This will use 3 SQL queries to build this object graph
- * List list =
- *     DB.find(Order.class)
- *         .fetch("details", new FetchConfig().query())
- *         .fetch("customer", new FetchConfig().queryFirst(5))
- *         .findList();
- *
- * // query 1) find order
- * // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
- * // query 3) find customer where id in (?,?,?,?,?) // first 5 customers
- *
- * }
- *

- * Example: Using "query joins" and partial objects - *

- *

- * - *

{@code
- * // This will use 3 SQL queries to build this object graph
- * List list =
- *     DB.find(Order.class)
- *         .select("status, shipDate")
- *         .fetch("details", "quantity, price", new FetchConfig().query())
- *         .fetch("details.product", "sku, name")
- *         .fetch("customer", "name", new FetchConfig().queryFirst(5))
- *         .fetch("customer.contacts")
- *         .fetch("customer.shippingAddress")
- *         .findList();
- *
- * // query 1) find order (status, shipDate)
- * // query 2) find orderDetail (quantity, price) fetch product (sku, name) where
- * // order.id in (?,? ...)
- * // query 3) find customer (name) fetch contacts (*) fetch shippingAddress (*)
- * // where id in (?,?,?,?,?)
- *
- * // Note: the fetch of "details.product" is automatically included into the
- * // fetch of "details"
- * //
- * // Note: the fetch of "customer.contacts" and "customer.shippingAddress"
- * // are automatically included in the fetch of "customer"
- * }
- *

- * You can use query() and lazy together on a single join. The query is executed - * immediately and the lazy defines the batch size to use for further lazy - * loading (if lazy loading is invoked). - *

- *

- *

{@code
- *
- * List list =
- *     DB.find(Order.class)
- *         .fetch("customer", new FetchConfig().query(10).lazy(5))
- *         .findList();
- *
- * // query 1) find order
- * // query 2) find customer where id in (?,?,?,?,?,?,?,?,?,?) // first 10 customers
- * // .. then if lazy loading of customers is invoked
- * // .. use a batch size of 5 to load the customers
- *
- * }
- *

- *

- * Example of controlling the lazy loading query: - *

- *

- * This gives us the ability to optimise the lazy loading query for a given use - * case. - *

- *

- *

{@code
- *
- * List list = DB.find(Order.class)
- *   .fetch("customer","name", new FetchConfig().lazy(5))
- *   .fetch("customer.contacts","contactName, phone, email")
- *   .fetch("customer.shippingAddress")
- *   .where().eq("status",Order.Status.NEW)
- *   .findList();
- *
- * // query 1) find order where status = Order.Status.NEW
- * //
- * // .. if lazy loading of customers is invoked
- * // .. use a batch size of 5 to load the customers
- *
- * }
* * @author mario * @author rbygrave @@ -142,149 +35,209 @@ public class FetchConfig implements Serializable { private static final long serialVersionUID = 1L; - private int lazyBatchSize = -1; + private static final int JOIN_MODE = 0; + private static final int QUERY_MODE = 1; + private static final int LAZY_MODE = 2; + private static final int CACHE_MODE = 3; - private int queryBatchSize = -1; - - private boolean queryAll; - - private boolean cache; + private int mode; + private int batchSize; + private int hashCode; /** - * Construct the fetch configuration object. + * Deprecated - migrate to one of the static factory methods like {@link FetchConfig#ofQuery()} + *

+ * Construct using default JOIN mode. */ + @Deprecated public FetchConfig() { + //this.mode = JOIN_MODE; + this.batchSize = 100; + this.hashCode = 1000; + } + + private FetchConfig(int mode, int batchSize) { + this.mode = mode; + this.batchSize = batchSize; + this.hashCode = mode + 10 * batchSize; } /** - * Specify that this path should be lazy loaded using the default batch load - * size. + * Return FetchConfig to eagerly fetch the relationship using L2 cache. + *

+ * Any cache misses will be loaded by secondary query to the database. */ + public static FetchConfig ofCache() { + return new FetchConfig(CACHE_MODE, 100); + } + + /** + * Return FetchConfig to eagerly fetch the relationship using a secondary query. + */ + public static FetchConfig ofQuery() { + return new FetchConfig(QUERY_MODE, 100); + } + + /** + * Return FetchConfig to eagerly fetch the relationship using a secondary with a given batch size. + */ + public static FetchConfig ofQuery(int batchSize) { + return new FetchConfig(QUERY_MODE, batchSize); + } + + /** + * Return FetchConfig to lazily load the relationship. + */ + public static FetchConfig ofLazy() { + return new FetchConfig(LAZY_MODE, 0); + } + + /** + * Return FetchConfig to lazily load the relationship specifying the batch size. + */ + public static FetchConfig ofLazy(int batchSize) { + return new FetchConfig(LAZY_MODE, batchSize); + } + + /** + * Return FetchConfig to fetch the relationship using SQL join. + */ + public static FetchConfig ofDefault() { + return new FetchConfig(JOIN_MODE, 100); + } + + /** + * We want to migrate away from mutating FetchConfig to a fully immutable FetchConfig. + */ + private FetchConfig mutate(int mode, int batchSize) { + if (batchSize < 0) { + throw new IllegalArgumentException("batch size " + batchSize + " must be > 0"); + } + this.mode = mode; + this.batchSize = batchSize; + this.hashCode = mode + 10 * batchSize; + return this; + } + + /** + * Deprecated - migrate to FetchConfig.ofLazy(). + */ + @Deprecated public FetchConfig lazy() { - this.lazyBatchSize = 0; - this.queryAll = false; - return this; + return mutate(LAZY_MODE, 0); } /** - * Specify that this path should be lazy loaded with a specified batch size. - * - * @param lazyBatchSize the batch size for lazy loading + * Deprecated - migrate to FetchConfig.ofLazy(batchSize). */ - public FetchConfig lazy(int lazyBatchSize) { - this.lazyBatchSize = lazyBatchSize; - this.queryAll = false; - return this; + @Deprecated + public FetchConfig lazy(int batchSize) { + return mutate(LAZY_MODE, batchSize); } /** + * Deprecated - migrate to FetchConfig.ofQuery(). + *

* Eagerly fetch the beans in this path as a separate query (rather than as * part of the main query). *

* This will use the default batch size for separate query which is 100. - *

*/ + @Deprecated public FetchConfig query() { - this.queryBatchSize = 0; - this.queryAll = true; - return this; - } - - /** - * Eagerly fetch the beans fetching the beans from the L2 bean cache - * and using the DB for beans not in the cache. - */ - public FetchConfig cache() { - this.cache = true; - this.queryBatchSize = 0; - this.queryAll = true; - return this; + return mutate(QUERY_MODE, 100); } /** + * Deprecated - migrate to FetchConfig.ofQuery(batchSize). + *

* Eagerly fetch the beans in this path as a separate query (rather than as * part of the main query). *

* The queryBatchSize is the number of parent id's that this separate query * will load per batch. - *

*

* This will load all beans on this path eagerly unless a {@link #lazy(int)} * is also used. - *

* - * @param queryBatchSize the batch size used to load beans on this path + * @param batchSize the batch size used to load beans on this path */ - public FetchConfig query(int queryBatchSize) { - this.queryBatchSize = queryBatchSize; - // queryAll true as long as a lazy batch size has not already been set - this.queryAll = (lazyBatchSize == -1); - return this; + @Deprecated + public FetchConfig query(int batchSize) { + return mutate(QUERY_MODE, batchSize); } /** + * Deprecated - migrate to FetchConfig.ofQuery(batchSize). + *

* Eagerly fetch the first batch of beans on this path. * This is similar to {@link #query(int)} but only fetches the first batch. *

* If there are more parent beans than the batch size then they will not be * loaded eagerly but instead use lazy loading. - *

* - * @param queryBatchSize the number of parent beans this path is populated for + * @param batchSize the number of parent beans this path is populated for */ - public FetchConfig queryFirst(int queryBatchSize) { - this.queryBatchSize = queryBatchSize; - this.queryAll = false; - return this; + @Deprecated + public FetchConfig queryFirst(int batchSize) { + return query(batchSize); } /** - * Return the batch size for lazy loading. + * Deprecated - migrate to FetchConfig.ofCache(). + *

+ * Eagerly fetch the beans fetching the beans from the L2 bean cache + * and using the DB for beans not in the cache. */ - public int getLazyBatchSize() { - return lazyBatchSize; + @Deprecated + public FetchConfig cache() { + return mutate(CACHE_MODE, 100); } /** - * Return the batch size for separate query load. + * Return the batch size for fetching. */ - public int getQueryBatchSize() { - return queryBatchSize; + public int getBatchSize() { + return batchSize; } /** - * Return true if the query fetch should fetch 'all' rather than just the - * 'first' batch. - */ - public boolean isQueryAll() { - return queryAll; - } - - /** - * Return true if this uses L2 bean cache. + * Return true if the fetch should use the L2 cache. */ public boolean isCache() { - return cache; + return mode == CACHE_MODE; + } + + /** + * Return true if the fetch should be a eager secondary query. + */ + public boolean isQuery() { + return mode == QUERY_MODE; + } + + /** + * Return true if the fetch should be a lazy query. + */ + public boolean isLazy() { + return mode == LAZY_MODE; + } + + /** + * Return true if the fetch should try to use SQL join. + */ + public boolean isJoin() { + return mode == JOIN_MODE; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - - FetchConfig that = (FetchConfig) o; - if (lazyBatchSize != that.lazyBatchSize) return false; - if (queryBatchSize != that.queryBatchSize) return false; - if (cache != that.cache) return false; - return queryAll == that.queryAll; + return (hashCode == ((FetchConfig) o).hashCode); } @Override public int hashCode() { - int result = lazyBatchSize; - result = 92821 * result + queryBatchSize; - result = 92821 * result + (queryAll ? 1 : 0); - result = 92821 * result + (cache ? 1 : 0); - return result; + return hashCode; } } diff --git a/ebean-api/src/main/java/io/ebean/Finder.java b/ebean-api/src/main/java/io/ebean/Finder.java index e91238344..3c9318e7d 100644 --- a/ebean-api/src/main/java/io/ebean/Finder.java +++ b/ebean-api/src/main/java/io/ebean/Finder.java @@ -37,7 +37,7 @@ import java.util.List; * } * } * - * @Entity + * @Entity * public class Customer extends BaseModel { * * public static final CustomerFinder find = new CustomerFinder(); @@ -80,7 +80,7 @@ public class Finder { * // ... add extra customer specific finder methods * } * - * @Entity + * @Entity * public class Customer extends BaseModel { * * public static final CustomerFinder find = new CustomerFinder(); diff --git a/ebean-api/src/main/java/io/ebean/Model.java b/ebean-api/src/main/java/io/ebean/Model.java index 5fe2205e1..ac2f11ea7 100644 --- a/ebean-api/src/main/java/io/ebean/Model.java +++ b/ebean-api/src/main/java/io/ebean/Model.java @@ -32,16 +32,16 @@ import io.ebean.bean.EntityBean; * // Typically there is a common base model that has some * // common properties like the ones below * - * @MappedSuperclass + * @MappedSuperclass * public class BaseModel extends Model { * - * @Id Long id; + * @Id Long id; * - * @Version Long version; + * @Version Long version; * - * @WhenCreated Timestamp whenCreated; + * @WhenCreated Timestamp whenCreated; * - * @WhenUpdated Timestamp whenUpdated; + * @WhenUpdated Timestamp whenUpdated; * * ... * } @@ -52,7 +52,7 @@ import io.ebean.bean.EntityBean; * * // Extend the mappedSuperclass * - * @Entity @Table(name="o_account") + * @Entity @Table(name="o_account") * public class Customer extends BaseModel { * * String name; diff --git a/ebean-api/src/main/java/io/ebean/OrderBy.java b/ebean-api/src/main/java/io/ebean/OrderBy.java index 820a76314..8b39fc19e 100644 --- a/ebean-api/src/main/java/io/ebean/OrderBy.java +++ b/ebean-api/src/main/java/io/ebean/OrderBy.java @@ -16,7 +16,7 @@ import java.util.Objects; * on the Query object. *

*/ -public final class OrderBy implements Serializable { +public class OrderBy implements Serializable { private static final long serialVersionUID = 9157089257745730539L; @@ -69,7 +69,6 @@ public final class OrderBy implements Serializable { * Add a property with ascending order to this OrderBy. */ public Query asc(String propertyName) { - list.add(new Property(propertyName, true)); return query; } @@ -98,7 +97,6 @@ public final class OrderBy implements Serializable { return query; } - /** * Return true if the property is known to be contained in the order by clause. */ @@ -207,7 +205,6 @@ public final class OrderBy implements Serializable { if (!(obj instanceof OrderBy)) { return false; } - OrderBy e = (OrderBy) obj; return e.list.equals(list); } @@ -249,7 +246,7 @@ public final class OrderBy implements Serializable { /** * A property and its ascending descending order. */ - public static final class Property implements Serializable { + public static class Property implements Serializable { private static final long serialVersionUID = 1546009780322478077L; @@ -415,13 +412,10 @@ public final class OrderBy implements Serializable { } private void parse(String orderByClause) { - if (orderByClause == null) { return; } - - String[] chunks = orderByClause.split(","); - for (String chunk : chunks) { + for (String chunk : orderByClause.split(",")) { Property p = parseProperty(chunk); if (p != null) { list.add(p); @@ -467,8 +461,7 @@ public final class OrderBy implements Serializable { if (s.startsWith("desc")) { return false; } - String m = "Expecting [" + s + "] to be asc or desc?"; - throw new RuntimeException(m); + throw new RuntimeException("Expecting [" + s + "] to be asc or desc?"); } private boolean isEmptyString(String s) { diff --git a/ebean-api/src/main/java/io/ebean/Pairs.java b/ebean-api/src/main/java/io/ebean/Pairs.java index 4f609f95a..bd5ee5137 100644 --- a/ebean-api/src/main/java/io/ebean/Pairs.java +++ b/ebean-api/src/main/java/io/ebean/Pairs.java @@ -18,7 +18,7 @@ import java.util.List; * * // where a bean is annotated with a complex * // natural key made of several properties - * @Cache(naturalKey = {"store","code","sku"}) + * @Cache(naturalKey = {"store","code","sku"}) * * * Pairs pairs = new Pairs("sku", "code"); diff --git a/ebean-api/src/main/java/io/ebean/Query.java b/ebean-api/src/main/java/io/ebean/Query.java index 200704cfd..0dc86905e 100644 --- a/ebean-api/src/main/java/io/ebean/Query.java +++ b/ebean-api/src/main/java/io/ebean/Query.java @@ -177,14 +177,15 @@ import java.util.stream.Stream; * * @param the type of Entity bean this query will fetch. */ -public interface Query { +public interface Query extends CancelableQuery { /** * The lock type (strength) to use with query FOR UPDATE row locking. */ enum LockType { /** - * The default lock type - See PlatformConfig.forUpdateNoKey option. + * The default lock type being either UPDATE or NO_KEY_UPDATE based on + * PlatformConfig.forUpdateNoKey configuration (Postgres option). */ DEFAULT, @@ -194,17 +195,17 @@ public interface Query { UPDATE, /** - * FOR NO KEY UPDATE. + * FOR NO KEY UPDATE (Postgres only). */ NO_KEY_UPDATE, /** - * FOR SHARE UPDATE. + * FOR SHARE (Postgres only). */ SHARE, /** - * FOR KEY SHARE UPDATE. + * FOR KEY SHARE (Postgres only). */ KEY_SHARE } @@ -290,15 +291,6 @@ public interface Query { */ UpdateQuery asUpdate(); - /** - * Cancel the query execution if supported by the underlying database and - * driver. - *

- * This must be called from a different thread to the query executor. - *

- */ - void cancel(); - /** * Return a copy of the query. *

@@ -513,7 +505,7 @@ public interface Query { *

*
{@code
    *
-   *  fetch(path, fetchProperties, new FetchConfig().query())
+   *  fetch(path, fetchProperties, FetchConfig.ofQuery())
    *
    * }
*

@@ -547,7 +539,7 @@ public interface Query { *

*
{@code
    *
-   *  fetch(path, fetchProperties, new FetchConfig().lazy())
+   *  fetch(path, fetchProperties, FetchConfig.ofLazy())
    *
    * }
*

@@ -572,7 +564,7 @@ public interface Query { * // fetch customers (their id, name and status) * List customers = DB.find(Customer.class) * .select("name, status") - * .fetch("contacts", "firstName,lastName,email", new FetchConfig().lazy(10)) + * .fetch("contacts", "firstName,lastName,email", FetchConfig.ofLazy(10)) * .findList(); * * }

@@ -609,7 +601,7 @@ public interface Query { *

*
{@code
    *
-   *  fetch(path, new FetchConfig().query())
+   *  fetch(path, FetchConfig.ofQuery())
    *
    * }
*

@@ -638,7 +630,7 @@ public interface Query { *

*
{@code
    *
-   *  fetch(path, new FetchConfig().lazy())
+   *  fetch(path, FetchConfig.ofLazy())
    *
    * }
*

@@ -661,7 +653,7 @@ public interface Query { * // fetch customers (their id, name and status) * List customers = DB.find(Customer.class) * // lazy fetch contacts with a batch size of 100 - * .fetch("contacts", new FetchConfig().lazy(100)) + * .fetch("contacts", FetchConfig.ofLazy(100)) * .findList(); * * } @@ -809,7 +801,7 @@ public interface Query { *

*

* This method is functionally equivalent to findIterate() but instead of using an - * iterator uses the Consumer interface which is better suited to use with Java8 closures. + * iterator uses the Consumer interface which is better suited to use with closures. *

*
{@code
    *
@@ -828,6 +820,21 @@ public interface Query {
    */
   void findEach(Consumer consumer);
 
+  /**
+   * Execute findEach streaming query batching the results for consuming.
+   * 

+ * This query execution will stream the results and is suited to consuming + * large numbers of results from the database. + *

+ * Typically we use this batch consumer when we want to do further processing on + * the beans and want to do that processing in batch form, for example - 100 at + * a time. + * + * @param batch The number of beans processed in the batch + * @param consumer Process the batch of beans + */ + void findEach(int batch, Consumer> consumer); + /** * Execute the query using callbacks to a visitor to process the resulting * beans one at a time. @@ -838,12 +845,12 @@ public interface Query { *

*

* This method is functionally equivalent to findIterate() but instead of using an - * iterator uses the Predicate (SAM) interface which is better suited to use with Java8 closures. + * iterator uses the Predicate interface which is better suited to use with closures. *

*
{@code
    *
    *  DB.find(Customer.class)
-   *     .fetch("contacts", new FetchConfig().query(2))
+   *     .fetchQuery("contacts")
    *     .where().eq("status", Status.NEW)
    *     .order().asc("id")
    *     .setMaxRows(2000)
@@ -1644,41 +1651,52 @@ public interface Query {
   String getGeneratedSql();
 
   /**
-   * Execute using "for update" clause which results in the DB locking the record.
+   * Execute the query with the given lock type and WAIT.
+   * 

+ * Note that forUpdate() is the same as + * withLock(LockType.UPDATE). + *

+ * Provides us with the ability to explicitly use Postgres + * SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks. */ - Query forUpdate(); + Query withLock(LockType lockType); /** - * Execute using "for update" with given lock type (currently Postgres only). + * Execute the query with the given lock type and lock wait. + *

+ * Note that forUpdateNoWait() is the same as + * withLock(LockType.UPDATE, LockWait.NOWAIT). + *

+ * Provides us with the ability to explicitly use Postgres + * SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks. */ - Query forUpdate(LockType lockType); + Query withLock(LockType lockType, LockWait lockWait); + + /** + * Execute using "for update" clause which results in the DB locking the record. + *

+ * The same as withLock(LockType.UPDATE, LockWait.WAIT). + */ + Query forUpdate(); /** * Execute using "for update" clause with "no wait" option. *

* This is typically a Postgres and Oracle only option at this stage. - *

+ *

+ * The same as withLock(LockType.UPDATE, LockWait.NOWAIT). */ Query forUpdateNoWait(); - /** - * Execute using "for update nowait" with given lock type (currently Postgres only). - */ - Query forUpdateNoWait(LockType lockType); - /** * Execute using "for update" clause with "skip locked" option. *

* This is typically a Postgres and Oracle only option at this stage. - *

+ *

+ * The same as withLock(LockType.UPDATE, LockWait.SKIPLOCKED). */ Query forUpdateSkipLocked(); - /** - * Execute using "for update skip locked" with given lock type (currently Postgres only). - */ - Query forUpdateSkipLocked(LockType lockType); - /** * Return true if this query has forUpdate set. */ diff --git a/ebean-api/src/main/java/io/ebean/QueryIterator.java b/ebean-api/src/main/java/io/ebean/QueryIterator.java index c158e62c6..6ad523151 100644 --- a/ebean-api/src/main/java/io/ebean/QueryIterator.java +++ b/ebean-api/src/main/java/io/ebean/QueryIterator.java @@ -56,7 +56,7 @@ import java.util.Iterator; * * @param the type of entity bean in the iteration */ -public interface QueryIterator extends Iterator, java.io.Closeable { +public interface QueryIterator extends Iterator, AutoCloseable { /** * Returns true if the iteration has more elements. @@ -70,12 +70,6 @@ public interface QueryIterator extends Iterator, java.io.Closeable { @Override T next(); - /** - * Remove is not allowed. - */ - @Override - void remove(); - /** * Close the underlying resources held by this iterator. */ diff --git a/ebean-api/src/main/java/io/ebean/RawSql.java b/ebean-api/src/main/java/io/ebean/RawSql.java index 1fe274a1e..901f1a43c 100644 --- a/ebean-api/src/main/java/io/ebean/RawSql.java +++ b/ebean-api/src/main/java/io/ebean/RawSql.java @@ -41,14 +41,14 @@ package io.ebean; *

Example OrderAggregate

*
{@code
  *  ...
- *   // @Sql indicates to that this bean
+ *   // @Sql indicates to that this bean
  *   // is based on RawSql rather than a table
  *
- *   @Entity
- *   @Sql
+ *   @Entity
+ *   @Sql
  *   public class OrderAggregate {
  *
- *    @OneToOne
+ *    @OneToOne
  *    Order order;
  *
  *    Double totalAmount;
@@ -107,7 +107,7 @@ package io.ebean;
  *
  *   List orders = DB.find(OrderAggregate.class)
  *     .setRawSql(rawSql)
- *     .fetch("order", "status,orderDate", new FetchConfig().query())
+ *     .fetch("order", "status,orderDate", FetchConfig.ofQuery())
  *     .fetch("order.customer", "name")
  *     .where().gt("order.id", 0)
  *     .having().gt("totalAmount", 20)
diff --git a/ebean-api/src/main/java/io/ebean/RowMapper.java b/ebean-api/src/main/java/io/ebean/RowMapper.java
index 47e96498d..b1d59c984 100644
--- a/ebean-api/src/main/java/io/ebean/RowMapper.java
+++ b/ebean-api/src/main/java/io/ebean/RowMapper.java
@@ -22,7 +22,7 @@ import java.sql.SQLException;
  *    //
  *    class CustomerMapper implements RowMapper {
  *
- *     @Override
+ *     @Override
  *     public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
  *
  *       long id = rset.getLong(1);
diff --git a/ebean-api/src/main/java/io/ebean/SqlQuery.java b/ebean-api/src/main/java/io/ebean/SqlQuery.java
index 8b5452ee7..9a878f233 100644
--- a/ebean-api/src/main/java/io/ebean/SqlQuery.java
+++ b/ebean-api/src/main/java/io/ebean/SqlQuery.java
@@ -37,7 +37,7 @@ import java.util.function.Predicate;
  *
  * }
*/ -public interface SqlQuery extends Serializable { +public interface SqlQuery extends Serializable, CancelableQuery { /** * Execute the query returning a list. @@ -365,5 +365,10 @@ public interface SqlQuery extends Serializable { * Return the list of values. */ List findList(); + + /** + * Find streaming the result effectively consuming a row at a time. + */ + void findEach(Consumer consumer); } } diff --git a/ebean-api/src/main/java/io/ebean/Transaction.java b/ebean-api/src/main/java/io/ebean/Transaction.java index b2bce9abf..0856a6e4e 100644 --- a/ebean-api/src/main/java/io/ebean/Transaction.java +++ b/ebean-api/src/main/java/io/ebean/Transaction.java @@ -59,6 +59,14 @@ public interface Transaction extends AutoCloseable { */ void register(TransactionCallback callback); + /** + * EXPERIMENTAL - turn on automatic persistence of dirty beans and batchMode true. + *

+ * With this turned on beans that are dirty in the persistence context + * are automatically persisted on flush() and commit(). + */ + void setAutoPersistUpdates(boolean autoPersistUpdates); + /** * Set a label on the transaction. *

@@ -339,15 +347,12 @@ public interface Transaction extends AutoCloseable { * The batch is automatically flushed when it hits the batch size and also when we * execute queries or when we mix UpdateSql and CallableSql with save and delete of * beans. - *

*

* We use {@link #flush()} to explicitly flush the batch and we can use * {@link #setFlushOnQuery(boolean)} and {@link #setFlushOnMixed(boolean)} * to control the automatic flushing behaviour. - *

*

* Example: batch processing of CallableSql executing every 10 rows - *

* *
{@code
    *
@@ -392,14 +397,11 @@ public interface Transaction extends AutoCloseable {
    * 

* This only takes effect when batch mode on the transaction has not already meant that * JDBC batch mode is being used. - *

*

* 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. - *

*

* This is effectively already turned on for all platforms apart from older Sql Server. - *

* * @param batchMode the batch mode to use per save(), insert(), update() or delete() * @see io.ebean.config.DatabaseConfig#setPersistBatchOnCascade(PersistBatch) @@ -422,15 +424,19 @@ public interface Transaction extends AutoCloseable { int getBatchSize(); /** - * Specify if you want batched inserts to use getGeneratedKeys. + * Specify if we want batched inserts to use getGeneratedKeys. *

* By default batched inserts will try to use getGeneratedKeys if it is * supported by the underlying jdbc driver and database. - *

*

- * You may want to turn getGeneratedKeys off when you are inserting a large - * number of objects and you don't care about getting back the ids. - *

+ * We want to turn off getGeneratedKeys when we are inserting a large + * number of objects and we don't care about getting back the ids. In this + * way we avoid the extra cost of getting back the generated id values + * from the database. + *

+ * Note that when we do turn off getGeneratedKeys then we have the limitation + * that after a bean has been inserted we are unable to then mutate the bean + * and update it in the same transaction as we have not obtained it's id value. */ void setGetGeneratedKeys(boolean getGeneratedKeys); @@ -449,13 +455,11 @@ public interface Transaction extends AutoCloseable { *

* If you want to execute both WITHOUT having the batch automatically flush * you need to call this with batchFlushOnMixed = false. - *

*

* Note that UpdateSql and CallableSql are ALWAYS executed first (before the * beans are executed). This is because the UpdateSql and CallableSql have * already been bound to their PreparedStatements. The beans on the other hand * have a 2 step process (delayed binding). - *

*/ void setFlushOnMixed(boolean batchFlushOnMixed); @@ -473,7 +477,6 @@ public interface Transaction extends AutoCloseable { *

* Calling this method with batchFlushOnQuery = false means that you can * execute a query and the batch will not be automatically flushed. - *

*/ void setFlushOnQuery(boolean batchFlushOnQuery); @@ -490,7 +493,6 @@ public interface Transaction extends AutoCloseable { * should be flushed prior to executing a query. *

* The default is for this to be true. - *

*/ boolean isFlushOnQuery(); @@ -507,7 +509,6 @@ public interface Transaction extends AutoCloseable { * flush the batch if you like. *

* Flushing occurs automatically when: - *

*