mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-09-20 11:17:36 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0683a5637b | ||
|
|
49c2a1f79e |
@@ -4,8 +4,6 @@ import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Query for performing native SQL queries that return DTO Bean's.
|
||||
@@ -49,36 +47,6 @@ public interface DtoQuery<T> extends StreamableQuery<DtoQuery<T>, T> {
|
||||
*/
|
||||
QueryIterator<T> findIterate();
|
||||
|
||||
/**
|
||||
* Execute the query 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(Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query iterating the results and batching them for the consumer.
|
||||
* <p>
|
||||
* 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<List<T>> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query 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(Predicate<T> consumer);
|
||||
|
||||
/**
|
||||
* Bind all the parameters using index positions.
|
||||
* <p>
|
||||
|
||||
@@ -3,6 +3,8 @@ package io.ebean;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@@ -62,4 +64,48 @@ public interface MappedQuery<D> extends StreamableQuery<MappedQuery<D>, D> {
|
||||
@Override
|
||||
Stream<D> findStream();
|
||||
|
||||
/**
|
||||
* Execute the query processing the mapped DTOs one at a time.
|
||||
* <p>
|
||||
* Mirrors {@link QueryBuilder#findEach(Consumer)} - the underlying entity graph query is
|
||||
* streamed one entity at a time and each entity is mapped to its target DTO lazily as it is
|
||||
* consumed, sharing one {@link DtoMapContext} across the whole callback so that repeated
|
||||
* references to the same source entity still de-duplicate to the same DTO instance.
|
||||
* <p>
|
||||
* This method is appropriate to process very large query results as the mapped DTOs are
|
||||
* consumed one at a time and do not need to be held in memory (unlike {@link #findList()}).
|
||||
*
|
||||
* @param consumer the consumer used to process the mapped DTOs.
|
||||
*/
|
||||
@Override
|
||||
void findEach(Consumer<D> consumer);
|
||||
/**
|
||||
* Execute findEach streaming query batching the mapped DTOs for consuming.
|
||||
* <p>
|
||||
* Mirrors {@link QueryBuilder#findEach(int, Consumer)} - typically used when we want to do
|
||||
* further processing on the mapped DTOs in batch form, for example 100 at a time. Each batch
|
||||
* shares one {@link DtoMapContext} with the rest of the query so that repeated references to
|
||||
* the same source entity still de-duplicate to the same DTO instance.
|
||||
*
|
||||
* @param batch The number of mapped DTOs processed in the batch
|
||||
* @param consumer Process the batch of mapped DTOs
|
||||
*/
|
||||
@Override
|
||||
void findEach(int batch, Consumer<List<D>> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query using callbacks to process the resulting mapped DTOs one at a time,
|
||||
* with the ability to stop processing part way through.
|
||||
* <p>
|
||||
* Mirrors {@link QueryBuilder#findEachWhile(Predicate)} - returning {@code false} after
|
||||
* processing a DTO stops the iteration through the query results. Sharing one
|
||||
* {@link DtoMapContext} across the whole callback so that repeated references to the same
|
||||
* source entity still de-duplicate to the same DTO instance.
|
||||
*
|
||||
* @param consumer the consumer used to process the mapped DTOs, returning {@code false} to
|
||||
* stop processing.
|
||||
*/
|
||||
@Override
|
||||
void findEachWhile(Predicate<D> consumer);
|
||||
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Build and execute an ORM query.
|
||||
@@ -807,84 +806,6 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
|
||||
*/
|
||||
<A> Set<A> findSingleAttributeSet();
|
||||
|
||||
/**
|
||||
* Execute the query processing the beans one at a time.
|
||||
* <p>
|
||||
* This method is appropriate to process very large query results as the
|
||||
* beans are consumed one at a time and do not need to be held in memory
|
||||
* (unlike #findList #findSet etc)
|
||||
* <p>
|
||||
* Note that internally Ebean can inform the JDBC driver that it is expecting larger
|
||||
* resultSet and specifically for MySQL this hint is required to stop it's JDBC driver
|
||||
* from buffering the entire resultSet. As such, for smaller resultSets findList() is
|
||||
* generally preferable.
|
||||
* <p>
|
||||
* Compared with #findEachWhile this will always process all the beans where as
|
||||
* #findEachWhile provides a way to stop processing the query result early before
|
||||
* all the beans have been read.
|
||||
* <p>
|
||||
* This method is functionally equivalent to findIterate() but instead of using an
|
||||
* iterator uses the Consumer interface which is better suited to use with closures.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* new QCustomer()
|
||||
* .status.equalTo(Status.NEW)
|
||||
* .orderBy().id.asc()
|
||||
* .findEach((Customer customer) -> {
|
||||
*
|
||||
* // do something with customer
|
||||
* System.out.println("-- visit " + customer);
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param consumer the consumer used to process the queried beans.
|
||||
*/
|
||||
void findEach(Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute findEach streaming query batching the results for consuming.
|
||||
* <p>
|
||||
* This query execution will stream the results and is suited to consuming
|
||||
* large numbers of results from the database.
|
||||
* <p>
|
||||
* 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<List<T>> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query using callbacks to a visitor to process the resulting
|
||||
* beans one at a time.
|
||||
* <p>
|
||||
* This method is functionally equivalent to findIterate() but instead of using an
|
||||
* iterator uses the Predicate interface which is better suited to use with closures.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* new QCustomer()
|
||||
* .status.equalTo(Status.NEW)
|
||||
* .orderBy().id.asc()
|
||||
* .findEachWhile((Customer customer) -> {
|
||||
*
|
||||
* // do something with customer
|
||||
* System.out.println("-- visit " + customer);
|
||||
*
|
||||
* // return true to continue processing or false to stop
|
||||
* return (customer.getId() < 40);
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param consumer the consumer used to process the queried beans.
|
||||
*/
|
||||
void findEachWhile(Predicate<T> consumer);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
|
||||
@@ -2,6 +2,9 @@ package io.ebean;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@@ -49,4 +52,50 @@ public interface StreamableQuery<SELF extends StreamableQuery<SELF, T>, T> exten
|
||||
*/
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
* Execute the query processing the results one at a time.
|
||||
* <p>
|
||||
* This method is appropriate to process very large query results as the results are
|
||||
* consumed one at a time and do not need to be held in memory (unlike {@link #findList()}).
|
||||
* <p>
|
||||
* Note that internally Ebean can inform the JDBC driver that it is expecting a larger
|
||||
* resultSet and specifically for MySQL this hint is required to stop its JDBC driver
|
||||
* from buffering the entire resultSet. As such, for smaller resultSets findList() is
|
||||
* generally preferable.
|
||||
* <p>
|
||||
* Compared with {@link #findEachWhile(Predicate)} this will always process all the results
|
||||
* whereas findEachWhile() provides a way to stop processing the query result early before
|
||||
* all the results have been read.
|
||||
*
|
||||
* @param consumer the consumer used to process the queried results.
|
||||
*/
|
||||
void findEach(Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute findEach streaming query batching the results for consuming.
|
||||
* <p>
|
||||
* This query execution will stream the results and is suited to consuming
|
||||
* large numbers of results from the database.
|
||||
* <p>
|
||||
* Typically, we use this batch consumer when we want to do further processing on
|
||||
* the results and want to do that processing in batch form, for example - 100 at
|
||||
* a time.
|
||||
*
|
||||
* @param batch The number of results processed in the batch
|
||||
* @param consumer Process the batch of results
|
||||
*/
|
||||
void findEach(int batch, Consumer<List<T>> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query using callbacks to process the resulting results one at a time,
|
||||
* with the ability to stop processing part way through.
|
||||
* <p>
|
||||
* Returning {@code false} after processing a result stops the iteration through the
|
||||
* query results.
|
||||
*
|
||||
* @param consumer the consumer used to process the queried results, returning
|
||||
* {@code false} to stop processing.
|
||||
*/
|
||||
void findEachWhile(Predicate<T> consumer);
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import io.ebeaninternal.api.SpiQuery;
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@@ -108,6 +110,27 @@ public final class DefaultMappedQuery<T, D> implements MappedQuery<D> {
|
||||
return query.findStream().map(source -> m.map(source, context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findEach(Consumer<D> consumer) {
|
||||
DtoMapper<T, D> m = mapper();
|
||||
DtoMapContext context = new DtoMapContext();
|
||||
query.findEach(source -> consumer.accept(m.map(source, context)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findEach(int batch, Consumer<List<D>> consumer) {
|
||||
DtoMapper<T, D> m = mapper();
|
||||
DtoMapContext context = new DtoMapContext();
|
||||
query.findEach(batch, sourceBatch -> consumer.accept(m.mapList(sourceBatch, context)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findEachWhile(Predicate<D> consumer) {
|
||||
DtoMapper<T, D> m = mapper();
|
||||
DtoMapContext context = new DtoMapContext();
|
||||
query.findEachWhile(source -> consumer.test(m.map(source, context)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappedQuery<D> usingMaster(boolean useMaster) {
|
||||
query.usingMaster(useMaster);
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.tests.dtomapping.model.Contact;
|
||||
import org.tests.dtomapping.model.Customer;
|
||||
import org.tests.dtomapping.model.query.QCustomer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
@@ -265,6 +266,81 @@ class TestQueryMapTo {
|
||||
assertThat(dtos.get(0).getCustomer()).isSameAs(dtos.get(1).getCustomer());
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapTo_findEach_expectAllDtosProcessedInOrder() {
|
||||
Customer customerA = new Customer("EachCoA");
|
||||
customerA.save();
|
||||
Customer customerB = new Customer("EachCoB");
|
||||
customerB.save();
|
||||
|
||||
List<String> names = new ArrayList<>();
|
||||
DB.find(Customer.class)
|
||||
.where().in("name", "EachCoA", "EachCoB")
|
||||
.orderBy().asc("name")
|
||||
.mapTo(CustomerDto.class)
|
||||
.findEach(dto -> names.add(dto.getName()));
|
||||
|
||||
assertThat(names).containsExactly("EachCoA", "EachCoB");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapTo_findEach_expectIdentityDedupSharedAcrossCallback() {
|
||||
Customer customer = new Customer("EachDedupCo");
|
||||
customer.save();
|
||||
new Contact("Jane", "Doe", customer).save();
|
||||
new Contact("John", "Doe", customer).save();
|
||||
|
||||
// both contacts share the same underlying Customer instance - the shared DtoMapContext used
|
||||
// across the whole findEach() call should still de-duplicate to the same nested DTO
|
||||
List<ContactDto> dtos = new ArrayList<>();
|
||||
DB.find(Contact.class)
|
||||
.where().eq("customer", customer)
|
||||
.orderBy().asc("firstName")
|
||||
.mapTo(ContactDto.class)
|
||||
.findEach(dtos::add);
|
||||
|
||||
assertThat(dtos).hasSize(2);
|
||||
assertThat(dtos.get(0).getCustomer()).isSameAs(dtos.get(1).getCustomer());
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapTo_findEachBatch_expectBatchedMappedDtos() {
|
||||
new Customer("BatchCoA").save();
|
||||
new Customer("BatchCoB").save();
|
||||
new Customer("BatchCoC").save();
|
||||
|
||||
List<List<String>> batches = new ArrayList<>();
|
||||
DB.find(Customer.class)
|
||||
.where().startsWith("name", "BatchCo")
|
||||
.orderBy().asc("name")
|
||||
.mapTo(CustomerDto.class)
|
||||
.findEach(2, batch -> batches.add(
|
||||
batch.stream().map(CustomerDto::getName).collect(Collectors.toList())));
|
||||
|
||||
assertThat(batches).hasSize(2);
|
||||
assertThat(batches.get(0)).containsExactly("BatchCoA", "BatchCoB");
|
||||
assertThat(batches.get(1)).containsExactly("BatchCoC");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapTo_findEachWhile_expectStopsWhenPredicateReturnsFalse() {
|
||||
new Customer("WhileCoA").save();
|
||||
new Customer("WhileCoB").save();
|
||||
new Customer("WhileCoC").save();
|
||||
|
||||
List<String> names = new ArrayList<>();
|
||||
DB.find(Customer.class)
|
||||
.where().startsWith("name", "WhileCo")
|
||||
.orderBy().asc("name")
|
||||
.mapTo(CustomerDto.class)
|
||||
.findEachWhile(dto -> {
|
||||
names.add(dto.getName());
|
||||
return !dto.getName().equals("WhileCoB");
|
||||
});
|
||||
|
||||
assertThat(names).containsExactly("WhileCoA", "WhileCoB");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapTo_cancel_atBegin_expectPersistenceException() {
|
||||
new Customer("CancelCo").save();
|
||||
|
||||
Reference in New Issue
Block a user