#919 - io.ebean package initial

This commit is contained in:
Rob Bygrave
2016-12-11 23:23:22 +13:00
parent 5821dc96b9
commit 971e2dc91b
2134 changed files with 10721 additions and 8652 deletions
@@ -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;