mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#1211 - ENH: Support L2 cache complex natural keys include findList() queries
This commit is contained in:
@@ -22,6 +22,11 @@ public enum CacheMode {
|
||||
*/
|
||||
ON(true, true),
|
||||
|
||||
/**
|
||||
* Only used for bean caching. We automatically use the cache for findOne() but not findList().
|
||||
*/
|
||||
AUTO(true, true),
|
||||
|
||||
/**
|
||||
* Do not read from cache, but write retrieved value to cache.
|
||||
* Use this, if you want to get the fresh value from database and a CacheMode.ON query will follow.
|
||||
|
||||
+16
@@ -1,5 +1,9 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Represents part of the "L2" server side cache.
|
||||
* <p>
|
||||
@@ -13,6 +17,18 @@ package io.ebean.cache;
|
||||
*/
|
||||
public interface ServerCache {
|
||||
|
||||
default Map<Object,Object> getAll(Set<Object> keys){
|
||||
|
||||
Map<Object,Object> map = new LinkedHashMap<>();
|
||||
for (Object key : keys) {
|
||||
Object value = get(key);
|
||||
if (value != null) {
|
||||
map.put(key, value);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value given the key.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The results of bean cache hit.
|
||||
*/
|
||||
public class BeanCacheResult<T> {
|
||||
|
||||
private List<Entry<T>> list = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Add an entry.
|
||||
*/
|
||||
public void add(T bean, Object key) {
|
||||
list.add(new Entry<>(bean, key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hits.
|
||||
*/
|
||||
public List<Entry<T>> hits() {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bean and cache key pair.
|
||||
*/
|
||||
static class Entry<T> {
|
||||
|
||||
private final T bean;
|
||||
private final Object key;
|
||||
|
||||
public Entry(T bean, Object key) {
|
||||
this.bean = bean;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the natural key or id value.
|
||||
*/
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean.
|
||||
*/
|
||||
public T getBean() {
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Natural key entry with name value pairs for each of the properties making up the key.
|
||||
*/
|
||||
public class NaturalKeyEntry {
|
||||
|
||||
private Map<String,Object> map = new HashMap<>();
|
||||
private Object key;
|
||||
private Object inValue;
|
||||
|
||||
/**
|
||||
* Used when query query just has a series of EQ expressions (no IN clause).
|
||||
*/
|
||||
public NaturalKeyEntry(String[] naturalKey, List<NaturalKeyEq> eqList) {
|
||||
this(naturalKey, eqList, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create when query uses an IN clause.
|
||||
*/
|
||||
public NaturalKeyEntry(String[] naturalKey, List<NaturalKeyEq> eqList, String inProperty, Object inValue) {
|
||||
for (NaturalKeyEq eq : eqList) {
|
||||
map.put(eq.property, eq.value);
|
||||
}
|
||||
if (inProperty != null) {
|
||||
map.put(inProperty, inValue);
|
||||
this.inValue = inValue;
|
||||
}
|
||||
this.key = calculateKey(naturalKey);
|
||||
}
|
||||
|
||||
|
||||
private Object calculateKey(String[] naturalKey) {
|
||||
|
||||
if (naturalKey.length == 1) {
|
||||
return map.get(naturalKey[0]);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String key : naturalKey) {
|
||||
sb.append(map.get(key)).append(";");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the natural cache key.
|
||||
*/
|
||||
public Object key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the inValue (used to remove from IN clause of original query).
|
||||
*/
|
||||
public Object getInValue() {
|
||||
return inValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* A property value pair in a natural key lookup.
|
||||
*/
|
||||
public class NaturalKeyEq {
|
||||
|
||||
final String property;
|
||||
final Object value;
|
||||
|
||||
public NaturalKeyEq(String property, Object value) {
|
||||
this.property = property;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Collects the data for processing the natural key cache processing.
|
||||
*/
|
||||
public class NaturalKeyQueryData<T> {
|
||||
|
||||
private final String[] naturalKey;
|
||||
|
||||
private Collection<?> inValues;
|
||||
|
||||
private String inProperty;
|
||||
|
||||
private List<NaturalKeyEq> eqList;
|
||||
|
||||
private NaturalKeySet set;
|
||||
|
||||
private int hitCount;
|
||||
|
||||
public NaturalKeyQueryData(String[] naturalKey) {
|
||||
this.naturalKey = naturalKey;
|
||||
}
|
||||
|
||||
private boolean matchProperty(String propName) {
|
||||
|
||||
for (String key : naturalKey) {
|
||||
if (key.equals(propName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match for IN expression. We only allow one IN clause.
|
||||
*/
|
||||
public boolean matchIn(String propName, Collection<?> sourceValues) {
|
||||
if (inProperty != null) {
|
||||
// only 1 IN allowed (to project naturalIds)
|
||||
return false;
|
||||
}
|
||||
if (matchProperty(propName)) {
|
||||
this.inProperty = propName;
|
||||
this.inValues = sourceValues;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match for an EQ expression.
|
||||
*/
|
||||
public boolean matchEq(String propName, Object bindValue) {
|
||||
if (matchProperty(propName)) {
|
||||
if (eqList == null) {
|
||||
eqList = new ArrayList<>();
|
||||
}
|
||||
eqList.add(new NaturalKeyEq(propName, bindValue));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the set of natural keys we will use.
|
||||
*/
|
||||
public NaturalKeySet buildKeys() {
|
||||
|
||||
if (!expressionCount() || !matchProperties()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.set = new NaturalKeySet();
|
||||
if (inValues == null) {
|
||||
// only one - a findOne()
|
||||
set.add(new NaturalKeyEntry(naturalKey, eqList));
|
||||
|
||||
} else {
|
||||
// a findList() with an IN clause so we project
|
||||
// for every IN value a natural key combination
|
||||
for (Object inValue : inValues) {
|
||||
set.add(new NaturalKeyEntry(naturalKey, eqList, inProperty, inValue));
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the properties match the natural key properties.
|
||||
*/
|
||||
private boolean matchProperties() {
|
||||
if (naturalKey.length == 1) {
|
||||
// simple single property case
|
||||
if (inProperty != null) {
|
||||
return inProperty.equals(naturalKey[0]);
|
||||
} else {
|
||||
return eqList.get(0).property.equals(naturalKey[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// multiple properties case
|
||||
Set<String> exprProps = new HashSet<>();
|
||||
if (inProperty != null) {
|
||||
exprProps.add(inProperty);
|
||||
}
|
||||
if (eqList != null) {
|
||||
for (NaturalKeyEq eq : eqList) {
|
||||
exprProps.add(eq.property);
|
||||
}
|
||||
}
|
||||
if (exprProps.size() != naturalKey.length) {
|
||||
return false;
|
||||
}
|
||||
for (String key : naturalKey) {
|
||||
if (!exprProps.remove(key)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return exprProps.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that all the natural key properties are defined.
|
||||
*/
|
||||
private boolean expressionCount() {
|
||||
|
||||
int defined = (inValues == null) ? 0 : 1;
|
||||
defined += (eqList == null) ? 0 : eqList.size();
|
||||
return defined == naturalKey.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of entries in the IN clause left remaining (to hit the DB with).
|
||||
*/
|
||||
public boolean allHits() {
|
||||
return hitCount > 0
|
||||
&& hitCount == set.size()
|
||||
&& (inValues == null || inValues.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the IN clause removing the hit entry.
|
||||
*/
|
||||
public List<T> removeHits(BeanCacheResult<T> cacheResult) {
|
||||
|
||||
List<BeanCacheResult.Entry<T>> hits = cacheResult.hits();
|
||||
this.hitCount = hits.size();
|
||||
|
||||
List<T> beans = new ArrayList<>(hitCount);
|
||||
|
||||
for (BeanCacheResult.Entry<T> hit : hits) {
|
||||
if (inValues != null) {
|
||||
Object naturalKey = hit.getKey();
|
||||
Object inValue = set.getInValue(naturalKey);
|
||||
inValues.remove(inValue);
|
||||
}
|
||||
beans.add(hit.getBean());
|
||||
}
|
||||
|
||||
return beans;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class NaturalKeySet {
|
||||
|
||||
|
||||
private final Map<Object, NaturalKeyEntry> map = new LinkedHashMap<>();
|
||||
|
||||
public NaturalKeySet() {
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
public void add(NaturalKeyEntry entry) {
|
||||
map.put(entry.key(), entry);
|
||||
}
|
||||
|
||||
public Set<Object> keys() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
public Object getInValue(Object naturalKey) {
|
||||
NaturalKeyEntry entry = map.get(naturalKey);
|
||||
return entry.getInValue();
|
||||
}
|
||||
}
|
||||
@@ -97,4 +97,9 @@ public interface SpiExpression extends Expression {
|
||||
* Return the bind Id value if this is a "equal to" expression for the id property.
|
||||
*/
|
||||
Object getIdEqualTo(String idName);
|
||||
|
||||
/**
|
||||
* Check for match to a natural key query returning false if it doesn't match.
|
||||
*/
|
||||
boolean naturalKey(NaturalKeyQueryData data);
|
||||
}
|
||||
|
||||
@@ -74,5 +74,5 @@ public interface SpiExpressionRequest {
|
||||
/**
|
||||
* Append IN expression taking into account platform and type support for Multi-value.
|
||||
*/
|
||||
void appendInExpression(boolean not, Object[] bindValues);
|
||||
void appendInExpression(boolean not, List<Object> bindValues);
|
||||
}
|
||||
|
||||
@@ -366,6 +366,17 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
*/
|
||||
ManyWhereJoins getManyWhereJoins();
|
||||
|
||||
/**
|
||||
* Reset AUTO mode to OFF for findList(). Expect explicit cache use with findList().
|
||||
*/
|
||||
void resetBeanCacheAutoMode();
|
||||
|
||||
/**
|
||||
* Collect natural key data for this query or null if the query does not match
|
||||
* the requirements of natural key lookup.
|
||||
*/
|
||||
NaturalKeyQueryData<T> naturalKey();
|
||||
|
||||
/**
|
||||
* Return a Natural Key bind parameter if supported by this query.
|
||||
*/
|
||||
@@ -579,27 +590,30 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
boolean hasMaxRowsOrFirstRow();
|
||||
|
||||
/**
|
||||
* Return true if the bean cache should be exclude for query or lazy loading.
|
||||
* Return true if the query should GET against bean cache.
|
||||
*/
|
||||
boolean isExcludeBeanCache();
|
||||
boolean isBeanCacheGet();
|
||||
|
||||
/**
|
||||
* Return true if this query should use the bean cache.
|
||||
* It is not skipped and bean caching is supported.
|
||||
* Return true if the query should PUT against the bean cache.
|
||||
*/
|
||||
boolean isUseBeanCache();
|
||||
boolean isBeanCachePut();
|
||||
|
||||
/**
|
||||
* Return true if the bean cache is being explicitly loaded via RECACHE mode.
|
||||
*/
|
||||
boolean isBeanCacheReload();
|
||||
|
||||
/**
|
||||
* Return the cache mode for using the bean cache (Get and Put).
|
||||
*/
|
||||
CacheMode getUseBeanCache();
|
||||
|
||||
/**
|
||||
* Return the cache mode if this query should use/check the query cache.
|
||||
*/
|
||||
CacheMode getUseQueryCache();
|
||||
|
||||
/**
|
||||
* Return true if the beans from this query should be loaded into the bean
|
||||
* cache.
|
||||
*/
|
||||
boolean isLoadBeanCache();
|
||||
|
||||
/**
|
||||
* Return true if the beans returned by this query should be read only.
|
||||
*/
|
||||
|
||||
@@ -144,7 +144,7 @@ public class BaseQueryTuner {
|
||||
default:
|
||||
// not using autoTune when explicitly loading the l2 bean cache
|
||||
// or when using Versions query
|
||||
return !query.isLoadBeanCache() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
|
||||
return !query.isBeanCacheReload() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public class CacheOptions {
|
||||
private final boolean enableBeanCache;
|
||||
private final boolean enableQueryCache;
|
||||
private final boolean readOnly;
|
||||
private final String naturalKey;
|
||||
private final String[] naturalKey;
|
||||
|
||||
/**
|
||||
* Construct for no caching.
|
||||
@@ -30,7 +30,7 @@ public class CacheOptions {
|
||||
/**
|
||||
* Construct with cache annotation.
|
||||
*/
|
||||
public CacheOptions(Cache cache, String naturalKey) {
|
||||
public CacheOptions(Cache cache, String[] naturalKey) {
|
||||
enableBeanCache = cache.enableBeanCache();
|
||||
enableQueryCache = cache.enableQueryCache();
|
||||
readOnly = cache.readOnly();
|
||||
@@ -61,7 +61,7 @@ public class CacheOptions {
|
||||
/**
|
||||
* Return the natural key property name.
|
||||
*/
|
||||
public String getNaturalKey() {
|
||||
public String[] getNaturalKey() {
|
||||
return naturalKey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1150,7 +1150,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
if (!query.isUseBeanCache() || (t != null && t.isSkipCache())) {
|
||||
if (!query.isBeanCacheGet() || (t != null && t.isSkipCache())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1179,7 +1179,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
|
||||
spiQuery.setType(Type.BEAN);
|
||||
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isLoadBeanCache()) {
|
||||
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isBeanCacheReload()) {
|
||||
// See if we can skip doing the fetch completely by getting the bean from the
|
||||
// persistence context or the bean cache
|
||||
T bean = findIdCheckPersistenceContextAndCache(t, spiQuery, spiQuery.getId());
|
||||
@@ -1217,22 +1217,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return findId(query, transaction);
|
||||
}
|
||||
|
||||
SpiTransaction t = (SpiTransaction) transaction;
|
||||
if (t == null) {
|
||||
t = currentServerTransaction();
|
||||
}
|
||||
if (t == null || !t.isSkipCache()) {
|
||||
id = spiQuery.getBeanDescriptor().cacheNaturalKeyIdLookup(spiQuery);
|
||||
if (id != null) {
|
||||
T bean = findIdCheckPersistenceContextAndCache(t, spiQuery, id);
|
||||
if (bean != null) {
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
if (transaction == null) {
|
||||
transaction = currentServerTransaction();
|
||||
}
|
||||
|
||||
// a query that is expected to return either 0 or 1 beans
|
||||
List<T> list = findList(query, t);
|
||||
List<T> list = findList(query, transaction, true);
|
||||
return extractUnique(list);
|
||||
}
|
||||
|
||||
@@ -1515,13 +1505,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> findList(Query<T> query, Transaction t) {
|
||||
return findList(query, t, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> List<T> findList(Query<T> query, Transaction t, boolean findOne) {
|
||||
|
||||
SpiOrmQueryRequest<T> request = createQueryRequest(Type.LIST, query, t);
|
||||
Object result = request.getFromQueryCache();
|
||||
if (result != null) {
|
||||
return (List<T>) result;
|
||||
if (!findOne) {
|
||||
request.resetBeanCacheAutoMode();
|
||||
Object result = request.getFromQueryCache();
|
||||
if (result != null) {
|
||||
return (List<T>) result;
|
||||
}
|
||||
}
|
||||
if ((t == null || !t.isSkipCache()) && request.getFromBeanCache()) {
|
||||
return request.getBeanCacheHits();
|
||||
}
|
||||
if (request.isUseDocStore()) {
|
||||
return docStore().findList(request);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
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.common.BeanList;
|
||||
import io.ebean.common.CopyOnFirstWriteList;
|
||||
import io.ebean.event.BeanFindController;
|
||||
import io.ebean.event.BeanQueryAdapter;
|
||||
@@ -28,6 +30,9 @@ 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.api.BeanCacheResult;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
import io.ebeaninternal.api.NaturalKeySet;
|
||||
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -73,6 +78,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
|
||||
private SpiQuerySecondary secondaryQueries;
|
||||
|
||||
private List<T> cacheBeans;
|
||||
|
||||
/**
|
||||
* Create the InternalQueryRequest.
|
||||
*/
|
||||
@@ -495,8 +502,75 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
beanDescriptor.putQueryPlan(queryPlanKey, queryPlan);
|
||||
}
|
||||
|
||||
public boolean isUseBeanCache() {
|
||||
return query.isUseBeanCache();
|
||||
@Override
|
||||
public void resetBeanCacheAutoMode() {
|
||||
query.resetBeanCacheAutoMode();
|
||||
}
|
||||
|
||||
public boolean isBeanCachePut() {
|
||||
return query.isBeanCachePut();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge in prior L2 bean cache hits with the query result.
|
||||
*/
|
||||
public void mergeCacheHits(BeanCollection<T> 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<T> orderBy = query.getOrderBy();
|
||||
if (orderBy != null) {
|
||||
beanDescriptor.sort(((BeanList<T>)result).getActualList(), orderBy.toStringFormat());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> getBeanCacheHits() {
|
||||
OrderBy<T> 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<T> 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<T> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -118,6 +118,24 @@ public interface SpiOrmQueryRequest<T> extends DocQueryRequest<T> {
|
||||
*/
|
||||
<A> A getFromQueryCache();
|
||||
|
||||
/**
|
||||
* Maybe hit the bean cache returning true if everything was obtained from the
|
||||
* cache (that there were no misses).
|
||||
*
|
||||
* Do this for findList() on many natural keys or many Ids.
|
||||
*/
|
||||
boolean getFromBeanCache();
|
||||
|
||||
/**
|
||||
* Return the bean cache hits (when all hits / no misses).
|
||||
*/
|
||||
List<T> getBeanCacheHits();
|
||||
|
||||
/**
|
||||
* Reset Bean cache mode AUTO - require explicit setting for bean cache use with findList().
|
||||
*/
|
||||
void resetBeanCacheAutoMode();
|
||||
|
||||
/**
|
||||
* Return the Database platform like clause.
|
||||
*/
|
||||
|
||||
@@ -63,6 +63,7 @@ import io.ebeaninternal.server.persist.DmlUtil;
|
||||
import io.ebeaninternal.server.query.CQueryPlan;
|
||||
import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.api.BeanCacheResult;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import io.ebeaninternal.server.rawsql.SpiRawSql;
|
||||
import io.ebeaninternal.server.text.json.ReadJson;
|
||||
@@ -93,6 +94,7 @@ import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
@@ -1189,6 +1191,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the natural key properties.
|
||||
*/
|
||||
public String[] getNaturalKey() {
|
||||
return cacheHelp.getNaturalKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is bean or query caching for this type.
|
||||
*/
|
||||
@@ -1204,6 +1213,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
return cacheHelp.isBeanCaching();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is a natural key defined for this bean type.
|
||||
*/
|
||||
public boolean isNaturalKeyCaching() {
|
||||
return cacheHelp.isNaturalKeyCaching();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is query caching for this type of bean.
|
||||
*/
|
||||
@@ -1370,10 +1386,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to hit the cache using the natural key.
|
||||
* Use natural key lookup to hit the bean cache.
|
||||
*/
|
||||
public Object cacheNaturalKeyIdLookup(SpiQuery<T> query) {
|
||||
return cacheHelp.naturalKeyIdLookup(query);
|
||||
public BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, Set<Object> keys) {
|
||||
return cacheHelp.naturalKeyLookup(context, keys);
|
||||
}
|
||||
|
||||
public void cacheNaturalKeyPut(Object id, Object newKey) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.api.BeanCacheResult;
|
||||
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
|
||||
import io.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import io.ebeaninternal.server.cache.CachedBeanData;
|
||||
@@ -16,16 +16,18 @@ import io.ebeaninternal.server.cache.SpiCacheManager;
|
||||
import io.ebeaninternal.server.core.CacheOptions;
|
||||
import io.ebeaninternal.server.core.PersistRequest;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
|
||||
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Helper for BeanDescriptor that manages the bean, query and collection caches.
|
||||
@@ -57,7 +59,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
private final String cacheName;
|
||||
|
||||
private final BeanPropertyAssocOne<?>[] propertiesOneImported;
|
||||
private final String naturalKeyProperty;
|
||||
private final String[] naturalKey;
|
||||
|
||||
private final ServerCache beanCache;
|
||||
private final ServerCache naturalKeyCache;
|
||||
@@ -83,7 +85,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
this.cacheOptions = cacheOptions;
|
||||
this.cacheSharableBeans = cacheSharableBeans;
|
||||
this.propertiesOneImported = propertiesOneImported;
|
||||
this.naturalKeyProperty = cacheOptions.getNaturalKey();
|
||||
this.naturalKey = cacheOptions.getNaturalKey();
|
||||
|
||||
if (!cacheOptions.isEnableQueryCache()) {
|
||||
this.queryCache = null;
|
||||
@@ -155,6 +157,13 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
return beanCache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is natural key caching for this type of bean.
|
||||
*/
|
||||
boolean isNaturalKeyCaching() {
|
||||
return naturalKeyCache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there is bean or query caching on this type.
|
||||
*/
|
||||
@@ -162,6 +171,13 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
return beanCache != null || queryCache != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the natural key properties.
|
||||
*/
|
||||
String[] getNaturalKey() {
|
||||
return naturalKey;
|
||||
}
|
||||
|
||||
CacheOptions getCacheOptions() {
|
||||
return cacheOptions;
|
||||
}
|
||||
@@ -314,36 +330,49 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the bean using the natural key lookup if available.
|
||||
* Use natural keys to hit the bean cache and return resulting hits.
|
||||
*/
|
||||
Object naturalKeyIdLookup(SpiQuery<T> query) {
|
||||
BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, Set<Object> keys) {
|
||||
|
||||
if (!isNaturalKeyCaching(query.isUseBeanCache())) {
|
||||
// no natural key caching for this query
|
||||
return null;
|
||||
if (context == null) {
|
||||
context = new DefaultPersistenceContext();
|
||||
}
|
||||
|
||||
// check if it is a find by unique id (using the natural key)
|
||||
NaturalKeyBindParam keyBindParam = query.getNaturalKeyBindParam();
|
||||
if (keyBindParam == null || !isNaturalKey(keyBindParam.getName())) {
|
||||
// query is not appropriate
|
||||
return null;
|
||||
}
|
||||
// naturalKey -> Id map
|
||||
Map<Object, Object> naturalKeyMap = naturalKeyCache.getAll(keys);
|
||||
|
||||
// try to lookup the id using the natural key
|
||||
Object id = naturalKeyCache.get(keyBindParam.getValue());
|
||||
if (natLog.isTraceEnabled()) {
|
||||
natLog.trace(" LOOKUP {}({}) - id:{}", cacheName, keyBindParam.getValue(), id);
|
||||
natLog.trace(" LOOKUP Many {}({}) - hits:{}", cacheName, keys, naturalKeyMap);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private boolean isNaturalKeyCaching(Boolean queryUseCache) {
|
||||
return naturalKeyCache != null && (queryUseCache == null || queryUseCache);
|
||||
}
|
||||
BeanCacheResult<T> result = new BeanCacheResult<>();
|
||||
if (naturalKeyMap.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isNaturalKey(String propName) {
|
||||
return propName != null && propName.equals(cacheOptions.getNaturalKey());
|
||||
// create reverse id -> natural key map
|
||||
Map<Object, Object> reverseMap = new HashMap<>();
|
||||
for (Map.Entry<Object, Object> entry : naturalKeyMap.entrySet()) {
|
||||
reverseMap.put(entry.getValue(), entry.getKey());
|
||||
}
|
||||
|
||||
Set<Object> ids = new HashSet<>(naturalKeyMap.values());
|
||||
Map<Object, Object> beanDataMap = beanCache.getAll(ids);
|
||||
if (beanLog.isTraceEnabled()) {
|
||||
beanLog.trace(" GET MANY {}({}) - hits:{}", cacheName, ids, beanDataMap.keySet());
|
||||
}
|
||||
// process the hits into beans etc
|
||||
for (Map.Entry<Object, Object> entry : beanDataMap.entrySet()) {
|
||||
|
||||
Object id = entry.getKey();
|
||||
CachedBeanData cachedBeanData = (CachedBeanData) entry.getValue();
|
||||
|
||||
T bean = convertToBean(id, false, context, cachedBeanData);
|
||||
Object naturalKey = reverseMap.get(id);
|
||||
result.add(bean, naturalKey);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -414,8 +443,8 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
getBeanCache().put(id, beanData);
|
||||
|
||||
if (naturalKeyProperty != null) {
|
||||
Object naturalKey = beanData.getData(naturalKeyProperty);
|
||||
if (naturalKey != null) {
|
||||
Object naturalKey = calculateNaturalKey(beanData);
|
||||
if (naturalKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(" PUT {}({}, {})", cacheName, naturalKey, id);
|
||||
@@ -425,6 +454,21 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private Object calculateNaturalKey(CachedBeanData beanData) {
|
||||
if (naturalKey.length == 1) {
|
||||
return beanData.getData(naturalKey[0]);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String key : naturalKey) {
|
||||
Object val = beanData.getData(key);
|
||||
if (val == null) {
|
||||
return null;
|
||||
}
|
||||
sb.append(val).append(";");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
CachedBeanData beanCacheGetData(Object id) {
|
||||
return (CachedBeanData) getBeanCache().get(id);
|
||||
}
|
||||
@@ -450,6 +494,10 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return convertToBean(id, readOnly, context, data);
|
||||
}
|
||||
|
||||
private T convertToBean(Object id, Boolean readOnly, PersistenceContext context, CachedBeanData data) {
|
||||
if (cacheSharableBeans && !Boolean.FALSE.equals(readOnly)) {
|
||||
Object bean = data.getSharableBean();
|
||||
if (bean != null) {
|
||||
@@ -716,7 +764,7 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
}
|
||||
|
||||
if (updateNaturalKey) {
|
||||
Object oldKey = existingData.getData(naturalKeyProperty);
|
||||
Object oldKey = calculateNaturalKey(existingData);
|
||||
if (oldKey != null) {
|
||||
if (natLog.isDebugEnabled()) {
|
||||
natLog.debug(".. update {} REMOVE({}) - old key for ({})", cacheName, oldKey, id);
|
||||
|
||||
@@ -405,17 +405,17 @@ public class DeployBeanDescriptor<T> {
|
||||
*/
|
||||
public void setCache(Cache cache) {
|
||||
|
||||
String naturalKey = null;
|
||||
if (!cache.naturalKey().isEmpty()) {
|
||||
// find the property and mark as natural key property
|
||||
String propName = cache.naturalKey().trim();
|
||||
DeployBeanProperty beanProperty = getBeanProperty(propName);
|
||||
String[] properties = cache.naturalKey();
|
||||
for (String property : properties) {
|
||||
DeployBeanProperty beanProperty = getBeanProperty(property);
|
||||
if (beanProperty != null) {
|
||||
beanProperty.setNaturalKey();
|
||||
naturalKey = propName;
|
||||
}
|
||||
}
|
||||
this.cacheOptions = new CacheOptions(cache, naturalKey);
|
||||
if (properties.length == 0) {
|
||||
properties = null;
|
||||
}
|
||||
this.cacheOptions = new CacheOptions(cache, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
/**
|
||||
* Base class for simple expressions.
|
||||
@@ -21,6 +22,12 @@ public abstract class AbstractExpression implements SpiExpression {
|
||||
this.propName = propName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// by default can't use naturalKey cache
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
// do nothing
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.ebean.ExampleExpression;
|
||||
import io.ebean.LikeType;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.api.ManyWhereJoins;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
@@ -11,7 +12,7 @@ import io.ebeaninternal.api.SpiExpressionValidation;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -87,6 +88,12 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// can't use naturalKey cache
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
// do nothing
|
||||
|
||||
@@ -27,6 +27,7 @@ import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.api.SpiExpressionValidation;
|
||||
import io.ebeaninternal.api.SpiJunction;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Timestamp;
|
||||
@@ -123,6 +124,12 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return new JunctionExpression<>(Junction.Type.FILTER, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// can't use naturalKey cache
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
simplifyEntries();
|
||||
|
||||
@@ -161,7 +161,7 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendInExpression(boolean not, Object[] bindValues) {
|
||||
public void appendInExpression(boolean not, List<Object> bindValues) {
|
||||
append(binder.getInExpression(not, bindValues));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.ebeaninternal.api.SpiExpressionValidation;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.query.CQuery;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -35,6 +36,12 @@ class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpress
|
||||
this.subQuery = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// can't use naturalKey cache
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
// do nothing
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.persist.MultiValueWrapper;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@@ -20,7 +21,7 @@ class InExpression extends AbstractExpression {
|
||||
|
||||
private final Collection<?> sourceValues;
|
||||
|
||||
private Object[] bindValues;
|
||||
private List<Object> bindValues;
|
||||
|
||||
private boolean multiValueSupported;
|
||||
|
||||
@@ -36,25 +37,31 @@ class InExpression extends AbstractExpression {
|
||||
this.not = not;
|
||||
}
|
||||
|
||||
private Object[] values() {
|
||||
private List<Object> values() {
|
||||
List<Object> vals = new ArrayList<>(sourceValues.size());
|
||||
for (Object sourceValue : sourceValues) {
|
||||
NamedParamHelp.valueAdd(vals, sourceValue);
|
||||
}
|
||||
return vals.toArray();
|
||||
return vals;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// can't use naturalKey cache for NOT IN
|
||||
return !not && data.matchIn(propName, bindValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareExpression(BeanQueryRequest<?> request) {
|
||||
bindValues = values();
|
||||
if (bindValues.length > 0) {
|
||||
multiValueSupported = request.isMultiValueSupported((bindValues[0]).getClass());
|
||||
if (bindValues.size() > 0) {
|
||||
multiValueSupported = request.isMultiValueSupported((bindValues.get(0)).getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDocQuery(DocQueryContext context) throws IOException {
|
||||
context.writeIn(propName, values(), not);
|
||||
context.writeIn(propName, values().toArray(), not);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,10 +73,10 @@ class InExpression extends AbstractExpression {
|
||||
}
|
||||
|
||||
if (prop == null) {
|
||||
if (bindValues.length > 0) {
|
||||
if (bindValues.size() > 0) {
|
||||
// if we have no property, we wrap them in a multi value wrapper.
|
||||
// later the binder will decide, which bind strategy to use.
|
||||
request.addBindValue(new MultiValueWrapper(Arrays.asList(bindValues)));
|
||||
request.addBindValue(new MultiValueWrapper(bindValues));
|
||||
}
|
||||
} else {
|
||||
List<Object> idList = new ArrayList<>();
|
||||
@@ -89,7 +96,7 @@ class InExpression extends AbstractExpression {
|
||||
@Override
|
||||
public void addSql(SpiExpressionRequest request) {
|
||||
|
||||
if (bindValues.length == 0) {
|
||||
if (bindValues.isEmpty()) {
|
||||
String expr = not ? "1=1" : "1=0";
|
||||
request.append(expr);
|
||||
return;
|
||||
@@ -102,7 +109,7 @@ class InExpression extends AbstractExpression {
|
||||
|
||||
if (prop != null) {
|
||||
request.append(prop.getAssocIdInExpr(propName));
|
||||
String inClause = prop.getAssocIdInValueExpr(not, bindValues.length);
|
||||
String inClause = prop.getAssocIdInValueExpr(not, bindValues.size());
|
||||
request.append(inClause);
|
||||
|
||||
} else {
|
||||
@@ -125,7 +132,7 @@ class InExpression extends AbstractExpression {
|
||||
builder.append(" ?");
|
||||
if (!multiValueSupported) {
|
||||
// query plan specific to the number of parameters in the IN clause
|
||||
builder.append(bindValues.length);
|
||||
builder.append(bindValues.size());
|
||||
}
|
||||
builder.append("]");
|
||||
}
|
||||
@@ -142,11 +149,11 @@ class InExpression extends AbstractExpression {
|
||||
@Override
|
||||
public boolean isSameByBind(SpiExpression other) {
|
||||
InExpression that = (InExpression) other;
|
||||
if (this.bindValues.length != that.bindValues.length) {
|
||||
if (this.bindValues.size() != that.bindValues.size()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < bindValues.length; i++) {
|
||||
if (!bindValues[i].equals(that.bindValues[i])) {
|
||||
for (int i = 0; i < bindValues.size(); i++) {
|
||||
if (!bindValues.get(i).equals(that.bindValues.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.api.SpiExpressionValidation;
|
||||
import io.ebeaninternal.api.SpiJunction;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.Timestamp;
|
||||
@@ -58,6 +59,12 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
|
||||
this.exprList = exprList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// can't use naturalKey cache
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplify nested expressions where possible.
|
||||
* <p>
|
||||
|
||||
@@ -8,6 +8,7 @@ import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.api.SpiExpressionValidation;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -56,6 +57,12 @@ abstract class LogicExpression implements SpiExpression {
|
||||
this.expTwo = (SpiExpression) expTwo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// can't use naturalKey cache
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
// do nothing
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.api.SpiExpressionValidation;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -23,6 +24,12 @@ class NestedPathWrapperExpression implements SpiExpression {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// can't use naturalKey cache
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
// do nothing
|
||||
@@ -59,7 +66,7 @@ class NestedPathWrapperExpression implements SpiExpression {
|
||||
@Override
|
||||
public void queryPlanHash(StringBuilder builder) {
|
||||
builder.append("NestedPath[");
|
||||
if (nestedPath != null){
|
||||
if (nestedPath != null) {
|
||||
builder.append("path:").append(nestedPath).append(" ");
|
||||
}
|
||||
delegate.queryPlanHash(builder);
|
||||
|
||||
@@ -2,12 +2,19 @@ package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
/**
|
||||
* Base abstract expression that does nothing for prepareExpression().
|
||||
*/
|
||||
abstract class NonPrepareExpression implements SpiExpression {
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// can't use naturalKey cache
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
// do nothing
|
||||
|
||||
@@ -6,6 +6,7 @@ import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.api.SpiExpressionValidation;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -16,6 +17,12 @@ class NoopExpression implements SpiExpression {
|
||||
|
||||
protected static final NoopExpression INSTANCE = new NoopExpression();
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// can't use naturalKey cache
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
// do nothing
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.api.SpiExpressionValidation;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -21,6 +22,12 @@ final class NotExpression implements SpiExpression {
|
||||
this.exp = (SpiExpression) exp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// can't use naturalKey cache
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simplify() {
|
||||
// do nothing
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.ebean.plugin.ExpressionPath;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
@@ -26,6 +27,15 @@ public class SimpleExpression extends AbstractValueExpression {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData data) {
|
||||
// can't use naturalKey cache for NOT IN
|
||||
if (type != Op.EQ) {
|
||||
return false;
|
||||
}
|
||||
return data.matchEq(propName, bindValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDocQuery(DocQueryContext context) throws IOException {
|
||||
if (type == Op.BETWEEN) {
|
||||
|
||||
@@ -39,9 +39,7 @@ public abstract class DLoadBaseContext {
|
||||
this.desc = desc;
|
||||
this.queryProps = queryProps;
|
||||
this.fullPath = parent.getFullPath(path);
|
||||
|
||||
this.hitCache = !parent.isExcludeBeanCache() && desc.isBeanCaching();
|
||||
|
||||
this.hitCache = parent.isBeanCacheGet() && desc.isBeanCaching();
|
||||
this.objectGraphNode = parent.getObjectGraphNode(path);
|
||||
|
||||
this.queryFetch = queryProps != null && queryProps.isQueryFetch();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.loadcontext;
|
||||
|
||||
import io.ebean.CacheMode;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.CallStack;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
@@ -41,7 +42,7 @@ public class DLoadContext implements LoadContext {
|
||||
private final boolean asDraft;
|
||||
private final Timestamp asOf;
|
||||
private final Boolean readOnly;
|
||||
private final boolean excludeBeanCache;
|
||||
private final CacheMode useBeanCache;
|
||||
private final int defaultBatchSize;
|
||||
private final boolean disableLazyLoading;
|
||||
private final boolean disableReadAudit;
|
||||
@@ -74,7 +75,7 @@ public class DLoadContext implements LoadContext {
|
||||
this.persistenceContext = persistenceContext;
|
||||
this.origin = initOrigin();
|
||||
this.defaultBatchSize = 100;
|
||||
this.excludeBeanCache = false;
|
||||
this.useBeanCache = CacheMode.OFF;
|
||||
this.asDraft = false;
|
||||
this.asOf = null;
|
||||
this.readOnly = false;
|
||||
@@ -107,7 +108,7 @@ public class DLoadContext implements LoadContext {
|
||||
this.readOnly = query.isReadOnly();
|
||||
this.disableReadAudit = query.isDisableReadAudit();
|
||||
this.disableLazyLoading = query.isDisableLazyLoading();
|
||||
this.excludeBeanCache = query.isExcludeBeanCache();
|
||||
this.useBeanCache = query.getUseBeanCache();
|
||||
this.useProfiling = query.getProfilingListener() != null;
|
||||
|
||||
ObjectGraphNode parentNode = query.getParentNode();
|
||||
@@ -158,8 +159,8 @@ public class DLoadContext implements LoadContext {
|
||||
registerSecondaryNode(many, props);
|
||||
}
|
||||
|
||||
protected boolean isExcludeBeanCache() {
|
||||
return excludeBeanCache;
|
||||
protected boolean isBeanCacheGet() {
|
||||
return useBeanCache.isGet();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -229,9 +229,9 @@ public class Binder {
|
||||
/**
|
||||
* Return the SQL in clause taking into account Multi-value support.
|
||||
*/
|
||||
public String getInExpression(boolean not, Object[] bindValues) {
|
||||
ScalarType<?> type = getScalarType(bindValues[0].getClass());
|
||||
return multiValueBind.getInExpression(not, type, bindValues.length);
|
||||
public String getInExpression(boolean not, List<Object> bindValues) {
|
||||
ScalarType<?> type = getScalarType(bindValues.get(0).getClass());
|
||||
return multiValueBind.getInExpression(not, type, bindValues.size());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -151,7 +151,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
|
||||
|
||||
SpiQuery<T> query = request.getQuery();
|
||||
|
||||
if (query.isLoadBeanCache()) {
|
||||
if (query.isBeanCachePut()) {
|
||||
// load the individual beans into the bean cache
|
||||
BeanDescriptor<T> descriptor = request.getBeanDescriptor();
|
||||
Collection<T> c = result.getActualDetails();
|
||||
@@ -160,6 +160,8 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
|
||||
}
|
||||
}
|
||||
|
||||
request.mergeCacheHits(result);
|
||||
|
||||
if (!result.isEmpty() && query.getUseQueryCache().isPut()) {
|
||||
// load the query result into the query cache
|
||||
result.setReadOnly(true);
|
||||
@@ -189,7 +191,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
|
||||
result = queryEngine.find(request);
|
||||
}
|
||||
|
||||
if (result != null && request.isUseBeanCache()) {
|
||||
if (result != null && request.isBeanCachePut()) {
|
||||
request.getBeanDescriptor().cacheBeanPut((EntityBean) result);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import io.ebeaninternal.api.BindParams;
|
||||
import io.ebeaninternal.api.CQueryPlanKey;
|
||||
import io.ebeaninternal.api.HashQuery;
|
||||
import io.ebeaninternal.api.ManyWhereJoins;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionList;
|
||||
import io.ebeaninternal.api.SpiExpressionValidation;
|
||||
@@ -193,9 +194,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
|
||||
private boolean usageProfiling = true;
|
||||
|
||||
private boolean loadBeanCache;
|
||||
|
||||
private boolean excludeBeanCache;
|
||||
private CacheMode useBeanCache = CacheMode.AUTO;
|
||||
|
||||
private CacheMode useQueryCache = CacheMode.OFF;
|
||||
|
||||
@@ -375,6 +374,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
@Override
|
||||
public DefaultOrmQuery<T> asDraft() {
|
||||
this.temporalMode = TemporalMode.DRAFT;
|
||||
this.useBeanCache = CacheMode.OFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -641,6 +641,28 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return !distinct && !singleAttribute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NaturalKeyQueryData<T> naturalKey() {
|
||||
|
||||
if (whereExpressions == null) {
|
||||
return null;
|
||||
}
|
||||
String[] naturalKey = beanDescriptor.getNaturalKey();
|
||||
if (naturalKey == null || naturalKey.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
NaturalKeyQueryData<T> data = new NaturalKeyQueryData<>(naturalKey);
|
||||
for (SpiExpression expression : whereExpressions.getUnderlyingList()) {
|
||||
// must be eq or in
|
||||
if (!expression.naturalKey(data)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NaturalKeyBindParam getNaturalKeyBindParam() {
|
||||
NaturalKeyBindParam namedBind = null;
|
||||
@@ -685,15 +707,13 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
copy.m2mIncludeJoin = m2mIncludeJoin;
|
||||
copy.profilingListener = profilingListener;
|
||||
|
||||
// copy.query = query;
|
||||
copy.rootTableAlias = rootTableAlias;
|
||||
copy.distinct = distinct;
|
||||
copy.sqlDistinct = sqlDistinct;
|
||||
copy.timeout = timeout;
|
||||
copy.mapKey = mapKey;
|
||||
copy.id = id;
|
||||
copy.loadBeanCache = loadBeanCache;
|
||||
copy.excludeBeanCache = excludeBeanCache;
|
||||
copy.useBeanCache = useBeanCache;
|
||||
copy.useQueryCache = useQueryCache;
|
||||
copy.readOnly = readOnly;
|
||||
if (detail != null) {
|
||||
@@ -833,7 +853,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
@Override
|
||||
public DefaultOrmQuery<T> setForUpdate(boolean forUpdate) {
|
||||
this.forUpdate = (forUpdate) ? ForUpdate.BASE : null;
|
||||
this.excludeBeanCache = true;
|
||||
this.useBeanCache = CacheMode.OFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -854,7 +874,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
|
||||
private DefaultOrmQuery<T> setForUpdateWithMode(ForUpdate mode) {
|
||||
this.forUpdate = mode;
|
||||
this.excludeBeanCache = true;
|
||||
this.useBeanCache = CacheMode.OFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1083,29 +1103,40 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExcludeBeanCache() {
|
||||
// not using L2 cache for asDraft() query
|
||||
return excludeBeanCache || isAsDraft();
|
||||
public boolean isBeanCachePut() {
|
||||
return beanDescriptor.isBeanCaching() && useBeanCache.isPut();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUseBeanCache() {
|
||||
return !isExcludeBeanCache() && beanDescriptor.isBeanCaching();
|
||||
public boolean isBeanCacheGet() {
|
||||
return beanDescriptor.isBeanCaching() && useBeanCache.isGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheMode getUseQueryCache() {
|
||||
// not using L2 cache for asDraft() query
|
||||
if (isAsDraft()) {
|
||||
return CacheMode.OFF;
|
||||
} else {
|
||||
return useQueryCache;
|
||||
public boolean isBeanCacheReload() {
|
||||
return CacheMode.RECACHE == useBeanCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBeanCacheAutoMode() {
|
||||
if (useBeanCache == CacheMode.AUTO) {
|
||||
useBeanCache = CacheMode.OFF;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheMode getUseBeanCache() {
|
||||
return useBeanCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheMode getUseQueryCache() {
|
||||
return useQueryCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultOrmQuery<T> setUseCache(boolean useCache) {
|
||||
this.excludeBeanCache = !useCache;
|
||||
this.useBeanCache = (useCache) ? CacheMode.ON: CacheMode.OFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1115,15 +1146,9 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoadBeanCache() {
|
||||
// not using L2 cache for asDraft() query
|
||||
return !isAsDraft() && loadBeanCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultOrmQuery<T> setLoadBeanCache(boolean loadBeanCache) {
|
||||
this.loadBeanCache = loadBeanCache;
|
||||
this.useBeanCache = CacheMode.RECACHE;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user