diff --git a/ebean-api/src/main/java/io/ebean/ExtendedServer.java b/ebean-api/src/main/java/io/ebean/ExtendedServer.java index d6dc9af97..db8c6d9f5 100644 --- a/ebean-api/src/main/java/io/ebean/ExtendedServer.java +++ b/ebean-api/src/main/java/io/ebean/ExtendedServer.java @@ -2,7 +2,6 @@ package io.ebean; import io.avaje.lang.Nullable; -import javax.persistence.NonUniqueResultException; import java.time.Clock; import java.util.List; import java.util.Map; @@ -15,470 +14,182 @@ import java.util.stream.Stream; /** * The extended API for Database. *

+ * Deprecated in favour of using {@link Query#usingTransaction(Transaction)} instead. + *

* This provides the finder methods that take an explicit transaction rather than obtaining * the transaction from the usual mechanism (which is ThreadLocal based). - *

- *

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

*

* Note that in all cases the transaction supplied can be null and in this case the Database * will use the normal mechanism to obtain the transaction to use. - *

*/ public interface ExtendedServer { /** - * Return the NOW time from the Clock. - */ - long clockNow(); - - /** + * Deprecated but no yet determined suitable replacement (to support testing only change of clock). + *

* Set the Clock to use for @WhenCreated and @WhenModified. *

* Note that we only expect to change the Clock for testing purposes. *

*/ + @Deprecated void setClock(Clock clock); /** - * Execute the query returning true if a row is found. - *

- * The query is executed using max rows of 1 and will only select the id property. - * This method is really just a convenient way to optimise a query to perform a - * 'does a row exist in the db' check. - *

- * - *

Example:

- *
{@code
-   *
-   *   boolean userExists = query().where().eq("email", "rob@foo.com").exists();
-   *
-   * }
- * - *

Example using a query bean:

- *
{@code
-   *
-   *   boolean userExists = new QContact().email.equalTo("rob@foo.com").exists();
-   *
-   * }
- * - * @return True if the query finds a matching row in the database + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated boolean exists(Query ormQuery, Transaction transaction); /** - * Return the number of 'top level' or 'root' entities this query should return. - * - * @see Query#findCount() - * @see Query#findFutureCount() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated int findCount(Query query, Transaction transaction); /** - * Return the Id values of the query as a List. - * - * @see Query#findIds() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated List findIds(Query query, Transaction transaction); /** - * Return a QueryIterator for the query. - *

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

- * 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. - * - * @see Query#findIterate() - * @see Query#findEach(Consumer) - * @see Query#findEachWhile(Predicate) + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated QueryIterator findIterate(Query query, Transaction transaction); /** - * Execute the query returning the result as a Stream. - *

- * Note that this can support very large queries iterating any number of results. - * To do so internally it can use multiple persistence contexts. - *

- * Note that the stream needs to be closed so use with try with resources. - *

+ * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated Stream findStream(Query query, Transaction transaction); /** - * Execute the query visiting the each bean one at a time. - *

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

- *

- * Internally this query using a PersistenceContext scoped to each bean (and the - * beans associated object graph). - *

- *

- *

{@code
-   *
-   *     DB.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);
-   *       });
-   *
-   * }
- * - * @see Query#findEach(Consumer) - * @see Query#findEachWhile(Predicate) + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated void findEach(Query query, Consumer consumer, Transaction transaction); /** - * Execute findEach with batch consumer. - * - * @see Query#findEach(int, Consumer) + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated void findEach(Query query, int batch, Consumer> consumer, Transaction t); /** - * Execute the query visiting the each bean one at a time. - *

- * Compared to findEach() this provides the ability to stop processing the query - * results early by returning false for the Predicate. - *

- *

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

- *

- * Internally this query using a PersistenceContext scoped to each bean (and the - * beans associated object graph). - *

- *

- *

{@code
-   *
-   *     DB.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;
-   *       });
-   *
-   * }
- * - * @see Query#findEach(Consumer) - * @see Query#findEachWhile(Predicate) + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated void findEachWhile(Query query, Predicate consumer, Transaction transaction); /** - * Return versions of a @History entity bean. - *

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

+ * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated List> findVersions(Query query, Transaction transaction); /** - * Execute a query returning a list of beans. - *

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

- *

- *

{@code
-   *
-   * List customers = DB.find(Customer.class)
-   *     .where().ilike("name", "rob%")
-   *     .findList();
-   *
-   * }
- * - * @param 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() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated List findList(Query query, Transaction transaction); /** - * Execute find row count query in a background thread. - *

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

- * - * @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() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated FutureRowCount findFutureCount(Query query, Transaction transaction); /** - * Execute find Id's query in a background thread. - *

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

- * - * @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() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated FutureIds findFutureIds(Query query, Transaction transaction); /** - * Execute find list query in a background thread returning a FutureList object. - *

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

- * 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() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated FutureList findFutureList(Query query, Transaction transaction); /** - * Return a PagedList for this query using firstRow and maxRows. - *

- * The benefit of using this over findList() is that it provides functionality to get the - * total row count etc. - *

- *

- * If maxRows is not set on the query prior to calling findPagedList() then a - * PersistenceException is thrown. - *

- *

- *

{@code
-   *
-   *  PagedList pagedList = DB.find(Order.class)
-   *       .setFirstRow(50)
-   *       .setMaxRows(20)
-   *       .findPagedList();
-   *
-   *       // fetch the total row count in the background
-   *       pagedList.loadRowCount();
-   *
-   *       List orders = pagedList.getList();
-   *       int totalRowCount = pagedList.getTotalRowCount();
-   *
-   * }
- * - * @return The PagedList - * @see Query#findPagedList() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated PagedList findPagedList(Query query, Transaction transaction); /** - * Execute the query returning a set of entity beans. - *

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

- *

- *

{@code
-   *
-   * Set customers = DB.find(Customer.class)
-   *     .where().ilike("name", "rob%")
-   *     .findSet();
-   *
-   * }
- * - * @param 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() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated Set findSet(Query query, Transaction transaction); /** - * Execute the query returning the entity beans in a Map. - *

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

- * - * @param 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() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated Map findMap(Query query, Transaction transaction); /** - * Execute the query returning a list of values for a single property. - *

- *

Example 1:

- *
{@code
-   *
-   *  List names =
-   *    DB.find(Customer.class)
-   *      .select("name")
-   *      .order().asc("name")
-   *      .findSingleAttributeList();
-   *
-   * }
- *

Example 2:

- *
{@code
-   *
-   *  List names =
-   *    DB.find(Customer.class)
-   *      .setDistinct(true)
-   *      .select("name")
-   *      .where().eq("status", Customer.Status.NEW)
-   *      .order().asc("name")
-   *      .setMaxRows(100)
-   *      .findSingleAttributeList();
-   *
-   * }
- * - * @return the list of values for the selected property - * @see Query#findSingleAttributeList() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated List
findSingleAttributeList(Query query, Transaction transaction); /** - * Execute the query returning a hashset of values for a single property. + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated Set findSingleAttributeSet(Query query, Transaction transaction); /** - * Execute the query returning at most one entity bean or null (if no matching - * bean is found). - *

- * This will throw a NonUniqueResultException if the query finds more than one result. - *

- *

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

- * - * @param 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() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated @Nullable T findOne(Query query, Transaction transaction); /** - * Similar to findOne() but returns an Optional (rather than nullable). + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated Optional findOneOrEmpty(Query query, Transaction transaction); /** - * Execute as a delete query deleting the 'root level' beans that match the predicates - * in the query. - *

- * Note that if the query includes joins then the generated delete statement may not be - * optimal depending on the database platform. - *

- * - * @param query the query used for the delete - * @param transaction the transaction to use (can be null) - * @param the type of entity bean to fetch. - * @return the number of beans/rows that were deleted + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated int delete(Query query, Transaction transaction); /** - * Execute the update query returning the number of rows updated. - *

- * The update query must be created using {@link Database#update(Class)}. - *

- * - * @param query the update query to execute - * @param transaction the optional transaction to use for the update (can be null) - * @param the type of entity bean - * @return The number of rows updated + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated int update(Query query, Transaction transaction); /** - * Execute the sql query returning a list of MapBean. - *

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

- * - * @param query the query to execute. - * @param transaction the transaction to use (can be null). - * @return the list of fetched MapBean. - * @see SqlQuery#findList() + * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated List findList(SqlQuery query, Transaction transaction); /** - * Execute the SqlQuery iterating a row at a time. - *

- * This streaming type query is useful for large query execution as only 1 row needs to be held in memory. - *

+ * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated void findEach(SqlQuery query, Consumer consumer, Transaction transaction); /** - * Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through. - *

- * Returning false after processing a row stops the iteration through the query results. - *

- *

- * This streaming type query is useful for large query execution as only 1 row needs to be held in memory. - *

+ * Deprecated migrate to using {@link Query#usingTransaction(Transaction)}. */ + @Deprecated void findEachWhile(SqlQuery query, Predicate consumer, Transaction transaction); /** - * Execute the sql query returning a single MapBean or null. - *

- * This will throw a PersistenceException if the query found more than one - * result. - *

- *

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

- * - * @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() + * Deprecated migrate to using {@link SqlQuery#usingTransaction(Transaction)}. */ + @Deprecated @Nullable SqlRow findOne(SqlQuery query, Transaction transaction); diff --git a/ebean-api/src/main/java/io/ebean/SqlQuery.java b/ebean-api/src/main/java/io/ebean/SqlQuery.java index 797e41381..7811382ed 100644 --- a/ebean-api/src/main/java/io/ebean/SqlQuery.java +++ b/ebean-api/src/main/java/io/ebean/SqlQuery.java @@ -2,8 +2,8 @@ package io.ebean; import io.avaje.lang.NonNullApi; import io.avaje.lang.Nullable; + import java.io.Serializable; -import java.math.BigDecimal; import java.util.List; import java.util.Optional; import java.util.function.Consumer; @@ -40,6 +40,11 @@ import java.util.function.Predicate; @NonNullApi public interface SqlQuery extends Serializable, CancelableQuery { + /** + * Execute the query using the given transaction. + */ + SqlQuery usingTransaction(Transaction transaction); + /** * Execute the query returning a list. */ @@ -298,6 +303,11 @@ public interface SqlQuery extends Serializable, CancelableQuery { */ interface TypeQuery { + /** + * Execute the query using the given transaction. + */ + TypeQuery usingTransaction(Transaction transaction); + /** * Return the single value. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index 57ca5e9bc..bcef08e57 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -25,6 +25,11 @@ import java.util.stream.Stream; */ public interface SpiEbeanServer extends SpiServer, ExtendedServer, BeanCollectionLoader { + /** + * Return the NOW time from the Clock. + */ + long clockNow(); + /** * Return true if the L2 cache has been disabled. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlQuery.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlQuery.java index e289b5b65..35e5d99f7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiSqlQuery.java @@ -1,10 +1,17 @@ package io.ebeaninternal.api; +import io.avaje.lang.Nullable; import io.ebean.SqlQuery; +import io.ebean.Transaction; /** * SQL query - Internal extension to SqlQuery. */ public interface SpiSqlQuery extends SqlQuery, SpiSqlBinding { + /** + * Return the transaction explicitly associated to the query. + */ + @Nullable + Transaction transaction(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 222a7af1f..8617c2616 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -1514,7 +1514,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { } private

P executeSqlQuery(Function fun, SpiSqlQuery query) { - RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, null); + RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, query.transaction()); try { request.initTransIfRequired(); return fun.apply(request); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java index ef3874008..1b25a56a6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/RelationalQueryRequest.java @@ -22,9 +22,6 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest { private int estimateCapacity; private int rows; - /** - * Create the BeanFindRequest. - */ RelationalQueryRequest(SpiEbeanServer server, RelationalQueryEngine engine, SqlQuery q, Transaction t) { super(server, (SpiSqlBinding) q, t); this.queryEngine = engine; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java index a68bb5d2a..f059e4b7c 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/querydefn/DefaultRelationalQuery.java @@ -2,10 +2,7 @@ package io.ebeaninternal.server.querydefn; import io.avaje.lang.NonNullApi; import io.avaje.lang.Nullable; -import io.ebean.RowConsumer; -import io.ebean.RowMapper; -import io.ebean.SqlQuery; -import io.ebean.SqlRow; +import io.ebean.*; import io.ebeaninternal.api.BindParams; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiSqlQuery; @@ -31,6 +28,7 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq private int timeout; private int bufferFetchSizeHint; private final BindParams bindParams = new BindParams(); + private Transaction transaction; /** * Additional supply a query detail object. @@ -40,19 +38,34 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq this.query = query; } + @Override + public Transaction transaction() { + return transaction; + } + + @Override + public SqlQuery usingTransaction(Transaction transaction) { + this.transaction = transaction; + return this; + } + + private void transaction(Transaction transaction) { + this.transaction = transaction; + } + @Override public void findEach(Consumer consumer) { - server.findEach(this, consumer, null); + server.findEach(this, consumer, transaction); } @Override public void findEachWhile(Predicate consumer) { - server.findEachWhile(this, consumer, null); + server.findEachWhile(this, consumer, transaction); } @Override public List findList() { - return server.findList(this, null); + return server.findList(this, transaction); } @Override @@ -62,7 +75,7 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq @Override public SqlRow findOne() { - return server.findOne(this, null); + return server.findOne(this, transaction); } @Override @@ -216,6 +229,12 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq this.type = type; } + @Override + public TypeQuery usingTransaction(Transaction transaction) { + transaction(transaction); + return this; + } + @Override public T findOne() { return findSingleAttribute(type); @@ -249,6 +268,12 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq this.mapper = mapper; } + @Override + public TypeQuery usingTransaction(Transaction transaction) { + transaction(transaction); + return this; + } + @Nullable @Override public T findOne() { diff --git a/ebean-test/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java b/ebean-test/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java index 99379e374..2c20016af 100644 --- a/ebean-test/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java +++ b/ebean-test/src/test/java/org/tests/query/sqlquery/SqlQueryTests.java @@ -196,6 +196,23 @@ public class SqlQueryTests extends BaseTestCase { assertThat(minCreated).isBefore(OffsetDateTime.now()); } + @Test + public void typeQuery_usingTransaction() { + ResetBasicData.reset(); + + String sql = "select max(unit_price) from o_order_detail where order_qty > ?"; + try (Transaction transaction = DB.createTransaction()) { + + BigDecimal maxPrice = DB.sqlQuery(sql) + .setParameter(1, 2) + .mapToScalar(BigDecimal.class) + .usingTransaction(transaction) + .findOne(); + + assertThat(maxPrice).isNotNull(); + } + } + static class CustDto { long id; @@ -224,6 +241,44 @@ public class SqlQueryTests extends BaseTestCase { private static final CustMapper CUST_MAPPER = new CustMapper(); + @Test + void queryUsingTransaction() { + ResetBasicData.reset(); + + try (Transaction txn = DB.createTransaction()) { + String sql = "select id, name, status from o_customer where name is not null"; + AtomicInteger counter = new AtomicInteger(); + DB.sqlQuery(sql) + .usingTransaction(txn) + .mapTo(CUST_MAPPER) + .findEach(custDto -> { + counter.incrementAndGet(); + assertThat(custDto.name).isNotNull(); + }); + + assertThat(counter.get()).isGreaterThan(0); + } + } + + @Test + void mapperUsingTransaction() { + ResetBasicData.reset(); + + try (Transaction txn = DB.createTransaction()) { + String sql = "select id, name, status from o_customer where name is not null"; + AtomicInteger counter = new AtomicInteger(); + DB.sqlQuery(sql) + .mapTo(CUST_MAPPER) + .usingTransaction(txn) + .findEach(custDto -> { + counter.incrementAndGet(); + assertThat(custDto.name).isNotNull(); + }); + + assertThat(counter.get()).isGreaterThan(0); + } + } + @Test public void findEach_mapper() {