mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#919 - io.ebean package initial
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Wrapper of the list of Id's.
|
||||
*/
|
||||
public class BeanIdList {
|
||||
|
||||
private final List<Object> idList;
|
||||
|
||||
private boolean hasMore;
|
||||
|
||||
public BeanIdList(List<Object> idList) {
|
||||
this.idList = idList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an Id to the list.
|
||||
*/
|
||||
public void add(Object id) {
|
||||
idList.add(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of Id's.
|
||||
*/
|
||||
public List<Object> getIdList() {
|
||||
return idList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if max rows was hit and there is more rows to fetch.
|
||||
*/
|
||||
public boolean isHasMore() {
|
||||
return hasMore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true when max rows is hit and there are more rows to fetch.
|
||||
*/
|
||||
public void setHasMore(boolean hasMore) {
|
||||
this.hasMore = hasMore;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Parameters used for binding to a statement.
|
||||
* <p>
|
||||
* Supports ordered or named parameters.
|
||||
* </p>
|
||||
*/
|
||||
public class BindParams implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 4541081933302086285L;
|
||||
|
||||
private final List<Param> positionedParameters = new ArrayList<>();
|
||||
|
||||
private final Map<String, Param> namedParameters = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* This is the sql. For named parameters this is the sql after the named
|
||||
* parameters have been replaced with question mark place holders and the
|
||||
* parameters have been ordered by addNamedParamInOrder().
|
||||
*/
|
||||
private String preparedSql;
|
||||
|
||||
/**
|
||||
* Bind hash and count used to detect when the bind values have changed such
|
||||
* that the generated SQL (with named parameters) needs to be recalculated.
|
||||
*/
|
||||
private int[] bindHash;
|
||||
|
||||
public BindParams() {
|
||||
}
|
||||
|
||||
public int queryBindHash() {
|
||||
int hc = namedParameters.hashCode();
|
||||
for (Param positionedParameter : positionedParameters) {
|
||||
hc = hc * 92821 + positionedParameter.hashCode();
|
||||
}
|
||||
return hc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash that should be included with the query plan.
|
||||
* <p>
|
||||
* This is to handle binding collections to in clauses. The number of values
|
||||
* in the collection effects the query (number of bind values) and so must be
|
||||
* taken into account when calculating the query hash.
|
||||
* </p>
|
||||
*/
|
||||
public void buildQueryPlanHash(HashQueryPlanBuilder builder) {
|
||||
int[] vals = calcQueryPlanHash();
|
||||
builder.add(vals[0]).bind(vals[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate and return a query plan bind hash with total bind count.
|
||||
*/
|
||||
public int[] calcQueryPlanHash() {
|
||||
int tempBindCount;
|
||||
int bc = 0;
|
||||
int hc = 92821;
|
||||
for (Param param : positionedParameters) {
|
||||
tempBindCount = param.queryBindCount();
|
||||
bc += tempBindCount;
|
||||
hc = hc * 92821 + tempBindCount;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Param> entry : namedParameters.entrySet()) {
|
||||
tempBindCount = entry.getValue().queryBindCount();
|
||||
bc += tempBindCount;
|
||||
hc = hc * 92821 + entry.getKey().hashCode();
|
||||
hc = hc * 92821 + tempBindCount;
|
||||
}
|
||||
|
||||
return new int[]{hc, bc};
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a deep copy of the BindParams.
|
||||
*/
|
||||
public BindParams copy() {
|
||||
BindParams copy = new BindParams();
|
||||
for (Param p : positionedParameters) {
|
||||
copy.positionedParameters.add(p.copy());
|
||||
}
|
||||
for (Entry<String, Param> entry : namedParameters.entrySet()) {
|
||||
copy.namedParameters.put(entry.getKey(), entry.getValue().copy());
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there are no bind parameters.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return positionedParameters.isEmpty() && namedParameters.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Natural Key bind param if supported.
|
||||
*/
|
||||
public NaturalKeyBindParam getNaturalKeyBindParam() {
|
||||
if (positionedParameters != null) {
|
||||
return null;
|
||||
}
|
||||
if (namedParameters != null && namedParameters.size() == 1) {
|
||||
Entry<String, Param> e = namedParameters.entrySet().iterator().next();
|
||||
return new NaturalKeyBindParam(e.getKey(), e.getValue().getInValue());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return positionedParameters.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if named parameters are being used and they have not yet been
|
||||
* ordered. The sql needs to be prepared (named replaced with ?) and the
|
||||
* parameters ordered.
|
||||
*/
|
||||
public boolean requiresNamedParamsPrepare() {
|
||||
return !namedParameters.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a null parameter using position.
|
||||
*/
|
||||
public void setNullParameter(int position, int jdbcType) {
|
||||
Param p = getParam(position);
|
||||
p.setInNullType(jdbcType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an In Out parameter using position.
|
||||
*/
|
||||
public void setParameter(int position, Object value, int outType) {
|
||||
|
||||
Param p = getParam(position);
|
||||
p.setInValue(value);
|
||||
p.setOutType(outType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Using position set the In value of a parameter. Note that for nulls you
|
||||
* must use setNullParameter.
|
||||
*/
|
||||
public void setParameter(int position, Object value) {
|
||||
|
||||
Param p = getParam(position);
|
||||
p.setInValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the parameter as an Out parameter using position.
|
||||
*/
|
||||
public void registerOut(int position, int outType) {
|
||||
Param p = getParam(position);
|
||||
p.setOutType(outType);
|
||||
}
|
||||
|
||||
private Param getParam(String name) {
|
||||
Param p = namedParameters.get(name);
|
||||
if (p == null) {
|
||||
p = new Param();
|
||||
namedParameters.put(name, p);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
private Param getParam(int position) {
|
||||
int more = position - positionedParameters.size();
|
||||
if (more > 0) {
|
||||
for (int i = 0; i < more; i++) {
|
||||
positionedParameters.add(new Param());
|
||||
}
|
||||
}
|
||||
return positionedParameters.get(position - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a named In Out parameter.
|
||||
*/
|
||||
public void setParameter(String name, Object value, int outType) {
|
||||
|
||||
Param p = getParam(name);
|
||||
p.setInValue(value);
|
||||
p.setOutType(outType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a named In parameter that is null.
|
||||
*/
|
||||
public void setNullParameter(String name, int jdbcType) {
|
||||
Param p = getParam(name);
|
||||
p.setInNullType(jdbcType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a named In parameter that is not null.
|
||||
*/
|
||||
public Param setParameter(String name, Object value) {
|
||||
|
||||
Param p = getParam(name);
|
||||
p.setInValue(value);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an encryption key as a bind value.
|
||||
* <p>
|
||||
* Needs special treatment as the value should not be included in a log.
|
||||
* </p>
|
||||
*/
|
||||
public Param setEncryptionKey(String name, Object value) {
|
||||
Param p = getParam(name);
|
||||
p.setEncryptionKey(value);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the named parameter as an Out parameter.
|
||||
*/
|
||||
public void registerOut(String name, int outType) {
|
||||
Param p = getParam(name);
|
||||
p.setOutType(outType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Parameter for a given position.
|
||||
*/
|
||||
public Param getParameter(int position) {
|
||||
// Used to read Out value by CallableSql
|
||||
return getParam(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the named parameter.
|
||||
*/
|
||||
public Param getParameter(String name) {
|
||||
return getParam(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the values of ordered parameters.
|
||||
*/
|
||||
public List<Param> positionedParameters() {
|
||||
return positionedParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sql with named parameters replaced with place holder ?.
|
||||
*/
|
||||
public void setPreparedSql(String preparedSql) {
|
||||
this.preparedSql = preparedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sql with ? place holders (named parameters have been processed
|
||||
* and ordered).
|
||||
*/
|
||||
public String getPreparedSql() {
|
||||
return preparedSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bind hash and count has not changed.
|
||||
*/
|
||||
public boolean isSameBindHash() {
|
||||
|
||||
if (bindHash == null) {
|
||||
bindHash = calcQueryPlanHash();
|
||||
return false;
|
||||
}
|
||||
int[] oldPlan = bindHash;
|
||||
bindHash = calcQueryPlanHash();
|
||||
return bindHash[0] == oldPlan[0] && bindHash[1] == oldPlan[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new positioned parameters orderedList.
|
||||
*/
|
||||
public OrderedList createOrderedList() {
|
||||
positionedParameters.clear();
|
||||
return new OrderedList(positionedParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* The bind parameters in the correct binding order.
|
||||
* <p>
|
||||
* This is the result of converting sql with named parameters
|
||||
* into sql with ? and ordered parameters.
|
||||
* </p>
|
||||
*/
|
||||
public static final class OrderedList {
|
||||
|
||||
private final List<Param> paramList;
|
||||
|
||||
private final StringBuilder preparedSql;
|
||||
|
||||
public OrderedList() {
|
||||
this(new ArrayList<>());
|
||||
}
|
||||
|
||||
public OrderedList(List<Param> paramList) {
|
||||
this.paramList = paramList;
|
||||
this.preparedSql = new StringBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a parameter in the correct binding order.
|
||||
*/
|
||||
public void add(Param param) {
|
||||
paramList.add(param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of bind parameters in this list.
|
||||
*/
|
||||
public int size() {
|
||||
return paramList.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ordered list of bind parameters.
|
||||
*/
|
||||
public List<Param> list() {
|
||||
return paramList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append parsedSql that has named parameters converted into ?.
|
||||
*/
|
||||
public void appendSql(String parsedSql) {
|
||||
preparedSql.append(parsedSql);
|
||||
}
|
||||
|
||||
public String getPreparedSql() {
|
||||
return preparedSql.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A In Out capable parameter for the CallableStatement.
|
||||
*/
|
||||
public static final class Param implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private boolean encryptionKey;
|
||||
|
||||
private boolean isInParam;
|
||||
|
||||
private boolean isOutParam;
|
||||
|
||||
private int type;
|
||||
|
||||
private Object inValue;
|
||||
|
||||
private Object outValue;
|
||||
|
||||
/**
|
||||
* Construct a Parameter.
|
||||
*/
|
||||
public Param() {
|
||||
}
|
||||
|
||||
public int queryBindCount() {
|
||||
if (inValue == null) {
|
||||
return 0;
|
||||
}
|
||||
if (inValue instanceof Collection<?>) {
|
||||
return ((Collection<?>) inValue).size();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deep copy of the Param.
|
||||
*/
|
||||
public Param copy() {
|
||||
Param copy = new Param();
|
||||
copy.isInParam = isInParam;
|
||||
copy.isOutParam = isOutParam;
|
||||
copy.type = type;
|
||||
copy.inValue = inValue;
|
||||
copy.outValue = outValue;
|
||||
return copy;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int hc = getClass().hashCode();
|
||||
hc = hc * 92821 + (isInParam ? 0 : 1);
|
||||
hc = hc * 92821 + (isOutParam ? 0 : 1);
|
||||
hc = hc * 92821 + (type);
|
||||
hc = hc * 92821 + (inValue == null ? 0 : inValue.hashCode());
|
||||
return hc;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
return o != null && (o == this || (o instanceof Param) && hashCode() == o.hashCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is an In parameter that needs to be bound before
|
||||
* execution.
|
||||
*/
|
||||
public boolean isInParam() {
|
||||
return isInParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is an out parameter that needs to be registered
|
||||
* before execution.
|
||||
*/
|
||||
public boolean isOutParam() {
|
||||
return isOutParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the jdbc type of this parameter. Used for registering Out
|
||||
* parameters and setting NULL In parameters.
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Out parameter type.
|
||||
*/
|
||||
public void setOutType(int type) {
|
||||
this.type = type;
|
||||
this.isOutParam = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the In value.
|
||||
*/
|
||||
public void setInValue(Object in) {
|
||||
this.inValue = in;
|
||||
this.isInParam = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an encryption key (which can not be logged).
|
||||
*/
|
||||
public void setEncryptionKey(Object in) {
|
||||
this.inValue = in;
|
||||
this.isInParam = true;
|
||||
this.encryptionKey = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the In parameter is NULL and the specific type that it
|
||||
* is.
|
||||
*/
|
||||
public void setInNullType(int type) {
|
||||
this.type = type;
|
||||
this.inValue = null;
|
||||
this.isInParam = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the OUT value that was retrieved. This value is set after
|
||||
* CallableStatement was executed.
|
||||
*/
|
||||
public Object getOutValue() {
|
||||
return outValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the In value. If this is null, then the type should be used to
|
||||
* specify the type of the null.
|
||||
*/
|
||||
public Object getInValue() {
|
||||
return inValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the OUT value returned by a CallableStatement after it has
|
||||
* executed.
|
||||
*/
|
||||
public void setOutValue(Object out) {
|
||||
this.outValue = out;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true do not include this value in a transaction log.
|
||||
*/
|
||||
public boolean isEncryptionKey() {
|
||||
return encryptionKey;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Key used for caching query plans for ORM and RawSql queries.
|
||||
*/
|
||||
public interface CQueryPlanKey {
|
||||
|
||||
/**
|
||||
* Used by read audit such that we can log read audit entries without the full sql
|
||||
* (which would make the read audit logs verbose).
|
||||
*/
|
||||
String getPartialKey();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
|
||||
/**
|
||||
* Helper to find classes taking into account the context class loader.
|
||||
*/
|
||||
public class ClassUtil {
|
||||
|
||||
/**
|
||||
* Return a new instance of the class using the default constructor.
|
||||
*/
|
||||
public static Object newInstance(String className) {
|
||||
|
||||
try {
|
||||
Class<?> cls = forName(className);
|
||||
return cls.newInstance();
|
||||
} catch (Exception e) {
|
||||
String msg = "Error constructing " + className;
|
||||
throw new IllegalArgumentException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a class taking into account a context class loader (if present).
|
||||
*/
|
||||
public static Class<?> forName(String name) throws ClassNotFoundException {
|
||||
return new ClassLoadContext().forName(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper to wrap the context and caller classLoaders (to use/try both).
|
||||
*/
|
||||
static class ClassLoadContext {
|
||||
|
||||
private final ClassLoader contextLoader;
|
||||
|
||||
private final ClassLoader callerLoader;
|
||||
|
||||
ClassLoadContext() {
|
||||
this.callerLoader = ClassUtil.class.getClassLoader();
|
||||
this.contextLoader = contextLoader();
|
||||
}
|
||||
|
||||
ClassLoader contextLoader() {
|
||||
ClassLoader loader = Thread.currentThread().getContextClassLoader();
|
||||
return (loader != null) ? loader : callerLoader;
|
||||
}
|
||||
|
||||
public Class<?> forName(String name) throws ClassNotFoundException {
|
||||
|
||||
try {
|
||||
return Class.forName(name, true, contextLoader);
|
||||
} catch (ClassNotFoundException e) {
|
||||
if (callerLoader == contextLoader) {
|
||||
throw e;
|
||||
} else {
|
||||
return Class.forName(name, true, callerLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* Optimistic concurrency mode used for updates and deletes.
|
||||
*/
|
||||
public enum ConcurrencyMode {
|
||||
|
||||
/**
|
||||
* No concurrency checking.
|
||||
*/
|
||||
NONE,
|
||||
|
||||
/**
|
||||
* Use a version column.
|
||||
*/
|
||||
VERSION
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* A hash key for a query including both the query plan and bind values.
|
||||
*/
|
||||
public class HashQuery {
|
||||
|
||||
private final CQueryPlanKey planHash;
|
||||
|
||||
private final int bindHash;
|
||||
|
||||
/**
|
||||
* Create the HashQuery.
|
||||
*/
|
||||
public HashQuery(CQueryPlanKey planHash, int bindHash) {
|
||||
this.planHash = planHash;
|
||||
this.bindHash = bindHash;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "HashQuery@" + Integer.toHexString(hashCode());
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int hc = 92821 * planHash.hashCode();
|
||||
hc = 92821 * hc + bindHash;
|
||||
return hc;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof HashQuery)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HashQuery e = (HashQuery) obj;
|
||||
return e.bindHash == bindHash && e.planHash.equals(planHash);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
/**
|
||||
* A hash for a query plan.
|
||||
*/
|
||||
public class HashQueryPlan {
|
||||
|
||||
private final String rawSql;
|
||||
|
||||
private final int planHash;
|
||||
|
||||
private final int bindCount;
|
||||
|
||||
public HashQueryPlan(String rawSql, int planHash, int bindCount) {
|
||||
this.rawSql = rawSql;
|
||||
this.planHash = planHash;
|
||||
this.bindCount = bindCount;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return planHash + ":" + bindCount + (rawSql != null ? ":r" : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return as a partial key. For rawSql hash the sql is part of the key and as such
|
||||
* needs to be included in order to have a complete key. Typically the MD5 of the sql
|
||||
* can be used as a shot form proxy for the actual sql.
|
||||
*/
|
||||
public String getPartialKey() {
|
||||
return planHash + "_" + bindCount;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int hc = planHash;
|
||||
hc = hc * 92821 + bindCount;
|
||||
hc = hc * 92821 + (rawSql == null ? 0 : rawSql.hashCode());
|
||||
return hc;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof HashQueryPlan)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HashQueryPlan e = (HashQueryPlan) obj;
|
||||
//noinspection StringEquality
|
||||
return e.planHash == planHash
|
||||
&& e.bindCount == bindCount
|
||||
&& ((e.rawSql == rawSql) || (e.rawSql != null && e.rawSql.equals(rawSql)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Used to build HashQueryPlan instances.
|
||||
*/
|
||||
public class HashQueryPlanBuilder {
|
||||
|
||||
private int planHash;
|
||||
|
||||
private int bindCount;
|
||||
|
||||
public HashQueryPlanBuilder() {
|
||||
this.planHash = 92821;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return planHash + ":" + bindCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a class to the hash calculation.
|
||||
*/
|
||||
public HashQueryPlanBuilder add(Class<?> cls) {
|
||||
planHash = planHash * 92821 + cls.getName().hashCode();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an object to the hash calculation.
|
||||
*/
|
||||
public HashQueryPlanBuilder add(Object object) {
|
||||
planHash = planHash * 92821 + (object == null ? 0 : object.hashCode());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the set with order being important.
|
||||
*/
|
||||
public HashQueryPlanBuilder addOrdered(Set<?> set) {
|
||||
if (set == null) {
|
||||
add(false);
|
||||
} else {
|
||||
add(true);
|
||||
for (Object o : set) {
|
||||
add(o);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an integer to the hash calculation.
|
||||
*/
|
||||
public HashQueryPlanBuilder add(int hashValue) {
|
||||
planHash = planHash * 92821 + (hashValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a boolean to the hash calculation.
|
||||
*/
|
||||
public HashQueryPlanBuilder add(boolean booleanValue) {
|
||||
planHash = planHash * 92821 + (booleanValue ? 92821 : 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a number to the bind count for the hash.
|
||||
*/
|
||||
public void bind(int extraBindCount) {
|
||||
bindCount += extraBindCount;
|
||||
}
|
||||
|
||||
public void bindIfNotNull(Object someValue) {
|
||||
if (someValue != null) {
|
||||
bindCount++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the calculated HashQueryPlan.
|
||||
*/
|
||||
public String build() {
|
||||
return planHash + "_" + bindCount;
|
||||
}
|
||||
|
||||
public int getPlanHash() {
|
||||
return planHash;
|
||||
}
|
||||
|
||||
public int getBindCount() {
|
||||
return bindCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.TxScope;
|
||||
|
||||
/**
|
||||
* Helper object to make AOP generated code simpler.
|
||||
*/
|
||||
public class HelpScopeTrans {
|
||||
|
||||
/**
|
||||
* Create a ScopeTrans for a given methods TxScope.
|
||||
*/
|
||||
public static ScopeTrans createScopeTrans(TxScope txScope) {
|
||||
|
||||
EbeanServer server = Ebean.getServer(txScope.getServerName());
|
||||
SpiEbeanServer iserver = (SpiEbeanServer) server;
|
||||
return iserver.createScopeTrans(txScope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exiting the method in an expected fashion.
|
||||
* <p>
|
||||
* That is returning successfully or via a caught exception.
|
||||
* Unexpected exceptions are caught via the Thread uncaughtExceptionHandler.
|
||||
* </p>
|
||||
*
|
||||
* @param returnOrThrowable the return or throwable object
|
||||
* @param opCode the opcode for ATHROW or ARETURN etc
|
||||
* @param scopeTrans the scoped transaction the method was run with.
|
||||
*/
|
||||
public static void onExitScopeTrans(Object returnOrThrowable, int opCode, ScopeTrans scopeTrans) {
|
||||
|
||||
scopeTrans.onExit(returnOrThrowable, opCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A buffer of beans for batch lazy loading and secondary query loading.
|
||||
*/
|
||||
public interface LoadBeanBuffer {
|
||||
|
||||
int getBatchSize();
|
||||
|
||||
List<EntityBeanIntercept> getBatch();
|
||||
|
||||
BeanDescriptor<?> getBeanDescriptor();
|
||||
|
||||
PersistenceContext getPersistenceContext();
|
||||
|
||||
String getFullPath();
|
||||
|
||||
void configureQuery(SpiQuery<?> query, String lazyLoadProperty);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
|
||||
/**
|
||||
* Controls the loading of ManyToOne and OneToOne relationships.
|
||||
*/
|
||||
public interface LoadBeanContext extends LoadSecondaryQuery {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Request for loading ManyToOne and OneToOne relationships.
|
||||
*/
|
||||
public class LoadBeanRequest extends LoadRequest {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LoadBeanRequest.class);
|
||||
|
||||
private final List<EntityBeanIntercept> batch;
|
||||
|
||||
private final LoadBeanBuffer loadBuffer;
|
||||
|
||||
private final String lazyLoadProperty;
|
||||
|
||||
private final boolean loadCache;
|
||||
|
||||
/**
|
||||
* Construct for lazy load request.
|
||||
*/
|
||||
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, String lazyLoadProperty, boolean loadCache) {
|
||||
this(LoadBuffer, null, true, lazyLoadProperty, loadCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for secondary query.
|
||||
*/
|
||||
public LoadBeanRequest(LoadBeanBuffer LoadBuffer, OrmQueryRequest<?> parentRequest) {
|
||||
this(LoadBuffer, parentRequest, false, null, false);
|
||||
}
|
||||
|
||||
private LoadBeanRequest(LoadBeanBuffer loadBuffer, OrmQueryRequest<?> parentRequest, boolean lazy,
|
||||
String lazyLoadProperty, boolean loadCache) {
|
||||
|
||||
super(parentRequest, lazy);
|
||||
this.loadBuffer = loadBuffer;
|
||||
this.batch = loadBuffer.getBatch();
|
||||
this.lazyLoadProperty = lazyLoadProperty;
|
||||
this.loadCache = loadCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getBeanType() {
|
||||
return loadBuffer.getBeanDescriptor().getBeanType();
|
||||
}
|
||||
|
||||
public boolean isLoadCache() {
|
||||
return loadCache;
|
||||
}
|
||||
|
||||
|
||||
public String getDescription() {
|
||||
return "path:" + loadBuffer.getFullPath() + " batch:" + batch.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the batch of beans to actually load.
|
||||
*/
|
||||
public List<EntityBeanIntercept> getBatch() {
|
||||
return batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the load context.
|
||||
*/
|
||||
public LoadBeanBuffer getLoadContext() {
|
||||
return loadBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property that invoked the lazy loading.
|
||||
*/
|
||||
public String getLazyLoadProperty() {
|
||||
return lazyLoadProperty;
|
||||
}
|
||||
|
||||
public int getBatchSize() {
|
||||
return getLoadContext().getBatchSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of Id values for the beans in the lazy load buffer.
|
||||
*/
|
||||
public List<Object> getIdList(int batchSize) {
|
||||
|
||||
List<Object> idList = new ArrayList<>(batchSize);
|
||||
|
||||
BeanDescriptor<?> desc = loadBuffer.getBeanDescriptor();
|
||||
for (EntityBeanIntercept ebi : batch) {
|
||||
EntityBean bean = ebi.getOwner();
|
||||
idList.add(desc.getId(bean));
|
||||
}
|
||||
|
||||
if (!idList.isEmpty()) {
|
||||
int extraIds = batchSize - batch.size();
|
||||
if (extraIds > 0) {
|
||||
// for performance make up the Id's to the batch size
|
||||
// so we get the same query (for Ebean and the db)
|
||||
Object firstId = idList.get(0);
|
||||
for (int i = 0; i < extraIds; i++) {
|
||||
// just add the first Id again
|
||||
idList.add(firstId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return idList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the query for lazy loading execution.
|
||||
*/
|
||||
public void configureQuery(SpiQuery<?> query, List<Object> idList) {
|
||||
|
||||
query.setMode(SpiQuery.Mode.LAZYLOAD_BEAN);
|
||||
query.setPersistenceContext(loadBuffer.getPersistenceContext());
|
||||
|
||||
String mode = isLazy() ? "+lazy" : "+query";
|
||||
query.setLoadDescription(mode, getDescription());
|
||||
|
||||
if (isLazy()) {
|
||||
// cascade the batch size (if set) for further lazy loading
|
||||
query.setLazyLoadBatchSize(getBatchSize());
|
||||
}
|
||||
|
||||
loadBuffer.configureQuery(query, lazyLoadProperty);
|
||||
|
||||
if (idList.size() == 1) {
|
||||
query.where().idEq(idList.get(0));
|
||||
} else {
|
||||
query.where().idIn(idList);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the beans into the L2 cache if that is requested and check for load failures due to deletes.
|
||||
*/
|
||||
public void postLoad(List<?> list) {
|
||||
|
||||
Set<Object> loadedIds = new HashSet<>();
|
||||
|
||||
BeanDescriptor<?> desc = loadBuffer.getBeanDescriptor();
|
||||
// collect Ids and maybe load bean cache
|
||||
for (Object aList : list) {
|
||||
EntityBean loadedBean = (EntityBean) aList;
|
||||
loadedIds.add(desc.getId(loadedBean));
|
||||
if (isLoadCache()) {
|
||||
desc.cacheBeanPut(loadedBean);
|
||||
}
|
||||
}
|
||||
|
||||
if (lazyLoadProperty != null) {
|
||||
for (EntityBeanIntercept ebi : batch) {
|
||||
// check if the underlying row in DB was deleted. Mark the bean as 'failed' if
|
||||
// necessary but allow processing to continue until it is accessed by client code
|
||||
Object id = desc.getId(ebi.getOwner());
|
||||
if (!loadedIds.contains(id)) {
|
||||
if (desc.isSoftDelete()) {
|
||||
// assume this is logically deleted (hence not found)
|
||||
desc.setSoftDeleteValue(ebi.getOwner());
|
||||
} else {
|
||||
logger.info("Lazy loading unsuccessful for type:" + desc.getName() + " id:" + id + " - expecting when bean has been deleted");
|
||||
ebi.setLazyLoadFailure(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
|
||||
/**
|
||||
* Controls the loading of reference objects for a query instance.
|
||||
*/
|
||||
public interface LoadContext {
|
||||
|
||||
/**
|
||||
* Return the minimum batch size when using QueryIterator with query joins.
|
||||
*/
|
||||
int getSecondaryQueriesMinBatchSize(int defaultQueryBatch);
|
||||
|
||||
/**
|
||||
* Execute any secondary (+query) queries if there are any defined.
|
||||
*
|
||||
* @param parentRequest the originating query request
|
||||
* @param forEach set true when using findEach iteration
|
||||
*/
|
||||
void executeSecondaryQueries(OrmQueryRequest<?> parentRequest, boolean forEach);
|
||||
|
||||
/**
|
||||
* Return the node for a given path which is used by AutoTune profiling.
|
||||
*/
|
||||
ObjectGraphNode getObjectGraphNode(String path);
|
||||
|
||||
/**
|
||||
* Return the persistence context used by this query and future lazy loading.
|
||||
*/
|
||||
PersistenceContext getPersistenceContext();
|
||||
|
||||
/**
|
||||
* Set the persistence context used by this query and future lazy loading.
|
||||
* <p>
|
||||
* Used by query iterator when processing large result sets.
|
||||
* </p>
|
||||
*/
|
||||
void resetPersistenceContext(PersistenceContext persistenceContext);
|
||||
|
||||
/**
|
||||
* Register a Bean for lazy loading.
|
||||
*/
|
||||
void register(String path, EntityBeanIntercept ebi);
|
||||
|
||||
/**
|
||||
* Register a collection for lazy loading.
|
||||
*/
|
||||
void register(String path, BeanCollection<?> bc);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A buffer of bean collections for batch lazy loading and secondary query loading.
|
||||
*/
|
||||
public interface LoadManyBuffer {
|
||||
|
||||
int getBatchSize();
|
||||
|
||||
List<BeanCollection<?>> getBatch();
|
||||
|
||||
BeanPropertyAssocMany<?> getBeanProperty();
|
||||
|
||||
ObjectGraphNode getObjectGraphNode();
|
||||
|
||||
BeanDescriptor<?> getBeanDescriptor();
|
||||
|
||||
PersistenceContext getPersistenceContext();
|
||||
|
||||
String getFullPath();
|
||||
|
||||
void configureQuery(SpiQuery<?> query);
|
||||
|
||||
boolean isUseDocStore();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
|
||||
/**
|
||||
* Controls the loading of OneToMany and ManyToMany relationships.
|
||||
*/
|
||||
public interface LoadManyContext extends LoadSecondaryQuery {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.lib.util.StringHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Request for loading Associated Many Beans.
|
||||
*/
|
||||
public class LoadManyRequest extends LoadRequest {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LoadManyRequest.class);
|
||||
|
||||
private final List<BeanCollection<?>> batch;
|
||||
|
||||
private final LoadManyBuffer loadContext;
|
||||
|
||||
private final boolean onlyIds;
|
||||
|
||||
private final boolean loadCache;
|
||||
|
||||
/**
|
||||
* Construct for lazy loading.
|
||||
*/
|
||||
public LoadManyRequest(LoadManyBuffer loadContext, boolean onlyIds, boolean loadCache) {
|
||||
this(loadContext, null, true, onlyIds, loadCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for secondary query.
|
||||
*/
|
||||
public LoadManyRequest(LoadManyBuffer loadContext, OrmQueryRequest<?> parentRequest) {
|
||||
this(loadContext, parentRequest, false, false, false);
|
||||
}
|
||||
|
||||
private LoadManyRequest(LoadManyBuffer loadContext, OrmQueryRequest<?> parentRequest, boolean lazy, boolean onlyIds, boolean loadCache) {
|
||||
super(parentRequest, lazy);
|
||||
this.loadContext = loadContext;
|
||||
this.batch = loadContext.getBatch();
|
||||
this.onlyIds = onlyIds;
|
||||
this.loadCache = loadCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getBeanType() {
|
||||
return loadContext.getBeanDescriptor().getBeanType();
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return "path:" + loadContext.getFullPath() + " size:" + batch.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the batch of collections to actually load.
|
||||
*/
|
||||
public List<BeanCollection<?>> getBatch() {
|
||||
return batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the load context.
|
||||
*/
|
||||
public LoadManyBuffer getLoadContext() {
|
||||
return loadContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if lazy loading should only load the id values.
|
||||
* <p>
|
||||
* This for use when lazy loading is invoked on methods such as clear() and removeAll() where it
|
||||
* generally makes sense to only fetch the Id values as the other property information is not
|
||||
* used.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isOnlyIds() {
|
||||
return onlyIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we should load the Collection ids into the cache.
|
||||
*/
|
||||
public boolean isLoadCache() {
|
||||
return loadCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the batch size used for this load context.
|
||||
*/
|
||||
public int getBatchSize() {
|
||||
return loadContext.getBatchSize();
|
||||
}
|
||||
|
||||
private List<Object> getParentIdList(int batchSize) {
|
||||
|
||||
ArrayList<Object> idList = new ArrayList<>(batchSize);
|
||||
|
||||
BeanPropertyAssocMany<?> many = getMany();
|
||||
for (BeanCollection<?> bc : batch) {
|
||||
idList.add(many.getParentId(bc.getOwnerBean()));
|
||||
}
|
||||
int extraIds = batchSize - batch.size();
|
||||
if (extraIds > 0) {
|
||||
Object firstId = idList.get(0);
|
||||
for (int i = 0; i < extraIds; i++) {
|
||||
idList.add(firstId);
|
||||
}
|
||||
}
|
||||
|
||||
return idList;
|
||||
}
|
||||
|
||||
private BeanPropertyAssocMany<?> getMany() {
|
||||
return loadContext.getBeanProperty();
|
||||
}
|
||||
|
||||
public SpiQuery<?> createQuery(EbeanServer server, int batchSize) {
|
||||
|
||||
BeanPropertyAssocMany<?> many = getMany();
|
||||
|
||||
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(many.getTargetType());
|
||||
String orderBy = many.getLazyFetchOrderBy();
|
||||
if (orderBy != null) {
|
||||
query.orderBy(orderBy);
|
||||
}
|
||||
|
||||
String extraWhere = many.getExtraWhere();
|
||||
if (extraWhere != null) {
|
||||
// replace special ${ta} placeholder with the base table alias
|
||||
// which is always t0 and add the extra where clause
|
||||
String ew = StringHelper.replaceString(extraWhere, "${ta}", "t0");
|
||||
query.where().raw(ew);
|
||||
}
|
||||
|
||||
query.setLazyLoadForParents(many);
|
||||
|
||||
List<Object> idList = getParentIdList(batchSize);
|
||||
many.addWhereParentIdIn(query, idList, loadContext.isUseDocStore());
|
||||
|
||||
query.setPersistenceContext(loadContext.getPersistenceContext());
|
||||
|
||||
String mode = isLazy() ? "+lazy" : "+query";
|
||||
query.setLoadDescription(mode, getDescription());
|
||||
|
||||
if (isLazy()) {
|
||||
// cascade the batch size (if set) for further lazy loading
|
||||
query.setLazyLoadBatchSize(getBatchSize());
|
||||
}
|
||||
|
||||
// potentially changes the joins and selected properties
|
||||
loadContext.configureQuery(query);
|
||||
|
||||
if (isOnlyIds()) {
|
||||
// override to just select the Id values
|
||||
query.select(many.getTargetIdProperty());
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* After the query execution check for empty collections and load L2 cache if desired.
|
||||
*/
|
||||
public void postLoad() {
|
||||
|
||||
BeanDescriptor<?> desc = loadContext.getBeanDescriptor();
|
||||
BeanPropertyAssocMany<?> many = getMany();
|
||||
|
||||
// check for BeanCollection's that where never processed
|
||||
// in the +query or +lazy load due to no rows (predicates)
|
||||
for (BeanCollection<?> bc : batch) {
|
||||
if (bc.checkEmptyLazyLoad()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
EntityBean ownerBean = bc.getOwnerBean();
|
||||
Object parentId = desc.getId(ownerBean);
|
||||
logger.debug("BeanCollection after lazy load was empty. type:" + ownerBean.getClass().getName() + " id:" + parentId + " owner:" + ownerBean);
|
||||
}
|
||||
} else if (isLoadCache()) {
|
||||
Object parentId = desc.getId(bc.getOwnerBean());
|
||||
desc.cacheManyPropPut(many, bc, parentId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.Transaction;
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
|
||||
/**
|
||||
* Request for loading Associated One Beans.
|
||||
*/
|
||||
public abstract class LoadRequest {
|
||||
|
||||
protected final OrmQueryRequest<?> parentRequest;
|
||||
|
||||
protected final Transaction transaction;
|
||||
|
||||
protected final boolean lazy;
|
||||
|
||||
public LoadRequest(OrmQueryRequest<?> parentRequest, boolean lazy) {
|
||||
|
||||
this.parentRequest = parentRequest;
|
||||
this.transaction = parentRequest == null ? null : parentRequest.getTransaction();
|
||||
this.lazy = lazy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated bean type for this load request.
|
||||
*/
|
||||
public abstract Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Log the just executed secondary query with the 'root' query if 'logSecondaryQuery' is set to
|
||||
* true. This is for testing purposes to confirm the secondary query executes etc.
|
||||
*/
|
||||
public void logSecondaryQuery(SpiQuery<?> query) {
|
||||
if (parentRequest != null && parentRequest.isLogSecondaryQuery()) {
|
||||
parentRequest.getQuery().logSecondaryQuery(query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a lazy load and false if it is a secondary query.
|
||||
*/
|
||||
public boolean isLazy() {
|
||||
return lazy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the transaction to use if this is a secondary query.
|
||||
* <p>
|
||||
* Lazy loading queries run in their own transaction.
|
||||
* </p>
|
||||
*/
|
||||
public Transaction getTransaction() {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the parent query is a findIterate() type query.
|
||||
* So one of - findIterate(), findEach(), findEachWhile() or findVisit().
|
||||
*/
|
||||
public boolean isParentFindIterate() {
|
||||
return parentRequest != null && parentRequest.getQuery().getType() == SpiQuery.Type.ITERATE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.core.OrmQueryRequest;
|
||||
|
||||
/**
|
||||
* Defines the method for executing secondary queries.
|
||||
* <p>
|
||||
* That is +query nodes in a orm query get executed after
|
||||
* the initial query as 'secondary' queries.
|
||||
* </p>
|
||||
*/
|
||||
public interface LoadSecondaryQuery {
|
||||
|
||||
/**
|
||||
* Execute the secondary query with a given batch size.
|
||||
*/
|
||||
void loadSecondaryQuery(OrmQueryRequest<?> parentRequest, boolean forEach);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import io.ebeaninternal.server.query.SplitName;
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Holds the joins needs to support the many where predicates.
|
||||
* These joins are independent of any 'fetch' joins on the many.
|
||||
*/
|
||||
public class ManyWhereJoins implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -6490181101871795417L;
|
||||
|
||||
private final TreeMap<String, PropertyJoin> joins = new TreeMap<>();
|
||||
|
||||
private StringBuilder formulaProperties = new StringBuilder();
|
||||
|
||||
private boolean formulaWithJoin;
|
||||
|
||||
private boolean aggregation;
|
||||
|
||||
/**
|
||||
* 'Mode' indicating that joins added while this is true are required to be outer joins.
|
||||
*/
|
||||
private boolean requireOuterJoins;
|
||||
|
||||
/**
|
||||
* Return the current 'mode' indicating if outer joins are currently required or not.
|
||||
*/
|
||||
public boolean isRequireOuterJoins() {
|
||||
return requireOuterJoins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'mode' to be that joins added are required to be outer joins.
|
||||
* This is set during the evaluation of disjunction predicates.
|
||||
*/
|
||||
public void setRequireOuterJoins(boolean requireOuterJoins) {
|
||||
this.requireOuterJoins = requireOuterJoins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a many where join.
|
||||
*/
|
||||
public void add(ElPropertyDeploy elProp) {
|
||||
|
||||
String join = elProp.getElPrefix();
|
||||
BeanProperty p = elProp.getBeanProperty();
|
||||
if (p instanceof BeanPropertyAssocMany<?>) {
|
||||
join = addManyToJoin(join, p.getName());
|
||||
}
|
||||
if (join != null) {
|
||||
addJoin(join);
|
||||
if (p != null) {
|
||||
String secondaryTableJoinPrefix = p.getSecondaryTableJoinPrefix();
|
||||
if (secondaryTableJoinPrefix != null) {
|
||||
addJoin(join + "." + secondaryTableJoinPrefix);
|
||||
}
|
||||
}
|
||||
addParentJoins(join);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For 'many' properties we also need to add the name of the
|
||||
* many property to get the full logical name of the join.
|
||||
*/
|
||||
private String addManyToJoin(String join, String manyPropName) {
|
||||
if (join == null) {
|
||||
return manyPropName;
|
||||
} else {
|
||||
return join + "." + manyPropName;
|
||||
}
|
||||
}
|
||||
|
||||
private void addParentJoins(String join) {
|
||||
String[] split = SplitName.split(join);
|
||||
if (split[0] != null) {
|
||||
addJoin(split[0]);
|
||||
addParentJoins(split[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void addJoin(String property) {
|
||||
SqlJoinType joinType = (requireOuterJoins) ? SqlJoinType.OUTER : SqlJoinType.INNER;
|
||||
joins.put(property, new PropertyJoin(property, joinType));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is an aggregation query or if there are no extra many where joins.
|
||||
*/
|
||||
public boolean requireSqlDistinct() {
|
||||
return !aggregation && !joins.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of many where joins.
|
||||
*/
|
||||
public Collection<PropertyJoin> getPropertyJoins() {
|
||||
return joins.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of property names for the many where joins.
|
||||
*/
|
||||
public TreeSet<String> getPropertyNames() {
|
||||
|
||||
TreeSet<String> propertyNames = new TreeSet<>();
|
||||
for (PropertyJoin join : joins.values()) {
|
||||
propertyNames.add(join.getProperty());
|
||||
}
|
||||
return propertyNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* In findRowCount query found a formula property with a join clause so building a select clause
|
||||
* specifically for the findRowCount query.
|
||||
*/
|
||||
public void addFormulaWithJoin(String propertyName) {
|
||||
if (formulaWithJoin) {
|
||||
formulaProperties.append(",");
|
||||
} else {
|
||||
formulaProperties = new StringBuilder();
|
||||
formulaWithJoin = true;
|
||||
}
|
||||
formulaProperties.append(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the query select includes a formula with join.
|
||||
*/
|
||||
public boolean isFormulaWithJoin() {
|
||||
return formulaWithJoin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the formula properties to build the select clause for a findRowCount query.
|
||||
*/
|
||||
public String getFormulaProperties() {
|
||||
return formulaProperties.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this as part of an aggregation query (so using group by clause).
|
||||
*/
|
||||
public void setAggregation() {
|
||||
aggregation = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure we have the join required to support the aggregation properties.
|
||||
*/
|
||||
public void addAggregationJoin(String property) {
|
||||
this.aggregation = true;
|
||||
joins.put(property, new PropertyJoin(property, SqlJoinType.INNER));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Object used as a synchronization monitor that is serializable.
|
||||
*/
|
||||
public class Monitor implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -2741687226680981940L;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.query.SqlJoinType;
|
||||
|
||||
/**
|
||||
* Represents a join required for a given property and whether than needs to be an outer join.
|
||||
*/
|
||||
public class PropertyJoin {
|
||||
|
||||
/**
|
||||
* The property name.
|
||||
*/
|
||||
private final String property;
|
||||
|
||||
/**
|
||||
* Set to true if the property needs to be an outer join.
|
||||
*/
|
||||
private final SqlJoinType joinType;
|
||||
|
||||
public PropertyJoin(String property, SqlJoinType joinType) {
|
||||
this.property = property;
|
||||
this.joinType = joinType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property that should be joined.
|
||||
*/
|
||||
public String getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this join is required to be an outer join.
|
||||
*/
|
||||
public SqlJoinType getSqlJoinType() {
|
||||
return joinType;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.TxScope;
|
||||
import io.ebean.PersistBatch;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Used internally to handle the scoping of transactions for methods.
|
||||
*/
|
||||
public class ScopeTrans implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
private static final int OPCODE_ATHROW = 191;
|
||||
|
||||
private final SpiTransactionScopeManager scopeMgr;
|
||||
|
||||
/**
|
||||
* The suspended transaction (can be null).
|
||||
*/
|
||||
private final SpiTransaction suspendedTransaction;
|
||||
|
||||
/**
|
||||
* The transaction in scope (can be null).
|
||||
*/
|
||||
private final SpiTransaction transaction;
|
||||
|
||||
/**
|
||||
* If true by default rollback on Checked exceptions.
|
||||
*/
|
||||
private final boolean rollbackOnChecked;
|
||||
|
||||
/**
|
||||
* True if the transaction was created and hence should be committed
|
||||
* on finally if it hasn't already been rolled back.
|
||||
*/
|
||||
private final boolean created;
|
||||
|
||||
/**
|
||||
* Explicit set of Exceptions that DO NOT cause a rollback to occur.
|
||||
*/
|
||||
private final ArrayList<Class<? extends Throwable>> noRollbackFor;
|
||||
|
||||
/**
|
||||
* Explicit set of Exceptions that DO cause a rollback to occur.
|
||||
*/
|
||||
private final ArrayList<Class<? extends Throwable>> rollbackFor;
|
||||
|
||||
private PersistBatch restoreBatch;
|
||||
|
||||
private PersistBatch restoreBatchOnCascade;
|
||||
|
||||
private int restoreBatchSize;
|
||||
|
||||
private Boolean restoreBatchGeneratedKeys;
|
||||
|
||||
/**
|
||||
* Flag set when a rollback has occurred.
|
||||
*/
|
||||
private boolean rolledBack;
|
||||
|
||||
|
||||
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope,
|
||||
SpiTransaction suspendedTransaction, SpiTransactionScopeManager scopeMgr) {
|
||||
|
||||
this.rollbackOnChecked = rollbackOnChecked;
|
||||
this.created = created;
|
||||
this.transaction = transaction;
|
||||
this.suspendedTransaction = suspendedTransaction;
|
||||
this.scopeMgr = scopeMgr;
|
||||
|
||||
this.noRollbackFor = txScope.getNoRollbackFor();
|
||||
this.rollbackFor = txScope.getRollbackFor();
|
||||
|
||||
if (transaction != null) {
|
||||
if (!created && txScope.isBatchSet() || txScope.isBatchOnCascadeSet() || txScope.isBatchSizeSet()) {
|
||||
restoreBatch = transaction.getBatch();
|
||||
restoreBatchOnCascade = transaction.getBatchOnCascade();
|
||||
restoreBatchSize = transaction.getBatchSize();
|
||||
restoreBatchGeneratedKeys = transaction.getBatchGetGeneratedKeys();
|
||||
}
|
||||
if (txScope.isBatchSet()) {
|
||||
transaction.setBatch(txScope.getBatch());
|
||||
}
|
||||
if (txScope.isBatchOnCascadeSet()) {
|
||||
transaction.setBatchOnCascade(txScope.getBatchOnCascade());
|
||||
}
|
||||
if (txScope.isBatchSizeSet()) {
|
||||
transaction.setBatchSize(txScope.getBatchSize());
|
||||
}
|
||||
if (txScope.isSkipGeneratedKeys()) {
|
||||
transaction.setBatchGetGeneratedKeys(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current/active transaction.
|
||||
*/
|
||||
protected SpiTransaction getTransaction() {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the Thread catches any uncaught exception.
|
||||
* For example, an unexpected NullPointerException or Error.
|
||||
*/
|
||||
public void uncaughtException(Thread thread, Throwable e) {
|
||||
|
||||
// rollback transaction if required
|
||||
caughtThrowable(e);
|
||||
|
||||
// reinstate suspended transaction
|
||||
onFinally();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returned via RETURN or expected Exception from the method.
|
||||
*
|
||||
* @param returnOrThrowable the return value or Throwable
|
||||
* @param opCode indicates
|
||||
*/
|
||||
public void onExit(Object returnOrThrowable, int opCode) {
|
||||
|
||||
if (opCode == OPCODE_ATHROW) {
|
||||
// exited with a Throwable
|
||||
caughtThrowable((Throwable) returnOrThrowable);
|
||||
}
|
||||
onFinally();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Commit if the transaction exists and has not already been rolled back.
|
||||
* Also reinstate the suspended transaction if there was one.
|
||||
*/
|
||||
public void onFinally() {
|
||||
|
||||
try {
|
||||
if (!rolledBack) {
|
||||
commitTransaction();
|
||||
}
|
||||
} finally {
|
||||
restoreSuspended();
|
||||
}
|
||||
}
|
||||
|
||||
protected void restoreSuspended() {
|
||||
if (suspendedTransaction != null) {
|
||||
// put the previously suspended transaction
|
||||
// back onto the ThreadLocal or equivalent
|
||||
scopeMgr.replace(suspendedTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
protected void commitTransaction() {
|
||||
if (created) {
|
||||
transaction.commit();
|
||||
} else {
|
||||
if (restoreBatch != null) {
|
||||
transaction.setBatch(restoreBatch);
|
||||
}
|
||||
if (restoreBatchOnCascade != null) {
|
||||
transaction.setBatchOnCascade(restoreBatchOnCascade);
|
||||
}
|
||||
if (restoreBatchSize > 0) {
|
||||
transaction.setBatchSize(restoreBatchSize);
|
||||
}
|
||||
if (restoreBatchGeneratedKeys != null) {
|
||||
transaction.setBatchGetGeneratedKeys(restoreBatchGeneratedKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An Error was caught and this ALWAYS causes a rollback to occur.
|
||||
* Returns the error and this should be thrown by the calling code.
|
||||
*/
|
||||
public Error caughtError(Error e) {
|
||||
rollback(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the underlying transaction as rollback only.
|
||||
*/
|
||||
public void setRollbackOnly() {
|
||||
if (transaction != null) {
|
||||
transaction.setRollbackOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An Exception was caught and may or may not cause a rollback to occur.
|
||||
* Returns the exception and this should be thrown by the calling code.
|
||||
*/
|
||||
public <T extends Throwable> T caughtThrowable(T e) {
|
||||
|
||||
if (isRollbackThrowable(e)) {
|
||||
rollback(e);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
protected void rollback(Throwable e) {
|
||||
if (transaction != null && transaction.isActive()) {
|
||||
// transaction is null for NOT_SUPPORTED and sometimes SUPPORTS
|
||||
// and Inactive (already rolled back) if nested REQUIRED
|
||||
transaction.rollback(e);
|
||||
}
|
||||
rolledBack = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this throwable should cause a rollback to occur.
|
||||
*/
|
||||
private boolean isRollbackThrowable(Throwable e) {
|
||||
|
||||
if (e instanceof Error) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (noRollbackFor != null) {
|
||||
for (Class<? extends Throwable> aNoRollbackFor : noRollbackFor) {
|
||||
if (aNoRollbackFor.equals(e.getClass())) {
|
||||
|
||||
// explicit no rollback for this one
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rollbackFor != null) {
|
||||
for (Class<? extends Throwable> aRollbackFor : rollbackFor) {
|
||||
if (aRollbackFor.equals(e.getClass())) {
|
||||
// explicit rollback for this one
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (e instanceof RuntimeException) {
|
||||
return true;
|
||||
|
||||
} else {
|
||||
// checked exceptions...
|
||||
// EJB defaults this to false which is not intuitive IMO
|
||||
// Ebean makes this configurable (default to true)
|
||||
return rollbackOnChecked;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.TransactionCallback;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.PersistBatch;
|
||||
import io.ebean.event.changelog.BeanChange;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistDeferredRelationship;
|
||||
import io.ebeaninternal.server.core.PersistRequest;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.persist.BatchControl;
|
||||
import io.ebeanservice.docstore.api.DocStoreTransaction;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.RollbackException;
|
||||
import java.io.IOException;
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* Wrapper of a ScopeTrans request and it's underlying transaction.
|
||||
*/
|
||||
public class ScopedTransaction implements SpiTransaction {
|
||||
|
||||
final ScopeTrans scopeTrans;
|
||||
|
||||
final SpiTransaction transaction;
|
||||
|
||||
boolean committed;
|
||||
|
||||
public ScopedTransaction(ScopeTrans scopeTrans) {
|
||||
this.scopeTrans = scopeTrans;
|
||||
this.transaction = scopeTrans.getTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commitAndContinue() throws RollbackException {
|
||||
transaction.commitAndContinue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() throws RollbackException {
|
||||
scopeTrans.commitTransaction();
|
||||
committed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() throws PersistenceException {
|
||||
scopeTrans.rollback(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(Throwable e) throws PersistenceException {
|
||||
scopeTrans.rollback(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollbackIfActive() {
|
||||
transaction.rollbackIfActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRollbackOnly() {
|
||||
scopeTrans.setRollbackOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRollbackOnly() {
|
||||
return transaction.isRollbackOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() throws PersistenceException {
|
||||
try {
|
||||
if (!committed) {
|
||||
scopeTrans.rollback(null);
|
||||
}
|
||||
} finally {
|
||||
scopeTrans.restoreSuspended();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTenantId(Object tenantId) {
|
||||
transaction.setTenantId(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTenantId() {
|
||||
return transaction.getTenantId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocStoreTransaction getDocStoreTransaction() {
|
||||
return transaction.getDocStoreTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocStoreMode getDocStoreMode() {
|
||||
return transaction.getDocStoreMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDocStoreMode(DocStoreMode mode) {
|
||||
transaction.setDocStoreMode(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDocStoreBatchSize() {
|
||||
return transaction.getDocStoreBatchSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDocStoreBatchSize(int batchSize) {
|
||||
transaction.setDocStoreBatchSize(batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return transaction.getLogPrefix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogSql() {
|
||||
return transaction.isLogSql();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogSummary() {
|
||||
return transaction.isLogSummary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSql(String msg) {
|
||||
transaction.logSql(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logSummary(String msg) {
|
||||
transaction.logSummary(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkipCache(boolean skipCache) {
|
||||
transaction.setSkipCache(skipCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipCache() {
|
||||
return transaction.isSkipCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBeanChange(BeanChange beanChange) {
|
||||
transaction.addBeanChange(beanChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendChangeLog(ChangeSet changes) {
|
||||
transaction.sendChangeLog(changes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeferred(PersistDeferredRelationship derived) {
|
||||
transaction.registerDeferred(derived);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeleteBean(Integer hash) {
|
||||
transaction.registerDeleteBean(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDeleteBean(Integer hash) {
|
||||
transaction.unregisterDeleteBean(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisteredDeleteBean(Integer hash) {
|
||||
return transaction.isRegisteredDeleteBean(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterBean(Object bean) {
|
||||
transaction.unregisterBean(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisteredBean(Object bean) {
|
||||
return transaction.isRegisteredBean(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return transaction.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(TransactionCallback callback) {
|
||||
transaction.register(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return transaction.isReadOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
transaction.setReadOnly(readOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return transaction.isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPersistCascade(boolean persistCascade) {
|
||||
transaction.setPersistCascade(persistCascade);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpdateAllLoadedProperties(boolean updateAllLoaded) {
|
||||
transaction.setUpdateAllLoadedProperties(updateAllLoaded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isUpdateAllLoadedProperties() {
|
||||
return transaction.isUpdateAllLoadedProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchMode(boolean useBatch) {
|
||||
transaction.setBatchMode(useBatch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatch(PersistBatch persistBatchMode) {
|
||||
transaction.setBatch(persistBatchMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistBatch getBatch() {
|
||||
return transaction.getBatch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
|
||||
transaction.setBatchOnCascade(batchOnCascadeMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistBatch getBatchOnCascade() {
|
||||
return transaction.getBatchOnCascade();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchSize(int batchSize) {
|
||||
transaction.setBatchSize(batchSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBatchSize() {
|
||||
return transaction.getBatchSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchGetGeneratedKeys(boolean getGeneratedKeys) {
|
||||
transaction.setBatchGetGeneratedKeys(getGeneratedKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean getBatchGetGeneratedKeys() {
|
||||
return transaction.getBatchGetGeneratedKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchFlushOnMixed(boolean batchFlushOnMixed) {
|
||||
transaction.setBatchFlushOnMixed(batchFlushOnMixed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchFlushOnQuery(boolean batchFlushOnQuery) {
|
||||
transaction.setBatchFlushOnQuery(batchFlushOnQuery);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatchFlushOnQuery() {
|
||||
return transaction.isBatchFlushOnQuery();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBatch() throws PersistenceException {
|
||||
transaction.flushBatch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection() {
|
||||
return transaction.getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes) {
|
||||
transaction.addModification(tableName, inserts, updates, deletes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putUserObject(String name, Object value) {
|
||||
transaction.putUserObject(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUserObject(String name) {
|
||||
return transaction.getUserObject(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depth(int diff) {
|
||||
transaction.depth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int depth() {
|
||||
return transaction.depth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExplicit() {
|
||||
return transaction.isExplicit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionEvent getEvent() {
|
||||
return transaction.getEvent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPersistCascade() {
|
||||
return transaction.isPersistCascade();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBatchThisRequest(PersistRequest.Type type) {
|
||||
return transaction.isBatchThisRequest(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BatchControl getBatchControl() {
|
||||
return transaction.getBatchControl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchControl(BatchControl control) {
|
||||
transaction.setBatchControl(control);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceContext getPersistenceContext() {
|
||||
return transaction.getPersistenceContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPersistenceContext(PersistenceContext context) {
|
||||
transaction.setPersistenceContext(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getInternalConnection() {
|
||||
return transaction.getInternalConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName) {
|
||||
return transaction.isSaveAssocManyIntersection(intersectionTable, beanName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request) {
|
||||
return transaction.checkBatchEscalationOnCascade(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBatchOnCascade() {
|
||||
transaction.flushBatchOnCascade();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBatchOnRollback() {
|
||||
transaction.flushBatchOnRollback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markNotQueryOnly() {
|
||||
transaction.markNotQueryOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkBatchEscalationOnCollection() {
|
||||
transaction.checkBatchEscalationOnCollection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBatchOnCollection() {
|
||||
transaction.flushBatchOnCollection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
transaction.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
|
||||
/**
|
||||
* Internal Extension to BackgroundExecutor with shutdown.
|
||||
*/
|
||||
public interface SpiBackgroundExecutor extends BackgroundExecutor {
|
||||
|
||||
/**
|
||||
* Shutdown any associated thread pools.
|
||||
*/
|
||||
void shutdown();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.CallableSql;
|
||||
|
||||
public interface SpiCallableSql extends CallableSql {
|
||||
|
||||
BindParams getBindParams();
|
||||
|
||||
TransactionEventTable getTransactionEventTable();
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.TxScope;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
import io.ebean.bean.BeanLoader;
|
||||
import io.ebean.bean.CallStack;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebean.config.dbplatform.DatabasePlatform;
|
||||
import io.ebean.event.readaudit.ReadAuditLogger;
|
||||
import io.ebean.event.readaudit.ReadAuditPrepare;
|
||||
import io.ebeaninternal.server.core.timezone.DataTimeZone;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.query.CQuery;
|
||||
import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Service Provider extension to EbeanServer.
|
||||
*/
|
||||
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
|
||||
|
||||
/**
|
||||
* For internal use, shutdown of the server invoked by JVM Shutdown.
|
||||
*/
|
||||
void shutdownManaged();
|
||||
|
||||
/**
|
||||
* Return true if query origins should be collected.
|
||||
*/
|
||||
boolean isCollectQueryOrigins();
|
||||
|
||||
/**
|
||||
* Return true if updates in JDBC batch should include all columns if unspecified on the transaction.
|
||||
*/
|
||||
boolean isUpdateAllPropertiesInBatch();
|
||||
|
||||
/**
|
||||
* Return the current Tenant Id.
|
||||
*/
|
||||
Object currentTenantId();
|
||||
|
||||
/**
|
||||
* Return the server configuration.
|
||||
*/
|
||||
ServerConfig getServerConfig();
|
||||
|
||||
/**
|
||||
* Return the DatabasePlatform for this server.
|
||||
*/
|
||||
DatabasePlatform getDatabasePlatform();
|
||||
|
||||
/**
|
||||
* Create an object to represent the current CallStack.
|
||||
* <p>
|
||||
* Typically used to identify the origin of queries for AutoTune and object
|
||||
* graph costing.
|
||||
* </p>
|
||||
*/
|
||||
CallStack createCallStack();
|
||||
|
||||
/**
|
||||
* Return the PersistenceContextScope to use defined at query or server level.
|
||||
*/
|
||||
PersistenceContextScope getPersistenceContextScope(SpiQuery<?> query);
|
||||
|
||||
/**
|
||||
* Clear the query execution statistics.
|
||||
*/
|
||||
void clearQueryStatistics();
|
||||
|
||||
/**
|
||||
* Return all the descriptors.
|
||||
*/
|
||||
List<BeanDescriptor<?>> getBeanDescriptors();
|
||||
|
||||
/**
|
||||
* Return the BeanDescriptor for a given type of bean.
|
||||
*/
|
||||
<T> BeanDescriptor<T> getBeanDescriptor(Class<T> type);
|
||||
|
||||
/**
|
||||
* Return BeanDescriptor using it's unique id.
|
||||
*/
|
||||
BeanDescriptor<?> getBeanDescriptorById(String className);
|
||||
|
||||
/**
|
||||
* Return BeanDescriptor using it's unique doc store queueId.
|
||||
*/
|
||||
BeanDescriptor<?> getBeanDescriptorByQueueId(String queueId);
|
||||
|
||||
/**
|
||||
* Return BeanDescriptors mapped to this table.
|
||||
*/
|
||||
List<BeanDescriptor<?>> getBeanDescriptors(String tableName);
|
||||
|
||||
/**
|
||||
* Process committed changes from another framework.
|
||||
* <p>
|
||||
* This notifies this instance of the framework that beans have been committed
|
||||
* externally to it. Either by another framework or clustered server. It uses
|
||||
* this to maintain its cache and text indexes appropriately.
|
||||
* </p>
|
||||
*/
|
||||
void externalModification(TransactionEventTable event);
|
||||
|
||||
/**
|
||||
* Create a ServerTransaction.
|
||||
* <p>
|
||||
* To specify to use the default transaction isolation use a value of -1.
|
||||
* </p>
|
||||
*/
|
||||
SpiTransaction createServerTransaction(boolean isExplicit, int isolationLevel);
|
||||
|
||||
/**
|
||||
* Return the current transaction or null if there is no current transaction.
|
||||
*/
|
||||
SpiTransaction getCurrentServerTransaction();
|
||||
|
||||
/**
|
||||
* Create a ScopeTrans for a method for the given scope definition.
|
||||
*/
|
||||
ScopeTrans createScopeTrans(TxScope txScope);
|
||||
|
||||
/**
|
||||
* Create a ServerTransaction for query purposes.
|
||||
*
|
||||
* @param tenantId For multi-tenant lazy loading provide the tenantId to use.
|
||||
*/
|
||||
SpiTransaction createQueryTransaction(Object tenantId);
|
||||
|
||||
/**
|
||||
* An event from another server in the cluster used to notify local
|
||||
* BeanListeners of remote inserts updates and deletes.
|
||||
*/
|
||||
void remoteTransactionEvent(RemoteTransactionEvent event);
|
||||
|
||||
/**
|
||||
* Compile a query.
|
||||
*/
|
||||
<T> CQuery<T> compileQuery(Query<T> query, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the findId's query but without copying the query.
|
||||
* <p>
|
||||
* Used so that the list of Id's can be made accessible to client code before
|
||||
* the query has finished (if executing in a background thread).
|
||||
* </p>
|
||||
*/
|
||||
<A, T> List<A> findIdsWithCopy(Query<T> query, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the findRowCount query but without copying the query.
|
||||
*/
|
||||
<T> int findRowCountWithCopy(Query<T> query, Transaction t);
|
||||
|
||||
/**
|
||||
* Load a batch of Associated One Beans.
|
||||
*/
|
||||
void loadBean(LoadBeanRequest loadRequest);
|
||||
|
||||
/**
|
||||
* Lazy load a batch of Many's.
|
||||
*/
|
||||
void loadMany(LoadManyRequest loadRequest);
|
||||
|
||||
/**
|
||||
* Return the default batch size for lazy loading.
|
||||
*/
|
||||
int getLazyLoadBatchSize();
|
||||
|
||||
/**
|
||||
* Return true if the type is known as an Entity or Xml type or a List Set or
|
||||
* Map of known bean types.
|
||||
*/
|
||||
boolean isSupportedType(java.lang.reflect.Type genericType);
|
||||
|
||||
/**
|
||||
* Collect query statistics by ObjectGraphNode. Used for Lazy loading reporting.
|
||||
*/
|
||||
void collectQueryStats(ObjectGraphNode objectGraphNode, long loadedBeanCount, long timeMicros);
|
||||
|
||||
/**
|
||||
* Return the ReadAuditLogger to use for logging all read audit events.
|
||||
*/
|
||||
ReadAuditLogger getReadAuditLogger();
|
||||
|
||||
/**
|
||||
* Return the ReadAuditPrepare used to populate the read audit events with
|
||||
* user context information (user id, user ip address etc).
|
||||
*/
|
||||
ReadAuditPrepare getReadAuditPrepare();
|
||||
|
||||
/**
|
||||
* Return the DataTimeZone to use when reading/writing timestamps via JDBC.
|
||||
*/
|
||||
DataTimeZone getDataTimeZone();
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.Expression;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.expression.DocQueryContext;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
* An expression that becomes part of a Where clause or Having clause.
|
||||
*/
|
||||
public interface SpiExpression extends Expression {
|
||||
|
||||
/**
|
||||
* Simplify nested expressions if possible.
|
||||
*/
|
||||
void simplify();
|
||||
|
||||
/**
|
||||
* Write the expression as an elastic search expression.
|
||||
*/
|
||||
void writeDocQuery(DocQueryContext context) throws IOException;
|
||||
|
||||
/**
|
||||
* Return the nested path for this expression.
|
||||
*/
|
||||
String nestedPath(BeanDescriptor<?> desc);
|
||||
|
||||
/**
|
||||
* Process "Many" properties populating ManyWhereJoins.
|
||||
* <p>
|
||||
* Predicates on Many properties require an extra independent join clause.
|
||||
* </p>
|
||||
*/
|
||||
void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins);
|
||||
|
||||
/**
|
||||
* Prepare the expression. For example, compile sub-query expressions etc.
|
||||
*/
|
||||
void prepareExpression(BeanQueryRequest<?> request);
|
||||
|
||||
/**
|
||||
* Calculate a hash value used to identify a query for AutoTune tuning.
|
||||
* <p>
|
||||
* That is, if the hash changes then the query will be considered different
|
||||
* from an AutoTune perspective and get different tuning.
|
||||
* </p>
|
||||
*/
|
||||
void queryPlanHash(HashQueryPlanBuilder builder);
|
||||
|
||||
/**
|
||||
* Return the hash value for the values that will be bound.
|
||||
*/
|
||||
int queryBindHash();
|
||||
|
||||
/**
|
||||
* Return true if the expression is the same without taking into account bind values.
|
||||
*/
|
||||
boolean isSameByPlan(SpiExpression other);
|
||||
|
||||
/**
|
||||
* Return true if the expression is the same with respect to bind values.
|
||||
*/
|
||||
boolean isSameByBind(SpiExpression other);
|
||||
|
||||
/**
|
||||
* Add some sql to the query.
|
||||
* <p>
|
||||
* This will contain ? as a place holder for each associated bind values.
|
||||
* </p>
|
||||
* <p>
|
||||
* The 'sql' added to the query can contain object property names rather
|
||||
* than db tables and columns. This 'sql' is later parsed converting the
|
||||
* logical property names to their full database column names.
|
||||
* </p>
|
||||
*
|
||||
* @param request the associated request.
|
||||
*/
|
||||
void addSql(SpiExpressionRequest request);
|
||||
|
||||
/**
|
||||
* Add the parameter values to be set against query. For each ? place holder
|
||||
* there should be a corresponding value that is added to the bindList.
|
||||
*
|
||||
* @param request the associated request.
|
||||
*/
|
||||
void addBindValues(SpiExpressionRequest request);
|
||||
|
||||
/**
|
||||
* Validate all the properties/paths associated with this expression.
|
||||
*/
|
||||
void validate(SpiExpressionValidation validation);
|
||||
|
||||
/**
|
||||
* Return a copy of the expression for use in the query plan key.
|
||||
*/
|
||||
SpiExpression copyForPlanKey();
|
||||
|
||||
/**
|
||||
* Return the bind Id value if this is a "equal to" expression for the id property.
|
||||
*/
|
||||
Object getIdEqualTo(String idName);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.ExpressionFactory;
|
||||
|
||||
public interface SpiExpressionFactory extends ExpressionFactory {
|
||||
|
||||
/**
|
||||
* Create another expression factory with a given sub path.
|
||||
*/
|
||||
ExpressionFactory createExpressionFactory();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.ExpressionList;
|
||||
import io.ebean.Junction;
|
||||
import io.ebeaninternal.server.expression.DocQueryContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Internal extension of ExpressionList.
|
||||
*/
|
||||
public interface SpiExpressionList<T> extends ExpressionList<T>, SpiExpression {
|
||||
|
||||
/**
|
||||
* Return the expression list as a Junction (for ElasticSearch).
|
||||
*/
|
||||
Junction<T> toJunction();
|
||||
|
||||
/**
|
||||
* Return the underlying list of expressions.
|
||||
*/
|
||||
List<SpiExpression> getUnderlyingList();
|
||||
|
||||
/**
|
||||
* Return a copy of the ExpressionList with the path trimmed for filterMany() expressions.
|
||||
*/
|
||||
SpiExpressionList<?> trimPath(int prefixTrim);
|
||||
|
||||
/**
|
||||
* Return true if this list is empty.
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Write the top level where expressions taking into account possible extra idEquals expression.
|
||||
*/
|
||||
void writeDocQuery(DocQueryContext context, SpiExpression idEquals) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.core.DbExpressionHandler;
|
||||
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Request object used for gathering expression sql and bind values.
|
||||
*/
|
||||
public interface SpiExpressionRequest {
|
||||
|
||||
/**
|
||||
* Return the DB specific handler for JSON and ARRAY expressions.
|
||||
*/
|
||||
DbExpressionHandler getDbPlatformHandler();
|
||||
|
||||
/**
|
||||
* Parse the logical property name to the deployment name.
|
||||
*/
|
||||
String parseDeploy(String logicalProp);
|
||||
|
||||
/**
|
||||
* Return the bean descriptor for the root type.
|
||||
*/
|
||||
BeanDescriptor<?> getBeanDescriptor();
|
||||
|
||||
/**
|
||||
* Return the associated QueryRequest.
|
||||
*/
|
||||
SpiOrmQueryRequest<?> getQueryRequest();
|
||||
|
||||
/**
|
||||
* Append to the expression sql.
|
||||
*/
|
||||
SpiExpressionRequest append(String sql);
|
||||
|
||||
/**
|
||||
* Add an encryption key to bind to this request.
|
||||
*/
|
||||
void addBindEncryptKey(Object encryptKey);
|
||||
|
||||
/**
|
||||
* Add a bind value to this request.
|
||||
*/
|
||||
void addBindValue(Object bindValue);
|
||||
|
||||
/**
|
||||
* Return the accumulated expression sql for all expressions in this request.
|
||||
*/
|
||||
String getSql();
|
||||
|
||||
/**
|
||||
* Return the ordered list of bind values for all expressions in this request.
|
||||
*/
|
||||
List<Object> getBindValues();
|
||||
|
||||
/**
|
||||
* Increments the parameter index and returns that value.
|
||||
*/
|
||||
int nextParameter();
|
||||
|
||||
/**
|
||||
* Append a DB Like clause.
|
||||
*/
|
||||
void appendLike();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.plugin.BeanType;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Property expression validation request for a given root bean type.
|
||||
*/
|
||||
public class SpiExpressionValidation {
|
||||
|
||||
private final BeanType<?> desc;
|
||||
|
||||
private final LinkedHashSet<String> unknown = new LinkedHashSet<>();
|
||||
|
||||
public SpiExpressionValidation(BeanType<?> desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the property expression (path) is valid.
|
||||
*/
|
||||
public void validate(String propertyName) {
|
||||
if (!desc.isValidExpression(propertyName)) {
|
||||
unknown.add(propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the set of properties considered as having unknown paths.
|
||||
*/
|
||||
public Set<String> getUnknownProperties() {
|
||||
return unknown;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.Junction;
|
||||
import io.ebeaninternal.server.expression.DocQueryContext;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* SPI methods for Junction.
|
||||
*/
|
||||
public interface SpiJunction<T> extends Junction<T> {
|
||||
|
||||
/**
|
||||
* Write the Junction taking into account it is implied.
|
||||
*/
|
||||
void writeDocQueryJunction(DocQueryContext context) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
public interface SpiNamedParam {
|
||||
|
||||
Object getValue();
|
||||
}
|
||||
@@ -0,0 +1,757 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.ExpressionList;
|
||||
import io.ebean.OrderBy;
|
||||
import io.ebean.PersistenceContextScope;
|
||||
import io.ebean.Query;
|
||||
import io.ebean.bean.CallStack;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.event.BeanQueryRequest;
|
||||
import io.ebean.event.readaudit.ReadEvent;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebeaninternal.server.autotune.ProfilingListener;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.deploy.TableJoin;
|
||||
import io.ebeaninternal.server.query.CancelableQuery;
|
||||
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import io.ebeaninternal.server.querydefn.OrmUpdateProperties;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Object Relational query - Internal extension to Query object.
|
||||
*/
|
||||
public interface SpiQuery<T> extends Query<T> {
|
||||
|
||||
enum Mode {
|
||||
NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true);
|
||||
|
||||
Mode(boolean loadContextBean) {
|
||||
this.loadContextBean = loadContextBean;
|
||||
}
|
||||
|
||||
private final boolean loadContextBean;
|
||||
|
||||
public boolean isLoadContextBean() {
|
||||
return loadContextBean;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of query result.
|
||||
*/
|
||||
enum Type {
|
||||
|
||||
/**
|
||||
* Find by Id or unique returning a single bean.
|
||||
*/
|
||||
BEAN,
|
||||
|
||||
/**
|
||||
* Find iterate type query - findEach(), findIterate() etc.
|
||||
*/
|
||||
ITERATE,
|
||||
|
||||
/**
|
||||
* Find returning a List.
|
||||
*/
|
||||
LIST,
|
||||
|
||||
/**
|
||||
* Find returning a Set.
|
||||
*/
|
||||
SET,
|
||||
|
||||
/**
|
||||
* Find returning a Map.
|
||||
*/
|
||||
MAP,
|
||||
|
||||
/**
|
||||
* Find the Id's.
|
||||
*/
|
||||
ID_LIST,
|
||||
|
||||
/**
|
||||
* Find single attribute.
|
||||
*/
|
||||
ATTRIBUTE,
|
||||
|
||||
/**
|
||||
* Find rowCount.
|
||||
*/
|
||||
ROWCOUNT,
|
||||
|
||||
/**
|
||||
* A subquery used as part of a where clause.
|
||||
*/
|
||||
SUBQUERY,
|
||||
|
||||
/**
|
||||
* Delete query.
|
||||
*/
|
||||
DELETE,
|
||||
|
||||
/**
|
||||
* Update query.
|
||||
*/
|
||||
UPDATE,
|
||||
}
|
||||
|
||||
enum TemporalMode {
|
||||
/**
|
||||
* Includes soft deletes rows in the result.
|
||||
*/
|
||||
SOFT_DELETED,
|
||||
|
||||
/**
|
||||
* Query runs against draft tables.
|
||||
*/
|
||||
DRAFT,
|
||||
|
||||
/**
|
||||
* Query runs against current data (normal).
|
||||
*/
|
||||
CURRENT,
|
||||
|
||||
/**
|
||||
* Query runs potentially returning many versions of the same bean.
|
||||
*/
|
||||
VERSIONS,
|
||||
|
||||
/**
|
||||
* Query runs 'As Of' a given date time.
|
||||
*/
|
||||
AS_OF;
|
||||
|
||||
/**
|
||||
* Return the mode of the query of if null return CURRENT mode.
|
||||
*/
|
||||
public static TemporalMode of(SpiQuery<?> query) {
|
||||
return (query != null) ? query.getTemporalMode() : TemporalMode.CURRENT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a single "equal to" expression for the Id.
|
||||
*/
|
||||
void checkIdEqualTo();
|
||||
|
||||
/**
|
||||
* Return true if AutoTune should be attempted on this query.
|
||||
*/
|
||||
boolean isAutoTunable();
|
||||
|
||||
/**
|
||||
* Return true if this is a native sql query.
|
||||
*/
|
||||
boolean isNativeSql();
|
||||
|
||||
/**
|
||||
* Return the unmodified native sql query (with named params etc).
|
||||
*/
|
||||
String getNativeSql();
|
||||
|
||||
/**
|
||||
* Return the bean descriptor for this query.
|
||||
*/
|
||||
BeanDescriptor<T> getBeanDescriptor();
|
||||
|
||||
/**
|
||||
* Return true if this query should be executed against the doc store.
|
||||
*/
|
||||
boolean isUseDocStore();
|
||||
|
||||
/**
|
||||
* For doc store query return the document index name to search against.
|
||||
* This is for partitioned indexes (like daily logstash indexes etc).
|
||||
*/
|
||||
String getDocIndexName();
|
||||
|
||||
/**
|
||||
* Return the PersistenceContextScope that this query should use.
|
||||
* <p>
|
||||
* This can be null and in that case use the default scope.
|
||||
* </p>
|
||||
*/
|
||||
PersistenceContextScope getPersistenceContextScope();
|
||||
|
||||
/**
|
||||
* Return the origin key.
|
||||
*/
|
||||
String getOriginKey();
|
||||
|
||||
/**
|
||||
* Return the default lazy load batch size.
|
||||
*/
|
||||
int getLazyLoadBatchSize();
|
||||
|
||||
/**
|
||||
* Return true if select all properties was used to ensure the property
|
||||
* invoking a lazy load was included in the query.
|
||||
*/
|
||||
boolean selectAllForLazyLoadProperty();
|
||||
|
||||
/**
|
||||
* Set the query mode.
|
||||
*/
|
||||
void setMode(Mode m);
|
||||
|
||||
/**
|
||||
* Return the query mode.
|
||||
*/
|
||||
Mode getMode();
|
||||
|
||||
/**
|
||||
* Return the Temporal mode for the query.
|
||||
*/
|
||||
TemporalMode getTemporalMode();
|
||||
|
||||
/**
|
||||
* Return true if this is a find versions between query.
|
||||
*/
|
||||
boolean isVersionsBetween();
|
||||
|
||||
/**
|
||||
* Return the find versions start timestamp.
|
||||
*/
|
||||
Timestamp getVersionStart();
|
||||
|
||||
/**
|
||||
* Return the find versions end timestamp.
|
||||
*/
|
||||
Timestamp getVersionEnd();
|
||||
|
||||
/**
|
||||
* Return true if this is a 'As Of' query.
|
||||
*/
|
||||
boolean isAsOfQuery();
|
||||
|
||||
/**
|
||||
* Return true if this is a 'As Draft' query.
|
||||
*/
|
||||
boolean isAsDraft();
|
||||
|
||||
/**
|
||||
* Return true if this query includes soft deleted rows.
|
||||
*/
|
||||
boolean isIncludeSoftDeletes();
|
||||
|
||||
/**
|
||||
* Return the asOf Timestamp which the query should run as.
|
||||
*/
|
||||
Timestamp getAsOf();
|
||||
|
||||
/**
|
||||
* Return true if the base table is using history.
|
||||
*/
|
||||
boolean isAsOfBaseTable();
|
||||
|
||||
/**
|
||||
* Set when the base table is using history.
|
||||
*/
|
||||
void setAsOfBaseTable();
|
||||
|
||||
/**
|
||||
* Increment the counter of tables used in 'As Of' query.
|
||||
*/
|
||||
void incrementAsOfTableCount();
|
||||
|
||||
/**
|
||||
* Return the table alias used for the base table.
|
||||
*/
|
||||
int getAsOfTableCount();
|
||||
|
||||
void addSoftDeletePredicate(String softDeletePredicate);
|
||||
|
||||
List<String> getSoftDeletePredicates();
|
||||
|
||||
/**
|
||||
* Return a copy of the query.
|
||||
*/
|
||||
SpiQuery<T> copy();
|
||||
|
||||
/**
|
||||
* Return a copy of the query attaching to a different EbeanServer.
|
||||
*/
|
||||
SpiQuery<T> copy(EbeanServer server);
|
||||
|
||||
/**
|
||||
* Return the type of query (List, Set, Map, Bean, rowCount etc).
|
||||
*/
|
||||
Type getType();
|
||||
|
||||
/**
|
||||
* Set the query type (List, Set etc).
|
||||
*/
|
||||
void setType(Type type);
|
||||
|
||||
/**
|
||||
* Return a more detailed description of the lazy or query load.
|
||||
*/
|
||||
String getLoadDescription();
|
||||
|
||||
/**
|
||||
* Return the load mode (+lazy or +query).
|
||||
*/
|
||||
String getLoadMode();
|
||||
|
||||
/**
|
||||
* This becomes a lazy loading query for a many relationship.
|
||||
*/
|
||||
void setLazyLoadForParents(BeanPropertyAssocMany<?> many);
|
||||
|
||||
/**
|
||||
* Return the lazy loading 'many' property.
|
||||
*/
|
||||
BeanPropertyAssocMany<?> getLazyLoadMany();
|
||||
|
||||
/**
|
||||
* Set the load mode (+lazy or +query) and the load description.
|
||||
*/
|
||||
void setLoadDescription(String loadMode, String loadDescription);
|
||||
|
||||
/**
|
||||
* Check that the named parameters have had their values set.
|
||||
*/
|
||||
void checkNamedParameters();
|
||||
|
||||
/**
|
||||
* Create a named parameter placeholder.
|
||||
*/
|
||||
SpiNamedParam createNamedParameter(String parameterName);
|
||||
|
||||
/**
|
||||
* Return the joins required to support predicates on the many properties.
|
||||
*/
|
||||
ManyWhereJoins getManyWhereJoins();
|
||||
|
||||
/**
|
||||
* Return a Natural Key bind parameter if supported by this query.
|
||||
*/
|
||||
NaturalKeyBindParam getNaturalKeyBindParam();
|
||||
|
||||
/**
|
||||
* Prepare the query for docstore execution with nested paths.
|
||||
*/
|
||||
void prepareDocNested();
|
||||
|
||||
/**
|
||||
* Set the query to be a delete query.
|
||||
*/
|
||||
void setDelete();
|
||||
|
||||
/**
|
||||
* Set the query to select the id property only.
|
||||
*/
|
||||
void setSelectId();
|
||||
|
||||
/**
|
||||
* Mark the query as selecting a single attribute.
|
||||
*/
|
||||
void setSingleAttribute();
|
||||
|
||||
/**
|
||||
* Return true if this is singleAttribute query.
|
||||
*/
|
||||
boolean isSingleAttribute();
|
||||
|
||||
/**
|
||||
* Return true if the query should include the Id property.
|
||||
* <p>
|
||||
* distinct and single attribute queries exclude the Id property.
|
||||
* </p>
|
||||
*/
|
||||
boolean isWithId();
|
||||
|
||||
/**
|
||||
* Set a filter to a join path.
|
||||
*/
|
||||
void setFilterMany(String prop, ExpressionList<?> filterMany);
|
||||
|
||||
/**
|
||||
* Set the tenantId to use for lazy loading.
|
||||
*/
|
||||
void setTenantId(Object tenantId);
|
||||
|
||||
/**
|
||||
* Return the tenantId to use for lazy loading.
|
||||
*/
|
||||
Object getTenantId();
|
||||
|
||||
/**
|
||||
* Set the path of the many when +query/+lazy loading query is executed.
|
||||
*/
|
||||
void setLazyLoadManyPath(String lazyLoadManyPath);
|
||||
|
||||
/**
|
||||
* Convert joins as necessary to query joins etc.
|
||||
*/
|
||||
SpiQuerySecondary convertJoins();
|
||||
|
||||
/**
|
||||
* Return the TransactionContext.
|
||||
* <p>
|
||||
* If no TransactionContext is present on the query then the
|
||||
* TransactionContext from the Transaction is used (transaction scoped
|
||||
* persistence context).
|
||||
* </p>
|
||||
*/
|
||||
PersistenceContext getPersistenceContext();
|
||||
|
||||
/**
|
||||
* Set an explicit TransactionContext (typically for a refresh query).
|
||||
* <p>
|
||||
* If no TransactionContext is present on the query then the
|
||||
* TransactionContext from the Transaction is used (transaction scoped
|
||||
* persistence context).
|
||||
* </p>
|
||||
*/
|
||||
void setPersistenceContext(PersistenceContext transactionContext);
|
||||
|
||||
/**
|
||||
* Return true if the query detail has neither select or joins specified.
|
||||
*/
|
||||
boolean isDetailEmpty();
|
||||
|
||||
/**
|
||||
* Return explicit AutoTune setting or null. If null then not explicitly
|
||||
* set so we use the default behaviour.
|
||||
*/
|
||||
Boolean isAutoTune();
|
||||
|
||||
/**
|
||||
* Set to true if you want to capture executed secondary queries.
|
||||
*/
|
||||
void setLogSecondaryQuery(boolean logSecondaryQuery);
|
||||
|
||||
/**
|
||||
* Return true if executed secondary queries should be captured.
|
||||
*/
|
||||
boolean isLogSecondaryQuery();
|
||||
|
||||
/**
|
||||
* Return the list of secondary queries that were executed.
|
||||
*/
|
||||
List<SpiQuery<?>> getLoggedSecondaryQueries();
|
||||
|
||||
/**
|
||||
* Log an executed secondary query.
|
||||
*/
|
||||
void logSecondaryQuery(SpiQuery<?> query);
|
||||
|
||||
/**
|
||||
* If return null then no profiling for this query. If a ProfilingListener is
|
||||
* returned this implies that profiling is turned on for this query (and all
|
||||
* the objects this query creates).
|
||||
*/
|
||||
ProfilingListener getProfilingListener();
|
||||
|
||||
/**
|
||||
* This has the effect of turning on profiling for this query.
|
||||
*/
|
||||
void setProfilingListener(ProfilingListener manager);
|
||||
|
||||
/**
|
||||
* Return the origin point for the query.
|
||||
* <p>
|
||||
* This MUST be call prior to a query being changed via tuning. This is
|
||||
* because the queryPlanHash is used to identify the query point.
|
||||
* </p>
|
||||
*/
|
||||
ObjectGraphNode setOrigin(CallStack callStack);
|
||||
|
||||
/**
|
||||
* Set the profile point of the bean or collection that is lazy loading.
|
||||
* <p>
|
||||
* This enables use to hook this back to the original 'root' query by the
|
||||
* queryPlanHash and stackPoint.
|
||||
* </p>
|
||||
*/
|
||||
void setParentNode(ObjectGraphNode node);
|
||||
|
||||
/**
|
||||
* Set the property that invoked the lazy load and MUST be included in the
|
||||
* lazy loading query.
|
||||
*/
|
||||
void setLazyLoadProperty(String lazyLoadProperty);
|
||||
|
||||
/**
|
||||
* Return the property that invoked lazy load.
|
||||
*/
|
||||
String getLazyLoadProperty();
|
||||
|
||||
/**
|
||||
* Used to hook back a lazy loading query to the original query (query
|
||||
* point).
|
||||
* <p>
|
||||
* This will return null or an "original" query.
|
||||
* </p>
|
||||
*/
|
||||
ObjectGraphNode getParentNode();
|
||||
|
||||
/**
|
||||
* Return false when this is a lazy load or refresh query for a bean.
|
||||
* <p>
|
||||
* We just take/copy the data from those beans and don't collect AutoTune
|
||||
* usage profiling on those lazy load or refresh beans.
|
||||
* </p>
|
||||
*/
|
||||
boolean isUsageProfiling();
|
||||
|
||||
/**
|
||||
* Set to false if this query should not be included in the AutoTune usage
|
||||
* profiling information.
|
||||
*/
|
||||
void setUsageProfiling(boolean usageProfiling);
|
||||
|
||||
/**
|
||||
* Prepare the query which prepares sub-query expressions and calculates
|
||||
* and returns the query plan key.
|
||||
* <p>
|
||||
* The query plan excludes actual bind values (as they don't effect the query plan).
|
||||
* </p>
|
||||
*/
|
||||
CQueryPlanKey prepare(BeanQueryRequest<?> request);
|
||||
|
||||
/**
|
||||
* Calculate a hash based on the bind values used in the query.
|
||||
* <p>
|
||||
* Combined with queryPlanHash() to return getQueryHash (a unique hash for a
|
||||
* query).
|
||||
* </p>
|
||||
*/
|
||||
int queryBindHash();
|
||||
|
||||
/**
|
||||
* Identifies queries that are exactly the same including bind variables.
|
||||
*/
|
||||
HashQuery queryHash();
|
||||
|
||||
/**
|
||||
* Return true if this is a RawSql query.
|
||||
*/
|
||||
boolean isRawSql();
|
||||
|
||||
/**
|
||||
* Return the Order By clause or null if there is none defined.
|
||||
*/
|
||||
OrderBy<T> getOrderBy();
|
||||
|
||||
/**
|
||||
* Can return null if no expressions where added to the where clause.
|
||||
*/
|
||||
SpiExpressionList<T> getWhereExpressions();
|
||||
|
||||
/**
|
||||
* Can return null if no expressions where added to the having clause.
|
||||
*/
|
||||
SpiExpressionList<T> getHavingExpressions();
|
||||
|
||||
/**
|
||||
* Return the text expressions.
|
||||
*/
|
||||
SpiExpressionList<T> getTextExpression();
|
||||
|
||||
/**
|
||||
* Returns true if either firstRow or maxRows has been set.
|
||||
*/
|
||||
boolean hasMaxRowsOrFirstRow();
|
||||
|
||||
/**
|
||||
* Return true if the bean cache should be exclude for query or lazy loading.
|
||||
*/
|
||||
boolean isExcludeBeanCache();
|
||||
|
||||
/**
|
||||
* Return true if this query should use the bean cache.
|
||||
* It is not skipped and bean caching is supported.
|
||||
*/
|
||||
boolean isUseBeanCache();
|
||||
|
||||
/**
|
||||
* Return true if this query should use/check the query cache.
|
||||
*/
|
||||
boolean isUseQueryCache();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
Boolean isReadOnly();
|
||||
|
||||
/**
|
||||
* Return the query timeout.
|
||||
*/
|
||||
int getTimeout();
|
||||
|
||||
/**
|
||||
* Return the bind parameters.
|
||||
*/
|
||||
BindParams getBindParams();
|
||||
|
||||
/**
|
||||
* Replace the query detail. This is used by the AutoTune feature to as a
|
||||
* fast way to set the query properties and joins.
|
||||
* <p>
|
||||
* Note care must be taken to keep the where, orderBy, firstRows and maxRows
|
||||
* held in the detail attributes.
|
||||
* </p>
|
||||
*/
|
||||
void setDetail(OrmQueryDetail detail);
|
||||
|
||||
/**
|
||||
* AutoTune tune the detail specifying properties to select on already defined joins
|
||||
* and adding extra joins where they are missing.
|
||||
*/
|
||||
boolean tuneFetchProperties(OrmQueryDetail detail);
|
||||
|
||||
/**
|
||||
* If this is a RawSql based entity set the default RawSql if not set.
|
||||
*/
|
||||
void setDefaultRawSqlIfRequired();
|
||||
|
||||
/**
|
||||
* Set to true if this query has been tuned by autoTune.
|
||||
*/
|
||||
void setAutoTuned(boolean autoTuned);
|
||||
|
||||
/**
|
||||
* Return the query detail.
|
||||
*/
|
||||
OrmQueryDetail getDetail();
|
||||
|
||||
/**
|
||||
* Return the extra join for a M2M lazy load.
|
||||
*/
|
||||
TableJoin getM2mIncludeJoin();
|
||||
|
||||
/**
|
||||
* Set the extra join for a M2M lazy load.
|
||||
*/
|
||||
void setM2MIncludeJoin(TableJoin includeTableJoin);
|
||||
|
||||
/**
|
||||
* Return the property used to specify keys for a map.
|
||||
*/
|
||||
String getMapKey();
|
||||
|
||||
/**
|
||||
* Return the maximum number of rows to return in the query.
|
||||
*/
|
||||
int getMaxRows();
|
||||
|
||||
/**
|
||||
* Return the index of the first row to return in the query.
|
||||
*/
|
||||
int getFirstRow();
|
||||
|
||||
/**
|
||||
* Return true if lazy loading has been disabled on the query.
|
||||
*/
|
||||
boolean isDisableLazyLoading();
|
||||
|
||||
/**
|
||||
* Internally set by Ebean when this query must use the DISTINCT keyword.
|
||||
* <p>
|
||||
* This does not exclude/remove the use of the id property.
|
||||
*/
|
||||
void setSqlDistinct(boolean sqlDistinct);
|
||||
|
||||
/**
|
||||
* Return true if this query has been specified by a user or internally by Ebean to use DISTINCT.
|
||||
*/
|
||||
boolean isDistinctQuery();
|
||||
|
||||
/**
|
||||
* Return true if this query has been specified by a user to use DISTINCT.
|
||||
*/
|
||||
boolean isDistinct();
|
||||
|
||||
/**
|
||||
* Set default select clauses where none have been explicitly defined.
|
||||
*/
|
||||
void setDefaultSelectClause();
|
||||
|
||||
/**
|
||||
* Set the generated sql for debug purposes.
|
||||
*/
|
||||
void setGeneratedSql(String generatedSql);
|
||||
|
||||
/**
|
||||
* Set the JDBC fetchSize buffer hint if not explicitly set.
|
||||
*/
|
||||
void setDefaultFetchBuffer(int fetchSize);
|
||||
|
||||
/**
|
||||
* Return the hint for Statement.setFetchSize().
|
||||
*/
|
||||
int getBufferFetchSizeHint();
|
||||
|
||||
/**
|
||||
* Return true if read auditing is disabled on this query.
|
||||
*/
|
||||
boolean isDisableReadAudit();
|
||||
|
||||
/**
|
||||
* Return true if this is a query executing in the background.
|
||||
*/
|
||||
boolean isFutureFetch();
|
||||
|
||||
/**
|
||||
* Set to true to indicate the query is executing in a background thread
|
||||
* asynchronously.
|
||||
*/
|
||||
void setFutureFetch(boolean futureFetch);
|
||||
|
||||
/**
|
||||
* Set the readEvent for future queries (as prepared in foreground thread).
|
||||
*/
|
||||
void setFutureFetchAudit(ReadEvent event);
|
||||
|
||||
/**
|
||||
* Read the readEvent for future queries (null otherwise).
|
||||
*/
|
||||
ReadEvent getFutureFetchAudit();
|
||||
|
||||
/**
|
||||
* Set the underlying cancelable query (with the PreparedStatement).
|
||||
*/
|
||||
void setCancelableQuery(CancelableQuery cancelableQuery);
|
||||
|
||||
/**
|
||||
* Return true if this query has been cancelled.
|
||||
*/
|
||||
boolean isCancelled();
|
||||
|
||||
/**
|
||||
* Return root table alias set by {@link #alias(String)} command.
|
||||
*/
|
||||
String getAlias();
|
||||
|
||||
/**
|
||||
* Validate the query returning the set of properties with unknown paths.
|
||||
*/
|
||||
Set<String> validate(BeanType<T> desc);
|
||||
|
||||
/**
|
||||
* Return the properties for an update query.
|
||||
*/
|
||||
OrmUpdateProperties getUpdateProperties();
|
||||
|
||||
/**
|
||||
* Simplify nested expression lists where possible.
|
||||
*/
|
||||
void simplifyExpressions();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The secondary query paths for 'query joins' and 'lazy loading'.
|
||||
*/
|
||||
public interface SpiQuerySecondary {
|
||||
|
||||
/**
|
||||
* Return a list of path/properties that are query join loaded.
|
||||
*/
|
||||
List<OrmQueryProperties> getQueryJoins();
|
||||
|
||||
/**
|
||||
* Return the list of path/properties that are lazy loaded.
|
||||
*/
|
||||
List<OrmQueryProperties> getLazyJoins();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.SqlUpdate;
|
||||
|
||||
public interface SpiSqlUpdate extends SqlUpdate {
|
||||
|
||||
BindParams getBindParams();
|
||||
|
||||
void setGeneratedSql(String sql);
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.annotation.DocStoreMode;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.event.changelog.BeanChange;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistDeferredRelationship;
|
||||
import io.ebeaninternal.server.core.PersistRequest;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.persist.BatchControl;
|
||||
import io.ebeanservice.docstore.api.DocStoreTransaction;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* Extends Transaction with additional API required on server.
|
||||
* <p>
|
||||
* Provides support for batching and TransactionContext.
|
||||
* </p>
|
||||
*/
|
||||
public interface SpiTransaction extends Transaction {
|
||||
|
||||
/**
|
||||
* Return the string prefix with the transaction id and label used in logging.
|
||||
*/
|
||||
String getLogPrefix();
|
||||
|
||||
/**
|
||||
* Return true if generated SQL and Bind values should be logged to the
|
||||
* transaction log.
|
||||
*/
|
||||
boolean isLogSql();
|
||||
|
||||
/**
|
||||
* Return true if summary level events should be logged to the transaction
|
||||
* log.
|
||||
*/
|
||||
boolean isLogSummary();
|
||||
|
||||
/**
|
||||
* Log a message to the SQL logger.
|
||||
*/
|
||||
void logSql(String msg);
|
||||
|
||||
/**
|
||||
* Log a message to the SUMMARY logger.
|
||||
*/
|
||||
void logSummary(String msg);
|
||||
|
||||
/**
|
||||
* Register a "Deferred Relationship" that requires an additional update later.
|
||||
*/
|
||||
void registerDeferred(PersistDeferredRelationship derived);
|
||||
|
||||
/**
|
||||
* Add a deleting bean to the registered list.
|
||||
* <p>
|
||||
* This is to handle bi-directional relationships where both sides Cascade.
|
||||
* </p>
|
||||
*/
|
||||
void registerDeleteBean(Integer hash);
|
||||
|
||||
/**
|
||||
* Unregister the hash of the bean.
|
||||
*/
|
||||
void unregisterDeleteBean(Integer hash);
|
||||
|
||||
/**
|
||||
* Return true if this is a bean that has already been saved/deleted.
|
||||
*/
|
||||
boolean isRegisteredDeleteBean(Integer hash);
|
||||
|
||||
/**
|
||||
* Unregister the persisted bean.
|
||||
*/
|
||||
void unregisterBean(Object bean);
|
||||
|
||||
/**
|
||||
* Return true if this is a bean that has already been persisted in the
|
||||
* current recursive save request. The goal is to stop recursively saving
|
||||
* the bean when cascade persist is on both sides of a relationship).
|
||||
* <p>
|
||||
* This will register the bean if it is not already.
|
||||
* </p>
|
||||
*/
|
||||
boolean isRegisteredBean(Object bean);
|
||||
|
||||
/**
|
||||
* Returns a String used to identify the transaction. This id is used for
|
||||
* Transaction logging.
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* Return true if this transaction has updateAllLoadedProperties set.
|
||||
* If null is returned the server default is used (set on ServerConfig).
|
||||
*/
|
||||
Boolean isUpdateAllLoadedProperties();
|
||||
|
||||
/**
|
||||
* Return the batchSize specifically set for this transaction or 0.
|
||||
* <p>
|
||||
* Returning 0 implies to use the system wide default batch size.
|
||||
* </p>
|
||||
*/
|
||||
DocStoreMode getDocStoreMode();
|
||||
|
||||
/**
|
||||
* Return the batch size to us for ElasticSearch Bulk API calls
|
||||
* as a result of this transaction.
|
||||
*/
|
||||
int getDocStoreBatchSize();
|
||||
|
||||
/**
|
||||
* Return the batchSize specifically set for this transaction or 0.
|
||||
* <p>
|
||||
* Returning 0 implies to use the system wide default batch size.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
int getBatchSize();
|
||||
|
||||
/**
|
||||
* Return the getGeneratedKeys setting for this transaction.
|
||||
*/
|
||||
Boolean getBatchGetGeneratedKeys();
|
||||
|
||||
/**
|
||||
* Modify and return the current 'depth' of the transaction.
|
||||
* <p>
|
||||
* As we cascade save or delete we traverse the object graph tree. Going up
|
||||
* to Assoc Ones the depth decreases and going down to Assoc Manys the depth
|
||||
* increases.
|
||||
* </p>
|
||||
* <p>
|
||||
* The depth is used for ordering batching statements. The lowest depth get
|
||||
* executed first during save.
|
||||
* </p>
|
||||
*/
|
||||
void depth(int diff);
|
||||
|
||||
/**
|
||||
* Return the current depth.
|
||||
*/
|
||||
int depth();
|
||||
|
||||
/**
|
||||
* Return true if this transaction was created explicitly via
|
||||
* <code>Ebean.beginTransaction()</code>.
|
||||
*/
|
||||
boolean isExplicit();
|
||||
|
||||
/**
|
||||
* Get the object that holds the event details.
|
||||
* <p>
|
||||
* This information is used maintain the table state, cache and text
|
||||
* indexes. On commit the Table modifications this generates is broadcast
|
||||
* around the cluster (if you have a cluster).
|
||||
* </p>
|
||||
*/
|
||||
TransactionEvent getEvent();
|
||||
|
||||
/**
|
||||
* Whether persistCascade is on for save and delete.
|
||||
*/
|
||||
boolean isPersistCascade();
|
||||
|
||||
/**
|
||||
* Return true if this request should be batched. Conversely returns false
|
||||
* if this request should be executed immediately.
|
||||
*/
|
||||
boolean isBatchThisRequest(PersistRequest.Type type);
|
||||
|
||||
/**
|
||||
* Return the BatchControl used to batch up persist requests.
|
||||
*/
|
||||
BatchControl getBatchControl();
|
||||
|
||||
/**
|
||||
* Set the BatchControl used to batch up persist requests. There should only be one
|
||||
* PersistQueue set per transaction.
|
||||
*/
|
||||
void setBatchControl(BatchControl control);
|
||||
|
||||
/**
|
||||
* Return the persistence context associated with this transaction.
|
||||
* <p>
|
||||
* You may wish to hold onto this and set it against another transaction
|
||||
* later. This is along the lines of 'extended persistence context'
|
||||
* behaviour.
|
||||
* </p>
|
||||
*/
|
||||
PersistenceContext getPersistenceContext();
|
||||
|
||||
/**
|
||||
* Set the persistence context to this transaction.
|
||||
* <p>
|
||||
* This could be considered similar to 'EJB3 Extended Persistence Context'.
|
||||
* In that you can get the PersistenceContext from a transaction, hold onto
|
||||
* it, and then set it back later to a second transaction. In general there
|
||||
* is one PersistenceContext per Transaction. The getPersistenceContext()
|
||||
* and setPersistenceContext() enable a developer to reuse a single
|
||||
* PersistenceContext with multiple transactions.
|
||||
* </p>
|
||||
*/
|
||||
void setPersistenceContext(PersistenceContext context);
|
||||
|
||||
/**
|
||||
* Return the underlying Connection for internal use.
|
||||
* <p>
|
||||
* If the connection is made from Transaction and the user code calls
|
||||
* that method we can no longer trust the query only status of a
|
||||
* Transaction.
|
||||
* </p>
|
||||
*/
|
||||
Connection getInternalConnection();
|
||||
|
||||
/**
|
||||
* Rollback if the transaction is active. This provides an internal
|
||||
* mechanism for rollback failures occur on commit().
|
||||
*/
|
||||
void rollbackIfActive();
|
||||
|
||||
/**
|
||||
* Return true if the manyToMany intersection should be persisted for this particular relationship direction.
|
||||
*/
|
||||
boolean isSaveAssocManyIntersection(String intersectionTable, String beanName);
|
||||
|
||||
/**
|
||||
* Return true if batch mode got escalated for this request (and associated cascades).
|
||||
*/
|
||||
boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request);
|
||||
|
||||
/**
|
||||
* If batch mode was turned on for the request then flush the batch.
|
||||
*/
|
||||
void flushBatchOnCascade();
|
||||
|
||||
/**
|
||||
* If batch was on then effectively clear the batch such that we can handle exceptions and continue.
|
||||
*/
|
||||
void flushBatchOnRollback();
|
||||
|
||||
/**
|
||||
* Mark the transaction explicitly as not being query only.
|
||||
*/
|
||||
void markNotQueryOnly();
|
||||
|
||||
/**
|
||||
* Potentially escalate batch mode on saving or deleting a collection.
|
||||
*/
|
||||
void checkBatchEscalationOnCollection();
|
||||
|
||||
/**
|
||||
* Flush batch if we escalated batch mode on saving or deleting a collection.
|
||||
*/
|
||||
void flushBatchOnCollection();
|
||||
|
||||
/**
|
||||
* Add a bean change to the change log.
|
||||
*/
|
||||
void addBeanChange(BeanChange beanChange);
|
||||
|
||||
/**
|
||||
* Send the change set to be prepared and then logged.
|
||||
*/
|
||||
void sendChangeLog(ChangeSet changeSet);
|
||||
|
||||
/**
|
||||
* Return a document store transaction.
|
||||
*/
|
||||
DocStoreTransaction getDocStoreTransaction();
|
||||
|
||||
/**
|
||||
* Set the current Tenant Id.
|
||||
*/
|
||||
void setTenantId(Object tenantId);
|
||||
|
||||
/**
|
||||
* Return the current Tenant Id.
|
||||
*/
|
||||
Object getTenantId();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
public interface SpiTransactionScopeManager {
|
||||
|
||||
void replace(SpiTransaction t);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.Update;
|
||||
|
||||
/**
|
||||
* Internal extension to the Update interface.
|
||||
*/
|
||||
public interface SpiUpdate<T> extends Update<T> {
|
||||
|
||||
/**
|
||||
* The type of the update request.
|
||||
*/
|
||||
enum OrmUpdateType {
|
||||
INSERT {
|
||||
public String toString() {
|
||||
return "Insert";
|
||||
}
|
||||
},
|
||||
UPDATE {
|
||||
public String toString() {
|
||||
return "Update";
|
||||
}
|
||||
},
|
||||
DELETE {
|
||||
public String toString() {
|
||||
return "Delete";
|
||||
}
|
||||
},
|
||||
UNKNOWN {
|
||||
public String toString() {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of bean being updated.
|
||||
*/
|
||||
Class<?> getBeanType();
|
||||
|
||||
/**
|
||||
* Return the type of this - insert, update or delete.
|
||||
*/
|
||||
OrmUpdateType getOrmUpdateType();
|
||||
|
||||
/**
|
||||
* Return the name of the table being modified.
|
||||
*/
|
||||
String getBaseTable();
|
||||
|
||||
/**
|
||||
* Return the update statement. This could be either sql or an orm update with bean types and property names.
|
||||
*/
|
||||
String getUpdateStatement();
|
||||
|
||||
/**
|
||||
* Return the timeout in seconds.
|
||||
*/
|
||||
int getTimeout();
|
||||
|
||||
/**
|
||||
* Return true if the cache should be notified to invalidate objects.
|
||||
*/
|
||||
boolean isNotifyCache();
|
||||
|
||||
/**
|
||||
* Return the bind parameters.
|
||||
*/
|
||||
BindParams getBindParams();
|
||||
|
||||
/**
|
||||
* Set the generated sql used.
|
||||
*/
|
||||
void setGeneratedSql(String sql);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.server.persist.dml.DmlHandler;
|
||||
import io.ebeaninternal.server.persist.dmlbind.Bindable;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* A plan for executing bean updates for a given set of changed properties.
|
||||
* <p>
|
||||
* This is a cachable plan with the purpose of being being able to skip some
|
||||
* phases of the update bean processing.
|
||||
* </p>
|
||||
* <p>
|
||||
* The plans are cached by the BeanDescriptors.
|
||||
* </>
|
||||
*/
|
||||
public interface SpiUpdatePlan {
|
||||
|
||||
/**
|
||||
* Return true if the set clause has no columns.
|
||||
* <p>
|
||||
* Can occur when the only columns updated have a updatable=false in their
|
||||
* deployment.
|
||||
* </p>
|
||||
*/
|
||||
boolean isEmptySetClause();
|
||||
|
||||
/**
|
||||
* Bind given the request and bean. The bean could be the oldValues bean
|
||||
* when binding a update or delete where clause with ALL concurrency mode.
|
||||
*/
|
||||
void bindSet(DmlHandler bind, EntityBean bean) throws SQLException;
|
||||
|
||||
/**
|
||||
* Return the time this plan was created.
|
||||
*/
|
||||
long getTimeCreated();
|
||||
|
||||
/**
|
||||
* Return the time this plan was last used.
|
||||
*/
|
||||
long getTimeLastUsed();
|
||||
|
||||
/**
|
||||
* Return the hash key for this plan.
|
||||
*/
|
||||
String getKey();
|
||||
|
||||
/**
|
||||
* Return the concurrency mode for this plan.
|
||||
*/
|
||||
ConcurrencyMode getMode();
|
||||
|
||||
/**
|
||||
* Return the update SQL statement.
|
||||
*/
|
||||
String getSql();
|
||||
|
||||
/**
|
||||
* Return the set of bindable update properties.
|
||||
*/
|
||||
Bindable getSet();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.transaction.DeleteByIdMap;
|
||||
import io.ebeanservice.docstore.api.DocStoreUpdates;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds information for a transaction. There is one TransactionEvent instance
|
||||
* per Transaction instance.
|
||||
* <p>
|
||||
* When the associated Transaction commits or rollback this information is sent
|
||||
* to the TransactionEventManager.
|
||||
* </p>
|
||||
*/
|
||||
public class TransactionEvent implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 7230903304106097120L;
|
||||
|
||||
/**
|
||||
* Flag indicating this is a local transaction (not from another server in
|
||||
* the cluster).
|
||||
*/
|
||||
private final transient boolean local;
|
||||
|
||||
private TransactionEventTable eventTables;
|
||||
|
||||
private transient TransactionEventBeans eventBeans;
|
||||
|
||||
private transient DeleteByIdMap deleteByIdMap;
|
||||
|
||||
/**
|
||||
* Create the TransactionEvent, one per Transaction.
|
||||
*/
|
||||
public TransactionEvent() {
|
||||
this.local = true;
|
||||
}
|
||||
|
||||
public void addDeleteById(BeanDescriptor<?> desc, Object id) {
|
||||
if (deleteByIdMap == null) {
|
||||
deleteByIdMap = new DeleteByIdMap();
|
||||
}
|
||||
deleteByIdMap.add(desc, id);
|
||||
}
|
||||
|
||||
public void addDeleteByIdList(BeanDescriptor<?> desc, List<Object> idList) {
|
||||
if (deleteByIdMap == null) {
|
||||
deleteByIdMap = new DeleteByIdMap();
|
||||
}
|
||||
deleteByIdMap.addList(desc, idList);
|
||||
}
|
||||
|
||||
public DeleteByIdMap getDeleteByIdMap() {
|
||||
return deleteByIdMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this was a local transaction. Returns false if this
|
||||
* transaction originated on another server in the cluster.
|
||||
*/
|
||||
public boolean isLocal() {
|
||||
return local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of PersistRequestBean's for this transaction.
|
||||
*/
|
||||
public List<PersistRequestBean<?>> getPersistRequestBeans() {
|
||||
return (eventBeans == null) ? null : eventBeans.getRequests();
|
||||
}
|
||||
|
||||
public TransactionEventTable getEventTables() {
|
||||
return eventTables;
|
||||
}
|
||||
|
||||
public void add(String tableName, boolean inserts, boolean updates, boolean deletes) {
|
||||
if (eventTables == null) {
|
||||
eventTables = new TransactionEventTable();
|
||||
}
|
||||
eventTables.add(tableName, inserts, updates, deletes);
|
||||
}
|
||||
|
||||
public void add(TransactionEventTable table) {
|
||||
if (eventTables == null) {
|
||||
eventTables = new TransactionEventTable();
|
||||
}
|
||||
eventTables.add(table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a inserted updated or deleted bean to the event.
|
||||
*/
|
||||
public void add(PersistRequestBean<?> request) {
|
||||
|
||||
if (request.isNotify()) {
|
||||
// either a BeanListener or Cache is interested
|
||||
if (eventBeans == null) {
|
||||
eventBeans = new TransactionEventBeans();
|
||||
}
|
||||
eventBeans.add(request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build and return the cache changeSet.
|
||||
*/
|
||||
public CacheChangeSet buildCacheChanges(boolean viewInvalidation) {
|
||||
CacheChangeSet changeSet = new CacheChangeSet(viewInvalidation);
|
||||
if (eventBeans != null) {
|
||||
eventBeans.notifyCache(changeSet);
|
||||
}
|
||||
if (deleteByIdMap != null) {
|
||||
deleteByIdMap.notifyCache(changeSet);
|
||||
}
|
||||
return changeSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add any relevant PersistRequestBean's to DocStoreUpdates for later processing.
|
||||
*/
|
||||
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
|
||||
|
||||
List<PersistRequestBean<?>> persistRequestBeans = getPersistRequestBeans();
|
||||
if (persistRequestBeans != null) {
|
||||
for (PersistRequestBean<?> persistRequestBean : persistRequestBeans) {
|
||||
persistRequestBean.addDocStoreUpdates(docStoreUpdates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.cache.CacheChangeSet;
|
||||
import io.ebeaninternal.server.core.PersistRequestBean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Lists of inserted updated and deleted beans that have a BeanPersistListener.
|
||||
* <p>
|
||||
* These beans will be sent to the appropriate BeanListeners after a successful
|
||||
* commit of the transaction.
|
||||
* </p>
|
||||
*/
|
||||
public class TransactionEventBeans {
|
||||
|
||||
final ArrayList<PersistRequestBean<?>> requests = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Return the list of PersistRequests that BeanListeners are interested in.
|
||||
*/
|
||||
public List<PersistRequestBean<?>> getRequests() {
|
||||
return requests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bean for BeanListener notification.
|
||||
*/
|
||||
public void add(PersistRequestBean<?> request) {
|
||||
|
||||
requests.add(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the cache changes.
|
||||
*/
|
||||
public void notifyCache(CacheChangeSet changeSet) {
|
||||
for (PersistRequestBean<?> request : requests) {
|
||||
request.notifyCache(changeSet);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.event.BulkTableEvent;
|
||||
import io.ebeaninternal.server.cluster.BinaryMessage;
|
||||
import io.ebeaninternal.server.cluster.BinaryMessageList;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public final class TransactionEventTable implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 2236555729767483264L;
|
||||
|
||||
private final Map<String, TableIUD> map = new HashMap<>();
|
||||
|
||||
public String toString() {
|
||||
return "TransactionEventTable " + map.values();
|
||||
}
|
||||
|
||||
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
|
||||
|
||||
for (TableIUD tableIud : map.values()) {
|
||||
tableIud.writeBinaryMessage(msgList);
|
||||
}
|
||||
}
|
||||
|
||||
public void readBinaryMessage(DataInput dataInput) throws IOException {
|
||||
|
||||
TableIUD tableIud = TableIUD.readBinaryMessage(dataInput);
|
||||
map.put(tableIud.getTableName(), tableIud);
|
||||
}
|
||||
|
||||
|
||||
public void add(TransactionEventTable table) {
|
||||
|
||||
for (TableIUD iud : table.values()) {
|
||||
add(iud);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(String table, boolean insert, boolean update, boolean delete) {
|
||||
|
||||
table = table.toUpperCase();
|
||||
|
||||
add(new TableIUD(table, insert, update, delete));
|
||||
}
|
||||
|
||||
public void add(TableIUD newTableIUD) {
|
||||
|
||||
TableIUD existingTableIUD = map.put(newTableIUD.getTableName(), newTableIUD);
|
||||
if (existingTableIUD != null) {
|
||||
newTableIUD.add(existingTableIUD);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
public Collection<TableIUD> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
public static class TableIUD implements Serializable, BulkTableEvent {
|
||||
|
||||
private static final long serialVersionUID = -1958317571064162089L;
|
||||
|
||||
private final String table;
|
||||
private boolean insert;
|
||||
private boolean update;
|
||||
private boolean delete;
|
||||
|
||||
public TableIUD(String table, boolean insert, boolean update, boolean delete) {
|
||||
this.table = table;
|
||||
this.insert = insert;
|
||||
this.update = update;
|
||||
this.delete = delete;
|
||||
}
|
||||
|
||||
public static TableIUD readBinaryMessage(DataInput dataInput) throws IOException {
|
||||
|
||||
String table = dataInput.readUTF();
|
||||
boolean insert = dataInput.readBoolean();
|
||||
boolean update = dataInput.readBoolean();
|
||||
boolean delete = dataInput.readBoolean();
|
||||
|
||||
return new TableIUD(table, insert, update, delete);
|
||||
}
|
||||
|
||||
public void writeBinaryMessage(BinaryMessageList msgList) throws IOException {
|
||||
|
||||
BinaryMessage msg = new BinaryMessage(table.length() + 10);
|
||||
DataOutputStream os = msg.getOs();
|
||||
os.writeInt(BinaryMessage.TYPE_TABLEIUD);
|
||||
os.writeUTF(table);
|
||||
os.writeBoolean(insert);
|
||||
os.writeBoolean(update);
|
||||
os.writeBoolean(delete);
|
||||
|
||||
msgList.add(msg);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "TableIUD " + table + " i:" + insert + " u:" + update + " d:" + delete;
|
||||
}
|
||||
|
||||
private void add(TableIUD other) {
|
||||
if (other.insert) {
|
||||
insert = true;
|
||||
}
|
||||
if (other.update) {
|
||||
update = true;
|
||||
}
|
||||
if (other.delete) {
|
||||
delete = true;
|
||||
}
|
||||
}
|
||||
|
||||
public String getTableName() {
|
||||
return table;
|
||||
}
|
||||
|
||||
public boolean isInsert() {
|
||||
return insert;
|
||||
}
|
||||
|
||||
public boolean isUpdate() {
|
||||
return update;
|
||||
}
|
||||
|
||||
public boolean isDelete() {
|
||||
return delete;
|
||||
}
|
||||
|
||||
public boolean isUpdateOrDelete() {
|
||||
return update || delete;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package io.ebeaninternal.api;
|
||||
@@ -0,0 +1,101 @@
|
||||
package io.ebeaninternal.extraddl.model;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
import javax.xml.bind.annotation.XmlValue;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Java class for anonymous complex type.
|
||||
* <p>
|
||||
* <p>The following schema fragment specifies the expected content contained within this class.
|
||||
* <p>
|
||||
* <pre>
|
||||
* <complexType>
|
||||
* <simpleContent>
|
||||
* <extension base="<http://www.w3.org/2001/XMLSchema>string">
|
||||
* <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
|
||||
* <attribute name="platforms" type="{http://www.w3.org/2001/XMLSchema}string" />
|
||||
* </extension>
|
||||
* </simpleContent>
|
||||
* </complexType>
|
||||
* </pre>
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "", propOrder = {
|
||||
"value"
|
||||
})
|
||||
@XmlRootElement(name = "ddl-script")
|
||||
public class DdlScript {
|
||||
|
||||
@XmlValue
|
||||
protected String value;
|
||||
@XmlAttribute(name = "name", required = true)
|
||||
protected String name;
|
||||
@XmlAttribute(name = "platforms")
|
||||
protected String platforms;
|
||||
|
||||
/**
|
||||
* Gets the value of the value property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the value property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the name property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the name property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setName(String value) {
|
||||
this.name = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the platforms property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getPlatforms() {
|
||||
return platforms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the platforms property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setPlatforms(String value) {
|
||||
this.platforms = value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package io.ebeaninternal.extraddl.model;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Java class for anonymous complex type.
|
||||
* <p>
|
||||
* <p>The following schema fragment specifies the expected content contained within this class.
|
||||
* <p>
|
||||
* <pre>
|
||||
* <complexType>
|
||||
* <complexContent>
|
||||
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
|
||||
* <sequence>
|
||||
* <element ref="{http://ebean-orm.github.io/xml/ns/extraddl}ddl-script" maxOccurs="unbounded"/>
|
||||
* </sequence>
|
||||
* </restriction>
|
||||
* </complexContent>
|
||||
* </complexType>
|
||||
* </pre>
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "", propOrder = {
|
||||
"ddlScript"
|
||||
})
|
||||
@XmlRootElement(name = "extra-ddl")
|
||||
public class ExtraDdl {
|
||||
|
||||
@XmlElement(name = "ddl-script", required = true)
|
||||
protected List<DdlScript> ddlScript;
|
||||
|
||||
/**
|
||||
* Gets the value of the ddlScript property.
|
||||
* <p>
|
||||
* <p>
|
||||
* This accessor method returns a reference to the live list,
|
||||
* not a snapshot. Therefore any modification you make to the
|
||||
* returned list will be present inside the JAXB object.
|
||||
* This is why there is not a <CODE>set</CODE> method for the ddlScript property.
|
||||
* <p>
|
||||
* <p>
|
||||
* For example, to add a new item, do as follows:
|
||||
* <pre>
|
||||
* getDdlScript().add(newItem);
|
||||
* </pre>
|
||||
* <p>
|
||||
* <p>
|
||||
* <p>
|
||||
* Objects of the following type(s) are allowed in the list
|
||||
* {@link DdlScript }
|
||||
*/
|
||||
public List<DdlScript> getDdlScript() {
|
||||
if (ddlScript == null) {
|
||||
ddlScript = new ArrayList<>();
|
||||
}
|
||||
return this.ddlScript;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package io.ebeaninternal.extraddl.model;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Read ExtraDdl from an XML document.
|
||||
*/
|
||||
public class ExtraDdlXmlReader {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ExtraDdlXmlReader.class);
|
||||
|
||||
/**
|
||||
* Return the combined extra DDL that should be run given the platform name.
|
||||
*/
|
||||
public static String buildExtra(String platformName) {
|
||||
|
||||
ExtraDdl read = ExtraDdlXmlReader.read("/extra-ddl.xml");
|
||||
if (read == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(300);
|
||||
for (DdlScript script : read.getDdlScript()) {
|
||||
if (matchPlatform(platformName, script.getPlatforms())) {
|
||||
logger.debug("include script {}", script.getName());
|
||||
sb.append(script.getValue()).append("\n");
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the script platforms is a match/supported for the given platform.
|
||||
*
|
||||
* @param platformName The database platform we are generating/running DDL for
|
||||
* @param platforms The platforms (comma delimited) this script should run for
|
||||
*/
|
||||
public static boolean matchPlatform(String platformName, String platforms) {
|
||||
if (platforms == null || platforms.trim().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
String[] names = platforms.split("[,;]");
|
||||
for (String name : names) {
|
||||
if (name.trim().toLowerCase().contains(platformName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return a ExtraDdl from an xml document at the given resource path.
|
||||
*/
|
||||
public static ExtraDdl read(String resourcePath) {
|
||||
|
||||
InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath);
|
||||
if (is == null) {
|
||||
// we expect this and check for null
|
||||
return null;
|
||||
}
|
||||
return read(is);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return a ExtraDdl from an xml document.
|
||||
*/
|
||||
public static ExtraDdl read(InputStream is) {
|
||||
|
||||
try {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(ExtraDdl.class);
|
||||
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
|
||||
return (ExtraDdl) unmarshaller.unmarshal(is);
|
||||
|
||||
} catch (JAXBException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ebeaninternal.extraddl.model;
|
||||
|
||||
import javax.xml.bind.annotation.XmlRegistry;
|
||||
|
||||
|
||||
/**
|
||||
* This object contains factory methods for each
|
||||
* Java content interface and Java element interface
|
||||
* generated in the io.ebeaninternal.extraddl.model package.
|
||||
* <p>An ObjectFactory allows you to programatically
|
||||
* construct new instances of the Java representation
|
||||
* for XML content. The Java representation of XML
|
||||
* content can consist of schema derived interfaces
|
||||
* and classes representing the binding of schema
|
||||
* type definitions, element declarations and model
|
||||
* groups. Factory methods for each of these are
|
||||
* provided in this class.
|
||||
*/
|
||||
@XmlRegistry
|
||||
public class ObjectFactory {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: io.ebeaninternal.extraddl.model
|
||||
*/
|
||||
public ObjectFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link DdlScript }
|
||||
*/
|
||||
public DdlScript createDdlScript() {
|
||||
return new DdlScript();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link ExtraDdl }
|
||||
*/
|
||||
public ExtraDdl createExtraDdl() {
|
||||
return new ExtraDdl();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
@javax.xml.bind.annotation.XmlSchema(namespace = "http://ebean-orm.github.io/xml/ns/extraddl", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
|
||||
package io.ebeaninternal.extraddl.model;
|
||||
@@ -0,0 +1,115 @@
|
||||
package io.ebeaninternal.server.autotune;
|
||||
|
||||
import io.ebean.bean.ObjectGraphOrigin;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Profiling information collected.
|
||||
*/
|
||||
public class AutoTuneCollection {
|
||||
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
|
||||
public Entry add(ObjectGraphOrigin origin, OrmQueryDetail detail, String sourceQuery) {
|
||||
Entry entry = new Entry(origin, detail, sourceQuery);
|
||||
entries.add(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public List<Entry> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Profiling entry at a given origin point.
|
||||
*/
|
||||
public static class Entry {
|
||||
|
||||
/**
|
||||
* Profiling origin point.
|
||||
*/
|
||||
private final ObjectGraphOrigin origin;
|
||||
|
||||
/**
|
||||
* The tuned query detail.
|
||||
*/
|
||||
private final OrmQueryDetail detail;
|
||||
|
||||
/**
|
||||
* The original/existing query detail.
|
||||
*/
|
||||
private final String originalQuery;
|
||||
|
||||
/**
|
||||
* Summary execution statistics for queries related to this origin point.
|
||||
*/
|
||||
private final List<EntryQuery> queries = new ArrayList<>();
|
||||
|
||||
public Entry(ObjectGraphOrigin origin, OrmQueryDetail detail, String originalQuery) {
|
||||
this.origin = origin;
|
||||
this.detail = detail;
|
||||
this.originalQuery = originalQuery;
|
||||
}
|
||||
|
||||
public void addQuery(EntryQuery entryQuery) {
|
||||
queries.add(entryQuery);
|
||||
}
|
||||
|
||||
public ObjectGraphOrigin getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
public OrmQueryDetail getDetail() {
|
||||
return detail;
|
||||
}
|
||||
|
||||
public String getOriginalQuery() {
|
||||
return originalQuery;
|
||||
}
|
||||
|
||||
public List<EntryQuery> getQueries() {
|
||||
return queries;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary query execution statistics for the origin point.
|
||||
*/
|
||||
public static class EntryQuery {
|
||||
|
||||
final String path;
|
||||
final long exeCount;
|
||||
final long totalBeanLoaded;
|
||||
final long totalMicros;
|
||||
|
||||
public EntryQuery(String path, long exeCount, long totalBeanLoaded, long totalMicros) {
|
||||
this.path = path;
|
||||
this.exeCount = exeCount;
|
||||
this.totalBeanLoaded = totalBeanLoaded;
|
||||
this.totalMicros = totalMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the relative path with empty string for the origin query.
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public long getExeCount() {
|
||||
return exeCount;
|
||||
}
|
||||
|
||||
public long getTotalBeanLoaded() {
|
||||
return totalBeanLoaded;
|
||||
}
|
||||
|
||||
public long getTotalMicros() {
|
||||
return totalMicros;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ebeaninternal.server.autotune;
|
||||
|
||||
import io.ebean.AutoTune;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
|
||||
/**
|
||||
* Collects and manages the the profile information.
|
||||
* <p>
|
||||
* The profile information is periodically converted into "tuned query details" -
|
||||
* which is used to automatically tune the queries that use AutoTune.
|
||||
* </p>
|
||||
* <p>
|
||||
* The "tuned query details" effectively are part of the query that has the
|
||||
* select() and join() information (but not the where clause, order by, limits
|
||||
* etc). These are applied to the query when tuneQuery() is called.
|
||||
* </p>
|
||||
*/
|
||||
public interface AutoTuneService extends AutoTune {
|
||||
|
||||
/**
|
||||
* Load the query tuning information.
|
||||
*/
|
||||
void startup();
|
||||
|
||||
/**
|
||||
* Called when a query thinks it should be automatically tuned by AutoTune.
|
||||
* <p>
|
||||
* Returns true if the query was tuned.
|
||||
* </p>
|
||||
*/
|
||||
boolean tuneQuery(SpiQuery<?> query);
|
||||
|
||||
/**
|
||||
* Fire a garbage collection (hint to the JVM). Assuming garbage collection
|
||||
* fires this will gather the usage profiling information.
|
||||
*/
|
||||
void collectProfiling();
|
||||
|
||||
/**
|
||||
* On shutdown fire garbage collection and collect statistics. Note that
|
||||
* usually we add a little delay (100 milliseconds) to give the garbage
|
||||
* collector plenty of time to do its thing and collect the profile
|
||||
* information.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ebeaninternal.server.autotune;
|
||||
|
||||
import io.ebean.bean.NodeUsageListener;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
|
||||
/**
|
||||
* Profiling listener gets call backs for node usage and the associated query executions.
|
||||
*/
|
||||
public interface ProfilingListener extends NodeUsageListener {
|
||||
|
||||
/**
|
||||
* Collect summary statistics for a query executed for the given node.
|
||||
*
|
||||
* @param node the node relative to the origin point
|
||||
* @param beans the number of beans loaded by the query
|
||||
* @param micros the query execution in microseconds
|
||||
*/
|
||||
void collectQueryInfo(ObjectGraphNode node, long beans, long micros);
|
||||
|
||||
/**
|
||||
* Return true if this request should be profiled (based on the
|
||||
* profiling ratio and collection count for this origin).
|
||||
*/
|
||||
boolean isProfileRequest(ObjectGraphNode origin, SpiQuery<?> query);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package io.ebeaninternal.server.autotune.model;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Java class for anonymous complex type.
|
||||
* <p>
|
||||
* The following schema fragment specifies the expected content contained within this class.
|
||||
* <p>
|
||||
* <pre>
|
||||
* <complexType>
|
||||
* <complexContent>
|
||||
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
|
||||
* <sequence>
|
||||
* <element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
|
||||
* <element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileDiff" minOccurs="0"/>
|
||||
* <element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileNew" minOccurs="0"/>
|
||||
* <element ref="{http://ebean-orm.github.io/xml/ns/autotune}profileEmpty" minOccurs="0"/>
|
||||
* </sequence>
|
||||
* </restriction>
|
||||
* </complexContent>
|
||||
* </complexType>
|
||||
* </pre>
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "", propOrder = {
|
||||
"origin",
|
||||
"profileDiff",
|
||||
"profileNew",
|
||||
"profileEmpty"
|
||||
})
|
||||
@XmlRootElement(name = "autotune")
|
||||
public class Autotune {
|
||||
|
||||
protected List<Origin> origin;
|
||||
protected ProfileDiff profileDiff;
|
||||
protected ProfileNew profileNew;
|
||||
protected ProfileEmpty profileEmpty;
|
||||
|
||||
/**
|
||||
* Gets the value of the origin property.
|
||||
* <p>
|
||||
* <p>
|
||||
* This accessor method returns a reference to the live list,
|
||||
* not a snapshot. Therefore any modification you make to the
|
||||
* returned list will be present inside the JAXB object.
|
||||
* This is why there is not a <CODE>set</CODE> method for the origin property.
|
||||
* <p>
|
||||
* <p>
|
||||
* For example, to add a new item, do as follows:
|
||||
* <pre>
|
||||
* getOrigin().add(newItem);
|
||||
* </pre>
|
||||
* <p>
|
||||
* <p>
|
||||
* <p>
|
||||
* Objects of the following type(s) are allowed in the list
|
||||
* {@link Origin }
|
||||
*/
|
||||
public List<Origin> getOrigin() {
|
||||
if (origin == null) {
|
||||
origin = new ArrayList<>();
|
||||
}
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the profileDiff property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link ProfileDiff }
|
||||
*/
|
||||
public ProfileDiff getProfileDiff() {
|
||||
return profileDiff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the profileDiff property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link ProfileDiff }
|
||||
*/
|
||||
public void setProfileDiff(ProfileDiff value) {
|
||||
this.profileDiff = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the profileNew property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link ProfileNew }
|
||||
*/
|
||||
public ProfileNew getProfileNew() {
|
||||
return profileNew;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the profileNew property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link ProfileNew }
|
||||
*/
|
||||
public void setProfileNew(ProfileNew value) {
|
||||
this.profileNew = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the profileEmpty property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link ProfileEmpty }
|
||||
*/
|
||||
public ProfileEmpty getProfileEmpty() {
|
||||
return profileEmpty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the profileEmpty property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link ProfileEmpty }
|
||||
*/
|
||||
public void setProfileEmpty(ProfileEmpty value) {
|
||||
this.profileEmpty = value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.ebeaninternal.server.autotune.model;
|
||||
|
||||
import javax.xml.bind.annotation.XmlRegistry;
|
||||
|
||||
|
||||
/**
|
||||
* This object contains factory methods for each
|
||||
* Java content interface and Java element interface
|
||||
* generated in the io.ebeaninternal.server.autotune.model package.
|
||||
* <p>An ObjectFactory allows you to programatically
|
||||
* construct new instances of the Java representation
|
||||
* for XML content. The Java representation of XML
|
||||
* content can consist of schema derived interfaces
|
||||
* and classes representing the binding of schema
|
||||
* type definitions, element declarations and model
|
||||
* groups. Factory methods for each of these are
|
||||
* provided in this class.
|
||||
*/
|
||||
@XmlRegistry
|
||||
public class ObjectFactory {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: io.ebeaninternal.server.autotune.model
|
||||
*/
|
||||
public ObjectFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link ProfileNew }
|
||||
*/
|
||||
public ProfileNew createProfileNew() {
|
||||
return new ProfileNew();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link Origin }
|
||||
*/
|
||||
public Origin createOrigin() {
|
||||
return new Origin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link ProfileEmpty }
|
||||
*/
|
||||
public ProfileEmpty createProfileEmpty() {
|
||||
return new ProfileEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link Autotune }
|
||||
*/
|
||||
public Autotune createAutotune() {
|
||||
return new Autotune();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance of {@link ProfileDiff }
|
||||
*/
|
||||
public ProfileDiff createProfileDiff() {
|
||||
return new ProfileDiff();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package io.ebeaninternal.server.autotune.model;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Java class for anonymous complex type.
|
||||
* <p>
|
||||
* <p>The following schema fragment specifies the expected content contained within this class.
|
||||
* <p>
|
||||
* <pre>
|
||||
* <complexType>
|
||||
* <complexContent>
|
||||
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
|
||||
* <sequence>
|
||||
* <element name="callStack" type="{http://www.w3.org/2001/XMLSchema}string" minOccurs="0"/>
|
||||
* </sequence>
|
||||
* <attribute name="key" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
|
||||
* <attribute name="beanType" type="{http://www.w3.org/2001/XMLSchema}string" />
|
||||
* <attribute name="detail" type="{http://www.w3.org/2001/XMLSchema}string" />
|
||||
* <attribute name="original" type="{http://www.w3.org/2001/XMLSchema}string" />
|
||||
* </restriction>
|
||||
* </complexContent>
|
||||
* </complexType>
|
||||
* </pre>
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "", propOrder = {
|
||||
"callStack"
|
||||
})
|
||||
@XmlRootElement(name = "origin")
|
||||
public class Origin {
|
||||
|
||||
protected String callStack;
|
||||
@XmlAttribute(name = "key", required = true)
|
||||
protected String key;
|
||||
@XmlAttribute(name = "beanType")
|
||||
protected String beanType;
|
||||
@XmlAttribute(name = "detail")
|
||||
protected String detail;
|
||||
@XmlAttribute(name = "original")
|
||||
protected String original;
|
||||
|
||||
/**
|
||||
* Gets the value of the callStack property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getCallStack() {
|
||||
return callStack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the callStack property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setCallStack(String value) {
|
||||
this.callStack = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the key property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the key property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setKey(String value) {
|
||||
this.key = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the beanType property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getBeanType() {
|
||||
return beanType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the beanType property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setBeanType(String value) {
|
||||
this.beanType = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the detail property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getDetail() {
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the detail property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setDetail(String value) {
|
||||
this.detail = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the original property.
|
||||
*
|
||||
* @return possible object is
|
||||
* {@link String }
|
||||
*/
|
||||
public String getOriginal() {
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of the original property.
|
||||
*
|
||||
* @param value allowed object is
|
||||
* {@link String }
|
||||
*/
|
||||
public void setOriginal(String value) {
|
||||
this.original = value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.ebeaninternal.server.autotune.model;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Java class for anonymous complex type.
|
||||
* <p>
|
||||
* <p>The following schema fragment specifies the expected content contained within this class.
|
||||
* <p>
|
||||
* <pre>
|
||||
* <complexType>
|
||||
* <complexContent>
|
||||
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
|
||||
* <sequence>
|
||||
* <element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
|
||||
* </sequence>
|
||||
* </restriction>
|
||||
* </complexContent>
|
||||
* </complexType>
|
||||
* </pre>
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "", propOrder = {
|
||||
"origin"
|
||||
})
|
||||
@XmlRootElement(name = "profileDiff")
|
||||
public class ProfileDiff {
|
||||
|
||||
protected List<Origin> origin;
|
||||
|
||||
/**
|
||||
* Gets the value of the origin property.
|
||||
* <p>
|
||||
* <p>
|
||||
* This accessor method returns a reference to the live list,
|
||||
* not a snapshot. Therefore any modification you make to the
|
||||
* returned list will be present inside the JAXB object.
|
||||
* This is why there is not a <CODE>set</CODE> method for the origin property.
|
||||
* <p>
|
||||
* <p>
|
||||
* For example, to add a new item, do as follows:
|
||||
* <pre>
|
||||
* getOrigin().add(newItem);
|
||||
* </pre>
|
||||
* <p>
|
||||
* <p>
|
||||
* <p>
|
||||
* Objects of the following type(s) are allowed in the list
|
||||
* {@link Origin }
|
||||
*/
|
||||
public List<Origin> getOrigin() {
|
||||
if (origin == null) {
|
||||
origin = new ArrayList<>();
|
||||
}
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.ebeaninternal.server.autotune.model;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Java class for anonymous complex type.
|
||||
* <p>
|
||||
* <p>The following schema fragment specifies the expected content contained within this class.
|
||||
* <p>
|
||||
* <pre>
|
||||
* <complexType>
|
||||
* <complexContent>
|
||||
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
|
||||
* <sequence>
|
||||
* <element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
|
||||
* </sequence>
|
||||
* </restriction>
|
||||
* </complexContent>
|
||||
* </complexType>
|
||||
* </pre>
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "", propOrder = {
|
||||
"origin"
|
||||
})
|
||||
@XmlRootElement(name = "profileEmpty")
|
||||
public class ProfileEmpty {
|
||||
|
||||
protected List<Origin> origin;
|
||||
|
||||
/**
|
||||
* Gets the value of the origin property.
|
||||
* <p>
|
||||
* <p>
|
||||
* This accessor method returns a reference to the live list,
|
||||
* not a snapshot. Therefore any modification you make to the
|
||||
* returned list will be present inside the JAXB object.
|
||||
* This is why there is not a <CODE>set</CODE> method for the origin property.
|
||||
* <p>
|
||||
* <p>
|
||||
* For example, to add a new item, do as follows:
|
||||
* <pre>
|
||||
* getOrigin().add(newItem);
|
||||
* </pre>
|
||||
* <p>
|
||||
* <p>
|
||||
* <p>
|
||||
* Objects of the following type(s) are allowed in the list
|
||||
* {@link Origin }
|
||||
*/
|
||||
public List<Origin> getOrigin() {
|
||||
if (origin == null) {
|
||||
origin = new ArrayList<>();
|
||||
}
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package io.ebeaninternal.server.autotune.model;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* <p>Java class for anonymous complex type.
|
||||
* <p>
|
||||
* <p>The following schema fragment specifies the expected content contained within this class.
|
||||
* <p>
|
||||
* <pre>
|
||||
* <complexType>
|
||||
* <complexContent>
|
||||
* <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
|
||||
* <sequence>
|
||||
* <element ref="{http://ebean-orm.github.io/xml/ns/autotune}origin" maxOccurs="unbounded" minOccurs="0"/>
|
||||
* </sequence>
|
||||
* </restriction>
|
||||
* </complexContent>
|
||||
* </complexType>
|
||||
* </pre>
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@XmlType(name = "", propOrder = {
|
||||
"origin"
|
||||
})
|
||||
@XmlRootElement(name = "profileNew")
|
||||
public class ProfileNew {
|
||||
|
||||
protected List<Origin> origin;
|
||||
|
||||
/**
|
||||
* Gets the value of the origin property.
|
||||
* <p>
|
||||
* <p>
|
||||
* This accessor method returns a reference to the live list,
|
||||
* not a snapshot. Therefore any modification you make to the
|
||||
* returned list will be present inside the JAXB object.
|
||||
* This is why there is not a <CODE>set</CODE> method for the origin property.
|
||||
* <p>
|
||||
* <p>
|
||||
* For example, to add a new item, do as follows:
|
||||
* <pre>
|
||||
* getOrigin().add(newItem);
|
||||
* </pre>
|
||||
* <p>
|
||||
* <p>
|
||||
* <p>
|
||||
* Objects of the following type(s) are allowed in the list
|
||||
* {@link Origin }
|
||||
*/
|
||||
public List<Origin> getOrigin() {
|
||||
if (origin == null) {
|
||||
origin = new ArrayList<>();
|
||||
}
|
||||
return this.origin;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
@javax.xml.bind.annotation.XmlSchema(namespace = "http://ebean-orm.github.io/xml/ns/autotune", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
|
||||
package io.ebeaninternal.server.autotune.model;
|
||||
@@ -0,0 +1,61 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebeaninternal.server.autotune.model.Autotune;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Event where all tuned query information is collected.
|
||||
* <p>
|
||||
* This is for writing the "all" file on shutdown when using runtime tuning.
|
||||
* </p>
|
||||
*/
|
||||
public class AutoTuneAllCollection {
|
||||
|
||||
final Autotune document = new Autotune();
|
||||
|
||||
final BaseQueryTuner queryTuner;
|
||||
|
||||
/**
|
||||
* Construct to collect/report all tuned queries.
|
||||
*/
|
||||
public AutoTuneAllCollection(BaseQueryTuner queryTuner) {
|
||||
this.queryTuner = queryTuner;
|
||||
loadAllTuned();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of origin elements in the document.
|
||||
*/
|
||||
public int size() {
|
||||
return document.getOrigin().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Autotune document object.
|
||||
*/
|
||||
public Autotune getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the document as an xml file.
|
||||
*/
|
||||
public void writeFile(String filePrefix, boolean withNow) {
|
||||
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, filePrefix, withNow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all the existing query tuning into the document.
|
||||
*/
|
||||
private void loadAllTuned() {
|
||||
|
||||
Collection<TunedQueryInfo> all = queryTuner.getAll();
|
||||
for (TunedQueryInfo tuned : all) {
|
||||
document.getOrigin().add(tuned.getOrigin());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebean.bean.ObjectGraphOrigin;
|
||||
import io.ebeaninternal.server.autotune.AutoTuneCollection;
|
||||
import io.ebeaninternal.server.autotune.model.Autotune;
|
||||
import io.ebeaninternal.server.autotune.model.Origin;
|
||||
import io.ebeaninternal.server.autotune.model.ProfileDiff;
|
||||
import io.ebeaninternal.server.autotune.model.ProfileNew;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
|
||||
/**
|
||||
* Event where profiling information is collected and processed for differences
|
||||
* relative to the current query tuning.
|
||||
*/
|
||||
public class AutoTuneDiffCollection {
|
||||
|
||||
final Autotune document = new Autotune();
|
||||
|
||||
final AutoTuneCollection profiling;
|
||||
|
||||
final BaseQueryTuner queryTuner;
|
||||
|
||||
final boolean updateTuning;
|
||||
|
||||
int newCount;
|
||||
|
||||
int diffCount;
|
||||
|
||||
/**
|
||||
* Construct to collect/report the new/diff query tuning entries.
|
||||
*/
|
||||
public AutoTuneDiffCollection(AutoTuneCollection profiling, BaseQueryTuner queryTuner, boolean updateTuning) {
|
||||
this.profiling = profiling;
|
||||
this.queryTuner = queryTuner;
|
||||
this.updateTuning = updateTuning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there are no new or diff entries.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return newCount == 0 && diffCount == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying Autotune document object.
|
||||
*/
|
||||
public Autotune getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of diff entries.
|
||||
*/
|
||||
public int getDiffCount() {
|
||||
return diffCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of new entries.
|
||||
*/
|
||||
public int getNewCount() {
|
||||
return newCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total new and diff entries.
|
||||
*/
|
||||
public int getChangeCount() {
|
||||
return newCount + diffCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the underlying document as an xml file.
|
||||
*/
|
||||
public void writeFile(String filePrefix) {
|
||||
|
||||
AutoTuneXmlWriter writer = new AutoTuneXmlWriter();
|
||||
writer.write(document, filePrefix, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process checking profiling entries against existing query tuning.
|
||||
*/
|
||||
public void process() {
|
||||
|
||||
for (AutoTuneCollection.Entry entry : profiling.getEntries()) {
|
||||
addToDocument(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the entry is new or diff and add as necessary.
|
||||
*/
|
||||
private void addToDocument(AutoTuneCollection.Entry entry) {
|
||||
|
||||
ObjectGraphOrigin point = entry.getOrigin();
|
||||
OrmQueryDetail profileDetail = entry.getDetail();
|
||||
|
||||
// compare with the existing query tuning entry
|
||||
OrmQueryDetail tuneDetail = queryTuner.get(point.getKey());
|
||||
if (tuneDetail == null) {
|
||||
addToDocumentNewEntry(entry, point);
|
||||
|
||||
} else if (!tuneDetail.isAutoTuneEqual(profileDetail)) {
|
||||
addToDocumentDiffEntry(entry, point, tuneDetail);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add as a diff entry.
|
||||
*/
|
||||
private void addToDocumentDiffEntry(AutoTuneCollection.Entry entry, ObjectGraphOrigin point, OrmQueryDetail tuneDetail) {
|
||||
|
||||
diffCount++;
|
||||
|
||||
Origin origin = createOrigin(entry, point, tuneDetail.toString());
|
||||
ProfileDiff diff = document.getProfileDiff();
|
||||
if (diff == null) {
|
||||
diff = new ProfileDiff();
|
||||
document.setProfileDiff(diff);
|
||||
}
|
||||
diff.getOrigin().add(origin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add as a "new" entry.
|
||||
*/
|
||||
private void addToDocumentNewEntry(AutoTuneCollection.Entry entry, ObjectGraphOrigin point) {
|
||||
|
||||
newCount++;
|
||||
|
||||
ProfileNew profileNew = document.getProfileNew();
|
||||
if (profileNew == null) {
|
||||
profileNew = new ProfileNew();
|
||||
document.setProfileNew(profileNew);
|
||||
}
|
||||
Origin origin = createOrigin(entry, point, entry.getOriginalQuery());
|
||||
profileNew.getOrigin().add(origin);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the XML Origin bean for the given entry and ObjectGraphOrigin.
|
||||
*/
|
||||
private Origin createOrigin(AutoTuneCollection.Entry entry, ObjectGraphOrigin point, String query) {
|
||||
|
||||
Origin origin = new Origin();
|
||||
origin.setKey(point.getKey());
|
||||
origin.setBeanType(point.getBeanType());
|
||||
origin.setDetail(entry.getDetail().toString());
|
||||
origin.setCallStack(point.getCallStack().description("\n"));
|
||||
origin.setOriginal(query);
|
||||
|
||||
if (updateTuning) {
|
||||
queryTuner.put(origin);
|
||||
}
|
||||
|
||||
return origin;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.server.autotune.AutoTuneService;
|
||||
|
||||
public class AutoTuneServiceFactory {
|
||||
|
||||
public static AutoTuneService create(SpiEbeanServer server, ServerConfig serverConfig) {
|
||||
|
||||
return new DefaultAutoTuneService(server, serverConfig);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
|
||||
import io.ebeaninternal.server.autotune.model.Autotune;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Reads a profiling xml document.
|
||||
*/
|
||||
public class AutoTuneXmlReader {
|
||||
|
||||
/**
|
||||
* Read and return a Profiling from an xml file.
|
||||
*/
|
||||
public static Autotune read(File file) {
|
||||
try {
|
||||
return readFile(file);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected static Autotune readFile(File file) throws IOException {
|
||||
if (!file.exists()) {
|
||||
return new Autotune();
|
||||
}
|
||||
FileInputStream is = new FileInputStream(file);
|
||||
try {
|
||||
return read(is);
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and return a Profiling from an xml document.
|
||||
*/
|
||||
public static Autotune read(InputStream is) {
|
||||
try {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(Autotune.class);
|
||||
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
|
||||
return (Autotune) unmarshaller.unmarshal(is);
|
||||
} catch (JAXBException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
|
||||
import io.ebeaninternal.server.autotune.model.Autotune;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Marshaller;
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Simple writer for output of the AutoTune Profiling as an XML document.
|
||||
*/
|
||||
public class AutoTuneXmlWriter {
|
||||
|
||||
/**
|
||||
* Return 'now' as a string to second precision.
|
||||
*/
|
||||
public static String now() {
|
||||
SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||
return df.format(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the document as xml file with the given prefix.
|
||||
*/
|
||||
public void write(Autotune document, String fileName, boolean withNow) {
|
||||
|
||||
SortAutoTuneDocument.sort(document);
|
||||
|
||||
if (withNow) {
|
||||
fileName += "-" + now() + ".xml";
|
||||
}
|
||||
|
||||
// write the file with serverName and now suffix as we can output the profiling many times
|
||||
write(document, new File(fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write Profiling to a file as xml.
|
||||
*/
|
||||
public void write(Autotune profiling, File file) {
|
||||
|
||||
try {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(Autotune.class);
|
||||
Marshaller marshaller = jaxbContext.createMarshaller();
|
||||
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
|
||||
marshaller.marshal(profiling, file);
|
||||
|
||||
} catch (JAXBException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebean.bean.CallStack;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.config.AutoTuneConfig;
|
||||
import io.ebean.config.AutoTuneMode;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.autotune.ProfilingListener;
|
||||
import io.ebeaninternal.server.autotune.model.Origin;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class BaseQueryTuner {
|
||||
|
||||
private final boolean queryTuning;
|
||||
|
||||
private final boolean profiling;
|
||||
|
||||
private final AutoTuneMode mode;
|
||||
|
||||
/**
|
||||
* Map of the tuned query details per profile query point.
|
||||
*/
|
||||
private final Map<String, TunedQueryInfo> tunedQueryInfoMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final ProfilingListener profilingListener;
|
||||
|
||||
/**
|
||||
* Flag set true when there is no profiling or query tuning.
|
||||
*/
|
||||
private final boolean skipAll;
|
||||
|
||||
public BaseQueryTuner(AutoTuneConfig config, SpiEbeanServer server, ProfilingListener profilingListener) {
|
||||
this.server = server;
|
||||
this.profilingListener = profilingListener;
|
||||
this.mode = config.getMode();
|
||||
this.queryTuning = config.isQueryTuning();
|
||||
this.profiling = config.isProfiling();
|
||||
this.skipAll = !queryTuning && !profiling;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the current tuned query entries.
|
||||
*/
|
||||
public Collection<TunedQueryInfo> getAll() {
|
||||
return tunedQueryInfoMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a query tuning entry.
|
||||
*/
|
||||
public void put(Origin origin) {
|
||||
|
||||
tunedQueryInfoMap.put(origin.getKey(), new TunedQueryInfo(origin));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the tuned query information.
|
||||
*/
|
||||
public void load(String key, TunedQueryInfo queryInfo) {
|
||||
tunedQueryInfoMap.put(key, queryInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the detail currently used for tuning.
|
||||
* This returns null if there is currently no matching tuning.
|
||||
*/
|
||||
public OrmQueryDetail get(String key) {
|
||||
TunedQueryInfo info = tunedQueryInfoMap.get(key);
|
||||
return (info == null) ? null : info.getTunedDetail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto tune the query and enable profiling.
|
||||
*/
|
||||
public boolean tuneQuery(SpiQuery<?> query) {
|
||||
|
||||
if (skipAll || !tunableQuery(query)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!useTuning(query)) {
|
||||
if (profiling) {
|
||||
profiling(query, server.createCallStack());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (query.getParentNode() != null) {
|
||||
// This is a +lazy/+query query with profiling on.
|
||||
// We continue to collect the profiling information.
|
||||
query.setProfilingListener(profilingListener);
|
||||
return true;
|
||||
}
|
||||
|
||||
// create a query point to identify the query
|
||||
CallStack stack = server.createCallStack();
|
||||
ObjectGraphNode origin = query.setOrigin(stack);
|
||||
|
||||
if (profiling) {
|
||||
if (profilingListener.isProfileRequest(origin, query)) {
|
||||
// collect more profiling based on profiling rate etc
|
||||
query.setProfilingListener(profilingListener);
|
||||
}
|
||||
}
|
||||
|
||||
if (queryTuning) {
|
||||
// get current "tuned fetch" for this query point
|
||||
TunedQueryInfo tuneInfo = tunedQueryInfoMap.get(origin.getOriginQueryPoint().getKey());
|
||||
return tuneInfo != null && tuneInfo.tuneQuery(query);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return false for row count, find ids, subQuery, delete and Versions queries.
|
||||
* <p>
|
||||
* These queries are not applicable for autoTune in that they don't have a select/fetch (fetch group).
|
||||
* </p>
|
||||
* <p>
|
||||
* We also exclude queries that are explicitly set to load the L2 bean cache as we want full beans
|
||||
* in that case.
|
||||
* </p>
|
||||
*/
|
||||
private boolean tunableQuery(SpiQuery<?> query) {
|
||||
SpiQuery.Type type = query.getType();
|
||||
switch (type) {
|
||||
case ROWCOUNT:
|
||||
case ID_LIST:
|
||||
case DELETE:
|
||||
case SUBQUERY:
|
||||
return false;
|
||||
default:
|
||||
// not using autoTune when explicitly loading the l2 bean cache
|
||||
// or when using Versions query
|
||||
return !query.isLoadBeanCache() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
|
||||
}
|
||||
}
|
||||
|
||||
private void profiling(SpiQuery<?> query, CallStack stack) {
|
||||
|
||||
// create a query point to identify the query
|
||||
ObjectGraphNode origin = query.setOrigin(stack);
|
||||
if (profilingListener.isProfileRequest(origin, query)) {
|
||||
// collect more profiling based on profiling rate etc
|
||||
query.setProfilingListener(profilingListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we should try to tune this query.
|
||||
*/
|
||||
private boolean useTuning(SpiQuery<?> query) {
|
||||
|
||||
Boolean autoTune = query.isAutoTune();
|
||||
if (autoTune != null) {
|
||||
// explicitly set...
|
||||
return autoTune;
|
||||
|
||||
} else {
|
||||
// determine using implicit mode...
|
||||
switch (mode) {
|
||||
case DEFAULT_ON:
|
||||
return true;
|
||||
|
||||
case DEFAULT_OFF:
|
||||
return false;
|
||||
|
||||
case DEFAULT_ONIFEMPTY:
|
||||
return query.isDetailEmpty();
|
||||
|
||||
default:
|
||||
throw new PersistenceException("Invalid AutoTuneMode " + mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the keys as a set.
|
||||
*/
|
||||
public Set<String> keySet() {
|
||||
return tunedQueryInfoMap.keySet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebean.config.AutoTuneConfig;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.autotune.AutoTuneCollection;
|
||||
import io.ebeaninternal.server.autotune.AutoTuneService;
|
||||
import io.ebeaninternal.server.autotune.model.Autotune;
|
||||
import io.ebeaninternal.server.autotune.model.Origin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Implementation of the AutoTuneService which is comprised of profiling and query tuning.
|
||||
*/
|
||||
public class DefaultAutoTuneService implements AutoTuneService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(DefaultAutoTuneService.class);
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
private final long defaultGarbageCollectionWait;
|
||||
|
||||
private final boolean skipGarbageCollectionOnShutdown;
|
||||
|
||||
private final boolean skipProfileReportingOnShutdown;
|
||||
|
||||
private final BaseQueryTuner queryTuner;
|
||||
|
||||
private final ProfileManager profileManager;
|
||||
|
||||
private final boolean profiling;
|
||||
|
||||
private final boolean queryTuning;
|
||||
|
||||
private final String tuningFile;
|
||||
|
||||
private final String profilingFile;
|
||||
|
||||
private final String serverName;
|
||||
|
||||
private final int profilingUpdateFrequency;
|
||||
|
||||
private long runtimeChangeCount;
|
||||
|
||||
public DefaultAutoTuneService(SpiEbeanServer server, ServerConfig serverConfig) {
|
||||
|
||||
AutoTuneConfig config = serverConfig.getAutoTuneConfig();
|
||||
|
||||
this.server = server;
|
||||
this.queryTuning = config.isQueryTuning();
|
||||
this.profiling = config.isProfiling();
|
||||
this.tuningFile = config.getQueryTuningFile();
|
||||
this.profilingFile = config.getProfilingFile();
|
||||
this.profilingUpdateFrequency = config.getProfilingUpdateFrequency();
|
||||
this.serverName = server.getName();
|
||||
this.profileManager = new ProfileManager(config, server);
|
||||
this.queryTuner = new BaseQueryTuner(config, server, profileManager);
|
||||
this.skipGarbageCollectionOnShutdown = config.isSkipGarbageCollectionOnShutdown();
|
||||
this.skipProfileReportingOnShutdown = config.isSkipProfileReportingOnShutdown();
|
||||
this.defaultGarbageCollectionWait = config.getGarbageCollectionWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the query tuning information from it's data store.
|
||||
*/
|
||||
@Override
|
||||
public void startup() {
|
||||
|
||||
if (queryTuning) {
|
||||
loadTuningFile();
|
||||
if (isRuntimeTuningUpdates()) {
|
||||
// periodically gather and update query tuning
|
||||
server.getBackgroundExecutor().executePeriodically(new ProfilingUpdate(), profilingUpdateFrequency, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the tuning should update periodically at runtime.
|
||||
*/
|
||||
private boolean isRuntimeTuningUpdates() {
|
||||
return profilingUpdateFrequency > 0;
|
||||
}
|
||||
|
||||
private class ProfilingUpdate implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
runtimeTuningUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load tuning information from an existing tuning file.
|
||||
*/
|
||||
private void loadTuningFile() {
|
||||
File file = new File(tuningFile);
|
||||
if (file.exists()) {
|
||||
loadAutoTuneProfiling(AutoTuneXmlReader.read(file));
|
||||
} else {
|
||||
// look for autotune as a resource
|
||||
InputStream stream = getClass().getResourceAsStream("/" + tuningFile);
|
||||
if (stream != null) {
|
||||
loadAutoTuneProfiling(AutoTuneXmlReader.read(stream));
|
||||
} else {
|
||||
logger.warn("AutoTune file {} not found - no initial automatic query tuning", tuningFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAutoTuneProfiling(Autotune profiling) {
|
||||
logger.info("AutoTune loading {} tuning entries", profiling.getOrigin().size());
|
||||
for (Origin origin : profiling.getOrigin()) {
|
||||
queryTuner.put(origin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect profiling, check for new/diff to existing tuning and apply changes.
|
||||
*/
|
||||
private void runtimeTuningUpdate() {
|
||||
|
||||
synchronized (this) {
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
AutoTuneCollection profiling = profileManager.profilingCollection(false);
|
||||
|
||||
AutoTuneDiffCollection event = new AutoTuneDiffCollection(profiling, queryTuner, true);
|
||||
event.process();
|
||||
if (event.isEmpty()) {
|
||||
long exeMillis = System.currentTimeMillis() - start;
|
||||
logger.debug("No query tuning updates for server:{} executionMillis:{}", serverName, exeMillis);
|
||||
|
||||
} else {
|
||||
// report the query tuning changes that have been made
|
||||
runtimeChangeCount += event.getChangeCount();
|
||||
event.writeFile(profilingFile + "-" + serverName + "-update");
|
||||
long exeMillis = System.currentTimeMillis() - start;
|
||||
logger.info("query tuning updates - new:{} diff:{} for server:{} executionMillis:{}", event.getNewCount(), event.getDiffCount(), serverName, exeMillis);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
logger.error("Error collecting or applying automatic query tuning", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void saveProfilingOnShutdown(boolean reset) {
|
||||
|
||||
synchronized (this) {
|
||||
if (isRuntimeTuningUpdates()) {
|
||||
runtimeTuningUpdate();
|
||||
outputAllTuning();
|
||||
|
||||
} else {
|
||||
|
||||
AutoTuneCollection profiling = profileManager.profilingCollection(reset);
|
||||
|
||||
AutoTuneDiffCollection event = new AutoTuneDiffCollection(profiling, queryTuner, false);
|
||||
event.process();
|
||||
if (event.isEmpty()) {
|
||||
logger.info("No new or diff entries for profiling server:{}", serverName);
|
||||
|
||||
} else {
|
||||
event.writeFile(profilingFile + "-" + serverName);
|
||||
logger.info("writing new:{} diff:{} profiling entries for server:{}", event.getNewCount(), event.getDiffCount(), serverName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output all the query tuning (the "all" file).
|
||||
* <p>
|
||||
* This is the originally loaded tuning plus any tuning changes picked up and applied at runtime.
|
||||
* </p>
|
||||
* <p>
|
||||
* This "all" file can be used as the next "ebean-autotune.xml" file.
|
||||
* </p>
|
||||
*/
|
||||
private void outputAllTuning() {
|
||||
|
||||
if (runtimeChangeCount == 0) {
|
||||
logger.info("no runtime query tuning changes for server:{}", serverName);
|
||||
|
||||
} else {
|
||||
AutoTuneAllCollection event = new AutoTuneAllCollection(queryTuner);
|
||||
int size = event.size();
|
||||
File existingTuning = new File(tuningFile);
|
||||
if (existingTuning.exists()) {
|
||||
// rename the existing autotune.xml file (appending 'now')
|
||||
if (!existingTuning.renameTo(new File(tuningFile + "." + AutoTuneXmlWriter.now()))) {
|
||||
logger.warn("Failed to rename autotune file [{}]", tuningFile);
|
||||
}
|
||||
}
|
||||
|
||||
event.writeFile(tuningFile, false);
|
||||
logger.info("query tuning detected [{}] changes, writing all [{}] tuning entries for server:{}", runtimeChangeCount, size, serverName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the listener.
|
||||
* <p>
|
||||
* We should try to collect the usage statistics by calling a System.gc().
|
||||
* This is necessary for use with short lived applications where garbage
|
||||
* collection may not otherwise occur at all.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (profiling) {
|
||||
if (!skipGarbageCollectionOnShutdown && !skipProfileReportingOnShutdown) {
|
||||
// trigger GC to update profiling information on recently executed queries
|
||||
collectProfiling(-1);
|
||||
}
|
||||
if (!skipProfileReportingOnShutdown) {
|
||||
saveProfilingOnShutdown(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the profiling.
|
||||
* <p>
|
||||
* When profiling updates are applied to tuning at runtime this reports all tuning and profiling combined.
|
||||
* When profiling is not applied at runtime then this reports the diff report with new and diff entries relative
|
||||
* to the existing tuning.
|
||||
* </p>
|
||||
*/
|
||||
public void reportProfiling() {
|
||||
saveProfilingOnShutdown(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for a System.gc() so that we gather node usage information.
|
||||
* <p>
|
||||
* Really only want to do this sparingly but useful just prior to shutdown
|
||||
* for short run application where garbage collection may otherwise not
|
||||
* occur at all.
|
||||
* </p>
|
||||
* <p>
|
||||
* waitMillis will do a thread sleep to give the garbage collection a little
|
||||
* time to do its thing assuming we are shutting down the VM.
|
||||
* </p>
|
||||
* <p>
|
||||
* If waitMillis is -1 then the defaultGarbageCollectionWait is used which
|
||||
* defaults to 100 milliseconds.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public void collectProfiling() {
|
||||
collectProfiling(-1);
|
||||
}
|
||||
|
||||
public void collectProfiling(long waitMillis) {
|
||||
System.gc();
|
||||
try {
|
||||
if (waitMillis < 0) {
|
||||
waitMillis = defaultGarbageCollectionWait;
|
||||
}
|
||||
Thread.sleep(waitMillis);
|
||||
} catch (InterruptedException e) {
|
||||
// restore the interrupted status
|
||||
Thread.currentThread().interrupt();
|
||||
logger.warn("Error while sleeping after System.gc() request.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto tune the query and enable profiling.
|
||||
*/
|
||||
@Override
|
||||
public boolean tuneQuery(SpiQuery<?> query) {
|
||||
return queryTuner.tuneQuery(query);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebean.bean.NodeUsageCollector;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.bean.ObjectGraphOrigin;
|
||||
import io.ebean.config.AutoTuneConfig;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.autotune.AutoTuneCollection;
|
||||
import io.ebeaninternal.server.autotune.ProfilingListener;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Manages the collection of object graph usage profiling.
|
||||
*/
|
||||
public class ProfileManager implements ProfilingListener {
|
||||
|
||||
private final boolean queryTuningAddVersion;
|
||||
|
||||
/**
|
||||
* Converted from a 0-100 int to a double. Effectively a percentage rate at
|
||||
* which to collect profiling information.
|
||||
*/
|
||||
private final double profilingRate;
|
||||
|
||||
private final int profilingBase;
|
||||
|
||||
/**
|
||||
* Map of the usage and query statistics gathered.
|
||||
*/
|
||||
private final Map<String, ProfileOrigin> profileMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
public ProfileManager(AutoTuneConfig config, SpiEbeanServer server) {
|
||||
this.server = server;
|
||||
this.profilingRate = config.getProfilingRate();
|
||||
this.profilingBase = config.getProfilingBase();
|
||||
this.queryTuningAddVersion = config.isQueryTuningAddVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isProfileRequest(ObjectGraphNode origin, SpiQuery<?> query) {
|
||||
|
||||
ProfileOrigin profileOrigin = profileMap.get(origin.getOriginQueryPoint().getKey());
|
||||
if (profileOrigin == null) {
|
||||
profileMap.put(origin.getOriginQueryPoint().getKey(), createProfileOrigin(origin, query));
|
||||
return true;
|
||||
} else {
|
||||
return profileOrigin.isProfile();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the profile origin noting the query detail currently being used.
|
||||
* <p>
|
||||
* For new profiling entries it is useful to compare the profiling against the current
|
||||
* query detail that is specified in the code (as the query might already be manually optimised).
|
||||
* </p>
|
||||
*/
|
||||
private ProfileOrigin createProfileOrigin(ObjectGraphNode origin, SpiQuery<?> query) {
|
||||
ProfileOrigin profileOrigin = new ProfileOrigin(origin.getOriginQueryPoint(), queryTuningAddVersion, profilingBase, profilingRate);
|
||||
// set the current query detail (fetch group) so that we can compare against profiling for new entries
|
||||
profileOrigin.setOriginalQuery(query.getDetail().toString());
|
||||
return profileOrigin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather query execution statistics. This could either be the originating
|
||||
* query in which case the parentNode will be null, or a lazy loading query
|
||||
* resulting from traversal of the object graph.
|
||||
*/
|
||||
public void collectQueryInfo(ObjectGraphNode node, long beans, long micros) {
|
||||
|
||||
if (node != null) {
|
||||
ObjectGraphOrigin origin = node.getOriginQueryPoint();
|
||||
if (origin != null) {
|
||||
ProfileOrigin stats = getProfileOrigin(origin);
|
||||
stats.collectQueryInfo(node, beans, micros);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect usage statistics from a node in the object graph.
|
||||
* <p>
|
||||
* This is sent to use from a EntityBeanIntercept when the finalise method
|
||||
* is called on the bean.
|
||||
* </p>
|
||||
*/
|
||||
public void collectNodeUsage(NodeUsageCollector usageCollector) {
|
||||
|
||||
ProfileOrigin profileOrigin = getProfileOrigin(usageCollector.getNode().getOriginQueryPoint());
|
||||
profileOrigin.collectUsageInfo(usageCollector);
|
||||
}
|
||||
|
||||
private ProfileOrigin getProfileOrigin(ObjectGraphOrigin originQueryPoint) {
|
||||
synchronized (monitor) {
|
||||
ProfileOrigin stats = profileMap.get(originQueryPoint.getKey());
|
||||
if (stats == null) {
|
||||
stats = new ProfileOrigin(originQueryPoint, queryTuningAddVersion, profilingBase, profilingRate);
|
||||
profileMap.put(originQueryPoint.getKey(), stats);
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all the profiling information.
|
||||
*/
|
||||
public AutoTuneCollection profilingCollection(boolean reset) {
|
||||
|
||||
AutoTuneCollection req = new AutoTuneCollection();
|
||||
|
||||
for (ProfileOrigin origin : profileMap.values()) {
|
||||
BeanDescriptor<?> desc = server.getBeanDescriptorById(origin.getOrigin().getBeanType());
|
||||
if (desc != null) {
|
||||
origin.profilingCollection(desc, req, reset);
|
||||
}
|
||||
}
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebean.bean.NodeUsageCollector;
|
||||
import io.ebean.bean.ObjectGraphNode;
|
||||
import io.ebean.bean.ObjectGraphOrigin;
|
||||
import io.ebean.text.PathProperties;
|
||||
import io.ebean.text.PathProperties.Props;
|
||||
import io.ebeaninternal.server.autotune.AutoTuneCollection;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class ProfileOrigin {
|
||||
|
||||
private static final long RESET_COUNT = -1000000000L;
|
||||
|
||||
private final ObjectGraphOrigin origin;
|
||||
|
||||
private final boolean queryTuningAddVersion;
|
||||
|
||||
private final int profilingBase;
|
||||
|
||||
private final double profilingRate;
|
||||
|
||||
private final Map<String, ProfileOriginQuery> queryStatsMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, ProfileOriginNodeUsage> nodeUsageMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final AtomicLong requestCount = new AtomicLong();
|
||||
|
||||
private final AtomicLong profileCount = new AtomicLong();
|
||||
|
||||
private String originalQuery;
|
||||
|
||||
public ProfileOrigin(ObjectGraphOrigin origin, boolean queryTuningAddVersion, int profilingBase, double profilingRate) {
|
||||
this.origin = origin;
|
||||
this.queryTuningAddVersion = queryTuningAddVersion;
|
||||
this.profilingBase = profilingBase;
|
||||
this.profilingRate = profilingRate;
|
||||
}
|
||||
|
||||
public String getOriginalQuery() {
|
||||
return originalQuery;
|
||||
}
|
||||
|
||||
public void setOriginalQuery(String originalQuery) {
|
||||
this.originalQuery = originalQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this query should be profiled based on a percentage rate.
|
||||
*/
|
||||
public boolean isProfile() {
|
||||
|
||||
long count = requestCount.incrementAndGet();
|
||||
if (count < profilingBase) {
|
||||
return true;
|
||||
}
|
||||
long hits = profileCount.get();
|
||||
if (profilingRate > (double) hits / count) {
|
||||
profileCount.incrementAndGet();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect profiling information with the option to reset the underlying profiling detail.
|
||||
*/
|
||||
public void profilingCollection(BeanDescriptor<?> rootDesc, AutoTuneCollection req, boolean reset) {
|
||||
|
||||
synchronized (monitor) {
|
||||
if (nodeUsageMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
OrmQueryDetail detail = buildDetail(rootDesc);
|
||||
AutoTuneCollection.Entry entry = req.add(origin, detail, originalQuery);
|
||||
|
||||
Collection<ProfileOriginQuery> values = queryStatsMap.values();
|
||||
for (ProfileOriginQuery queryEntry : values) {
|
||||
entry.addQuery(queryEntry.createEntryQuery(reset));
|
||||
}
|
||||
if (reset) {
|
||||
nodeUsageMap.clear();
|
||||
if (requestCount.get() > RESET_COUNT) {
|
||||
requestCount.set(profilingBase);
|
||||
profileCount.set(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private OrmQueryDetail buildDetail(BeanDescriptor<?> rootDesc) {
|
||||
|
||||
PathProperties pathProps = new PathProperties();
|
||||
for (ProfileOriginNodeUsage statsNode : nodeUsageMap.values()) {
|
||||
statsNode.buildTunedFetch(pathProps, rootDesc, queryTuningAddVersion);
|
||||
}
|
||||
|
||||
OrmQueryDetail detail = new OrmQueryDetail();
|
||||
|
||||
Collection<Props> pathProperties = pathProps.getPathProps();
|
||||
for (Props props : pathProperties) {
|
||||
if (!props.isEmpty()) {
|
||||
detail.fetch(props.getPath(), props.getPropertiesAsString(), null);
|
||||
}
|
||||
}
|
||||
|
||||
detail.sortFetchPaths(rootDesc);
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the origin.
|
||||
*/
|
||||
public ObjectGraphOrigin getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect query execution summary statistics.
|
||||
* <p>
|
||||
* This can give us a quick overview into bad lazy loading areas etc.
|
||||
* </p>
|
||||
*/
|
||||
public void collectQueryInfo(ObjectGraphNode node, long beansLoaded, long micros) {
|
||||
|
||||
String key = node.getPath();
|
||||
if (key == null) {
|
||||
key = "";
|
||||
}
|
||||
|
||||
ProfileOriginQuery stats = queryStatsMap.get(key);
|
||||
if (stats == null) {
|
||||
// a race condition but we don't care
|
||||
stats = new ProfileOriginQuery(key);
|
||||
queryStatsMap.put(key, stats);
|
||||
}
|
||||
stats.add(beansLoaded, micros);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect the usage information for from a instance for this node.
|
||||
*/
|
||||
public void collectUsageInfo(NodeUsageCollector profile) {
|
||||
|
||||
//logger.info("COLLECT USAGE {}", profile.toString());
|
||||
|
||||
if (!profile.isEmpty()) {
|
||||
ProfileOriginNodeUsage nodeStats = getNodeStats(profile.getNode().getPath());
|
||||
nodeStats.collectUsageInfo(profile);
|
||||
}
|
||||
}
|
||||
|
||||
private ProfileOriginNodeUsage getNodeStats(String path) {
|
||||
|
||||
synchronized (monitor) {
|
||||
// handle null paths as using ConcurrentHashMap
|
||||
path = (path == null) ? "" : path;
|
||||
ProfileOriginNodeUsage nodeStats = nodeUsageMap.get(path);
|
||||
if (nodeStats == null) {
|
||||
nodeStats = new ProfileOriginNodeUsage(path);
|
||||
nodeUsageMap.put(path, nodeStats);
|
||||
}
|
||||
return nodeStats;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebean.bean.NodeUsageCollector;
|
||||
import io.ebean.text.PathProperties;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
import io.ebeaninternal.server.query.SplitName;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Collects usages statistics for a given node in the object graph.
|
||||
*/
|
||||
public class ProfileOriginNodeUsage {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProfileOriginNodeUsage.class);
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final String path;
|
||||
|
||||
private int profileCount;
|
||||
|
||||
private int profileUsedCount;
|
||||
|
||||
private boolean modified;
|
||||
|
||||
private final Set<String> aggregateUsed = new LinkedHashSet<>();
|
||||
|
||||
public ProfileOriginNodeUsage(String path) {
|
||||
// handle null paths as using ConcurrentHashMap
|
||||
this.path = "".equals(path) ? null : path;
|
||||
}
|
||||
|
||||
protected void buildTunedFetch(PathProperties pathProps, BeanDescriptor<?> rootDesc, boolean addVersionProperty) {
|
||||
|
||||
synchronized (monitor) {
|
||||
|
||||
BeanDescriptor<?> desc = rootDesc;
|
||||
if (path != null) {
|
||||
ElPropertyValue elGetValue = rootDesc.getElGetValue(path);
|
||||
if (elGetValue == null) {
|
||||
logger.warn("AutoTune: Can't find join for path[" + path + "] for " + rootDesc.getName());
|
||||
return;
|
||||
} else {
|
||||
BeanProperty beanProperty = elGetValue.getBeanProperty();
|
||||
if (beanProperty instanceof BeanPropertyAssoc<?>) {
|
||||
desc = ((BeanPropertyAssoc<?>) beanProperty).getTargetDescriptor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String propName : aggregateUsed) {
|
||||
BeanProperty beanProp = desc.getBeanPropertyFromPath(propName);
|
||||
if (beanProp == null) {
|
||||
logger.warn("AutoTune: Can't find property[" + propName + "] for " + desc.getName());
|
||||
|
||||
} else {
|
||||
if (beanProp instanceof BeanPropertyAssoc<?>) {
|
||||
BeanPropertyAssoc<?> assocProp = (BeanPropertyAssoc<?>) beanProp;
|
||||
String targetIdProp = assocProp.getTargetIdProperty();
|
||||
String manyPath = SplitName.add(path, assocProp.getName());
|
||||
pathProps.addToPath(manyPath, targetIdProp);
|
||||
} else {
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (beanProp.isLob() && !beanProp.isFetchEager()) {
|
||||
// AutoTune will not include Lob's marked FetchLazy
|
||||
// (which is the default for Lob's so typical).
|
||||
} else {
|
||||
pathProps.addToPath(path, beanProp.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((modified || addVersionProperty) && desc != null) {
|
||||
BeanProperty versionProp = desc.getVersionProperty();
|
||||
if (versionProp != null) {
|
||||
pathProps.addToPath(path, versionProp.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect usage from a node.
|
||||
*/
|
||||
protected void collectUsageInfo(NodeUsageCollector profile) {
|
||||
|
||||
synchronized (monitor) {
|
||||
|
||||
Set<String> used = profile.getUsed();
|
||||
|
||||
profileCount++;
|
||||
if (!used.isEmpty()) {
|
||||
profileUsedCount++;
|
||||
aggregateUsed.addAll(used);
|
||||
}
|
||||
if (profile.isModified()) {
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "path[" + path + "] profileCount[" + profileCount + "] used[" + profileUsedCount + "] props" + aggregateUsed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebeaninternal.server.autotune.AutoTuneCollection;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
/**
|
||||
* Used to accumulate query execution statistics for paths relative to the origin query.
|
||||
*/
|
||||
public class ProfileOriginQuery implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -1133958958072778811L;
|
||||
|
||||
private final String path;
|
||||
|
||||
private final LongAdder exeCount = new LongAdder();
|
||||
|
||||
private final LongAdder totalBeanLoaded = new LongAdder();
|
||||
|
||||
private final LongAdder totalMicros = new LongAdder();
|
||||
|
||||
public ProfileOriginQuery(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public void add(long beansLoaded, long micros) {
|
||||
exeCount.increment();
|
||||
totalBeanLoaded.add(beansLoaded);
|
||||
totalMicros.add(micros);
|
||||
}
|
||||
|
||||
public AutoTuneCollection.EntryQuery createEntryQuery(boolean reset) {
|
||||
|
||||
if (reset) {
|
||||
return new AutoTuneCollection.EntryQuery(path, exeCount.sumThenReset(), totalBeanLoaded.sumThenReset(), totalMicros.sumThenReset());
|
||||
|
||||
} else {
|
||||
return new AutoTuneCollection.EntryQuery(path, exeCount.sum(), totalBeanLoaded.sum(), totalMicros.sum());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebeaninternal.server.autotune.model.Autotune;
|
||||
import io.ebeaninternal.server.autotune.model.Origin;
|
||||
import io.ebeaninternal.server.autotune.model.ProfileDiff;
|
||||
import io.ebeaninternal.server.autotune.model.ProfileEmpty;
|
||||
import io.ebeaninternal.server.autotune.model.ProfileNew;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Sorts Autotune document by
|
||||
*/
|
||||
public class SortAutoTuneDocument {
|
||||
|
||||
|
||||
/**
|
||||
* Set the diff and new entries by bean type followed by key.
|
||||
*/
|
||||
public static void sort(Autotune document) {
|
||||
|
||||
ProfileDiff profileDiff = document.getProfileDiff();
|
||||
if (profileDiff != null) {
|
||||
Collections.sort(profileDiff.getOrigin(), NAME_KEY_SORT);
|
||||
}
|
||||
ProfileNew profileNew = document.getProfileNew();
|
||||
if (profileNew != null) {
|
||||
Collections.sort(profileNew.getOrigin(), NAME_KEY_SORT);
|
||||
}
|
||||
ProfileEmpty profileEmpty = document.getProfileEmpty();
|
||||
if (profileEmpty != null) {
|
||||
Collections.sort(profileEmpty.getOrigin(), KEY_SORT);
|
||||
}
|
||||
List<Origin> origins = document.getOrigin();
|
||||
if (!origins.isEmpty()) {
|
||||
Collections.sort(origins, NAME_KEY_SORT);
|
||||
}
|
||||
}
|
||||
|
||||
private static final OriginNameKeySort NAME_KEY_SORT = new OriginNameKeySort();
|
||||
|
||||
private static final OriginKeySort KEY_SORT = new OriginKeySort();
|
||||
|
||||
/**
|
||||
* Comparator sort by bean type then key.
|
||||
*/
|
||||
private static class OriginNameKeySort implements Comparator<Origin> {
|
||||
|
||||
@Override
|
||||
public int compare(Origin o1, Origin o2) {
|
||||
int comp = o1.getBeanType().compareTo(o2.getBeanType());
|
||||
if (comp == 0) {
|
||||
comp = o1.getKey().compareTo(o2.getKey());
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator sort by bean type then key.
|
||||
*/
|
||||
private static class OriginKeySort implements Comparator<Origin> {
|
||||
|
||||
@Override
|
||||
public int compare(Origin o1, Origin o2) {
|
||||
return o1.getKey().compareTo(o2.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package io.ebeaninternal.server.autotune.service;
|
||||
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.autotune.model.Origin;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
|
||||
import io.ebeaninternal.server.querydefn.OrmQueryDetailParser;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Holds tuned query information. Is immutable so this represents the tuning at
|
||||
* a given point in time.
|
||||
*/
|
||||
public class TunedQueryInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 8661702592481810396L;
|
||||
|
||||
private final Origin origin;
|
||||
|
||||
private final OrmQueryDetail tunedDetail;
|
||||
|
||||
public TunedQueryInfo(Origin origin) {
|
||||
this.origin = origin;
|
||||
this.tunedDetail = new OrmQueryDetailParser(origin.getDetail()).parse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the origin entry (includes call stack and bean type).
|
||||
*/
|
||||
public Origin getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tuned detail (for comparison with profiling information).
|
||||
*/
|
||||
public OrmQueryDetail getTunedDetail() {
|
||||
return tunedDetail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tune the query by replacing its OrmQueryDetail with a tuned one.
|
||||
*
|
||||
* @return true if the query was tuned, otherwise false.
|
||||
*/
|
||||
public boolean tuneQuery(SpiQuery<?> query) {
|
||||
if (tunedDetail == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean tuned;
|
||||
if (query.isDetailEmpty()) {
|
||||
tuned = true;
|
||||
// tune by 'replacement'
|
||||
query.setDetail(tunedDetail.copy());
|
||||
} else {
|
||||
// tune by 'addition'
|
||||
tuned = query.tuneFetchProperties(tunedDetail);
|
||||
}
|
||||
if (tuned) {
|
||||
query.setAutoTuned(true);
|
||||
}
|
||||
return tuned;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return tunedDetail.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
/**
|
||||
* A change to the cache.
|
||||
*/
|
||||
public interface CacheChange {
|
||||
|
||||
/**
|
||||
* Apply the change.
|
||||
*/
|
||||
void apply();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
/**
|
||||
* Change to remove bean from L2 cache.
|
||||
*/
|
||||
class CacheChangeBeanRemove implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> descriptor;
|
||||
|
||||
private final Object id;
|
||||
|
||||
CacheChangeBeanRemove(BeanDescriptor<?> descriptor, Object id) {
|
||||
this.descriptor = descriptor;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
descriptor.cacheHandleDeleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Put a new bean entry into the cache.
|
||||
*/
|
||||
class CacheChangeBeanUpdate implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> desc;
|
||||
private final Object id;
|
||||
private final Map<String, Object> changes;
|
||||
private final boolean updateNaturalKey;
|
||||
private final long version;
|
||||
|
||||
CacheChangeBeanUpdate(BeanDescriptor<?> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
this.desc = desc;
|
||||
this.id = id;
|
||||
this.changes = changes;
|
||||
this.updateNaturalKey = updateNaturalKey;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
desc.cacheBeanUpdate(id, changes, updateNaturalKey, version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
/**
|
||||
* Change the natural key mapping for a bean.
|
||||
*/
|
||||
class CacheChangeNaturalKeyPut implements CacheChange {
|
||||
|
||||
private final BeanDescriptor<?> descriptor;
|
||||
private final Object id;
|
||||
private final Object newKey;
|
||||
|
||||
CacheChangeNaturalKeyPut(BeanDescriptor<?> descriptor, Object id, Object newKey) {
|
||||
this.descriptor = descriptor;
|
||||
this.id = id;
|
||||
this.newKey = newKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
descriptor.cacheNaturalKeyPut(id, newKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* List of changes to be applied to L2 cache.
|
||||
*/
|
||||
public class CacheChangeSet {
|
||||
|
||||
private final List<CacheChange> entries = new ArrayList<>();
|
||||
|
||||
private final Set<BeanDescriptor<?>> queryCaches = new HashSet<>();
|
||||
|
||||
private final Map<ManyKey, ManyChange> manyChangeMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Set of "base tables" modified used to invalidate entities based on views.
|
||||
*/
|
||||
private final Set<String> viewInvalidation = new HashSet<>();
|
||||
|
||||
private final boolean viewEntityInvalidation;
|
||||
|
||||
/**
|
||||
* Construct specifying if we also need to process invalidation for entities based on views.
|
||||
*/
|
||||
public CacheChangeSet(boolean viewEntityInvalidation) {
|
||||
this.viewEntityInvalidation = viewEntityInvalidation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the changes to the L2 cache except entity/view invalidation.
|
||||
* <p>
|
||||
* Return the set of table changes to process invalidation for entities based on views.
|
||||
*/
|
||||
public Set<String> apply() {
|
||||
for (BeanDescriptor<?> entry : queryCaches) {
|
||||
entry.clearQueryCache();
|
||||
}
|
||||
for (CacheChange entry : entries) {
|
||||
entry.apply();
|
||||
}
|
||||
for (CacheChange entry : manyChangeMap.values()) {
|
||||
entry.apply();
|
||||
}
|
||||
return viewInvalidation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entry to clear a query cache.
|
||||
*/
|
||||
public void addClearQuery(BeanDescriptor<?> descriptor) {
|
||||
queryCaches.add(descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add many property clear.
|
||||
*/
|
||||
public <T> void addManyClear(BeanDescriptor<T> desc, String manyProperty) {
|
||||
many(desc, manyProperty).setClear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add many property remove.
|
||||
*/
|
||||
public <T> void addManyRemove(BeanDescriptor<T> desc, String manyProperty, Object parentId) {
|
||||
many(desc, manyProperty).addRemove(parentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add many property put.
|
||||
*/
|
||||
public <T> void addManyPut(BeanDescriptor<T> desc, String manyProperty, Object parentId, CachedManyIds entry) {
|
||||
many(desc, manyProperty).addPut(parentId, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* On bean insert register table for view based entity invalidation.
|
||||
*/
|
||||
public void addBeanInsert(String baseTable) {
|
||||
if (viewEntityInvalidation) {
|
||||
viewInvalidation.add(baseTable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a bean from the cache.
|
||||
*/
|
||||
public <T> void addBeanRemove(BeanDescriptor<T> desc, Object id) {
|
||||
entries.add(new CacheChangeBeanRemove(desc, id));
|
||||
if (viewEntityInvalidation) {
|
||||
viewInvalidation.add(desc.getBaseTable());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a bean entry.
|
||||
*/
|
||||
public <T> void addBeanUpdate(BeanDescriptor<T> desc, Object id, Map<String, Object> changes, boolean updateNaturalKey, long version) {
|
||||
entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version));
|
||||
if (viewEntityInvalidation) {
|
||||
viewInvalidation.add(desc.getBaseTable());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a natural key.
|
||||
*/
|
||||
public <T> void addNaturalKeyPut(BeanDescriptor<T> desc, Object id, Object val) {
|
||||
entries.add(new CacheChangeNaturalKeyPut(desc, id, val));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ManyChange for the given descriptor and property manyProperty.
|
||||
*/
|
||||
private ManyChange many(BeanDescriptor<?> desc, String manyProperty) {
|
||||
ManyKey key = new ManyKey(desc, manyProperty);
|
||||
ManyChange manyChange = manyChangeMap.get(key);
|
||||
if (manyChange == null) {
|
||||
manyChange = new ManyChange(key);
|
||||
manyChangeMap.put(key, manyChange);
|
||||
}
|
||||
return manyChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes for a specific many property.
|
||||
*/
|
||||
private static class ManyChange implements CacheChange {
|
||||
|
||||
final ManyKey key;
|
||||
|
||||
final List<Object> removes = new ArrayList<>();
|
||||
|
||||
final Map<Object, CachedManyIds> puts = new LinkedHashMap<>();
|
||||
|
||||
boolean clear;
|
||||
|
||||
ManyChange(ManyKey key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all entries.
|
||||
*/
|
||||
void setClear() {
|
||||
this.clear = true;
|
||||
removes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entry for the given parentId.
|
||||
*/
|
||||
void addRemove(Object parentId) {
|
||||
if (!clear) {
|
||||
removes.add(parentId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put entry for the given parentId.
|
||||
*/
|
||||
void addPut(Object parentId, CachedManyIds entry) {
|
||||
puts.put(parentId, entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
if (clear) {
|
||||
key.cacheClear();
|
||||
} else {
|
||||
for (Map.Entry<Object, CachedManyIds> entry : puts.entrySet()) {
|
||||
key.cachePut(entry.getKey(), entry.getValue());
|
||||
}
|
||||
for (Object parentId : removes) {
|
||||
key.cacheRemove(parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for changes on a many property.
|
||||
*/
|
||||
private static class ManyKey {
|
||||
|
||||
private final BeanDescriptor<?> desc;
|
||||
|
||||
private final String manyProperty;
|
||||
|
||||
ManyKey(BeanDescriptor<?> desc, String manyProperty) {
|
||||
this.desc = desc;
|
||||
this.manyProperty = manyProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ManyKey manyKey = (ManyKey) o;
|
||||
return desc.equals(manyKey.desc) && manyProperty.equals(manyKey.manyProperty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 92821 * desc.hashCode() + manyProperty.hashCode();
|
||||
}
|
||||
|
||||
void cacheClear() {
|
||||
desc.cacheManyPropClear(manyProperty);
|
||||
}
|
||||
|
||||
void cachePut(Object parentId, CachedManyIds entry) {
|
||||
desc.cacheManyPropPut(manyProperty, parentId, entry);
|
||||
}
|
||||
|
||||
void cacheRemove(Object parentId) {
|
||||
desc.cacheManyPropRemove(manyProperty, parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Data held in the bean cache for cached beans.
|
||||
*/
|
||||
public class CachedBeanData implements Externalizable {
|
||||
|
||||
private long whenCreated;
|
||||
private long version;
|
||||
private String discValue;
|
||||
private Map<String, Object> data;
|
||||
|
||||
/**
|
||||
* The sharable bean is effectively transient (near cache only).
|
||||
*/
|
||||
private transient Object sharableBean;
|
||||
|
||||
/**
|
||||
* Construct from a loaded bean.
|
||||
*/
|
||||
public CachedBeanData(Object sharableBean, String discValue, Map<String, Object> data, long version) {
|
||||
this.whenCreated = System.currentTimeMillis();
|
||||
this.sharableBean = sharableBean;
|
||||
this.discValue = discValue;
|
||||
this.data = data;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from serialisation.
|
||||
*/
|
||||
public CachedBeanData() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeLong(version);
|
||||
out.writeLong(whenCreated);
|
||||
boolean hasDisc = discValue != null;
|
||||
out.writeBoolean(hasDisc);
|
||||
if (hasDisc) {
|
||||
out.writeUTF(discValue);
|
||||
}
|
||||
out.writeInt(data.size());
|
||||
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
||||
out.writeUTF(entry.getKey());
|
||||
out.writeObject(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
version = in.readLong();
|
||||
whenCreated = in.readLong();
|
||||
if (in.readBoolean()) {
|
||||
discValue = in.readUTF();
|
||||
}
|
||||
data = new LinkedHashMap<>();
|
||||
int count = in.readInt();
|
||||
for (int i = 0; i < count; i++) {
|
||||
String key = in.readUTF();
|
||||
Object val = in.readObject();
|
||||
data.put(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return data.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return a new version of CachedBeanData based on this
|
||||
* entry applying the given changes.
|
||||
*/
|
||||
public CachedBeanData update(Map<String, Object> changes, long version) {
|
||||
|
||||
Map<String, Object> copy = new HashMap<>();
|
||||
copy.putAll(data);
|
||||
copy.putAll(changes);
|
||||
return new CachedBeanData(null, discValue, copy, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return when the cached data was created.
|
||||
*/
|
||||
public long getWhenCreated() {
|
||||
return whenCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the version value.
|
||||
*/
|
||||
public long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the raw discriminator value.
|
||||
*/
|
||||
public String getDiscValue() {
|
||||
return discValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a sharable (immutable read only) bean. Near cache only use.
|
||||
*/
|
||||
public Object getSharableBean() {
|
||||
return sharableBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the property is held.
|
||||
*/
|
||||
public boolean isLoaded(String propertyName) {
|
||||
return data.containsKey(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value for a given property name.
|
||||
*/
|
||||
public Object getData(String propertyName) {
|
||||
return data.get(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the property data.
|
||||
*/
|
||||
public Map<String, Object> getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CachedBeanDataFromBean {
|
||||
|
||||
|
||||
public static CachedBeanData extract(BeanDescriptor<?> desc, EntityBean bean) {
|
||||
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
|
||||
BeanProperty idProperty = desc.getIdProperty();
|
||||
if (idProperty != null) {
|
||||
int propertyIndex = idProperty.getPropertyIndex();
|
||||
if (ebi.isLoadedProperty(propertyIndex)) {
|
||||
data.put(idProperty.getName(), idProperty.getCacheDataValue(bean));
|
||||
}
|
||||
}
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
|
||||
// extract all the non-many properties
|
||||
for (BeanProperty prop : props) {
|
||||
if (ebi.isLoadedProperty(prop.getPropertyIndex())) {
|
||||
data.put(prop.getName(), prop.getCacheDataValue(bean));
|
||||
}
|
||||
}
|
||||
|
||||
long version = desc.getVersion(bean);
|
||||
EntityBean sharableBean = createSharableBean(desc, bean, ebi);
|
||||
return new CachedBeanData(sharableBean, desc.getDiscValue(), data, version);
|
||||
}
|
||||
|
||||
private static EntityBean createSharableBean(BeanDescriptor<?> desc, EntityBean bean, EntityBeanIntercept beanEbi) {
|
||||
|
||||
if (!desc.isCacheSharableBeans() || !beanEbi.isFullyLoadedBean()) {
|
||||
return null;
|
||||
}
|
||||
if (beanEbi.isReadOnly()) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
// create a readOnly sharable instance by copying the data
|
||||
EntityBean sharableBean = desc.createEntityBean();
|
||||
BeanProperty idProp = desc.getIdProperty();
|
||||
if (idProp != null) {
|
||||
Object v = idProp.getValue(bean);
|
||||
idProp.setValue(sharableBean, v);
|
||||
}
|
||||
BeanProperty[] propertiesNonTransient = desc.propertiesNonTransient();
|
||||
for (BeanProperty aPropertiesNonTransient : propertiesNonTransient) {
|
||||
Object v = aPropertiesNonTransient.getValue(bean);
|
||||
aPropertiesNonTransient.setValue(sharableBean, v);
|
||||
}
|
||||
EntityBeanIntercept intercept = sharableBean._ebean_intercept();
|
||||
intercept.setReadOnly(true);
|
||||
intercept.setLoaded();
|
||||
return sharableBean;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
|
||||
public class CachedBeanDataToBean {
|
||||
|
||||
|
||||
public static void load(BeanDescriptor<?> desc, EntityBean bean, CachedBeanData cacheBeanData, PersistenceContext context) {
|
||||
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
|
||||
BeanProperty idProperty = desc.getIdProperty();
|
||||
if (idProperty != null) {
|
||||
// load the id property
|
||||
loadProperty(bean, cacheBeanData, ebi, idProperty, context);
|
||||
}
|
||||
|
||||
// load the non-many properties
|
||||
BeanProperty[] props = desc.propertiesNonMany();
|
||||
for (BeanProperty prop : props) {
|
||||
loadProperty(bean, cacheBeanData, ebi, prop, context);
|
||||
}
|
||||
|
||||
BeanPropertyAssocMany<?>[] many = desc.propertiesMany();
|
||||
for (BeanPropertyAssocMany<?> aMany : many) {
|
||||
aMany.createReferenceIfNull(bean);
|
||||
}
|
||||
|
||||
ebi.setLoadedLazy();
|
||||
}
|
||||
|
||||
private static void loadProperty(EntityBean bean, CachedBeanData cacheBeanData, EntityBeanIntercept ebi, BeanProperty prop, PersistenceContext context) {
|
||||
|
||||
if (cacheBeanData.isLoaded(prop.getName())) {
|
||||
if (!ebi.isLoadedProperty(prop.getPropertyIndex())) {
|
||||
Object value = cacheBeanData.getData(prop.getName());
|
||||
prop.setCacheDataValue(bean, value, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInput;
|
||||
import java.io.ObjectOutput;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The cached data for O2M and M2M relationships.
|
||||
* <p>
|
||||
* This is effectively just the Id values for each of the beans in the collection.
|
||||
* </p>
|
||||
*/
|
||||
public class CachedManyIds implements Externalizable {
|
||||
|
||||
private List<Object> idList;
|
||||
|
||||
public CachedManyIds(List<Object> idList) {
|
||||
this.idList = idList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for serialization.
|
||||
*/
|
||||
public CachedManyIds() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeExternal(ObjectOutput out) throws IOException {
|
||||
out.writeInt(idList.size());
|
||||
for (Object id : idList) {
|
||||
out.writeObject(id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
|
||||
int size = in.readInt();
|
||||
idList = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
idList.add(in.readObject());
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return idList.toString();
|
||||
}
|
||||
|
||||
public List<Object> getIdList() {
|
||||
return idList;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheManager;
|
||||
|
||||
/**
|
||||
* Adapts SpiCacheManager to ServerCacheManager.
|
||||
* <p>
|
||||
* Used to hide the Supplier part of the SpiCacheManager API from public use.
|
||||
* </p>
|
||||
*/
|
||||
public class DefaultCacheAdapter implements ServerCacheManager {
|
||||
|
||||
private final SpiCacheManager cacheManager;
|
||||
|
||||
public DefaultCacheAdapter(SpiCacheManager cacheManager) {
|
||||
this.cacheManager = cacheManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocalL2Caching() {
|
||||
return cacheManager.isLocalL2Caching();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCache getNaturalKeyCache(Class<?> beanType) {
|
||||
return cacheManager.getNaturalKeyCache(beanType).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCache getBeanCache(Class<?> beanType) {
|
||||
return cacheManager.getBeanCache(beanType).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCache getCollectionIdsCache(Class<?> beanType, String propertyName) {
|
||||
return cacheManager.getCollectionIdsCache(beanType, propertyName).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCache getQueryCache(Class<?> beanType) {
|
||||
return cacheManager.getQueryCache(beanType).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(Class<?> beanType) {
|
||||
cacheManager.getBeanCache(beanType).get().clear();
|
||||
cacheManager.getQueryCache(beanType).get().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAll() {
|
||||
cacheManager.clearAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.annotation.CacheBeanTuning;
|
||||
import io.ebean.annotation.CacheQueryTuning;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Manages the construction of caches.
|
||||
*/
|
||||
class DefaultCacheHolder {
|
||||
|
||||
private final ConcurrentHashMap<String, ServerCache> allCaches = new ConcurrentHashMap<>();
|
||||
|
||||
private final ServerCacheFactory cacheFactory;
|
||||
|
||||
private final ServerCacheOptions beanDefault;
|
||||
private final ServerCacheOptions queryDefault;
|
||||
|
||||
private final CurrentTenantProvider tenantProvider;
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*
|
||||
* @param cacheFactory the factory for creating the cache
|
||||
* @param beanDefault the default options for tuning bean caches
|
||||
* @param queryDefault the default options for tuning query caches
|
||||
*/
|
||||
DefaultCacheHolder(ServerCacheFactory cacheFactory, ServerCacheOptions beanDefault, ServerCacheOptions queryDefault, CurrentTenantProvider tenantProvider) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.beanDefault = beanDefault;
|
||||
this.queryDefault = queryDefault;
|
||||
this.tenantProvider = tenantProvider;
|
||||
}
|
||||
|
||||
Supplier<ServerCache> getCache(Class<?> beanType, String cacheKey, ServerCacheType type) {
|
||||
|
||||
if (tenantProvider == null) {
|
||||
return new SimpleSupplier(getCacheInternal(beanType, cacheKey, type));
|
||||
}
|
||||
return new TenantSupplier(beanType, cacheKey, type);
|
||||
}
|
||||
|
||||
private String key(String cacheKey, ServerCacheType type) {
|
||||
return cacheKey + type.code();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache for a given bean type.
|
||||
*/
|
||||
private ServerCache getCacheInternal(Class<?> beanType, String cacheKey, ServerCacheType type) {
|
||||
|
||||
String fullKey = key(cacheKey, type);
|
||||
return allCaches.computeIfAbsent(fullKey, s -> createCache(beanType, type, fullKey));
|
||||
}
|
||||
|
||||
private ServerCache createCache(Class<?> beanType, ServerCacheType type, String key) {
|
||||
ServerCacheOptions options = getCacheOptions(beanType, type);
|
||||
return cacheFactory.createCache(type, key, options);
|
||||
}
|
||||
|
||||
void clearAll() {
|
||||
for (ServerCache serverCache : allCaches.values()) {
|
||||
serverCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cache options for a given bean type.
|
||||
*/
|
||||
ServerCacheOptions getCacheOptions(Class<?> beanType, ServerCacheType type) {
|
||||
switch (type) {
|
||||
case QUERY:
|
||||
return getQueryOptions(beanType);
|
||||
default:
|
||||
return getBeanOptions(beanType);
|
||||
}
|
||||
}
|
||||
|
||||
private ServerCacheOptions getQueryOptions(Class<?> cls) {
|
||||
CacheQueryTuning tuning = cls.getAnnotation(CacheQueryTuning.class);
|
||||
if (tuning != null) {
|
||||
return new ServerCacheOptions(tuning).applyDefaults(queryDefault);
|
||||
}
|
||||
return queryDefault.copy();
|
||||
}
|
||||
|
||||
private ServerCacheOptions getBeanOptions(Class<?> cls) {
|
||||
CacheBeanTuning tuning = cls.getAnnotation(CacheBeanTuning.class);
|
||||
if (tuning != null) {
|
||||
return new ServerCacheOptions(tuning).applyDefaults(beanDefault);
|
||||
}
|
||||
return beanDefault.copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-Tenant based cache supplier.
|
||||
*/
|
||||
private class TenantSupplier implements Supplier<ServerCache> {
|
||||
|
||||
final Class<?> beanType;
|
||||
final String key;
|
||||
final ServerCacheType type;
|
||||
|
||||
private TenantSupplier(Class<?> beanType, String key, ServerCacheType type) {
|
||||
this.beanType = beanType;
|
||||
this.key = key;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCache get() {
|
||||
String fullKey = key + "_" + tenantProvider.currentId();
|
||||
return getCacheInternal(beanType, fullKey, type);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SimpleSupplier implements Supplier<ServerCache> {
|
||||
|
||||
final ServerCache underlying;
|
||||
|
||||
private SimpleSupplier(ServerCache underlying) {
|
||||
this.underlying = underlying;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCache get() {
|
||||
return underlying;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheStatistics;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
/**
|
||||
* The default cache implementation.
|
||||
* <p>
|
||||
* It is base on ConcurrentHashMap with periodic trimming using a TimerTask.
|
||||
* The periodic trimming means that an LRU list does not have to be maintained.
|
||||
* </p>
|
||||
*/
|
||||
public class DefaultServerCache implements ServerCache {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DefaultServerCache.class);
|
||||
|
||||
/**
|
||||
* Compare by last access time (for LRU eviction).
|
||||
*/
|
||||
public static final CompareByLastAccess BY_LAST_ACCESS = new CompareByLastAccess();
|
||||
|
||||
/**
|
||||
* The underlying map (ConcurrentHashMap or similar)
|
||||
*/
|
||||
protected final Map<Object, CacheEntry> map;
|
||||
|
||||
protected final LongAdder missCount = new LongAdder();
|
||||
protected final LongAdder hitCount = new LongAdder();
|
||||
protected final LongAdder insertCount = new LongAdder();
|
||||
protected final LongAdder updateCount = new LongAdder();
|
||||
protected final LongAdder removeCount = new LongAdder();
|
||||
protected final LongAdder clearCount = new LongAdder();
|
||||
|
||||
protected final LongAdder evictByIdle = new LongAdder();
|
||||
protected final LongAdder evictByTTL = new LongAdder();
|
||||
protected final LongAdder evictByLRU = new LongAdder();
|
||||
protected final LongAdder evictCount = new LongAdder();
|
||||
protected final LongAdder evictMicros = new LongAdder();
|
||||
|
||||
protected final Object monitor = new Object();
|
||||
|
||||
protected final String name;
|
||||
|
||||
protected int maxSize;
|
||||
|
||||
protected final int trimFrequency;
|
||||
|
||||
protected int maxIdleSecs;
|
||||
|
||||
protected int maxSecsToLive;
|
||||
|
||||
/**
|
||||
* Construct using a ConcurrentHashMap and cache options.
|
||||
*/
|
||||
public DefaultServerCache(String name, ServerCacheOptions options) {
|
||||
this(name, new ConcurrentHashMap<>(), options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct passing in name, map and base eviction controls as ServerCacheOptions.
|
||||
*/
|
||||
public DefaultServerCache(String name, Map<Object, CacheEntry> map, ServerCacheOptions options) {
|
||||
this(name, map, options.getMaxSize(), options.getMaxIdleSecs(), options.getMaxSecsToLive(), options.getTrimFrequency());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct passing in name, map and base eviction controls.
|
||||
*/
|
||||
public DefaultServerCache(String name, Map<Object, CacheEntry> map, int maxSize, int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
|
||||
this.name = name;
|
||||
this.map = map;
|
||||
this.maxSize = maxSize;
|
||||
this.maxIdleSecs = maxIdleSecs;
|
||||
this.maxSecsToLive = maxSecsToLive;
|
||||
this.trimFrequency = determineTrim(maxIdleSecs, maxSecsToLive, trimFrequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a good trimFrequency as half of maxIdleSecs (or maxSecsToLive).
|
||||
*/
|
||||
int determineTrim(int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
|
||||
if (trimFrequency > 0) {
|
||||
return trimFrequency;
|
||||
}
|
||||
if (maxIdleSecs > 0) {
|
||||
return maxIdleSecs / 2 - 1;
|
||||
}
|
||||
if (maxSecsToLive > 0) {
|
||||
return maxSecsToLive / 2 - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void periodicTrim(BackgroundExecutor executor) {
|
||||
|
||||
EvictionRunnable trim = new EvictionRunnable();
|
||||
|
||||
// default to trimming the cache every 60 seconds
|
||||
long trimFreqSecs = (trimFrequency == 0) ? 60 : trimFrequency;
|
||||
executor.executePeriodically(trim, trimFreqSecs, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerCacheStatistics getStatistics(boolean reset) {
|
||||
|
||||
ServerCacheStatistics cacheStats = new ServerCacheStatistics();
|
||||
cacheStats.setCacheName(name);
|
||||
cacheStats.setMaxSize(maxSize);
|
||||
|
||||
// these counters won't necessarily be consistent with
|
||||
// respect to each other as activity can occur while
|
||||
// they are being calculated here but they should be good enough
|
||||
// and we don't want to reduce concurrent use to make them consistent
|
||||
long clear = reset ? clearCount.sumThenReset() : clearCount.sum();
|
||||
long remove = reset ? removeCount.sumThenReset() : removeCount.sum();
|
||||
long update = reset ? updateCount.sumThenReset() : updateCount.sum();
|
||||
long insert = reset ? insertCount.sumThenReset() : insertCount.sum();
|
||||
long miss = reset ? missCount.sumThenReset() : missCount.sum();
|
||||
long hit = reset ? hitCount.sumThenReset() : hitCount.sum();
|
||||
|
||||
long evict = reset ? evictCount.sumThenReset() : evictCount.sum();
|
||||
long evictTime = reset ? evictMicros.sumThenReset() : evictMicros.sum();
|
||||
long evictIdle = reset ? evictByIdle.sumThenReset() : evictByIdle.sum();
|
||||
long evictTTL = reset ? evictByTTL.sumThenReset() : evictByTTL.sum();
|
||||
long evictLRU = reset ? evictByLRU.sumThenReset() : evictByLRU.sum();
|
||||
|
||||
int size = size();
|
||||
|
||||
cacheStats.setSize(size);
|
||||
cacheStats.setHitCount(hit);
|
||||
cacheStats.setMissCount(miss);
|
||||
cacheStats.setInsertCount(insert);
|
||||
cacheStats.setUpdateCount(update);
|
||||
cacheStats.setRemoveCount(remove);
|
||||
cacheStats.setClearCount(clear);
|
||||
|
||||
cacheStats.setEvictionRunCount(evict);
|
||||
cacheStats.setEvictionRunMicros(evictTime);
|
||||
cacheStats.setEvictByIdle(evictIdle);
|
||||
cacheStats.setEvictByTTL(evictTTL);
|
||||
cacheStats.setEvictByLRU(evictLRU);
|
||||
|
||||
return cacheStats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHitRatio() {
|
||||
|
||||
long mc = missCount.sum();
|
||||
long hc = hitCount.sum();
|
||||
|
||||
long totalCount = hc + mc;
|
||||
if (totalCount == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return (int) (hc * 100 / totalCount);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the cache.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache.
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
clearCount.increment();
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value from the cache.
|
||||
*/
|
||||
@Override
|
||||
public Object get(Object key) {
|
||||
|
||||
CacheEntry entry = map.get(key);
|
||||
if (entry == null) {
|
||||
missCount.increment();
|
||||
return null;
|
||||
|
||||
} else {
|
||||
// Important that hitCount.increment() MUST be low latency under concurrent
|
||||
// use hence must use LongAdder or better here
|
||||
hitCount.increment();
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a value into the cache.
|
||||
*/
|
||||
@Override
|
||||
public Object put(Object key, Object value) {
|
||||
CacheEntry entry = map.put(key, new CacheEntry(key, value));
|
||||
if (entry == null) {
|
||||
insertCount.increment();
|
||||
return null;
|
||||
} else {
|
||||
updateCount.increment();
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an entry from the cache.
|
||||
*/
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
CacheEntry entry = map.remove(key);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
} else {
|
||||
removeCount.increment();
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of elements in the cache.
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the size to trim to based on the max size.
|
||||
* <p>
|
||||
* This returns 90% of the max size.
|
||||
* </p>
|
||||
*/
|
||||
protected int getTrimSize() {
|
||||
return (maxSize * 90 / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the eviction based on Idle time, Time to live and LRU last access.
|
||||
*/
|
||||
public void runEviction() {
|
||||
|
||||
long trimForMaxSize;
|
||||
if (maxSize == 0) {
|
||||
trimForMaxSize = 0;
|
||||
} else {
|
||||
trimForMaxSize = size() - maxSize;
|
||||
}
|
||||
|
||||
if (maxIdleSecs == 0 && maxSecsToLive == 0 && trimForMaxSize < 0) {
|
||||
// nothing to trim on this cache
|
||||
return;
|
||||
}
|
||||
|
||||
long startNanos = System.nanoTime();
|
||||
|
||||
long trimmedByIdle = 0;
|
||||
long trimmedByTTL = 0;
|
||||
long trimmedByLRU = 0;
|
||||
|
||||
ArrayList<CacheEntry> activeList = new ArrayList<>();
|
||||
|
||||
long idleExpire = System.currentTimeMillis() - (maxIdleSecs * 1000);
|
||||
long ttlExpire = System.currentTimeMillis() - (maxSecsToLive * 1000);
|
||||
|
||||
Iterator<CacheEntry> it = map.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
CacheEntry cacheEntry = it.next();
|
||||
if (maxIdleSecs > 0 && idleExpire > cacheEntry.getLastAccessTime()) {
|
||||
it.remove();
|
||||
trimmedByIdle++;
|
||||
|
||||
} else if (maxSecsToLive > 0 && ttlExpire > cacheEntry.getCreateTime()) {
|
||||
it.remove();
|
||||
trimmedByTTL++;
|
||||
|
||||
} else if (trimForMaxSize > 0) {
|
||||
activeList.add(cacheEntry);
|
||||
}
|
||||
}
|
||||
|
||||
if (trimForMaxSize > 0) {
|
||||
trimmedByLRU = activeList.size() - maxSize;
|
||||
if (trimmedByLRU > 0) {
|
||||
// sort into last access time ascending
|
||||
Collections.sort(activeList, BY_LAST_ACCESS);
|
||||
int trimSize = getTrimSize();
|
||||
for (int i = trimSize; i < activeList.size(); i++) {
|
||||
// remove if still in the cache
|
||||
map.remove(activeList.get(i).getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long exeNanos = System.nanoTime() - startNanos;
|
||||
long exeMicros = TimeUnit.MICROSECONDS.convert(exeNanos, TimeUnit.NANOSECONDS);
|
||||
|
||||
// increment the eviction statistics
|
||||
evictMicros.add(exeMicros);
|
||||
evictCount.increment();
|
||||
evictByIdle.add(trimmedByIdle);
|
||||
evictByTTL.add(trimmedByTTL);
|
||||
evictByLRU.add(trimmedByLRU);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Executed trim of cache {} in [{}]millis idle[{}] timeToLive[{}] accessTime[{}]"
|
||||
, name, exeMicros, trimmedByIdle, trimmedByTTL, trimmedByLRU);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runnable that calls the eviction routine.
|
||||
*/
|
||||
public class EvictionRunnable implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
runEviction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator for sorting by last access time.
|
||||
*/
|
||||
public static class CompareByLastAccess implements Comparator<CacheEntry>, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public int compare(CacheEntry entry1, CacheEntry entry2) {
|
||||
long x = entry1.getLastAccessTime();
|
||||
long y = entry2.getLastAccessTime();
|
||||
return (x < y) ? -1 : ((x == y) ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the value to additionally hold createTime and lastAccessTime and hit counter.
|
||||
*/
|
||||
public static class CacheEntry {
|
||||
|
||||
private final Object key;
|
||||
private final Object value;
|
||||
private final long createTime;
|
||||
private long lastAccessTime;
|
||||
|
||||
public CacheEntry(Object key, Object value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.createTime = System.currentTimeMillis();
|
||||
this.lastAccessTime = createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the entry key.
|
||||
*/
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the entry value.
|
||||
*/
|
||||
public Object getValue() {
|
||||
// long assignment should be atomic these days (Ref Cliff Click)
|
||||
lastAccessTime = System.currentTimeMillis();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time the entry was created.
|
||||
*/
|
||||
public long getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the time the entry was last accessed.
|
||||
*/
|
||||
public long getLastAccessTime() {
|
||||
return lastAccessTime;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
|
||||
|
||||
/**
|
||||
* Default implementation of ServerCacheFactory.
|
||||
*/
|
||||
class DefaultServerCacheFactory implements ServerCacheFactory {
|
||||
|
||||
private final BackgroundExecutor executor;
|
||||
|
||||
/**
|
||||
* Construct when l2 cache is disabled.
|
||||
*/
|
||||
public DefaultServerCacheFactory() {
|
||||
this.executor = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct with executor service.
|
||||
*/
|
||||
public DefaultServerCacheFactory(BackgroundExecutor executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public ServerCache createCache(ServerCacheType type, String cacheKey, ServerCacheOptions cacheOptions) {
|
||||
|
||||
DefaultServerCache cache = new DefaultServerCache(cacheKey, cacheOptions);
|
||||
if (executor != null) {
|
||||
cache.periodicTrim(executor);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.ServerCache;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
import io.ebean.config.CurrentTenantProvider;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
||||
/**
|
||||
* Manages the bean and query caches.
|
||||
*/
|
||||
public class DefaultServerCacheManager implements SpiCacheManager {
|
||||
|
||||
private final DefaultCacheHolder cacheHolder;
|
||||
|
||||
private final boolean localL2Caching;
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*/
|
||||
public DefaultServerCacheManager(boolean localL2Caching, CurrentTenantProvider tenantProvider, ServerCacheFactory cacheFactory,
|
||||
ServerCacheOptions beanDefault, ServerCacheOptions queryDefault) {
|
||||
this.localL2Caching = localL2Caching;
|
||||
this.cacheHolder = new DefaultCacheHolder(cacheFactory, beanDefault, queryDefault, tenantProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct when l2 cache is disabled.
|
||||
*/
|
||||
public DefaultServerCacheManager() {
|
||||
this(true, null, new DefaultServerCacheFactory(), new ServerCacheOptions(), new ServerCacheOptions());
|
||||
}
|
||||
|
||||
public boolean isLocalL2Caching() {
|
||||
return localL2Caching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all caches.
|
||||
*/
|
||||
public void clearAll() {
|
||||
cacheHolder.clearAll();
|
||||
}
|
||||
|
||||
public Supplier<ServerCache> getCollectionIdsCache(Class<?> beanType, String propertyName) {
|
||||
return cacheHolder.getCache(beanType, name(beanType) + "." + propertyName, ServerCacheType.COLLECTION_IDS);
|
||||
}
|
||||
|
||||
public Supplier<ServerCache> getNaturalKeyCache(Class<?> beanType) {
|
||||
return cacheHolder.getCache(beanType, name(beanType), ServerCacheType.NATURAL_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query cache for a given bean type.
|
||||
*/
|
||||
public Supplier<ServerCache> getQueryCache(Class<?> beanType) {
|
||||
return cacheHolder.getCache(beanType, name(beanType), ServerCacheType.QUERY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bean cache for a given bean type.
|
||||
*/
|
||||
public Supplier<ServerCache> getBeanCache(Class<?> beanType) {
|
||||
return cacheHolder.getCache(beanType, name(beanType), ServerCacheType.BEAN);
|
||||
}
|
||||
|
||||
private String name(Class<?> beanType) {
|
||||
return beanType.getName();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.BackgroundExecutor;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCachePlugin;
|
||||
import io.ebean.config.ServerConfig;
|
||||
|
||||
/**
|
||||
* Default implementation of ServerCachePlugin.
|
||||
*/
|
||||
public class DefaultServerCachePlugin implements ServerCachePlugin {
|
||||
|
||||
/**
|
||||
* Creates the default ServerCacheFactory.
|
||||
*/
|
||||
@Override
|
||||
public ServerCacheFactory create(ServerConfig config, BackgroundExecutor executor) {
|
||||
return new DefaultServerCacheFactory(executor);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.ServerCache;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* The cache service for server side caching of beans and query results.
|
||||
*/
|
||||
public interface SpiCacheManager {
|
||||
|
||||
/**
|
||||
* Return true if the L2 caching is local.
|
||||
* <p>
|
||||
* Local L2 caching means that the cache updates should occur in foreground
|
||||
* rather than background processing.
|
||||
* </p>
|
||||
*/
|
||||
boolean isLocalL2Caching();
|
||||
|
||||
/**
|
||||
* Return the cache for mapping natural keys to id values.
|
||||
*/
|
||||
Supplier<ServerCache> getNaturalKeyCache(Class<?> beanType);
|
||||
|
||||
/**
|
||||
* Return the cache for beans of a particular type.
|
||||
*/
|
||||
Supplier<ServerCache> getBeanCache(Class<?> beanType);
|
||||
|
||||
/**
|
||||
* Return the cache for associated many properties of a bean type.
|
||||
*/
|
||||
Supplier<ServerCache> getCollectionIdsCache(Class<?> beanType, String propertyName);
|
||||
|
||||
/**
|
||||
* Return the cache for query results of a particular type of bean.
|
||||
*/
|
||||
Supplier<ServerCache> getQueryCache(Class<?> beanType);
|
||||
|
||||
/**
|
||||
* Clear all the caches.
|
||||
*/
|
||||
void clearAll();
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
@@ -0,0 +1,130 @@
|
||||
package io.ebeaninternal.server.changelog;
|
||||
|
||||
import io.ebean.ValuePair;
|
||||
import io.ebean.event.changelog.BeanChange;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
import io.ebean.event.changelog.ChangeType;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Builds JSON document for a bean change.
|
||||
*/
|
||||
public class ChangeJsonBuilder {
|
||||
|
||||
protected final JsonFactory jsonFactory = new JsonFactory();
|
||||
|
||||
protected final JsonContext json;
|
||||
|
||||
protected ChangeJsonBuilder(JsonContext json) {
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the bean change as JSON.
|
||||
*/
|
||||
public void writeBeanJson(Writer writer, BeanChange bean, ChangeSet changeSet, int position) throws IOException {
|
||||
|
||||
JsonGenerator generator = jsonFactory.createGenerator(writer);
|
||||
|
||||
writeBeanChange(generator, bean, changeSet, position);
|
||||
|
||||
generator.flush();
|
||||
generator.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the bean change as JSON document containing the transaction header details.
|
||||
*/
|
||||
protected void writeBeanChange(JsonGenerator gen, BeanChange bean, ChangeSet changeSet, int position) throws IOException {
|
||||
|
||||
gen.writeStartObject();
|
||||
|
||||
writeBeanTransactionDetails(gen, changeSet, position);
|
||||
|
||||
gen.writeStringField("object", bean.getTable());
|
||||
gen.writeStringField("objectId", bean.getId().toString());
|
||||
gen.writeStringField("change", bean.getType().getCode());
|
||||
gen.writeNumberField("eventTime", bean.getEventTime());
|
||||
|
||||
writeBeanValues(gen, bean);
|
||||
|
||||
gen.writeEndObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Denormalise by writing the transaction header details.
|
||||
*/
|
||||
protected void writeBeanTransactionDetails(JsonGenerator gen, ChangeSet changeSet, int position) throws IOException {
|
||||
|
||||
gen.writeStringField("txnId", changeSet.getTxnId());
|
||||
gen.writeStringField("txnState", changeSet.getTxnState().getCode());
|
||||
gen.writeNumberField("txnBatch", changeSet.getTxnBatch());
|
||||
gen.writeNumberField("txnPosition", position);
|
||||
String source = changeSet.getSource();
|
||||
if (source != null) {
|
||||
gen.writeStringField("source", source);
|
||||
}
|
||||
String userId = changeSet.getUserId();
|
||||
if (userId != null) {
|
||||
gen.writeStringField("userId", userId);
|
||||
}
|
||||
String userIpAddress = changeSet.getUserIpAddress();
|
||||
if (userIpAddress != null) {
|
||||
gen.writeStringField("userIpAddress", userIpAddress);
|
||||
}
|
||||
Map<String, String> userContext = changeSet.getUserContext();
|
||||
if (userContext != null && !userContext.isEmpty()) {
|
||||
gen.writeObjectFieldStart("userContext");
|
||||
for (Map.Entry<String, String> entry : userContext.entrySet()) {
|
||||
gen.writeStringField(entry.getKey(), entry.getValue());
|
||||
}
|
||||
gen.writeEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For insert and update write the new/old values.
|
||||
*/
|
||||
protected void writeBeanValues(JsonGenerator gen, BeanChange bean) throws IOException {
|
||||
|
||||
if (bean.getType() != ChangeType.DELETE) {
|
||||
gen.writeFieldName("values");
|
||||
gen.writeStartObject();
|
||||
writeValuePairs(bean, gen);
|
||||
gen.writeEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all the value pairs suppressing null values.
|
||||
* <p>
|
||||
* We are intentionally keeping the same new/old structure for both inserts and updates.
|
||||
* </p>
|
||||
*/
|
||||
protected void writeValuePairs(BeanChange bean, JsonGenerator gen) throws IOException {
|
||||
|
||||
for (Map.Entry<String, ValuePair> entry : bean.getValues().entrySet()) {
|
||||
gen.writeFieldName(entry.getKey());
|
||||
gen.writeStartObject();
|
||||
ValuePair value = entry.getValue();
|
||||
Object newValue = value.getNewValue();
|
||||
if (newValue != null) {
|
||||
gen.writeFieldName("new");
|
||||
json.writeScalar(gen, newValue);
|
||||
}
|
||||
Object oldValue = value.getOldValue();
|
||||
if (oldValue != null) {
|
||||
gen.writeFieldName("old");
|
||||
json.writeScalar(gen, oldValue);
|
||||
}
|
||||
gen.writeEndObject();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package io.ebeaninternal.server.changelog;
|
||||
|
||||
import io.ebean.event.changelog.BeanChange;
|
||||
import io.ebean.event.changelog.ChangeLogListener;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
import io.ebean.event.changelog.ChangeType;
|
||||
import io.ebean.plugin.Plugin;
|
||||
import io.ebean.plugin.SpiServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Logs the change sets in JSON to logger named <code>org.avaje.ebean.ChangeLog</code>.
|
||||
* <p>
|
||||
* The logged entries duplicate/denormalise the transaction details so that each bean change
|
||||
* is fully contained with the transaction information.
|
||||
* </p>
|
||||
*/
|
||||
public class DefaultChangeLogListener implements ChangeLogListener, Plugin {
|
||||
|
||||
/**
|
||||
* The usual application specific logger.
|
||||
*/
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DefaultChangeLogListener.class);
|
||||
|
||||
/**
|
||||
* The named logger we send the change set payload to. Can be externally configured as desired.
|
||||
*/
|
||||
protected static final Logger changeLog = LoggerFactory.getLogger("org.avaje.ebean.ChangeLog");
|
||||
|
||||
/**
|
||||
* Used to build the JSON.
|
||||
*/
|
||||
protected ChangeJsonBuilder jsonBuilder;
|
||||
|
||||
/**
|
||||
* A bigger default buffer for bean inserts and updates (that have value pairs).
|
||||
*/
|
||||
protected int defaultBufferSize = 400;
|
||||
|
||||
/**
|
||||
* Expected to be a reasonable buffer size for deletes (which do not have value pairs).
|
||||
*/
|
||||
protected int defaultDeleteBufferSize = 250;
|
||||
|
||||
public DefaultChangeLogListener() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the underlying JSON handler.
|
||||
*/
|
||||
@Override
|
||||
public void configure(SpiServer server) {
|
||||
jsonBuilder = new ChangeJsonBuilder(server.json());
|
||||
|
||||
Properties properties = server.getServerConfig().getProperties();
|
||||
if (properties != null) {
|
||||
String bufferSize = properties.getProperty("ebean.changeLog.bufferSize");
|
||||
if (bufferSize != null) {
|
||||
defaultBufferSize = Integer.parseInt(bufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void online(boolean online) {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(ChangeSet changeSet) {
|
||||
|
||||
List<BeanChange> changes = changeSet.getChanges();
|
||||
for (int i = 0; i < changes.size(); i++) {
|
||||
// log each bean change as a separate log entry
|
||||
BeanChange beanChange = changes.get(i);
|
||||
try {
|
||||
StringWriter writer = new StringWriter(getBufferSize(beanChange));
|
||||
jsonBuilder.writeBeanJson(writer, beanChange, changeSet, i);
|
||||
changeLog.info(writer.toString());
|
||||
} catch (Exception e) {
|
||||
logger.error("Exception logging beanChange " + beanChange.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a decent buffer size based on the bean change.
|
||||
*/
|
||||
protected int getBufferSize(BeanChange beanChange) {
|
||||
|
||||
return ChangeType.DELETE == beanChange.getType() ? defaultDeleteBufferSize : defaultBufferSize;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ebeaninternal.server.changelog;
|
||||
|
||||
import io.ebean.event.changelog.ChangeLogPrepare;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
|
||||
/**
|
||||
* Placeholder/default implementation that does not do anything.
|
||||
* <p>
|
||||
* Generally an implementation should be provided that reads context
|
||||
* information such as user id and user ip address etc and sets that
|
||||
* on the changeSet.
|
||||
* </p>
|
||||
*/
|
||||
public class DefaultChangeLogPrepare implements ChangeLogPrepare {
|
||||
|
||||
/**
|
||||
* Just return true to send change set through to the logger.
|
||||
*/
|
||||
@Override
|
||||
public boolean prepare(ChangeSet changeSet) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package io.ebeaninternal.server.changelog;
|
||||
|
||||
import io.ebean.annotation.ChangeLog;
|
||||
import io.ebean.annotation.ChangeLogInsertMode;
|
||||
import io.ebean.event.BeanPersistRequest;
|
||||
import io.ebean.event.changelog.ChangeLogFilter;
|
||||
import io.ebean.event.changelog.ChangeLogRegister;
|
||||
import io.ebeaninternal.server.deploy.parse.AnnotationBase;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Default implementation of ChangeLogRegister.
|
||||
*/
|
||||
public class DefaultChangeLogRegister implements ChangeLogRegister {
|
||||
|
||||
private static final BasicFilter INCLUDE_INSERTS = new BasicFilter(true);
|
||||
|
||||
private static final BasicFilter EXCLUDE_INSERTS = new BasicFilter(false);
|
||||
|
||||
private final boolean defaultInsertsInclude;
|
||||
|
||||
/**
|
||||
* Create with the default insertsIncluded from ServerConfig.
|
||||
*/
|
||||
public DefaultChangeLogRegister(boolean defaultInsertsInclude) {
|
||||
this.defaultInsertsInclude = defaultInsertsInclude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangeLogFilter getChangeFilter(Class<?> beanType) {
|
||||
|
||||
ChangeLog changeLog = getChangeLog(beanType);
|
||||
if (changeLog == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] updatesThatInclude = changeLog.updatesThatInclude();
|
||||
if (updatesThatInclude.length == 0) {
|
||||
return insertModeInclude(changeLog.inserts()) ? INCLUDE_INSERTS : EXCLUDE_INSERTS;
|
||||
}
|
||||
|
||||
Set<String> updateProps = new HashSet<>();
|
||||
Collections.addAll(updateProps, updatesThatInclude);
|
||||
|
||||
return new UpdateFilter(insertModeInclude(changeLog.inserts()), updateProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and return the ChangeLog annotation in the inheritance hierarchy.
|
||||
*/
|
||||
private ChangeLog getChangeLog(Class<?> beanType) {
|
||||
return AnnotationBase.findAnnotation(beanType, ChangeLog.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if inserts should be included in the change log.
|
||||
*/
|
||||
private boolean insertModeInclude(ChangeLogInsertMode inserts) {
|
||||
if (inserts == ChangeLogInsertMode.DEFAULT) {
|
||||
// return the default as per the ServerConfig
|
||||
return defaultInsertsInclude;
|
||||
}
|
||||
return ChangeLogInsertMode.INCLUDE == inserts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic filter that only handles include inserts flag.
|
||||
*/
|
||||
protected static class BasicFilter implements ChangeLogFilter {
|
||||
|
||||
final boolean includeInserts;
|
||||
|
||||
BasicFilter(boolean includeInserts) {
|
||||
this.includeInserts = includeInserts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeInsert(BeanPersistRequest<?> insertRequest) {
|
||||
return includeInserts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeUpdate(BeanPersistRequest<?> updateRequest) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeDelete(BeanPersistRequest<?> deleteRequest) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter that takes into account a set of properties to check for updates
|
||||
* as well as the include inserts flag.
|
||||
*/
|
||||
protected static class UpdateFilter extends BasicFilter {
|
||||
|
||||
final Set<String> updateProperties;
|
||||
|
||||
UpdateFilter(boolean includeInserts, Set<String> updateProperties) {
|
||||
super(includeInserts);
|
||||
this.updateProperties = updateProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeUpdate(BeanPersistRequest<?> updateRequest) {
|
||||
return updateRequest.hasDirtyProperty(updateProperties);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package io.ebeaninternal.server.cluster;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
|
||||
/**
|
||||
* Represents a relatively small independent message.
|
||||
* <p>
|
||||
* In general terms we break up a potentially large object like
|
||||
* RemoteTransactionEvent into many smaller BinaryMessages. This is so that if
|
||||
* they don't all fit on a single Packet we can easily break them up and put
|
||||
* them on multiple packets.
|
||||
* </p>
|
||||
* <p>
|
||||
* Also note that for the Multicast approach a Packet will generally contain
|
||||
* many messages each directed to different members of the cluster. So it would
|
||||
* be common for many Ack, Resend and Control messages to all be contained in a
|
||||
* single packet.
|
||||
* </p>
|
||||
*/
|
||||
public class BinaryMessage {
|
||||
|
||||
public static final int TYPE_MSGCONTROL = 0;
|
||||
public static final int TYPE_BEANIUD = 1;
|
||||
public static final int TYPE_TABLEIUD = 2;
|
||||
|
||||
public static final int TYPE_MSGACK = 8;
|
||||
public static final int TYPE_MSGRESEND = 9;
|
||||
|
||||
private final ByteArrayOutputStream buffer;
|
||||
private final DataOutputStream os;
|
||||
private byte[] bytes;
|
||||
|
||||
/**
|
||||
* Create with an estimated buffer size.
|
||||
*/
|
||||
public BinaryMessage(int bufSize) {
|
||||
this.buffer = new ByteArrayOutputStream(bufSize);
|
||||
this.os = new DataOutputStream(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DataOutputStream to write content to.
|
||||
*/
|
||||
public DataOutputStream getOs() {
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the content as a byte array.
|
||||
*/
|
||||
public byte[] getByteArray() {
|
||||
if (bytes == null) {
|
||||
bytes = buffer.toByteArray();
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.ebeaninternal.server.cluster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds a List of BinaryMessage's.
|
||||
*
|
||||
* @author rbygrave
|
||||
*/
|
||||
public class BinaryMessageList {
|
||||
|
||||
final ArrayList<BinaryMessage> list = new ArrayList<>();
|
||||
|
||||
public void add(BinaryMessage msg) {
|
||||
list.add(msg);
|
||||
}
|
||||
|
||||
public List<BinaryMessage> getList() {
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.ebeaninternal.server.cluster;
|
||||
|
||||
import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
|
||||
|
||||
|
||||
/**
|
||||
* Sends messages to the cluster members.
|
||||
*/
|
||||
public interface ClusterBroadcast {
|
||||
|
||||
/**
|
||||
* Inform the other cluster members that this instance has come online and
|
||||
* start any listeners etc.
|
||||
*/
|
||||
void startup();
|
||||
|
||||
/**
|
||||
* Inform the other cluster members that this instance is leaving and
|
||||
* shutdown any listeners.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* Send a transaction event to all the members of the cluster.
|
||||
*/
|
||||
void broadcast(RemoteTransactionEvent remoteTransEvent);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package io.ebeaninternal.server.cluster;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Factory to create the cluster broadcast service.
|
||||
*/
|
||||
public interface ClusterBroadcastFactory {
|
||||
|
||||
/**
|
||||
* Create the cluster transport with the manager and deployment properties.
|
||||
*/
|
||||
ClusterBroadcast create(ClusterManager manager, Properties properties);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package io.ebeaninternal.server.cluster;
|
||||
|
||||
import io.ebean.EbeanServer;
|
||||
import io.ebean.config.ContainerConfig;
|
||||
import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Manages the cluster service.
|
||||
*/
|
||||
public class ClusterManager {
|
||||
|
||||
private static final Logger clusterLogger = LoggerFactory.getLogger("org.avaje.ebean.Cluster");
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClusterManager.class);
|
||||
|
||||
private final ConcurrentHashMap<String, EbeanServer> serverMap = new ConcurrentHashMap<>();
|
||||
|
||||
private final Object monitor = new Object();
|
||||
|
||||
private final ClusterBroadcast broadcast;
|
||||
|
||||
private boolean started;
|
||||
|
||||
public ClusterManager(ContainerConfig config) {
|
||||
if (!config.isClusterActive()) {
|
||||
broadcast = null;
|
||||
} else {
|
||||
ClusterBroadcastFactory factory = createFactory();
|
||||
broadcast = factory.create(this, config.getProperties());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ClusterTransportFactory via ServiceLoader.
|
||||
*/
|
||||
private ClusterBroadcastFactory createFactory() {
|
||||
|
||||
ServiceLoader<ClusterBroadcastFactory> load = ServiceLoader.load(ClusterBroadcastFactory.class);
|
||||
ClusterBroadcastFactory factory = null;
|
||||
Iterator<ClusterBroadcastFactory> iterator = load.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
factory = iterator.next();
|
||||
}
|
||||
if (factory == null) {
|
||||
throw new IllegalStateException("No ClusterTransportFactory found in classpath. "
|
||||
+ " Probably need to add the avaje-ebeanorm-cluster dependency");
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
public void registerServer(EbeanServer server) {
|
||||
synchronized (monitor) {
|
||||
serverMap.put(server.getName(), server);
|
||||
if (!started) {
|
||||
startup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public EbeanServer getServer(String name) {
|
||||
synchronized (monitor) {
|
||||
return serverMap.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
private void startup() {
|
||||
started = true;
|
||||
if (broadcast != null) {
|
||||
broadcast.startup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if clustering is on.
|
||||
*/
|
||||
public boolean isClustering() {
|
||||
return broadcast != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the message headers and payload to every server in the cluster.
|
||||
*/
|
||||
public void broadcast(RemoteTransactionEvent event) {
|
||||
if (broadcast != null) {
|
||||
if (clusterLogger.isDebugEnabled()) {
|
||||
clusterLogger.debug("sending: {}", event);
|
||||
}
|
||||
broadcast.broadcast(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the service and Deregister from the cluster.
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (broadcast != null) {
|
||||
logger.info("ClusterManager shutdown ");
|
||||
broadcast.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
|
||||
<TITLE>AvajeLib</TITLE>
|
||||
</HEAD>
|
||||
<Body BGCOLOR="#ffffff">
|
||||
Clustering service for an application.
|
||||
<P>
|
||||
A framework for supporting clustering of servers.
|
||||
</P>
|
||||
</Body>
|
||||
</HTML>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user