mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#1297 - ENH: Add DtoQuery ... such that we can map native SQL queries automatically into "DTO beans"
This commit is contained in:
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user