
io.ebeaninternal.server.core.OrmQueryRequest Maven / Gradle / Ivy
package io.ebeaninternal.server.core;
import io.ebean.CacheMode;
import io.ebean.OrderBy;
import io.ebean.PersistenceContextScope;
import io.ebean.QueryIterator;
import io.ebean.Version;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.common.BeanList;
import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanQueryAdapter;
import io.ebean.text.json.JsonReadOptions;
import io.ebeaninternal.api.BeanCacheResult;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.HashQuery;
import io.ebeaninternal.api.LoadContext;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.NaturalKeySet;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Type;
import io.ebeaninternal.api.SpiQuerySecondary;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.DeployParser;
import io.ebeaninternal.server.deploy.DeployPropertyParserMap;
import io.ebeaninternal.server.loadcontext.DLoadContext;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Wraps the objects involved in executing a Query.
*/
public final class OrmQueryRequest extends BeanRequest implements SpiOrmQueryRequest {
private static final Logger log = LoggerFactory.getLogger(OrmQueryRequest.class);
private final BeanDescriptor beanDescriptor;
private final OrmQueryEngine queryEngine;
private final SpiQuery query;
private final BeanFindController finder;
private final Boolean readOnly;
private LoadContext loadContext;
private PersistenceContext persistenceContext;
private JsonReadOptions jsonRead;
private HashQuery cacheKey;
private CQueryPlanKey queryPlanKey;
private SpiQuerySecondary secondaryQueries;
private List cacheBeans;
private BeanPropertyAssocMany> manyProperty;
private boolean inlineCountDistinct;
private Set dependentTables;
/**
* Create the InternalQueryRequest.
*/
public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery query, SpiTransaction t) {
super(server, t);
this.beanDescriptor = query.getBeanDescriptor();
this.finder = beanDescriptor.getBeanFinder();
this.queryEngine = queryEngine;
this.query = query;
this.readOnly = query.isReadOnly();
}
public PersistenceException translate(String bindLog, String sql, SQLException e) {
return queryEngine.translate(this, bindLog, sql, e);
}
@Override
public void profileLocationById() {
if (query.getProfileLocation() == null) {
query.setProfileLocation(beanDescriptor.profileLocationById());
}
}
@Override
public void profileLocationAll() {
if (query.getProfileLocation() == null && query.isFindAll()) {
query.setProfileLocation(beanDescriptor.profileLocationAll());
}
}
@Override
public boolean isDeleteByStatement() {
return beanDescriptor.isDeleteByStatement();
}
@Override
public boolean isMultiValueIdSupported() {
return beanDescriptor.isMultiValueIdSupported();
}
@Override
public boolean isMultiValueSupported(Class> valueType) {
return queryEngine.isMultiValueSupported(valueType);
}
/**
* Mark the transaction as not being query only.
*/
@Override
public void markNotQueryOnly() {
transaction.markNotQueryOnly();
}
/**
* Return the database platform like clause.
*/
@Override
public String getDBLikeClause(boolean rawLikeExpression) {
return ebeanServer.getDatabasePlatform().getLikeClause(rawLikeExpression);
}
/**
* Return the database platform escaped like string.
*/
@Override
public String escapeLikeString(String value) {
return ebeanServer.getDatabasePlatform().escapeLikeString(value);
}
@Override
public void executeSecondaryQueries(boolean forEach) {
// disable lazy loading leaves loadContext null
if (loadContext != null) {
loadContext.executeSecondaryQueries(this, forEach);
}
}
/**
* For use with QueryIterator and secondary queries this returns the minimum
* batch size that should be loaded before executing the secondary queries.
*
* If -1 is returned then NO secondary queries are registered and simple
* iteration is fine.
*
*/
public int getSecondaryQueriesMinBatchSize(int defaultQueryBatch) {
return loadContext.getSecondaryQueriesMinBatchSize(defaultQueryBatch);
}
/**
* Return the Normal, sharedInstance, ReadOnly state of this query.
*/
public Boolean isReadOnly() {
return readOnly;
}
/**
* Return the BeanDescriptor for the associated bean.
*/
@Override
public BeanDescriptor getBeanDescriptor() {
return beanDescriptor;
}
/**
* Return the graph context for this query.
*/
public LoadContext getGraphContext() {
return loadContext;
}
@Override
public boolean isUseDocStore() {
return query.isUseDocStore();
}
/**
* Run BeanQueryAdapter preQuery() if needed.
*/
private void adapterPreQuery() {
BeanQueryAdapter queryAdapter = beanDescriptor.getQueryAdapter();
if (queryAdapter != null) {
queryAdapter.preQuery(this);
}
}
/**
* Prepare the query and calculate the query plan key.
*/
void prepareQuery() {
beanDescriptor.prepareQuery(query);
adapterPreQuery();
this.secondaryQueries = query.convertJoins();
this.queryPlanKey = query.prepare(this);
}
public boolean isNativeSql() {
return query.isNativeSql();
}
public boolean isRawSql() {
return query.isRawSql();
}
public DeployParser createDeployParser() {
if (query.isRawSql()) {
return new DeployPropertyParserMap(query.getRawSql().getColumnMapping().getMapping());
} else {
return beanDescriptor.parser();
}
}
/**
* Return the PersistenceContext used for this request.
*/
public PersistenceContext getPersistenceContext() {
return persistenceContext;
}
/**
* Add the bean to the persistence context.
*/
public void persistenceContextAdd(EntityBean bean) {
Object id = beanDescriptor.getId(bean);
beanDescriptor.contextPut(persistenceContext, id, bean);
}
/**
* This will create a local (readOnly) transaction if no current transaction
* exists.
*
* A transaction may have been passed in explicitly or currently be active in
* the thread local. If not, then a readOnly transaction is created to execute
* this query.
*
*/
@Override
public void initTransIfRequired() {
// first check if the query requires its own transaction
if (transaction == null) {
if (query.getType().isUpdate()) {
// bulk update or delete query
transaction = ebeanServer.beginServerTransaction();
} else {
// create an implicit transaction to execute this query
// potentially using read-only DataSource with autoCommit
transaction = ebeanServer.createQueryTransaction(query.getTenantId());
}
createdTransaction = true;
}
persistenceContext = getPersistenceContext(query, transaction);
loadContext = new DLoadContext(this, secondaryQueries);
}
/**
* Rollback the transaction if it was created for this request.
*/
@Override
public void rollbackTransIfRequired() {
if (createdTransaction) {
try {
transaction.end();
} catch (Exception e) {
// Just log this and carry on. A previous exception has been
// thrown and if this rollback throws exception it likely means
// that the connection is broken (and the dataSource and db will cleanup)
log.error("Error trying to rollback a transaction (after a prior exception thrown)", e);
}
}
}
/**
* Return the JsonReadOptions taking into account lazy loading and persistence context.
*/
@Override
public JsonReadOptions createJsonReadOptions() {
persistenceContext = getPersistenceContext(query, transaction);
if (query.getPersistenceContext() == null) {
query.setPersistenceContext(persistenceContext);
}
jsonRead = new JsonReadOptions();
jsonRead.setPersistenceContext(persistenceContext);
if (!query.isDisableLazyLoading()) {
loadContext = new DLoadContext(this, secondaryQueries);
jsonRead.setLoadContext(loadContext);
}
return jsonRead;
}
/**
* For iterate queries reset the persistenceContext and loadContext.
*/
public void flushPersistenceContextOnIterate() {
persistenceContext = new DefaultPersistenceContext();
loadContext.resetPersistenceContext(persistenceContext);
if (jsonRead != null) {
jsonRead.setPersistenceContext(persistenceContext);
jsonRead.setLoadContext(loadContext);
}
}
/**
* Get the TransactionContext either explicitly set on the query or
* transaction scoped.
*/
private PersistenceContext getPersistenceContext(SpiQuery> query, SpiTransaction t) {
// check if there is already a persistence context set which is the case
// when lazy loading or query joins are executed
PersistenceContext ctx = query.getPersistenceContext();
if (ctx != null) return ctx;
// determine the scope (from the query and then server)
PersistenceContextScope scope = ebeanServer.getPersistenceContextScope(query);
return (scope == PersistenceContextScope.QUERY || t == null) ? new DefaultPersistenceContext() : t.getPersistenceContext();
}
/**
* Will end a locally created transaction.
*
* It ends the query only transaction.
*
*/
@Override
public void endTransIfRequired() {
if (createdTransaction && transaction.isActive()) {
transaction.commit();
}
}
/**
* Return true if this is a find by id (rather than List Set or Map).
*/
public boolean isFindById() {
return query.getType() == Type.BEAN;
}
/**
* Return true if this is a findEach, findIterate type query where we expect many results.
*/
public boolean isFindIterate() {
return query.getType() == Type.ITERATE;
}
/**
* Execute the query as a delete.
*/
@Override
public int delete() {
return notifyCache(queryEngine.delete(this), false);
}
/**
* Execute the query as a update.
*/
@Override
public int update() {
return notifyCache(queryEngine.update(this), true);
}
private int notifyCache(int rows, boolean update) {
if (rows > 0) {
beanDescriptor.cacheUpdateQuery(update, transaction);
}
return rows;
}
@Override
public SpiResultSet findResultSet() {
return queryEngine.findResultSet(this);
}
/**
* Execute the query as findById.
*/
@Override
public Object findId() {
return queryEngine.findId(this);
}
@Override
public int findCount() {
return queryEngine.findCount(this);
}
@Override
public List findIds() {
return queryEngine.findIds(this);
}
@Override
public void findEach(Consumer consumer) {
try (QueryIterator it = queryEngine.findIterate(this)) {
while (it.hasNext()) {
consumer.accept(it.next());
}
}
}
@Override
public void findEachWhile(Predicate consumer) {
try (QueryIterator it = queryEngine.findIterate(this)) {
while (it.hasNext()) {
if (!consumer.test(it.next())) {
break;
}
}
}
}
@Override
public QueryIterator findIterate() {
return queryEngine.findIterate(this);
}
/**
* Execute the query as findList.
*/
@Override
@SuppressWarnings("unchecked")
public List findList() {
return (List) queryEngine.findMany(this);
}
@Override
public List> findVersions() {
return queryEngine.findVersions(this);
}
/**
* Execute the query as findSet.
*/
@Override
@SuppressWarnings("unchecked")
public Set findSet() {
return (Set) queryEngine.findMany(this);
}
/**
* Execute the query as findMap.
*/
@Override
@SuppressWarnings("unchecked")
public Map findMap() {
String mapKey = query.getMapKey();
if (mapKey == null) {
BeanProperty idProp = beanDescriptor.getIdProperty();
if (idProp != null) {
query.setMapKey(idProp.getName());
} else {
throw new PersistenceException("No mapKey specified for query");
}
}
return (Map) queryEngine.findMany(this);
}
/**
* Execute the findSingleAttributeList query.
*/
@Override
public List findSingleAttributeList() {
return queryEngine.findSingleAttributeList(this);
}
/**
* Return a bean specific finder if one has been set.
*/
public BeanFindController getBeanFinder() {
return finder;
}
/**
* Return the find that is to be performed.
*/
@Override
public SpiQuery getQuery() {
return query;
}
/**
* Determine and return the ToMany property that is included in the query.
*/
public BeanPropertyAssocMany> determineMany() {
manyProperty = beanDescriptor.getManyProperty(query);
return manyProperty;
}
/**
* Return the many property that is fetched in the query or null if there is not one.
*/
public BeanPropertyAssocMany> getManyProperty() {
return manyProperty;
}
/**
* Return a queryPlan for the current query if one exists. Returns null if no
* query plan for this query exists.
*/
public CQueryPlan getQueryPlan() {
return beanDescriptor.getQueryPlan(queryPlanKey);
}
/**
* Return the queryPlanHash.
*
* This identifies the query plan for a given bean type. It effectively
* matches a SQL statement with ? bind variables. A query plan can be reused
* with just the bind variables changing.
*
*/
public CQueryPlanKey getQueryPlanKey() {
return queryPlanKey;
}
/**
* Put the QueryPlan into the cache.
*/
public void putQueryPlan(CQueryPlan queryPlan) {
beanDescriptor.putQueryPlan(queryPlanKey, queryPlan);
}
@Override
public void resetBeanCacheAutoMode(boolean findOne) {
query.resetBeanCacheAutoMode(findOne);
}
public boolean isQueryCachePut() {
return cacheKey != null && query.getUseQueryCache().isPut();
}
public boolean isBeanCachePut() {
return !transaction.isSkipCache() && query.isBeanCachePut();
}
/**
* Merge in prior L2 bean cache hits with the query result.
*/
public void mergeCacheHits(BeanCollection result) {
if (cacheBeans != null && !cacheBeans.isEmpty()) {
for (T hit : cacheBeans) {
result.internalAdd(hit);
}
// resort in memory here after merging the cache hits with the DB hits
if (result instanceof BeanList) {
OrderBy orderBy = query.getOrderBy();
if (orderBy != null) {
beanDescriptor.sort(((BeanList)result).getActualList(), orderBy.toStringFormat());
}
}
}
}
@Override
public List getBeanCacheHits() {
OrderBy orderBy = query.getOrderBy();
if (orderBy != null) {
beanDescriptor.sort(cacheBeans, orderBy.toStringFormat());
}
return cacheBeans;
}
@Override
public boolean getFromBeanCache() {
if (!query.isBeanCacheGet()) {
return false;
}
// check if the query can use the bean cache
// 1. Find by Ids
// - hit beanCache with Ids
// - keep cache beans, ensure query modified to fetch misses
// - query and Load misses into bean cache
// - merge the 2 results and return
//
if (!beanDescriptor.isNaturalKeyCaching()) {
return false;
}
NaturalKeyQueryData data = query.naturalKey();
if (data != null) {
NaturalKeySet naturalKeySet = data.buildKeys();
if (naturalKeySet != null) {
// use the natural keys to lookup Ids to then hit the bean cache
BeanCacheResult cacheResult = beanDescriptor.naturalKeyLookup(persistenceContext, naturalKeySet.keys());
// adjust the query (IN clause) based on the cache hits
this.cacheBeans = data.removeHits(cacheResult);
return data.allHits();
}
}
return false;
}
/**
* Try to get the query result from the query cache.
*/
@Override
@SuppressWarnings("unchecked")
public Object getFromQueryCache() {
if (query.getUseQueryCache() == CacheMode.OFF || (transaction != null && transaction.isSkipCache())) {
return null;
} else {
cacheKey = query.queryHash();
}
if (!query.getUseQueryCache().isGet()) {
return null;
}
Object cached = beanDescriptor.queryCacheGet(cacheKey);
if (cached != null && isAuditReads() && readAuditQueryType()) {
if (cached instanceof BeanCollection) {
// raw sql can't use L2 cache so normal queries only in here
Collection actualDetails = ((BeanCollection)cached).getActualDetails();
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy