#1297 - ENH: Add DtoQuery ... such that we can map native SQL queries automatically into "DTO beans"

This commit is contained in:
Rob Bygrave
2018-03-02 23:32:01 +13:00
parent 5801f7620d
commit aad5b33db2
47 changed files with 2293 additions and 273 deletions
+133
View File
@@ -0,0 +1,133 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Query for performing native SQL queries that return DTO Bean's.
* <p>
* These beans are just normal classes. They must have public constructors
* and setters.
* <p>
* Constructors with arguments are used if the number of constructor arguments
* matches the number of columns in the resultSet.
* </p>
* <p>
* If the number of columns in the resultSet is greater than the largest constructor
* then the largest constructor is used for the first columns and remaining columns
* are mapped by setter methods.
* </p>
*
* <pre>{@code
*
* // CustomerDto is just a 'bean like' class
* // with public constructor(s) and public setter methods
*
* String sql = "select id, name from customer where name like :name and status_code = :status";
*
* List<CustomerDto> beans =
* Ebean.findDto(CustomrDto.class, sql)
* .setParameter("name", "Acme%")
* .setParameter("status", "ACTIVE")
* .findList();
*
* }</pre>
*/
public interface DtoQuery<T> {
/**
* Execute the query returning a list.
*/
@Nonnull
List<T> findList();
/**
* 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 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);
/**
* Execute the query returning a single bean.
*/
@Nullable
T findOne();
/**
* Execute the query returning an optional bean.
*/
@Nonnull
Optional<T> findOneOrEmpty();
/**
* The same as bind for named parameters.
*/
DtoQuery<T> setParameter(String name, Object value);
/**
* The same as bind for positioned parameters.
*/
DtoQuery<T> setParameter(int position, Object value);
/**
* Set the index of the first row of the results to return.
*/
DtoQuery<T> setFirstRow(int firstRow);
/**
* Set the maximum number of query results to return.
*/
DtoQuery<T> setMaxRows(int maxRows);
/**
* When resultSet columns are not able to be mapped to a bean property then instead of
* throwing effectively skip reading that column.
*/
DtoQuery<T> setRelaxedMode();
/**
* Set a label on the query to make it easier to identify queries related to query execution statistics.
*
* @param label A label that is unique to the DTO bean type.
*/
DtoQuery<T> setLabel(String label);
/**
* Set a timeout on this query.
* <p>
* This will typically result in a call to setQueryTimeout() on a
* preparedStatement. If the timeout occurs an exception will be thrown - this
* will be a SQLException wrapped up in a PersistenceException.
* </p>
*
* @param secs the query timeout limit in seconds. Zero means there is no limit.
*/
DtoQuery<T> setTimeout(int secs);
/**
* A hint which for JDBC translates to the Statement.fetchSize().
* <p>
* Gives the JDBC driver a hint as to the number of rows that should be
* fetched from the database when more rows are needed for ResultSet.
* </p>
*/
DtoQuery<T> setBufferFetchSizeHint(int bufferFetchSizeHint);
}
+15
View File
@@ -1116,6 +1116,21 @@ public final class Ebean {
return serverMgr.getDefaultServer().findNative(beanType, nativeSql);
}
/**
* Create a Query for DTO beans.
* <p>
* DTO beans are just normal bean like classes with public constructor(s) and setters.
* They do not need to be registered with Ebean before use.
* </p>
*
* @param dtoType The type of the DTO bean the rows will be mapped into.
* @param sql The SQL query to execute.
* @param <T> The type of the DTO bean.
*/
public static <T> DtoQuery<T> findDto(Class<T> dtoType, String sql) {
return serverMgr.getDefaultServer().findDto(dtoType, sql);
}
/**
* Create an Update query to perform a bulk update.
* <p>
+13
View File
@@ -424,6 +424,19 @@ public interface EbeanServer {
*/
<T> Update<T> createUpdate(Class<T> beanType, String ormUpdate);
/**
* Create a Query for DTO beans.
* <p>
* DTO beans are just normal bean like classes with public constructor(s) and setters.
* They do not need to be registered with Ebean before use.
* </p>
*
* @param dtoType The type of the DTO bean the rows will be mapped into.
* @param sql The SQL query to execute.
* @param <T> The type of the DTO bean.
*/
<T> DtoQuery<T> findDto(Class<T> dtoType, String sql);
/**
* Create a SqlQuery for executing native sql
* query statements.
@@ -12,6 +12,11 @@ public interface MetaInfoManager {
*/
List<MetaTimedMetric> collectTransactionStatistics(boolean reset);
/**
* Collect query plan statistics (new, will migrate ORM query stats over to this).
*/
List<MetaQueryMetric> collectQueryStatistics(boolean reset);
/**
* Collect and return the non-empty query plan statistics for all the beans.
* <p>
@@ -0,0 +1,23 @@
package io.ebean.meta;
/**
* Query execution metrics.
*/
public interface MetaQueryMetric extends MetaTimedMetric {
/**
* The type of entity or DTO bean.
*/
Class<?> getType();
/**
* The label for the query (can be null).
*/
String getLabel();
/**
* The actual SQL of the query.
*/
String getSql();
}
@@ -27,17 +27,24 @@ public interface MetaTimedMetric {
long getCount();
/**
* Return the total execution time.
* Return the total execution time in micros.
*/
long getTotal();
/**
* Return the max execution time.
* Return the max execution time in micros.
*/
long getMax();
/**
* Return the mean execution time.
* Return the mean execution time in micros.
*/
long getMean();
/**
* Return the total beans or rows processed or loaded.
*
* This will be 0 if the metric isn't a query plan (like transaction execution statistics).
*/
long getBeanCount();
}
@@ -0,0 +1,46 @@
package io.ebeaninternal.api;
import io.ebean.DtoQuery;
import io.ebeaninternal.server.dto.DtoMappingRequest;
import io.ebeaninternal.server.dto.DtoQueryPlan;
/**
* Internal extension to DtoQuery.
*/
public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
/**
* Return the key for query plan.
*/
String planKey();
/**
* Get the query plan for the cache.
*/
DtoQueryPlan getQueryPlan(String planKey);
/**
* Build the query plan.
*/
DtoQueryPlan buildPlan(DtoMappingRequest request);
/**
* Put the query plan into the cache.
*/
void putQueryPlan(String planKey, DtoQueryPlan plan);
/**
* Return true if the query is in relaxed mapping mode.
*/
boolean isRelaxedMode();
/**
* Return the label for the query.
*/
String getLabel();
/**
* Return the associated DTO bean type.
*/
Class<T> getType();
}
@@ -20,6 +20,8 @@ import io.ebeaninternal.server.query.CQuery;
import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Service Provider extension to EbeanServer.
@@ -224,4 +226,24 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
void scopedTransactionExit(Object returnOrThrowable, int opCode);
/**
* DTO findList query.
*/
<T> List<T> findDtoList(SpiDtoQuery<T> query);
/**
* DTO findOne query.
*/
<T> T findDtoOne(SpiDtoQuery<T> query);
/**
* DTO findEach query.
*/
<T> void findDtoEach(SpiDtoQuery<T> query, Consumer<T> consumer);
/**
* DTO findEachWhile query.
*/
<T> void findDtoEachWhile(SpiDtoQuery<T> query, Predicate<T> consumer);
}
@@ -0,0 +1,38 @@
package io.ebeaninternal.api;
/**
* SQL query binding (for SqlQuery and DtoQuery).
*/
public interface SpiSqlBinding {
/**
* Return the named or positioned parameters.
*/
BindParams getBindParams();
/**
* return the query.
*/
String getQuery();
/**
* Return the first row to fetch.
*/
int getFirstRow();
/**
* Return the maximum number of rows to fetch.
*/
int getMaxRows();
/**
* Return the query timeout.
*/
int getTimeout();
/**
* Return the hint for Statement.setFetchSize().
*/
int getBufferFetchSizeHint();
}
@@ -5,36 +5,6 @@ import io.ebean.SqlQuery;
/**
* SQL query - Internal extension to SqlQuery.
*/
public interface SpiSqlQuery extends SqlQuery {
/**
* Return the named or positioned parameters.
*/
BindParams getBindParams();
/**
* return the query.
*/
String getQuery();
/**
* Return the first row to fetch.
*/
int getFirstRow();
/**
* Return the maximum number of rows to fetch.
*/
int getMaxRows();
/**
* Return the query timeout.
*/
int getTimeout();
/**
* Return the hint for Statement.setFetchSize().
*/
int getBufferFetchSizeHint();
public interface SpiSqlQuery extends SqlQuery, SpiSqlBinding {
}
@@ -22,4 +22,13 @@ public interface MetricFactory {
*/
TimedMetric createTimedMetric(String name);
/**
* Create a Timed metric.
*/
QueryPlanMetric createQueryPlanMetric(Class<?> type, String label, String sql);
/**
* Return a instance used to collect Query plan metrics.
*/
QueryPlanCollector createCollector(boolean reset);
}
@@ -0,0 +1,26 @@
package io.ebeaninternal.metric;
import io.ebean.meta.MetaQueryMetric;
import java.util.List;
/**
* Object used to collect query plan metrics.
*/
public interface QueryPlanCollector {
/**
* Return true if the statistics should be reset.
*/
boolean isReset();
/**
* Add the query plan statistic.
*/
void add(MetaQueryMetric stats);
/**
* Return all the collected query plan statistics.
*/
List<MetaQueryMetric> complete();
}
@@ -0,0 +1,17 @@
package io.ebeaninternal.metric;
/**
* Internal Query plan metric holder.
*/
public interface QueryPlanMetric {
/**
* Return the underlying timed metric.
*/
TimedMetric getMetric();
/**
* Collect the non-empty query plan metrics.
*/
void collect(QueryPlanCollector collector);
}
@@ -12,7 +12,12 @@ public interface TimedMetric {
/**
* Add a time event (usually in microseconds).
*/
void add(long value);
void add(long micros);
/**
* Add a time event with the number of loaded beans or rows.
*/
void add(long micros, long beans);
/**
* Return true if there are no metrics collected since the last collection.
@@ -0,0 +1,182 @@
package io.ebeaninternal.server.core;
import io.ebean.EbeanServer;
import io.ebean.Transaction;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlBinding;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.TrimLogSql;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.util.BindParamsParser;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Wraps the objects involved in executing a SQL / Relational Query.
*/
public abstract class AbstractSqlQueryRequest {
private final SpiSqlBinding query;
protected final SpiEbeanServer ebeanServer;
protected SpiTransaction trans;
private boolean createdTransaction;
protected String sql;
protected ResultSet resultSet;
protected String bindLog = "";
protected PreparedStatement pstmt;
protected long startNano;
/**
* Create the BeanFindRequest.
*/
AbstractSqlQueryRequest(SpiEbeanServer server, SpiSqlBinding query, Transaction t) {
this.ebeanServer = server;
this.query = query;
this.trans = (SpiTransaction) t;
}
/**
* Create a transaction if none currently exists.
*/
public void initTransIfRequired() {
if (trans == null) {
trans = ebeanServer.currentServerTransaction();
if (trans == null || !trans.isActive()) {
// create a local readOnly transaction
trans = ebeanServer.createQueryTransaction(null);
createdTransaction = true;
}
}
}
/**
* End the transaction if it was locally created.
*/
public void endTransIfRequired() {
if (createdTransaction) {
trans.commit();
}
}
public EbeanServer getEbeanServer() {
return ebeanServer;
}
public SpiTransaction getTransaction() {
return trans;
}
public boolean isLogSql() {
return trans.isLogSql();
}
abstract void setResultSet(ResultSet resultSet) throws SQLException;
/**
* Return the bindLog for this request.
*/
public String getBindLog() {
return bindLog;
}
/**
* Return true if we can navigate to the next row.
*/
public boolean next() throws SQLException {
return resultSet.next();
}
protected abstract void requestComplete();
/**
* Close the underlying resources.
*/
public void close() {
requestComplete();
JdbcClose.close(resultSet);
JdbcClose.close(pstmt);
}
/**
* Prepare the SQL taking into account named bind parameters.
*/
private void prepareSql() {
String sql = query.getQuery();
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
// convert any named parameters if required
sql = BindParamsParser.parse(bindParams, sql);
}
this.sql = limitOffset(sql);
}
private String limitOffset(String sql) {
int firstRow = query.getFirstRow();
int maxRows = query.getMaxRows();
if (firstRow > 0 || maxRows > 0) {
return ebeanServer.getDatabasePlatform().getBasicSqlLimiter().limit(sql, firstRow, maxRows);
}
return sql;
}
/**
* Prepare and execute the SQL using the Binder.
*/
public void executeSql(Binder binder) throws SQLException {
startNano = System.nanoTime();
prepareSql();
Connection conn = trans.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
this.bindLog = binder.bind(bindParams, pstmt, conn);
}
if (isLogSql()) {
String logSql = TrimLogSql.trim(sql);
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
trans.logSql(logSql);
}
setResultSet(pstmt.executeQuery());
}
/**
* Return the SQL executed for this query.
*/
public String getSql() {
return sql;
}
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.core;
import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaObjectGraphNodeStats;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaQueryPlanStatistic;
import io.ebean.meta.MetaTimedMetric;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -26,6 +27,11 @@ public class DefaultMetaInfoManager implements MetaInfoManager {
return server.collectTransactionStatistics(reset);
}
@Override
public List<MetaQueryMetric> collectQueryStatistics(boolean reset) {
return server.collectQueryStatistics(reset);
}
@Override
public List<MetaQueryPlanStatistic> collectQueryPlanStatistics(boolean reset) {
@@ -5,6 +5,7 @@ import io.ebean.BackgroundExecutor;
import io.ebean.BeanState;
import io.ebean.CallableSql;
import io.ebean.DocumentStore;
import io.ebean.DtoQuery;
import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
import io.ebean.Filter;
@@ -47,6 +48,7 @@ import io.ebean.event.BeanPersistController;
import io.ebean.event.readaudit.ReadAuditLogger;
import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.Plugin;
@@ -58,6 +60,7 @@ import io.ebeaninternal.api.LoadBeanRequest;
import io.ebeaninternal.api.LoadManyRequest;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiQuery;
@@ -74,6 +77,8 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.dto.DtoBeanDescriptor;
import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.el.ElFilter;
import io.ebeaninternal.server.grammer.EqlParser;
import io.ebeaninternal.server.lib.ShutdownManager;
@@ -86,6 +91,8 @@ import io.ebeaninternal.server.query.LimitOffsetPagedList;
import io.ebeaninternal.server.query.QueryFutureIds;
import io.ebeaninternal.server.query.QueryFutureList;
import io.ebeaninternal.server.query.QueryFutureRowCount;
import io.ebeaninternal.server.query.dto.DtoQueryEngine;
import io.ebeaninternal.server.querydefn.DefaultDtoQuery;
import io.ebeaninternal.server.querydefn.DefaultOrmQuery;
import io.ebeaninternal.server.querydefn.DefaultOrmUpdate;
import io.ebeaninternal.server.querydefn.DefaultRelationalQuery;
@@ -147,9 +154,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final OrmQueryEngine queryEngine;
private final RelationalQueryEngine relationalQueryEngine;
private final DtoQueryEngine dtoQueryEngine;
private final ServerCacheManager serverCacheManager;
private final DtoBeanManager dtoBeanManager;
private final BeanDescriptorManager beanDescriptorManager;
private final AutoTuneService autoTuneService;
@@ -220,6 +229,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
this.dtoBeanManager = config.getDtoBeanManager();
this.serverConfig = config.getServerConfig();
this.objectGraphStats = new ConcurrentHashMap<>();
this.metaInfoManager = new DefaultMetaInfoManager(this);
@@ -249,6 +259,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.persister = config.createPersister(this);
this.queryEngine = config.createOrmQueryEngine();
this.relationalQueryEngine = config.createRelationalQueryEngine();
this.dtoQueryEngine = config.createDtoQueryEngine();
this.autoTuneService = config.createAutoTuneService(this);
this.readAuditPrepare = config.getReadAuditPrepare();
@@ -967,6 +978,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultOrmUpdate<>(beanType, this, desc.getBaseTable(), ormUpdate);
}
@Override
public <T> DtoQuery<T> findDto(Class<T> dtoType, String sql) {
DtoBeanDescriptor<T> descriptor = dtoBeanManager.getDescriptor(dtoType);
return new DefaultDtoQuery<>(this, descriptor, sql.trim());
}
@Override
public SqlQuery createSqlQuery(String sql) {
return new DefaultRelationalQuery(this, sql.trim());
@@ -1500,6 +1518,54 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public <T> void findDtoEach(SpiDtoQuery<T> query, Consumer<T> consumer) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
try {
request.initTransIfRequired();
request.findEach(consumer);
} finally {
request.endTransIfRequired();
}
}
@Override
public <T> void findDtoEachWhile(SpiDtoQuery<T> query, Predicate<T> consumer) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
try {
request.initTransIfRequired();
request.findEachWhile(consumer);
} finally {
request.endTransIfRequired();
}
}
@Override
public <T> List<T> findDtoList(SpiDtoQuery<T> query) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
try {
request.initTransIfRequired();
return request.findList();
} finally {
request.endTransIfRequired();
}
}
@Override
public <T> T findDtoOne(SpiDtoQuery<T> query) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
try {
request.initTransIfRequired();
return extractUnique(request.findList());
} finally {
request.endTransIfRequired();
}
}
/**
* Persist the bean by either performing an insert or update.
*/
@@ -2188,4 +2254,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return null;
}
public List<MetaQueryMetric> collectQueryStatistics(boolean reset) {
return dtoBeanManager.collectStats(reset);
}
}
@@ -0,0 +1,103 @@
package io.ebeaninternal.server.core;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.dto.DtoColumn;
import io.ebeaninternal.server.dto.DtoMappingRequest;
import io.ebeaninternal.server.dto.DtoQueryPlan;
import io.ebeaninternal.server.query.dto.DtoQueryEngine;
import io.ebeaninternal.server.type.DataReader;
import io.ebeaninternal.server.type.RsetDataReader;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Wraps the objects involved in executing a DtoQuery.
*/
public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
private final SpiDtoQuery<T> query;
private final DtoQueryEngine queryEngine;
private DtoQueryPlan plan;
private DataReader dataReader;
private int beanCount;
DtoQueryRequest(SpiEbeanServer server, DtoQueryEngine engine, SpiDtoQuery<T> query) {
super(server, query, null);
this.queryEngine = engine;
this.query = query;
}
@Override
protected void setResultSet(ResultSet resultSet) throws SQLException {
this.resultSet = resultSet;
this.dataReader = new RsetDataReader(ebeanServer.getDataTimeZone(), resultSet);
obtainPlan();
}
private void obtainPlan() throws SQLException {
String planKey = query.planKey();
plan = query.getQueryPlan(planKey);
if (plan == null) {
plan = query.buildPlan(mappingRequest());
query.putQueryPlan(planKey, plan);
}
}
@Override
protected void requestComplete() {
if (plan != null) {
long exeMicros = (System.nanoTime() - startNano) / 1000L;
plan.collect(exeMicros, beanCount);
}
}
public void findEach(Consumer<T> consumer) {
queryEngine.findEach(this, consumer);
}
public void findEachWhile(Predicate<T> consumer) {
queryEngine.findEachWhile(this, consumer);
}
public List<T> findList() {
return queryEngine.findList(this);
}
@SuppressWarnings("unchecked")
public T readNextBean() throws SQLException {
beanCount++;
dataReader.resetColumnPosition();
return (T)plan.readRow(dataReader);
}
private DtoMappingRequest mappingRequest() throws SQLException {
return new DtoMappingRequest(query, sql, readMeta());
}
private DtoColumn[] readMeta() throws SQLException {
ResultSetMetaData metaData = resultSet.getMetaData();
int cols = metaData.getColumnCount();
DtoColumn[] meta = new DtoColumn[cols];
for (int i = 0; i < cols; i++) {
int pos = i+1;
String columnLabel = metaData.getColumnLabel(pos);
if (columnLabel == null) {
columnLabel = metaData.getColumnName(pos);
}
meta[i] = new DtoColumn(columnLabel);
}
return meta;
}
}
@@ -40,6 +40,7 @@ import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory
import io.ebeaninternal.server.deploy.parse.DeployCreateProperties;
import io.ebeaninternal.server.deploy.parse.DeployInherit;
import io.ebeaninternal.server.deploy.parse.DeployUtil;
import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.DefaultPersister;
@@ -48,6 +49,7 @@ import io.ebeaninternal.server.persist.platform.PostgresMultiValueBind;
import io.ebeaninternal.server.query.CQueryEngine;
import io.ebeaninternal.server.query.DefaultOrmQueryEngine;
import io.ebeaninternal.server.query.DefaultRelationalQueryEngine;
import io.ebeaninternal.server.query.dto.DtoQueryEngine;
import io.ebeaninternal.server.readaudit.DefaultReadAuditLogger;
import io.ebeaninternal.server.readaudit.DefaultReadAuditPrepare;
import io.ebeaninternal.server.text.json.DJsonContext;
@@ -97,6 +99,8 @@ public class InternalConfiguration {
private final TypeManager typeManager;
private final DtoBeanManager dtoBeanManager;
private final DataTimeZone dataTimeZone;
private final Binder binder;
@@ -149,6 +153,7 @@ public class InternalConfiguration {
this.deployCreateProperties = new DeployCreateProperties(typeManager);
this.deployUtil = new DeployUtil(typeManager, serverConfig);
this.dtoBeanManager = new DtoBeanManager(typeManager);
this.beanDescriptorManager = new BeanDescriptorManager(this);
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy();
@@ -294,8 +299,12 @@ public class InternalConfiguration {
return AutoTuneServiceFactory.create(server, serverConfig);
}
public DtoQueryEngine createDtoQueryEngine() {
return new DtoQueryEngine(binder);
}
public RelationalQueryEngine createRelationalQueryEngine() {
return new DefaultRelationalQueryEngine(binder, serverConfig.getDatabaseBooleanTrue());
return new DefaultRelationalQueryEngine(binder, serverConfig.getDatabaseBooleanTrue(), serverConfig.getDbTypeConfig().getDbUuid().useBinaryOptimized());
}
public OrmQueryEngine createOrmQueryEngine() {
@@ -435,7 +444,7 @@ public class InternalConfiguration {
/**
* Create the TransactionScopeManager taking into account JTA or external transaction manager.
*/
public TransactionScopeManager createTransactionScopeManager() {
private TransactionScopeManager createTransactionScopeManager() {
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
if (externalTransactionManager == null && serverConfig.isUseJtaTransactionManager()) {
@@ -505,4 +514,8 @@ public class InternalConfiguration {
public MultiValueBind getMultiValueBind() {
return multiValueBind;
}
public DtoBeanManager getDtoBeanManager() {
return dtoBeanManager;
}
}
@@ -9,6 +9,11 @@ import java.util.function.Predicate;
public interface RelationalQueryEngine {
/**
* Return a new SqlRow with appropriate mapping for DB true and optimised binary UUID etc.
*/
SqlRow createSqlRow(int estimateCapacity);
/**
* Find a list of beans using relational query.
*/
@@ -1,24 +1,11 @@
package io.ebeaninternal.server.core;
import io.ebean.EbeanServer;
import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.Transaction;
import io.ebean.util.JdbcClose;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.TrimLogSql;
import io.ebeaninternal.server.query.DefaultSqlRow;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.util.BindParamsParser;
import io.ebeaninternal.api.SpiSqlBinding;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
@@ -30,63 +17,36 @@ import java.util.function.Predicate;
/**
* Wraps the objects involved in executing a SqlQuery.
*/
public final class RelationalQueryRequest {
private final SpiSqlQuery query;
public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
private final RelationalQueryEngine queryEngine;
private final SpiEbeanServer ebeanServer;
private SpiTransaction trans;
private boolean createdTransaction;
private String sql;
private ResultSet resultSet;
private int rowCount;
private String bindLog = "";
private String[] propertyNames;
private int estimateCapacity;
private PreparedStatement pstmt;
private int rows;
/**
* Create the BeanFindRequest.
*/
RelationalQueryRequest(SpiEbeanServer server, RelationalQueryEngine engine, SqlQuery q, Transaction t) {
this.ebeanServer = server;
super(server, (SpiSqlBinding) q, t);
this.queryEngine = engine;
this.query = (SpiSqlQuery) q;
this.trans = (SpiTransaction) t;
}
/**
* Create a transaction if none currently exists.
*/
public void initTransIfRequired() {
if (trans == null) {
trans = ebeanServer.currentServerTransaction();
if (trans == null || !trans.isActive()) {
// create a local readOnly transaction
trans = ebeanServer.beginServerTransaction();
createdTransaction = true;
}
}
@Override
protected void setResultSet(ResultSet resultSet) throws SQLException {
this.resultSet = resultSet;
this.propertyNames = getPropertyNames();
// calculate the initialCapacity of the Map to reduce rehashing
float initCap = (propertyNames.length) / 0.7f;
this.estimateCapacity = (int) initCap + 1;
}
/**
* End the transaction if it was locally created.
*/
public void endTransIfRequired() {
if (createdTransaction) {
ebeanServer.commitTransaction();
}
@Override
protected void requestComplete() {
}
public void findEach(Consumer<SqlRow> consumer) {
@@ -101,37 +61,6 @@ public final class RelationalQueryRequest {
return queryEngine.findList(this);
}
/**
* Return the find that is to be performed.
*/
public SpiSqlQuery getQuery() {
return query;
}
public EbeanServer getEbeanServer() {
return ebeanServer;
}
public SpiTransaction getTransaction() {
return trans;
}
public boolean isLogSql() {
return trans.isLogSql();
}
public boolean isLogSummary() {
return trans.isLogSummary();
}
private void setResultSet(ResultSet resultSet) throws SQLException {
this.resultSet = resultSet;
this.propertyNames = getPropertyNames();
// calculate the initialCapacity of the Map to reduce rehashing
float initCap = (propertyNames.length) / 0.7f;
this.estimateCapacity = (int) initCap + 1;
}
/**
* Build the list of property names.
*/
@@ -147,36 +76,14 @@ public final class RelationalQueryRequest {
return propNames.toArray(new String[propNames.size()]);
}
/**
* Return the bindLog for this request.
*/
public String getBindLog() {
return bindLog;
}
/**
* Return true if we can navigate to the next row.
*/
public boolean next() throws SQLException {
rowCount++;
return resultSet.next();
}
/**
* Close the underlying resources.
*/
public void close() {
JdbcClose.close(resultSet);
JdbcClose.close(pstmt);
}
/**
* Read and return the next SqlRow.
*/
public SqlRow createNewRow(String dbTrueValue) throws SQLException {
ServerConfig.DbUuid dbUuid = ebeanServer.getServerConfig().getDbTypeConfig().getDbUuid();
SqlRow sqlRow = new DefaultSqlRow(estimateCapacity, 0.75f, dbTrueValue, dbUuid.useBinaryOptimized());
public SqlRow createNewRow() throws SQLException {
rows++;
SqlRow sqlRow = queryEngine.createSqlRow(estimateCapacity);
int index = 0;
for (String propertyName : propertyNames) {
index++;
@@ -186,76 +93,11 @@ public final class RelationalQueryRequest {
return sqlRow;
}
/**
* Prepare the SQL taking into account named bind parameters.
*/
private void prepareSql() {
String sql = query.getQuery();
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
// convert any named parameters if required
sql = BindParamsParser.parse(bindParams, sql);
public void logSummary() {
if (trans.isLogSummary()) {
long micros = (System.nanoTime() - startNano) / 1000L;
trans.logSummary("SqlQuery rows[" + rows + "] micros[" + micros + "] bind[" + bindLog + "]");
}
this.sql = limitOffset(sql);
}
private String limitOffset(String sql) {
int firstRow = query.getFirstRow();
int maxRows = query.getMaxRows();
if (firstRow > 0 || maxRows > 0) {
return ebeanServer.getDatabasePlatform().getBasicSqlLimiter().limit(sql, firstRow, maxRows);
}
return sql;
}
/**
* Prepare and execute the SQL using the Binder.
*/
public void executeSql(Binder binder) throws SQLException {
prepareSql();
Connection conn = trans.getInternalConnection();
// synchronise for query.cancel() support
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
this.bindLog = binder.bind(bindParams, pstmt, conn);
}
if (isLogSql()) {
String logSql = TrimLogSql.trim(sql);
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
trans.logSql(logSql);
}
setResultSet(pstmt.executeQuery());
}
/**
* Return the SQL executed for this query.
*/
public String getSql() {
return sql;
}
/**
* Return the rows read.
*/
public int getRowCount() {
return rowCount - 1;
}
}
@@ -0,0 +1,45 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.metric.QueryPlanCollector;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages the query plans for a given DTO bean type.
*/
public class DtoBeanDescriptor<T> {
private final Map<String, DtoQueryPlan> plans = new ConcurrentHashMap<>();
private final Class<T> dtoType;
private final DtoMeta meta;
DtoBeanDescriptor(Class<T> dtoType, DtoMeta meta) {
this.dtoType = dtoType;
this.meta = meta;
}
public Class<T> getType() {
return dtoType;
}
public DtoQueryPlan getQueryPlan(String planKey) {
return plans.get(planKey);
}
public DtoQueryPlan buildPlan(DtoMappingRequest request) {
return meta.match(request);
}
public void putQueryPlan(String planKey, DtoQueryPlan plan) {
plans.put(planKey, plan);
}
public void collectStats(QueryPlanCollector collector) {
for (DtoQueryPlan plan : plans.values()) {
plan.collectStats(collector);
}
}
}
@@ -0,0 +1,53 @@
package io.ebeaninternal.server.dto;
import io.ebean.meta.MetaQueryMetric;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebeaninternal.server.type.TypeManager;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Manages all the DTO bean descriptors.
*/
public class DtoBeanManager {
private final TypeManager typeManager;
private final Map<Class, DtoBeanDescriptor> descriptorMap = new ConcurrentHashMap<>();
public DtoBeanManager(TypeManager typeManager) {
this.typeManager = typeManager;
}
/**
* Return the descriptor for the given DTO bean class.
*/
@SuppressWarnings("unchecked")
public <T> DtoBeanDescriptor<T> getDescriptor(Class<T> dtoType) {
return descriptorMap.computeIfAbsent(dtoType, this::createDescriptor);
}
private <T> DtoBeanDescriptor createDescriptor(Class<T> dtoType) {
try {
DtoMeta meta = new DtoMetaBuilder(dtoType, typeManager).build();
return new DtoBeanDescriptor<>(dtoType, meta);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
public List<MetaQueryMetric> collectStats(boolean reset) {
QueryPlanCollector collector = MetricFactory.get().createCollector(reset);
for (DtoBeanDescriptor value : descriptorMap.values()) {
value.collectStats(collector);
}
return collector.complete();
}
}
@@ -0,0 +1,23 @@
package io.ebeaninternal.server.dto;
/**
* A column in the resultSet that we want to map to a bean property.
*/
public class DtoColumn {
private final String label;
public DtoColumn(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
@Override
public String toString() {
return label;
}
}
@@ -0,0 +1,49 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.QueryPlanMetric;
/**
* Request to map a resultSet columns for a query into a DTO bean.
*/
public class DtoMappingRequest {
private final Class type;
private final String label;
private final String sql;
private final boolean relaxedMode;
private final DtoColumn[] columnMeta;
public DtoMappingRequest(SpiDtoQuery query, String sql, DtoColumn[] columnMeta) {
this.type = query.getType();
this.label = query.getLabel();
this.sql = sql;
this.relaxedMode = query.isRelaxedMode();
this.columnMeta = columnMeta;
}
public DtoColumn[] getColumnMeta() {
return columnMeta;
}
public boolean isRelaxedMode() {
return relaxedMode;
}
public String getLabel() {
return label;
}
public String getSql() {
return sql;
}
public QueryPlanMetric createMetric() {
return MetricFactory.get().createQueryPlanMetric(type, label, sql);
}
}
@@ -0,0 +1,119 @@
package io.ebeaninternal.server.dto;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Holds property and constructor meta data for a given DTO bean type.
*
* Uses this to map a mapping request (columns) to a 'query plan' (constructor and setters).
*/
class DtoMeta {
private final Class<?> dtoType;
private final Map<String, DtoMetaProperty> propMap = new LinkedHashMap<>();
private final Map<Integer, DtoMetaConstructor> constructorMap = new LinkedHashMap<>();
private final DtoMetaConstructor defaultConstructor;
private final DtoMetaConstructor maxArgConstructor;
DtoMeta(Class<?> dtoType, List<DtoMetaConstructor> constructors, List<DtoMetaProperty> properties) {
this.dtoType = dtoType;
for (DtoMetaProperty property : properties) {
propMap.put(property.getName().toUpperCase(), property);
}
int maxArg = 0;
DtoMetaConstructor defaultConstructor = null;
DtoMetaConstructor maxArgConstructor = null;
for (DtoMetaConstructor constructor : constructors) {
int args = constructor.getArgCount();
constructorMap.put(args, constructor);
if (args == 0) {
defaultConstructor = constructor;
} else if (args > maxArg) {
maxArgConstructor = constructor;
maxArg = args;
}
}
this.defaultConstructor = defaultConstructor;
this.maxArgConstructor = maxArgConstructor;
}
public DtoQueryPlan match(DtoMappingRequest request) {
DtoColumn[] cols = request.getColumnMeta();
int colLen = cols.length;
DtoMetaConstructor constructor = constructorMap.get(colLen);
if (constructor != null) {
return new DtoQueryPlanConstructor(request, constructor);
}
if (maxArgConstructor != null && colLen > maxArgConstructor.getArgCount()) {
// maxArgConst + setters
return matchMaxArgPlusSetters(request);
}
if (defaultConstructor != null) {
return matchSetters(request);
}
String msg = "Unable to map the resultSet columns " + Arrays.toString(cols)
+ " to the bean type ["+dtoType+"] as the number of columns in the resultSet is less than the constructor"
+ " (and that there is no default constructor) ?";
throw new IllegalStateException(msg);
}
private DtoQueryPlanConPlus matchMaxArgPlusSetters(DtoMappingRequest request) {
int firstOnes = maxArgConstructor.getArgCount();
DtoColumn[] cols = request.getColumnMeta();
DtoReadSet[] setterProps = new DtoReadSet[cols.length - firstOnes];
int pos = 0;
for (int i = firstOnes; i < cols.length; i++) {
String label = cols[i].getLabel();
DtoReadSet property = propMap.get(label.toUpperCase());
if (property == null || property.isReadOnly()) {
if (request.isRelaxedMode()) {
property = DtoReadSetColumnSkip.INSTANCE;
} else {
throw new IllegalStateException("Unable to map DB column " + cols[i] + " to a property with a setter method on " + dtoType);
}
}
setterProps[pos++] = property;
}
return new DtoQueryPlanConPlus(request, maxArgConstructor, setterProps);
}
private DtoQueryPlan matchSetters(DtoMappingRequest request) {
DtoColumn[] cols = request.getColumnMeta();
DtoReadSet[] setterProps = new DtoReadSet[cols.length];
for (int i = 0; i < cols.length; i++) {
String label = cols[i].getLabel();
DtoReadSet property = propMap.get(label.toUpperCase());
if (property == null || property.isReadOnly()) {
if (request.isRelaxedMode()) {
property = DtoReadSetColumnSkip.INSTANCE;
} else {
throw new IllegalStateException("Unable to map DB column " + cols[i] + " to a property with a setter method on " + dtoType);
}
}
setterProps[i] = property;
}
return new DtoQueryPlanConSetter(request, defaultConstructor, setterProps);
}
}
@@ -0,0 +1,77 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.TypeManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
/**
* Build the DtoMeta for a bean.
* <p>
* Use TypeManager to map bean property types to ScalarTypes.
*/
class DtoMetaBuilder {
private static final Logger log = LoggerFactory.getLogger(DtoMetaBuilder.class);
private final TypeManager typeManager;
private final Class<?> dtoType;
private final List<DtoMetaProperty> properties = new ArrayList<>();
private final List<DtoMetaConstructor> constructorList = new ArrayList<>();
DtoMetaBuilder(Class<?> dtoType, TypeManager typeManager) {
this.dtoType = dtoType;
this.typeManager = typeManager;
}
public DtoMeta build() throws IntrospectionException {
readConstructors();
readProperties();
return new DtoMeta(dtoType, constructorList, properties);
}
private void readProperties() throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(dtoType);
for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
if (include(propertyDescriptor)) {
try {
properties.add(new DtoMetaProperty(typeManager, propertyDescriptor, dtoType));
} catch (Exception e) {
log.debug("exclude on " + dtoType + " property " + propertyDescriptor.getName(), e);
}
}
}
}
private void readConstructors() {
Constructor<?>[] constructors = dtoType.getConstructors();
for (Constructor<?> constructor : constructors) {
try {
constructorList.add(new DtoMetaConstructor(typeManager, constructor, dtoType));
} catch (Exception e) {
// we don't want that constructor
log.debug("exclude on " + dtoType + " constructor " + constructor, e);
}
}
}
private boolean include(PropertyDescriptor property) {
return !property.getName().equals("class");
}
}
@@ -0,0 +1,67 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.TypeManager;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.sql.SQLException;
class DtoMetaConstructor {
private final Class<?>[] types;
private final MethodHandle handle;
private final ScalarType<?>[] scalarTypes;
DtoMetaConstructor(TypeManager typeManager, Constructor<?> constructor, Class<?> someClass) throws NoSuchMethodException, IllegalAccessException {
this.types = constructor.getParameterTypes();
this.scalarTypes = new ScalarType[types.length];
for (int i = 0; i < types.length; i++) {
scalarTypes[i] = typeManager.getScalarType(types[i]);
}
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
this.handle = lookup.findConstructor(someClass, typeFor(types));
}
private MethodType typeFor(Class<?>[] types) {
return MethodType.methodType(void.class, types);
}
Class<?>[] getTypes() {
return types;
}
int getArgCount() {
return types.length;
}
Object defaultConstructor() {
try {
return handle.invokeWithArguments();
} catch (Throwable e) {
throw new RuntimeException("Unexpected error invoking constructor", e);
}
}
public Object process(DataReader dataReader) throws SQLException {
Object[] values = new Object[scalarTypes.length];
for (int i = 0; i < scalarTypes.length; i++) {
values[i] = scalarTypes[i].read(dataReader);
}
return invoke(values);
}
private Object invoke(Object... args) {
try {
return handle.invokeWithArguments(args);
} catch (Throwable e) {
throw new RuntimeException("Unexpected error invoking constructor", e);
}
}
}
@@ -0,0 +1,68 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.TypeManager;
import java.beans.PropertyDescriptor;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.sql.SQLException;
class DtoMetaProperty implements DtoReadSet {
private final Class<?> dtoType;
private final String name;
private final MethodHandle setter;
private final ScalarType<?> scalarType;
DtoMetaProperty(TypeManager typeManager, PropertyDescriptor descriptor, Class<?> dtoType) throws IllegalAccessException, NoSuchMethodException {
this.dtoType = dtoType;
this.name = descriptor.getName();
Method writeMethod = descriptor.getWriteMethod();
if (writeMethod != null) {
Class<?> propertyType = descriptor.getPropertyType();
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
this.setter = lookup.findVirtual(dtoType, writeMethod.getName(), MethodType.methodType(void.class, propertyType));
this.scalarType = typeManager.getScalarType(propertyType);
} else {
this.scalarType = null;
this.setter = null;
}
}
String getName() {
return name;
}
@Override
public boolean isReadOnly() {
return scalarType == null;
}
@Override
public void readSet(Object bean, DataReader dataReader) throws SQLException {
Object value = scalarType.read(dataReader);
invoke(bean, value);
}
private void invoke(Object instance, Object arg) {
try {
setter.invoke(instance, arg);
} catch (Throwable e) {
throw new RuntimeException("Error calling setter for property " + fullname() + " with arg: " + arg, e);
}
}
private String fullname() {
return dtoType.getName() + "." + name;
}
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebeaninternal.server.type.DataReader;
import java.sql.SQLException;
/**
* Knows how to read and map rows into a Bean.
*/
public interface DtoQueryPlan {
/**
* Read the row data and return the DTO bean.
*/
Object readRow(DataReader dataReader) throws SQLException;
/**
* Add an event to the query execution statistics.
*/
void collect(long exeMicros, int rows);
/**
* Collect the query plan statistics.
*/
void collectStats(QueryPlanCollector collector);
}
@@ -0,0 +1,28 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebeaninternal.metric.QueryPlanMetric;
import io.ebeaninternal.metric.TimedMetric;
abstract class DtoQueryPlanBase implements DtoQueryPlan {
private final QueryPlanMetric planMetric;
private final TimedMetric metric;
DtoQueryPlanBase(DtoMappingRequest request) {
this.planMetric = request.createMetric();
this.metric = planMetric.getMetric();
}
@Override
public void collect(long exeTime, int rows) {
metric.add(exeTime, rows);
}
@Override
public void collectStats(QueryPlanCollector collector) {
planMetric.collect(collector);
}
}
@@ -0,0 +1,32 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import java.sql.SQLException;
/**
* Plan based on Constructor plus some setter methods.
*/
class DtoQueryPlanConPlus extends DtoQueryPlanBase {
private final DtoMetaConstructor maxArgConstructor;
private final DtoReadSet[] setterProps;
DtoQueryPlanConPlus(DtoMappingRequest request, DtoMetaConstructor maxArgConstructor, DtoReadSet[] setterProps) {
super(request);
this.maxArgConstructor = maxArgConstructor;
this.setterProps = setterProps;
}
@Override
public Object readRow(DataReader dataReader) throws SQLException {
Object bean = maxArgConstructor.process(dataReader);
for (DtoReadSet setterProp : setterProps) {
setterProp.readSet(bean, dataReader);
}
return bean;
}
}
@@ -0,0 +1,32 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import java.sql.SQLException;
/**
* Plan based on default constructor and setter methods.
*/
class DtoQueryPlanConSetter extends DtoQueryPlanBase {
private final DtoMetaConstructor defaultConstructor;
private final DtoReadSet[] setterProps;
DtoQueryPlanConSetter(DtoMappingRequest request, DtoMetaConstructor defaultConstructor, DtoReadSet[] setterProps) {
super(request);
this.defaultConstructor = defaultConstructor;
this.setterProps = setterProps;
}
@Override
public Object readRow(DataReader dataReader) throws SQLException {
Object bean = defaultConstructor.defaultConstructor();
for (DtoReadSet setterProp : setterProps) {
setterProp.readSet(bean, dataReader);
}
return bean;
}
}
@@ -0,0 +1,24 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import java.sql.SQLException;
/**
* Plan based on mapping via single constructor only.
*/
class DtoQueryPlanConstructor extends DtoQueryPlanBase {
private final DtoMetaConstructor constructor;
DtoQueryPlanConstructor(DtoMappingRequest request, DtoMetaConstructor constructor) {
super(request);
this.constructor = constructor;
}
@Override
public Object readRow(DataReader dataReader) throws SQLException {
return constructor.process(dataReader);
}
}
@@ -0,0 +1,21 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
import java.sql.SQLException;
/**
* Read and set a property value.
*/
public interface DtoReadSet {
/**
* Read the value from the dataReader and set it to the bean.
*/
void readSet(Object bean, DataReader dataReader) throws SQLException;
/**
* Return true if this maps to a read only property (no setter method).
*/
boolean isReadOnly();
}
@@ -0,0 +1,21 @@
package io.ebeaninternal.server.dto;
import io.ebeaninternal.server.type.DataReader;
/**
* Placeholder to skip reading a column that isn't mapped to a bean property.
*/
class DtoReadSetColumnSkip implements DtoReadSet {
static final DtoReadSet INSTANCE = new DtoReadSetColumnSkip();
@Override
public void readSet(Object bean, DataReader dataReader) {
dataReader.incrementPos(1);
}
@Override
public boolean isReadOnly() {
return false;
}
}
@@ -1,6 +1,8 @@
package io.ebeaninternal.server.profile;
import io.ebeaninternal.metric.MetricFactory;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebeaninternal.metric.QueryPlanMetric;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricMap;
@@ -18,4 +20,14 @@ public class DMetricFactory implements MetricFactory {
public TimedMetric createTimedMetric(String name) {
return new DTimedMetric(name);
}
@Override
public QueryPlanMetric createQueryPlanMetric(Class<?> type, String label, String sql) {
return new DQueryPlanMetric(new DQueryPlanMeta(type, label, sql), createTimedMetric(label));
}
@Override
public QueryPlanCollector createCollector(boolean reset) {
return new DQueryPlanCollector(reset);
}
}
@@ -0,0 +1,33 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaQueryMetric;
import io.ebeaninternal.metric.QueryPlanCollector;
import java.util.ArrayList;
import java.util.List;
class DQueryPlanCollector implements QueryPlanCollector {
private final boolean reset;
private final List<MetaQueryMetric> list = new ArrayList<>();
DQueryPlanCollector(boolean reset) {
this.reset = reset;
}
@Override
public boolean isReset() {
return reset;
}
@Override
public void add(MetaQueryMetric stats) {
list.add(stats);
}
@Override
public List<MetaQueryMetric> complete() {
return list;
}
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.server.profile;
class DQueryPlanMeta {
private final Class<?> type;
private final String label;
private final String sql;
DQueryPlanMeta(Class<?> type, String label, String sql) {
this.type = type;
this.label = label;
this.sql = sql;
}
public Class<?> getType() {
return type;
}
public String getLabel() {
return label;
}
public String getSql() {
return sql;
}
@Override
public String toString() {
return "type:" + type + " label:" + label;
}
}
@@ -0,0 +1,102 @@
package io.ebeaninternal.server.profile;
import io.ebean.meta.MetaQueryMetric;
import io.ebeaninternal.metric.QueryPlanCollector;
import io.ebeaninternal.metric.QueryPlanMetric;
import io.ebeaninternal.metric.TimedMetric;
import io.ebeaninternal.metric.TimedMetricStats;
class DQueryPlanMetric implements QueryPlanMetric {
private final DQueryPlanMeta meta;
private final TimedMetric metric;
DQueryPlanMetric(DQueryPlanMeta meta, TimedMetric metric) {
this.meta = meta;
this.metric = metric;
}
@Override
public void collect(QueryPlanCollector collector) {
TimedMetricStats stats = metric.collect(collector.isReset());
if (stats != null) {
collector.add(new Stats(meta, stats));
}
}
@Override
public TimedMetric getMetric() {
return metric;
}
private static class Stats implements MetaQueryMetric {
private final DQueryPlanMeta meta;
private final TimedMetricStats stats;
private Stats(DQueryPlanMeta meta, TimedMetricStats stats) {
this.meta = meta;
this.stats = stats;
}
@Override
public String toString() {
return meta +" "+ stats;
}
@Override
public Class<?> getType() {
return meta.getType();
}
@Override
public String getLabel() {
return meta.getLabel();
}
@Override
public String getSql() {
return meta.getSql();
}
@Override
public String getName() {
return stats.getName();
}
@Override
public String getLocation() {
return stats.getLocation();
}
@Override
public long getStartTime() {
return stats.getStartTime();
}
@Override
public long getCount() {
return stats.getCount();
}
@Override
public long getTotal() {
return stats.getTotal();
}
@Override
public long getMax() {
return stats.getMax();
}
@Override
public long getMean() {
return stats.getMean();
}
@Override
public long getBeanCount() {
return stats.getBeanCount();
}
}
}
@@ -19,7 +19,9 @@ class DTimeMetricStats implements TimedMetricStats {
private final long max;
DTimeMetricStats(String name, long collectionStart, long count, long total, long max) {
private final long beanCount;
DTimeMetricStats(String name, long collectionStart, long count, long total, long max, long beanCount) {
this.name = name;
this.startTime = collectionStart;
this.count = count;
@@ -27,6 +29,7 @@ class DTimeMetricStats implements TimedMetricStats {
// collection is racy so sanitize the max value if it has not been set
// this most likely would happen when count = 1 so max = mean
this.max = max != Long.MIN_VALUE ? max : (count < 1 ? 0 : Math.round(total / count));
this.beanCount = beanCount;
}
@Override
@@ -40,7 +43,8 @@ class DTimeMetricStats implements TimedMetricStats {
}
sb.append("count:").append(count)
.append(" total:").append(total)
.append(" max:").append(max);
.append(" max:").append(max)
.append(" beanCount:").append(beanCount);
return sb.toString();
}
@@ -99,4 +103,8 @@ class DTimeMetricStats implements TimedMetricStats {
return (count < 1) ? 0L : Math.round((double)(total / count));
}
@Override
public long getBeanCount() {
return beanCount;
}
}
@@ -16,15 +16,17 @@ import java.util.concurrent.atomic.LongAdder;
*/
class DTimedMetric implements TimedMetric {
protected final String name;
private final String name;
protected final LongAdder count = new LongAdder();
private final LongAdder beanCount = new LongAdder();
protected final LongAdder total = new LongAdder();
private final LongAdder count = new LongAdder();
protected final LongAccumulator max = new LongAccumulator(Math::max, Long.MIN_VALUE);
private final LongAdder total = new LongAdder();
protected final AtomicLong startTime = new AtomicLong(System.currentTimeMillis());
private final LongAccumulator max = new LongAccumulator(Math::max, Long.MIN_VALUE);
private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis());
DTimedMetric(String name) {
this.name = name;
@@ -41,6 +43,12 @@ class DTimedMetric implements TimedMetric {
max.accumulate(value);
}
@Override
public void add(long micros, long beans) {
add(micros);
beanCount.add(beans);
}
@Override
public boolean isEmpty() {
return count.sum() == 0;
@@ -75,14 +83,15 @@ class DTimedMetric implements TimedMetric {
if (reset) {
// Note these values are not guaranteed to be consistent wrt each other
// but should be reasonably consistent (small time between count and total)
final long beans = beanCount.sumThenReset();
final long maxVal = max.getThenReset();
final long totalVal = total.sumThenReset();
final long countVal = count.sumThenReset();
final long startTimeVal = startTime.getAndSet(System.currentTimeMillis());
return new DTimeMetricStats(name, startTimeVal, countVal, totalVal, maxVal);
return new DTimeMetricStats(name, startTimeVal, countVal, totalVal, maxVal, beans);
} else {
return new DTimeMetricStats(name, startTime.get(), count.sum(), total.sum(), max.get());
return new DTimeMetricStats(name, startTime.get(), count.sum(), total.sum(), max.get(), beanCount.sum());
}
}
@@ -96,32 +105,4 @@ class DTimedMetric implements TimedMetric {
total.reset();
}
/**
* Return the start time.
*/
public long getStartTime() {
return startTime.get();
}
/**
* Return the count of values.
*/
public long getCount() {
return count.sum();
}
/**
* Return the total of values.
*/
public long getTotal() {
return total.sum();
}
/**
* Return the max value.
*/
public long getMax() {
return max.get();
}
}
@@ -22,15 +22,22 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
private final String dbTrueValue;
public DefaultRelationalQueryEngine(Binder binder, String dbTrueValue) {
private final boolean binaryOptimizedUUID;
public DefaultRelationalQueryEngine(Binder binder, String dbTrueValue, boolean binaryOptimizedUUID) {
this.binder = binder;
this.dbTrueValue = dbTrueValue == null ? "true" : dbTrueValue;
this.binaryOptimizedUUID = binaryOptimizedUUID;
}
@Override
public SqlRow createSqlRow(int estimateCapacity) {
return new DefaultSqlRow(estimateCapacity, 0.75f, dbTrueValue, binaryOptimizedUUID);
}
@Override
public void findEach(RelationalQueryRequest request, Predicate<SqlRow> consumer) {
long startTime = System.currentTimeMillis();
try {
request.executeSql(binder);
while (request.next()) {
@@ -38,7 +45,7 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
break;
}
}
logSummary(request, startTime);
request.logSummary();
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
@@ -51,14 +58,12 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
@Override
public void findEach(RelationalQueryRequest request, Consumer<SqlRow> consumer) {
long startTime = System.currentTimeMillis();
try {
request.executeSql(binder);
while (request.next()) {
consumer.accept(readRow(request));
}
logSummary(request, startTime);
request.logSummary();
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
@@ -71,17 +76,14 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
@Override
public List<SqlRow> findList(RelationalQueryRequest request) {
long startTime = System.currentTimeMillis();
try {
request.executeSql(binder);
List<SqlRow> rows = new ArrayList<>();
while (request.next()) {
rows.add(readRow(request));
}
logSummary(request, startTime);
request.logSummary();
return rows;
} catch (Exception e) {
@@ -92,19 +94,11 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
}
private void logSummary(RelationalQueryRequest request, long startTime) {
if (request.isLogSummary()) {
long exeTime = System.currentTimeMillis() - startTime;
request.getTransaction().logSummary("SqlQuery rows[" + request.getRowCount() + "] time[" + exeTime + "] bind[" + request.getBindLog() + "]");
}
}
/**
* Read the row from the ResultSet and return as a MapBean.
*/
private SqlRow readRow(RelationalQueryRequest request) throws SQLException {
return request.createNewRow(dbTrueValue);
return request.createNewRow();
}
}
@@ -0,0 +1,66 @@
package io.ebeaninternal.server.query.dto;
import io.ebeaninternal.server.core.DtoQueryRequest;
import io.ebeaninternal.server.core.Message;
import io.ebeaninternal.server.persist.Binder;
import javax.persistence.PersistenceException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
public class DtoQueryEngine {
private final Binder binder;
public DtoQueryEngine(Binder binder) {
this.binder = binder;
}
public <T> List<T> findList(DtoQueryRequest<T> request) {
try {
request.executeSql(binder);
List<T> rows = new ArrayList<>();
while (request.next()) {
rows.add(request.readNextBean());
}
return rows;
} catch (Throwable e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
public <T> void findEach(DtoQueryRequest<T> request, Consumer<T> consumer) {
try {
request.executeSql(binder);
while (request.next()) {
consumer.accept(request.readNextBean());
}
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
public <T> void findEachWhile(DtoQueryRequest<T> request, Predicate<T> consumer) {
try {
request.executeSql(binder);
while (request.next()) {
if (!consumer.test(request.readNextBean())) {
break;
}
}
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
}
@@ -0,0 +1,196 @@
package io.ebeaninternal.server.querydefn;
import io.ebean.DtoQuery;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.dto.DtoBeanDescriptor;
import io.ebeaninternal.server.dto.DtoMappingRequest;
import io.ebeaninternal.server.dto.DtoQueryPlan;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Default implementation of DtoQuery.
*/
public class DefaultDtoQuery<T> implements SpiDtoQuery<T> {
private final SpiEbeanServer server;
private final DtoBeanDescriptor<T> descriptor;
private String sql;
private int firstRow;
private int maxRows;
private int timeout;
private int bufferFetchSizeHint;
private boolean relaxedMode;
private String label;
/**
* Bind parameters when using the query language.
*/
private final BindParams bindParams = new BindParams();
/**
* Additional supply a query detail object.
*/
public DefaultDtoQuery(SpiEbeanServer server, DtoBeanDescriptor<T> descriptor, String sql) {
this.server = server;
this.descriptor = descriptor;
this.sql = sql;
}
@Override
public String planKey() {
return sql+":first"+firstRow+":max"+maxRows;
}
@Override
public DtoQueryPlan getQueryPlan(String planKey) {
return descriptor.getQueryPlan(planKey);
}
@Override
public DtoQueryPlan buildPlan(DtoMappingRequest request) {
return descriptor.buildPlan(request);
}
@Override
public void putQueryPlan(String planKey, DtoQueryPlan plan) {
descriptor.putQueryPlan(planKey, plan);
}
@Override
public void findEach(Consumer<T> consumer) {
server.findDtoEach(this, consumer);
}
@Override
public void findEachWhile(Predicate<T> consumer) {
server.findDtoEachWhile(this, consumer);
}
@Override
public List<T> findList() {
return server.findDtoList(this);
}
@Override
public T findOne() {
return server.findDtoOne(this);
}
@Override
public Optional<T> findOneOrEmpty() {
return Optional.ofNullable(findOne());
}
@Override
public DtoQuery<T> setParameter(int position, Object value) {
bindParams.setParameter(position, value);
return this;
}
@Override
public DtoQuery<T> setParameter(String paramName, Object value) {
bindParams.setParameter(paramName, value);
return this;
}
@Override
public String toString() {
return "DtoQuery [" + sql + "]";
}
@Override
public Class<T> getType() {
return descriptor.getType();
}
@Override
public DtoQuery<T> setRelaxedMode() {
this.relaxedMode = true;
return this;
}
@Override
public boolean isRelaxedMode() {
return relaxedMode;
}
@Override
public DtoQuery<T> setLabel(String label) {
this.label = label;
return this;
}
@Override
public String getLabel() {
return label;
}
@Override
public int getFirstRow() {
return firstRow;
}
@Override
public DtoQuery<T> setFirstRow(int firstRow) {
this.firstRow = firstRow;
return this;
}
@Override
public int getMaxRows() {
return maxRows;
}
@Override
public DtoQuery<T> setMaxRows(int maxRows) {
this.maxRows = maxRows;
return this;
}
@Override
public int getTimeout() {
return timeout;
}
@Override
public DtoQuery<T> setTimeout(int secs) {
this.timeout = secs;
return this;
}
@Override
public BindParams getBindParams() {
return bindParams;
}
@Override
public DtoQuery<T> setBufferFetchSizeHint(int bufferFetchSizeHint) {
this.bufferFetchSizeHint = bufferFetchSizeHint;
return this;
}
@Override
public int getBufferFetchSizeHint() {
return bufferFetchSizeHint;
}
@Override
public String getQuery() {
return sql;
}
}
+336
View File
@@ -0,0 +1,336 @@
package io.ebean;
import io.ebean.meta.MetaQueryMetric;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tests.model.basic.ResetBasicData;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
public class DtoQueryTest extends BaseTestCase {
private static final Logger log = LoggerFactory.getLogger(DtoQueryTest.class);
@Test
public void dto_findList_constructorMatch() {
ResetBasicData.reset();
DtoQuery<DCust> dtoQuery = server().findDto(DCust.class, "select id, name from o_customer");
List<DCust> list = dtoQuery.findList();
log.info(list.toString());
assertThat(list).isNotEmpty();
}
@Test
public void dto_findEach_constructorMatch() {
ResetBasicData.reset();
LoggedSqlCollector.start();
server().findDto(DCust.class, "select id, name from o_customer where id > :id")
.setParameter("id", 0)
.findEach(it -> log.info("got " + it.getId() + " " + it.getName()));
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains("select id, name from o_customer where id > ?");
}
@Test
public void dto_findEachWhile_constructorMatch() {
ResetBasicData.reset();
LoggedSqlCollector.start();
server().findDto(DCust.class, "select id, name from o_customer where id > :id order by id desc")
.setParameter("id", 0)
.findEachWhile(customer -> {
log.info("got " + customer.getId() + " " + customer.getName());
return customer.getId() > 3;
});
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains("select id, name from o_customer where id > ?");
}
@Test
public void dto_findOneEmpty() {
ResetBasicData.reset();
Optional<DCust> rob = server().findDto(DCust.class, "select id, name from o_customer where name = :name")
.setParameter("name", "Fiona")
.findOneOrEmpty();
assertThat(rob.isPresent()).isTrue();
Optional<DCust> oneOrEmpty = server().findDto(DCust.class, "select id, name from o_customer where name = :name")
.setParameter("name", "DoesNotExistMyFriend")
.findOneOrEmpty();
assertThat(oneOrEmpty.isPresent()).isFalse();
}
@Test
public void dto_findOne() {
ResetBasicData.reset();
DCust fiona = server().findDto(DCust.class, "select id, name from o_customer where name = :name")
.setParameter("name", "Fiona")
.findOne();
assertThat(fiona.getName()).isEqualTo("Fiona");
DCust empty = server().findDto(DCust.class, "select id, name from o_customer where name = :name")
.setParameter("name", "DoesNotExistMyFriend")
.findOne();
assertThat(empty).isNull();
}
@Test
public void dto_queryPlanHits() {
ResetBasicData.reset();
server().getMetaInfoManager().collectQueryStatistics(true);
String[] names = {"Rob", "Fiona", "Shrek"};
for (String name : names) {
List<DCust> custs = server().findDto(DCust.class, "select c3.id, c3.name from o_customer c3 where c3.name = :name")
.setLabel("basic")
.setParameter("name", name)
.findList();
log.info("Found " + custs);
}
List<MetaQueryMetric> stats = server().getMetaInfoManager().collectQueryStatistics(false);
assertThat(stats).hasSize(1);
MetaQueryMetric queryMetric = stats.get(0);
assertThat(queryMetric.getLabel()).isEqualTo("basic");
assertThat(queryMetric.getCount()).isEqualTo(3);
server().findDto(DCust.class, "select c4.id, c4.name from o_customer c4 where lower(c4.name) = :name")
.setLabel("basic2")
.setParameter("name", "rob")
.findList();
stats = server().getMetaInfoManager().collectQueryStatistics(true);
assertThat(stats).hasSize(2);
log.info("stats " + stats);
}
@Test
public void dto_findList_relaxedMode() {
ResetBasicData.reset();
List<DCust3> list = server().findDto(DCust3.class, "select id, name, 42 as total, '42' as something_we_cannot_map from o_customer")
.setRelaxedMode()
.findList();
log.info(list.toString());
assertThat(list).isNotEmpty();
}
@Test
public void dto_findList_relaxedMode_defaultConstructor() {
ResetBasicData.reset();
List<DCust2> list = server().findDto(DCust2.class, "select id, '42' as something_we_cannot_map, name from o_customer")
.setRelaxedMode()
.findList();
log.info(list.toString());
assertThat(list).isNotEmpty();
}
@Test
public void dto_findList_constructorPlusMatch() {
ResetBasicData.reset();
String sql = "select c.id, c.name, count(o.id) as totalOrders " +
"from o_customer c " +
"join o_order o on o.kcustomer_id = c.id " +
"where c.name like :name " +
"group by c.id, c.name";
List<DCust> dtos = server().findDto(DCust.class, sql)
.setParameter("name", "Rob")
.findList();
log.info(dtos.toString());
assertThat(dtos).isNotEmpty();
}
@Test
public void dto_findList_setters() {
ResetBasicData.reset();
DtoQuery<DCust2> dtoQuery = server().findDto(DCust2.class, "select id, name from o_customer");
List<DCust2> list = dtoQuery.findList();
assertThat(list).isNotEmpty();
}
@Test
public void dto3_findList_constructorMatch() {
ResetBasicData.reset();
List<DCust3> robs = server().findDto(DCust3.class, "select id, name, 42 as totalOrders from o_customer where name like ?")
.setParameter(1, "Rob")
.setMaxRows(10)
.findList();
log.info(robs.toString());
assertThat(robs).isNotEmpty();
}
@Test
public void dto3_findList_settersMatch() {
ResetBasicData.reset();
List<DCust3> robs = server().findDto(DCust3.class, "select id, name from o_customer where name = :name")
.setParameter("name", "Rob")
.findList();
log.info(robs.toString());
assertThat(robs).isNotEmpty();
}
public static class DCust {
final Integer id;
final String name;
int totalOrders;
public DCust(Integer id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "id:" + id + " name:" + name + " totalOrders:" + totalOrders;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public int getTotalOrders() {
return totalOrders;
}
public void setTotalOrders(int totalOrders) {
this.totalOrders = totalOrders;
}
}
public static class DCust2 {
Integer id;
String name;
@Override
public String toString() {
return "id:" + id + " name:" + name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class DCust3 {
Integer id;
String name;
int totalOrders;
public DCust3() {
}
public DCust3(Integer id, String name, int totalOrders) {
this.id = id;
this.name = name;
this.totalOrders = totalOrders;
}
@Override
public String toString() {
return "id:" + id + " name:" + name + " totalOrders:" + totalOrders;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public int getTotalOrders() {
return totalOrders;
}
public void setTotalOrders(int totalOrders) {
this.totalOrders = totalOrders;
}
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
}
@@ -5,6 +5,7 @@ import io.ebean.BackgroundExecutor;
import io.ebean.BeanState;
import io.ebean.CallableSql;
import io.ebean.DocumentStore;
import io.ebean.DtoQuery;
import io.ebean.ExpressionFactory;
import io.ebean.Filter;
import io.ebean.FutureIds;
@@ -388,6 +389,31 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
@Override
public <T> void findDtoEach(SpiDtoQuery<T> query, Consumer<T> consumer) {
}
@Override
public <T> void findDtoEachWhile(SpiDtoQuery<T> query, Predicate<T> consumer) {
}
@Override
public <T> List<T> findDtoList(SpiDtoQuery<T> query) {
return null;
}
@Override
public <T> T findDtoOne(SpiDtoQuery<T> query) {
return null;
}
@Override
public <D> DtoQuery<D> findDto(Class<D> dtoType, String sql) {
return null;
}
@Override
public SqlQuery createSqlQuery(String sql) {
return null;