#802 - Find generics on findIds() - refactor internals of find ids to use find single attribute

This commit is contained in:
Robin Bygrave
2016-08-04 17:28:41 +12:00
parent 1be30f1671
commit 3c6eab6c85
25 changed files with 162 additions and 463 deletions
@@ -757,9 +757,9 @@ public interface EbeanServer {
/**
* Return the Id values of the query as a List.
*
* @see com.avaje.ebean.Query#findIds()
* @see Query#findIds()
*/
<T> List<Object> findIds(Query<T> query, Transaction transaction);
<A> List<A> findIds(Query<?> query, Transaction transaction);
/**
* Execute the query visiting the each bean one at a time.
@@ -174,7 +174,7 @@ public interface ExpressionList<T> {
*
* @see Query#findIds()
*/
List<Object> findIds();
<A> List<A> findIds();
/**
* Return the count of entities this query should return.
@@ -9,8 +9,6 @@ import java.util.concurrent.Future;
* It extends the java.util.concurrent.Future with the ability to get the Id's
* while the query is still executing in the background.
* </p>
*
* @author rbygrave
*/
public interface FutureIds<T> extends Future<List<Object>> {
@@ -19,16 +17,4 @@ public interface FutureIds<T> extends Future<List<Object>> {
*/
Query<T> getQuery();
/**
* Return the list of Id's which could be partially populated.
* <p>
* That is the query getting the id's could still be running and adding id's
* to this list.
* </p>
* <p>
* To get the list of Id's ensuring the query has finished use the
* {@link Future#get()} method instead of this one.
* </p>
*/
List<Object> getPartialIds();
}
+1 -1
View File
@@ -598,7 +598,7 @@ public abstract class Model {
* <p>
* Equivalent to {@link Query#findIds()}
*/
public List<Object> findIds() {
public <A> List<A> findIds() {
return query().findIds();
}
+1 -1
View File
@@ -542,7 +542,7 @@ public interface Query<T> {
*
* @see EbeanServer#findIds(Query, Transaction)
*/
List<Object> findIds();
<A> List<A> findIds();
/**
* Execute the query processing the beans one at a time.
@@ -145,7 +145,7 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* the query has finished (if executing in a background thread).
* </p>
*/
<T> List<Object> findIdsWithCopy(Query<T> query, Transaction t);
<A> List<A> findIdsWithCopy(Query<?> query, Transaction t);
/**
* Execute the findRowCount query but without copying the query.
@@ -251,21 +251,6 @@ public interface SpiQuery<T> extends Query<T> {
List<String> getSoftDeletePredicates();
/**
* Set the list of Id's that is being populated.
* <p>
* This is a mutating list of id's and we are setting this so that other
* threads have access to the id's before the id query has finished.
* </p>
*/
void setIdList(List<Object> ids);
/**
* Return the list of Id's that is currently being fetched by a background
* thread.
*/
List<Object> getIdList();
/**
* Return a copy of the query.
*/
@@ -1191,16 +1191,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
public <T> List<Object> findIds(Query<T> query, Transaction t) {
public <A> List<A> findIds(Query<?> query, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) query).copy();
return findIdsWithCopy(copy, t);
return findIdsWithCopy(((SpiQuery<?>) query).copy(), t);
}
public <T> List<Object> findIdsWithCopy(Query<T> query, Transaction t) {
public <A> List<A> findIdsWithCopy(Query<?> query, Transaction t) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ID_LIST, query, t);
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, query, t);
try {
request.initTransIfRequired();
return request.findIds();
@@ -1258,12 +1256,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery<T> copy = ((SpiQuery<T>) query).copy();
copy.setFutureFetch(true);
// this is the list we will put the id's in ... create it now so
// it is available for other threads to read while the id query
// is still executing (we don't need to wait for it to finish)
List<Object> idList = Collections.synchronizedList(new ArrayList<Object>());
copy.setIdList(idList);
Transaction newTxn = createTransaction();
CallableQueryIds<T> call = new CallableQueryIds<T>(this, copy, newTxn);
@@ -44,7 +44,7 @@ public interface OrmQueryEngine {
/**
* Execute the find id's query.
*/
<T> BeanIdList findIds(OrmQueryRequest<T> request);
<A> List<A> findIds(OrmQueryRequest<?> request);
/**
* Execute the query as a delete statement.
@@ -304,9 +304,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return queryEngine.findRowCount(this);
}
public List<Object> findIds() {
BeanIdList idList = queryEngine.findIds(this);
return idList.getIdList();
public <A> List<A> findIds() {
return queryEngine.findIds(this);
}
public void findEach(QueryEachConsumer<T> consumer) {
@@ -70,7 +70,7 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
/**
* Execute the find ids query.
*/
List<Object> findIds();
<A> List<A> findIds();
/**
* Execute the find returning a QueryIterator and visitor pattern.
@@ -354,7 +354,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
@Override
public List<Object> findIds() {
public <A> List<A> findIds() {
return query.findIds();
}
@@ -189,33 +189,12 @@ public class CQueryBuilder {
}
/**
* Build the row count query.
* Build the find ids query.
*/
public <T> CQueryFetchIds buildFetchIdsQuery(OrmQueryRequest<T> request) {
public <T> CQueryFetchSingleAttribute buildFetchIdsQuery(OrmQueryRequest<T> request) {
SpiQuery<T> query = request.getQuery();
query.setSelectId();
CQueryPredicates predicates = new CQueryPredicates(binder, request);
CQueryPlan queryPlan = request.getQueryPlan();
if (queryPlan != null) {
// skip building the SqlTree and Sql string
predicates.prepare(false);
return new CQueryFetchIds(request, predicates, queryPlan.getSql());
}
// use RawSql or generated Sql
predicates.prepare(true);
SqlTree sqlTree = createSqlTree(request, predicates, getHistorySupport(query), getDraftSupport(query));
SqlLimitResponse s = buildSql(null, request, predicates, sqlTree);
String sql = s.getSql();
// cache the query plan
queryPlan = new CQueryPlan(request, sql, sqlTree, false, s.isIncludesRowNumberColumn(), predicates.getLogWhereSql());
request.putQueryPlan(queryPlan);
return new CQueryFetchIds(request, predicates, sql);
request.getQuery().setSelectId();
return buildFetchAttributeQuery(request);
}
/**
@@ -1,17 +1,16 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.Version;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebeaninternal.api.BeanIdList;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.server.core.DiffHelp;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.core.QueryIterator;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.lib.util.Str;
import com.avaje.ebeaninternal.server.persist.Binder;
@@ -94,8 +93,13 @@ public class CQueryEngine {
public <A> List<A> findSingleAttributeList(OrmQueryRequest<?> request) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchAttributeQuery(request);
return findAttributeList(request, rcQuery);
}
@SuppressWarnings("unchecked")
private <A> List<A> findAttributeList(OrmQueryRequest<?> request, CQueryFetchSingleAttribute rcQuery) {
try {
List list = rcQuery.findList();
List<A> list = (List<A>)rcQuery.findList();
if (request.isLogSql()) {
logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog());
}
@@ -112,29 +116,10 @@ public class CQueryEngine {
/**
* Build and execute the find Id's query.
*/
public <T> BeanIdList findIds(OrmQueryRequest<T> request) {
public <A> List<A> findIds(OrmQueryRequest<?> request) {
CQueryFetchIds rcQuery = queryBuilder.buildFetchIdsQuery(request);
try {
BeanIdList list = rcQuery.findIds();
if (request.isLogSql()) {
logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog());
}
if (request.isLogSummary()) {
request.getTransaction().logSummary(rcQuery.getSummary());
}
if (request.getQuery().isFutureFetch()) {
// end the transaction for futureFindIds (it had it's own one)
logger.debug("Future findIds completed!");
request.getTransaction().end();
}
return list;
} catch (SQLException e) {
throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql());
}
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchIdsQuery(request);
return findAttributeList(request, rcQuery);
}
private <T> void logGeneratedSql(OrmQueryRequest<T> request, String sql, String bindLog) {
@@ -1,208 +0,0 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiQuery.Mode;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
import com.avaje.ebeaninternal.server.type.DataReader;
import com.avaje.ebeaninternal.server.type.RsetDataReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
/**
* Base compiled query request for single attribute queries.
*/
public abstract class CQueryFetchBase {
private static final Logger logger = LoggerFactory.getLogger(CQueryFetchBase.class);
/**
* The overall find request wrapper object.
*/
protected final OrmQueryRequest<?> request;
protected final BeanDescriptor<?> desc;
protected final SpiQuery<?> query;
/**
* Where clause predicates.
*/
protected final CQueryPredicates predicates;
/**
* The final sql that is generated.
*/
protected final String sql;
protected RsetDataReader dataReader;
/**
* The statement used to create the resultSet.
*/
protected PreparedStatement pstmt;
protected String bindLog;
protected int executionTimeMicros;
protected int rowCount;
protected final int maxRows;
/**
* Create the Sql select based on the request.
*/
public CQueryFetchBase(OrmQueryRequest<?> request, CQueryPredicates predicates, String sql) {
this.request = request;
this.query = request.getQuery();
this.sql = sql;
this.maxRows = query.getMaxRows();
query.setGeneratedSql(sql);
this.desc = request.getBeanDescriptor();
this.predicates = predicates;
}
/**
* Return the bind log.
*/
public String getBindLog() {
return bindLog;
}
/**
* Return the generated sql.
*/
public String getGeneratedSql() {
return sql;
}
protected ResultSet prepareExecute() throws SQLException {
SpiTransaction t = request.getTransaction();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
ResultSet rset = pstmt.executeQuery();
dataReader = new RsetDataReader(request.getDataTimeZone(), rset);
return rset;
}
/**
* Close the resources.
* <p>
* The jdbc resultSet and statement need to be closed. Its important that
* this method is called.
* </p>
*/
protected void close() {
try {
if (dataReader != null) {
dataReader.close();
dataReader = null;
}
} catch (SQLException e) {
logger.error("Error closing DataReader", e);
}
try {
if (pstmt != null) {
pstmt.close();
pstmt = null;
}
} catch (SQLException e) {
logger.error("Error closing PreparedStatement", e);
}
}
protected class DbContext implements DbReadContext {
public void propagateState(Object e) {
throw new RuntimeException("Not Called");
}
public Mode getQueryMode() {
return Mode.NORMAL;
}
public DataReader getDataReader() {
return dataReader;
}
public Boolean isReadOnly() {
return Boolean.FALSE;
}
@Override
public boolean isDisableLazyLoading() {
return false;
}
public boolean isRawSql() {
return false;
}
public void register(String path, EntityBeanIntercept ebi) {
}
public void register(String path, BeanCollection<?> bc) {
}
public BeanPropertyAssocMany<?> getManyProperty() {
// always null
return null;
}
public PersistenceContext getPersistenceContext() {
// always null
return null;
}
public boolean isAutoTuneProfiling() {
return false;
}
public void profileBean(EntityBeanIntercept ebi, String prefix) {
// no-op
}
public void setCurrentPrefix(String currentPrefix, Map<String, String> pathMap) {
// no-op
}
public void setLazyLoadedChildBean(EntityBean loadedBean, Object lazyLoadParentId) {
// no-op
}
@Override
public boolean isDraftQuery() {
return false;
}
}
}
@@ -1,96 +0,0 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebeaninternal.api.BeanIdList;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Executes the select row count query.
*/
public class CQueryFetchIds extends CQueryFetchBase {
/**
* Create the Sql select based on the request.
*/
public CQueryFetchIds(OrmQueryRequest<?> request, CQueryPredicates predicates, String sql) {
super(request, predicates, sql);
}
/**
* Return a summary description of this query.
*/
public String getSummary() {
StringBuilder sb = new StringBuilder(80);
sb.append("FindIds exeMicros[").append(executionTimeMicros)
.append("] rows[").append(rowCount)
.append("] type[").append(desc.getName())
.append("] predicates[").append(predicates.getLogWhereSql())
.append("] bind[").append(bindLog).append("]");
return sb.toString();
}
/**
* Execute the query returning the row count.
*/
public BeanIdList findIds() throws SQLException {
long startNano = System.nanoTime();
try {
// get the list that we are going to put the id's into.
// This was already set so that it is available to be
// read by other threads (it is a synchronised list)
List<Object> idList = query.getIdList();
if (idList == null) {
// running in foreground thread (not FutureIds query)
idList = Collections.synchronizedList(new ArrayList<Object>());
query.setIdList(idList);
}
BeanIdList result = new BeanIdList(idList);
ResultSet rset = prepareExecute();
boolean hitMaxRows = false;
boolean hasMoreRows = false;
rowCount = 0;
DbReadContext ctx = new DbContext();
while (rset.next()) {
Object idValue = desc.getIdBinder().read(ctx);
idList.add(idValue);
// reset back to 0
dataReader.resetColumnPosition();
rowCount++;
if (maxRows > 0 && rowCount == maxRows) {
hitMaxRows = true;
hasMoreRows = rset.next();
break;
}
}
if (hitMaxRows) {
result.setHasMore(hasMoreRows);
}
long exeNano = System.nanoTime() - startNano;
executionTimeMicros = (int) exeNano / 1000;
return result;
} finally {
close();
}
}
}
@@ -1,20 +1,58 @@
package com.avaje.ebeaninternal.server.query;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.type.RsetDataReader;
import com.avaje.ebeaninternal.server.type.ScalarType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.ResultSet;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* Executes the select row count query.
* Base compiled query request for single attribute queries.
*/
public class CQueryFetchSingleAttribute extends CQueryFetchBase {
class CQueryFetchSingleAttribute {
private final BeanProperty property;
private static final Logger logger = LoggerFactory.getLogger(CQueryFetchSingleAttribute.class);
/**
* The overall find request wrapper object.
*/
private final OrmQueryRequest<?> request;
private final BeanDescriptor<?> desc;
private final SpiQuery<?> query;
/**
* Where clause predicates.
*/
private final CQueryPredicates predicates;
/**
* The final sql that is generated.
*/
private final String sql;
private RsetDataReader dataReader;
/**
* The statement used to create the resultSet.
*/
private PreparedStatement pstmt;
private String bindLog;
private int executionTimeMicros;
private int rowCount;
private final ScalarType<Object> scalarType;
@@ -22,15 +60,20 @@ public class CQueryFetchSingleAttribute extends CQueryFetchBase {
* Create the Sql select based on the request.
*/
public CQueryFetchSingleAttribute(OrmQueryRequest<?> request, CQueryPredicates predicates, CQueryPlan plan) {
super(request, predicates, plan.getSql());
this.property = plan.getSingleProperty();
this.scalarType = property.getScalarType();
this.request = request;
this.query = request.getQuery();
this.sql = plan.getSql();
this.desc = request.getBeanDescriptor();
this.predicates = predicates;
this.scalarType = plan.getSingleProperty().getScalarType();
query.setGeneratedSql(sql);
}
/**
* Return a summary description of this query.
*/
public String getSummary() {
protected String getSummary() {
StringBuilder sb = new StringBuilder(80);
sb.append("FindAttr exeMicros[").append(executionTimeMicros)
.append("] rows[").append(rowCount)
@@ -44,15 +87,16 @@ public class CQueryFetchSingleAttribute extends CQueryFetchBase {
/**
* Execute the query returning the row count.
*/
public List<Object> findList() throws SQLException {
protected List<Object> findList() throws SQLException {
long startNano = System.nanoTime();
try {
prepareExecute();
List<Object> result = new ArrayList<Object>();
ResultSet rset = prepareExecute();
while (rset.next()) {
while (dataReader.next()) {
result.add(scalarType.read(dataReader));
dataReader.resetColumnPosition();
rowCount++;
@@ -68,4 +112,61 @@ public class CQueryFetchSingleAttribute extends CQueryFetchBase {
}
}
/**
* Return the bind log.
*/
protected String getBindLog() {
return bindLog;
}
/**
* Return the generated sql.
*/
protected String getGeneratedSql() {
return sql;
}
private void prepareExecute() throws SQLException {
SpiTransaction t = request.getTransaction();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
dataReader = new RsetDataReader(request.getDataTimeZone(), pstmt.executeQuery());
}
/**
* Close the resources.
* <p>
* The jdbc resultSet and statement need to be closed. Its important that
* this method is called.
* </p>
*/
private void close() {
try {
if (dataReader != null) {
dataReader.close();
dataReader = null;
}
} catch (SQLException e) {
logger.error("Error closing DataReader", e);
}
try {
if (pstmt != null) {
pstmt.close();
pstmt = null;
}
} catch (SQLException e) {
logger.error("Error closing PreparedStatement", e);
}
}
}
@@ -65,7 +65,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
return queryEngine.findRowCount(request);
}
public <T> BeanIdList findIds(OrmQueryRequest<T> request) {
public <A> List<A> findIds(OrmQueryRequest<?> request) {
flushJdbcBatchOnQuery(request);
return queryEngine.findIds(request);
@@ -1,12 +1,12 @@
package com.avaje.ebeaninternal.server.query;
import java.util.List;
import java.util.concurrent.FutureTask;
import com.avaje.ebean.FutureIds;
import com.avaje.ebean.Query;
import com.avaje.ebean.Transaction;
import java.util.List;
import java.util.concurrent.FutureTask;
/**
* Default implementation of FutureIds.
*/
@@ -31,10 +31,6 @@ public class QueryFutureIds<T> extends BaseFuture<List<Object>> implements Futur
return call.query;
}
public List<Object> getPartialIds() {
return call.query.getIdList();
}
public boolean cancel(boolean mayInterruptIfRunning) {
call.query.cancel();
return super.cancel(mayInterruptIfRunning);
@@ -123,8 +123,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
*/
private ReadEvent futureFetchAudit;
private List<Object> partialIds;
private int timeout;
/**
@@ -1509,16 +1507,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return disableReadAudit;
}
@Override
public List<Object> getIdList() {
return partialIds;
}
@Override
public void setIdList(List<Object> partialIds) {
this.partialIds = partialIds;
}
@Override
public boolean isFutureFetch() {
return futureFetch;