#1407 - ENH: Add SqlQuery RowMapper and RowConsumer ... for raw JDBC ResultSet mapping and consuming

This commit is contained in:
rob bygrave
2018-06-08 02:04:38 +12:00
parent fb32cfa170
commit 7cbae758fc
11 changed files with 421 additions and 13 deletions
+40
View File
@@ -0,0 +1,40 @@
package io.ebean;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Used with SqlQuery to process potentially large queries reading directly from the JDBC ResultSet.
* <p>
* This provides a low level option that reads directly from the JDBC ResultSet.
* </p>
*
* <pre>{@code
*
* String sql = "select id, name, status from o_customer order by name desc";
*
* Ebean.createSqlQuery(sql)
* .findEachRow((resultSet, rowNum) -> {
*
* // read directly from ResultSet
*
* long id = resultSet.getLong(1);
* String name = resultSet.getString(2);
*
* // do something interesting with the data
*
* });
*
* }</pre>
*/
@FunctionalInterface
public interface RowConsumer {
/**
* Read the data from the ResultSet and process it.
*
* @param resultSet The JDBC ResultSet positioned to the current row
* @param rowNum The number of the current row being mapped.
*/
void accept(ResultSet resultSet, int rowNum) throws SQLException;
}
+62
View File
@@ -0,0 +1,62 @@
package io.ebean;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Used with SqlQuery to map raw JDBC ResultSet to objects.
* <p>
* This provides a low level mapping option with direct use of JDBC ResultSet
* with the option of having logic in the mapping. For example, only map some
* columns depending on the values read from other columns.
* </p>
* <p>
* For straight mapping into beans then DtoQuery would be the first choice as
* it can automatically map the ResultSet into beans.
* </p>
*
* <pre>{@code
*
* //
* // A mapper from ResultSet into our CustomerDto bean
* //
* class CustomerMapper implements RowMapper<CustomerDto> {
*
* @Override
* public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
*
* long id = rset.getLong(1);
* String name = rset.getString(2);
* String status = rset.getString(3);
*
* return new CustomerDto(id, name, status);
* }
* }
*
*
* //
* // Then use the mapper
* //
*
* String sql = "select id, name, status from o_customer where name = ?";
*
* CustomerDto rob = Ebean.createSqlQuery(sql)
* .setParameter(1, "Rob")
* .findOne(CUSTOMER_MAPPER);
*
*
* }</pre>
*
* @param <T> The type the row data is mapped into.
*/
@FunctionalInterface
public interface RowMapper<T> {
/**
* Read the data from the ResultSet and map to the return type.
*
* @param resultSet The JDBC ResultSet positioned to the current row
* @param rowNum The number of the current row being mapped.
*/
T map(ResultSet resultSet, int rowNum) throws SQLException;
}
+44
View File
@@ -77,6 +77,50 @@ public interface SqlQuery extends Serializable {
@Nullable
SqlRow findOne();
/**
* Execute the query returning a single result using the mapper.
*
* @param mapper Used to map each ResultSet row into the result object.
*/
<T> T findOne(RowMapper<T> mapper);
/**
* Execute the query returning a list using the mapper.
*
* @param mapper Used to map each ResultSet row into the result object.
*/
<T> List<T> findList(RowMapper<T> mapper);
/**
* Execute the query reading each row from ResultSet using the RowConsumer.
* <p>
* This provides a low level option that reads directly from the JDBC ResultSet
* and is good for processing very large results where (unlike findList) we don't
* hold all the results in memory but instead can process row by row.
* </p>
*
* <pre>{@code
*
* String sql = "select id, name, status from customer order by name desc";
*
* Ebean.createSqlQuery(sql)
* .findEachRow((resultSet, rowNum) -> {
*
* // read directly from ResultSet
*
* long id = resultSet.getLong(1);
* String name = resultSet.getString(2);
*
* // do something interesting with the data
*
* });
*
* }</pre>
*
* @param consumer Used to read and process each ResultSet row.
*/
void findEachRow(RowConsumer consumer);
/**
* Execute the query returning an optional row.
*/
@@ -4,6 +4,8 @@ import io.ebean.DtoQuery;
import io.ebean.EbeanServer;
import io.ebean.PersistenceContextScope;
import io.ebean.Query;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.Transaction;
import io.ebean.TxScope;
import io.ebean.bean.BeanCollectionLoader;
@@ -244,6 +246,21 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
<T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls);
/**
* SqlQuery find one with mapper.
*/
<T> T findOneMapper(SpiSqlQuery query, RowMapper<T> mapper);
/**
* SqlQuery find list with mapper.
*/
<T> List<T> findListMapper(SpiSqlQuery query, RowMapper<T> mapper);
/**
* SqlQuery find each with consumer.
*/
void findEachRow(SpiSqlQuery query, RowConsumer consumer);
/**
* DTO findList query.
*/
@@ -19,6 +19,8 @@ import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.SqlUpdate;
@@ -1586,29 +1588,39 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public <T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls) {
private <P> P executeSqlQuery(Function<RelationalQueryRequest, P> fun, SpiSqlQuery query) {
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, null);
try {
request.initTransIfRequired();
return request.findSingleAttributeList(cls);
return fun.apply(request);
} finally {
request.endTransIfRequired();
}
}
@Override
public void findEachRow(SpiSqlQuery query, RowConsumer consumer) {
executeSqlQuery((req) -> req.findEachRow(consumer), query);
}
@Override
public <T> List<T> findListMapper(SpiSqlQuery query, RowMapper<T> mapper) {
return executeSqlQuery((req) -> req.findListMapper(mapper), query);
}
@Override
public <T> T findOneMapper(SpiSqlQuery query, RowMapper<T> mapper) {
return executeSqlQuery((req) -> req.findOneMapper(mapper), query);
}
@Override
public <T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls) {
return executeSqlQuery((req) -> req.findSingleAttributeList(cls), query);
}
@Override
public <T> T findSingleAttribute(SpiSqlQuery query, Class<T> cls) {
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, null);
try {
request.initTransIfRequired();
return request.findSingleAttribute(cls);
} finally {
request.endTransIfRequired();
}
return executeSqlQuery((req) -> req.findSingleAttribute(cls), query);
}
@Override
@@ -1,6 +1,8 @@
package io.ebeaninternal.server.core;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlRow;
import io.ebean.meta.MetricVisitor;
@@ -40,6 +42,21 @@ public interface RelationalQueryEngine {
*/
<T> List<T> findSingleAttributeList(RelationalQueryRequest request, Class<T> cls);
/**
* Find one via mapper.
*/
<T> T findOneMapper(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find list via mapper.
*/
<T> List<T> findListMapper(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find each via raw consumer.
*/
void findEachRow(RelationalQueryRequest request, RowConsumer mapper);
/**
* Collect SQL query execution statistics.
*/
@@ -1,5 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.Transaction;
@@ -53,6 +55,19 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
}
}
boolean findEachRow(RowConsumer mapper) {
queryEngine.findEachRow(this, mapper);
return true;
}
<T> List<T> findListMapper(RowMapper<T> mapper) {
return queryEngine.findListMapper(this, mapper);
}
<T> T findOneMapper(RowMapper<T> mapper) {
return queryEngine.findOneMapper(this, mapper);
}
public <T> List<T> findSingleAttributeList(Class<T> cls) {
return queryEngine.findSingleAttributeList(this, cls);
}
@@ -119,4 +134,28 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
public void incrementRows() {
rows++;
}
public <T> List<T> mapList(RowMapper<T> mapper) throws SQLException {
List<T> list = new ArrayList<>();
while (next()) {
list.add(mapper.map(resultSet, rows++));
}
return list;
}
public <T> T mapOne(RowMapper<T> mapper) throws SQLException {
if (!next()) {
return null;
} else {
return mapper.map(resultSet, rows++);
}
}
public void mapEach(RowConsumer consumer) throws SQLException {
while (next()) {
consumer.accept(resultSet, rows++);
}
}
}
@@ -1,5 +1,7 @@
package io.ebeaninternal.server.query;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlRow;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
@@ -92,6 +94,53 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
}
@Override
public <T> T findOneMapper(RelationalQueryRequest request, RowMapper<T> mapper) {
try {
request.executeSql(binder, SpiQuery.Type.BEAN);
T value = request.mapOne(mapper);
request.logSummary();
return value;
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@Override
public <T> List<T> findListMapper(RelationalQueryRequest request, RowMapper<T> mapper) {
try {
request.executeSql(binder, SpiQuery.Type.LIST);
List<T> list = request.mapList(mapper);
request.logSummary();
return list;
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@Override
public void findEachRow(RelationalQueryRequest request, RowConsumer consumer) {
try {
request.executeSql(binder, SpiQuery.Type.LIST);
request.mapEach(consumer);
request.logSummary();
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@SuppressWarnings("unchecked")
@Override
public <T> List<T> findSingleAttributeList(RelationalQueryRequest request, Class<T> cls) {
@@ -1,5 +1,7 @@
package io.ebeaninternal.server.querydefn;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlRow;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
@@ -69,6 +71,21 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
return server.findSingleAttributeList(this, cls);
}
@Override
public <T> T findOne(RowMapper<T> mapper) {
return server.findOneMapper(this, mapper);
}
@Override
public <T> List<T> findList(RowMapper<T> mapper) {
return server.findListMapper(this, mapper);
}
@Override
public void findEachRow(RowConsumer consumer) {
server.findEachRow(this, consumer);
}
@Override
public SqlRow findOne() {
return server.findOne(this, null);