Fix for #207: Add support for PeristenceContextScope QUERY and NONE (in addition to the existing TRANSACTION scope)

This commit is contained in:
rbygrave
2014-11-20 00:53:32 +13:00
parent 55eaa0b484
commit 6677a6199c
15 changed files with 472 additions and 35 deletions
@@ -0,0 +1,52 @@
package com.avaje.ebean;
/**
* Defines the scope for PersistenceContext.
* <p/>
* Ebean has traditionally used Transaction scope for the PersistenceContext. This is used to change the scope to
* use (by default) and explicitly set the scope to use for an individual query.
*
* @see com.avaje.ebean.config.ServerConfig#setPersistenceContextScope(PersistenceContextScope)
* @see com.avaje.ebean.Query#setPersistenceContextScope(PersistenceContextScope)
*/
public enum PersistenceContextScope {
/**
* PersistenceContext is scoped to the transaction.
* <p/>
* If a transaction spans 2 or more queries that fetch the same bean in terms of same type
* and same Id value then they share the same bean instance.
* <p/>
* You may want to change to use QUERY scope when you want a query executing in a transaction to effectively
* ignore beans that have already been loaded (by other queries in the same transaction) and instead get a
* 'fresh copy' of the bean.
*/
TRANSACTION,
/**
* PersistenceContext is scoped to the query.
* <p/>
* This means that for this query running in an existing transaction then it will effectively ignore any beans
* that have already been queried/loaded by prior queries in the same transaction.
* <p/>
* You may use QUERY scope on a query that is executed in a transaction and you want to get a 'fresh copy' of the bean.
*/
QUERY,
/**
* EXPERIMENTAL FEATURE - This is not expected to be used and somewhat experimental.
* This NONE option effectively means that a PersistenceContext is not used when building the object graph and
* subsequent lazy loading.
* <p/>
* You should ONLY use this when treating the resulting object graph as read only and even then you would be best
* to use QUERY (or TRANSACTION).
* <p/>
* A query executed with NONE can build a object graph where there are multiple instances that represent the same
* 'logical bean' by type and Id value (multiple instances of 'Customer 42'). Getting multiple instances that
* represent the same logical bean (same row in the database) means that it is potentially dangerous/confusing to
* use this scope when modifying the beans as multiple instances represent the same underlying rows in the database.
* <p/>
* Generally you would expect to always use TRANSACTION or QUERY scope.
*/
NONE
}
+15
View File
@@ -301,6 +301,21 @@ public interface Query<T> extends Serializable {
*/
public Query<T> copy();
/**
* Specify the PersistenceContextScope to use for this query.
* <p/>
* When this is not set the 'default' configured on {@link com.avaje.ebean.config.ServerConfig#setPersistenceContextScope(PersistenceContextScope)}
* is used - this value defaults to {@link com.avaje.ebean.PersistenceContextScope#TRANSACTION}.
* <p/>
* Note that the same persistence Context is used for subsequent lazy loading and query join queries.
* <p/>
* Note that #findEach uses a 'per object graph' PersistenceContext so this scope is ignored for
* queries executed as #findIterate, #findEach, #findEachWhile.
*
* @param scope The scope to use for this query and subsequent lazy loading.
*/
public Query<T> setPersistenceContextScope(PersistenceContextScope scope);
/**
* Return the ExpressionFactory used by this query.
*/
@@ -1,27 +1,22 @@
package com.avaje.ebean.config;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import com.avaje.ebean.EbeanServerFactory;
import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.annotation.Encrypted;
import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.GlobalProperties.PropertySource;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.config.dbplatform.DbEncrypt;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanQueryAdapter;
import com.avaje.ebean.event.BulkTableEventListener;
import com.avaje.ebean.event.ServerConfigStartup;
import com.avaje.ebean.event.TransactionEventListener;
import com.avaje.ebean.event.*;
import com.avaje.ebean.meta.MetaInfoManager;
import com.avaje.ebean.util.ClassUtil;
import com.fasterxml.jackson.core.JsonFactory;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
/**
* The configuration used for creating a EbeanServer.
* <p>
@@ -247,6 +242,11 @@ public class ServerConfig {
private boolean collectQueryOrigins;
/**
* The default PersistenceContextScope used if one is not explicitly set on a query.
*/
private PersistenceContextScope persistenceContextScope = PersistenceContextScope.TRANSACTION;
private JsonFactory jsonFactory;
private boolean localTimeWithNanos;
@@ -254,8 +254,7 @@ public class ServerConfig {
private boolean durationWithNanos;
/**
* Construct a Server Configuration for programmatically creating an
* EbeanServer.
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
public ServerConfig() {
@@ -1305,6 +1304,35 @@ public class ServerConfig {
this.persistListeners = persistListeners;
}
/**
* Return the default PersistenceContextScope to be used if one is not explicitly set on a query.
* <p/>
* The PersistenceContextScope can specified on each query via {@link com.avaje.ebean
* .Query#setPersistenceContextScope(com.avaje.ebean.PersistenceContextScope)}. If it
* is not set on the query this default scope is used.
*
* @see com.avaje.ebean.Query#setPersistenceContextScope(com.avaje.ebean.PersistenceContextScope)
*/
public PersistenceContextScope getPersistenceContextScope() {
// if somehow null return TRANSACTION scope
return persistenceContextScope == null ? PersistenceContextScope.TRANSACTION : persistenceContextScope;
}
/**
* Set the PersistenceContext scope to be used if one is not explicitly set on a query.
* <p/>
* This defaults to {@link PersistenceContextScope#TRANSACTION}.
* <p/>
* The PersistenceContextScope can specified on each query via {@link com.avaje.ebean
* .Query#setPersistenceContextScope(com.avaje.ebean.PersistenceContextScope)}. If it
* is not set on the query this scope is used.
*
* @see com.avaje.ebean.Query#setPersistenceContextScope(com.avaje.ebean.PersistenceContextScope)
*/
public void setPersistenceContextScope(PersistenceContextScope persistenceContextScope) {
this.persistenceContextScope = persistenceContextScope;
}
/**
* Load the settings from the ebean.properties file.
*/
@@ -1359,8 +1387,6 @@ public class ServerConfig {
/**
* This is broken out for the same reason as above - preserve existing behaviour but let it be overridden.
*
* @param p
*/
protected void loadAutofetchConfig(PropertySource p) {
autofetchConfig.loadSettings(p);
@@ -1418,6 +1444,8 @@ public class ServerConfig {
int batchSize = p.getInt("batch.size", 20);
persistBatchSize = p.getInt("persistBatchSize", batchSize);
persistenceContextScope = PersistenceContextScope.valueOf(p.get("persistenceContextScope","TRANSACTION"));
dataSourceJndiName = p.get("dataSourceJndiName", null);
databaseSequenceBatchSize = p.getInt("databaseSequenceBatchSize", 20);
databaseBooleanTrue = p.get("databaseBooleanTrue", null);
@@ -2,10 +2,7 @@ package com.avaje.ebeaninternal.api;
import java.util.List;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Query;
import com.avaje.ebean.Transaction;
import com.avaje.ebean.TxScope;
import com.avaje.ebean.*;
import com.avaje.ebean.bean.BeanCollectionLoader;
import com.avaje.ebean.bean.BeanLoader;
import com.avaje.ebean.bean.CallStack;
@@ -63,6 +60,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
public CallStack createCallStack();
/**
* Return the PersistenceContextScope to use defined at query or server level.
*/
public PersistenceContextScope getPersistenceContextScope(SpiQuery<?> query);
/**
* Return the DDL generator.
*/
@@ -5,6 +5,7 @@ import java.util.List;
import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.OrderBy;
import com.avaje.ebean.PersistenceContextScope;
import com.avaje.ebean.Query;
import com.avaje.ebean.bean.BeanCollectionTouched;
import com.avaje.ebean.bean.CallStack;
@@ -79,7 +80,15 @@ public interface SpiQuery<T> extends Query<T> {
*/
SUBQUERY
}
/**
* Return the PersistenceContextScope that this query should use.
* <p>
* This can be null and in that case use the default scope.
* </p>
*/
public PersistenceContextScope getPersistenceContextScope();
/**
* Return true if select all properties was used to ensure the property
* invoking a lazy load was included in the query.
@@ -40,10 +40,13 @@ public class DefaultServerCacheManager implements ServerCacheManager {
cacheFactory.init(server);
this.ebeanServer = (SpiEbeanServer)server;
}
public void setCaching(Class<?> beanType, boolean useCache) {
ebeanServer.getBeanDescriptor(beanType).getCacheOptions().setUseCache(useCache);
}
/**
* Set bean caching on or off for a given bean type.
*/
public void setCaching(Class<?> beanType, boolean useCache) {
ebeanServer.getBeanDescriptor(beanType).setUseCache(useCache);
}
/**
* Clear both the bean cache and the query cache for a
@@ -137,7 +137,6 @@ public final class DefaultServer implements SpiEbeanServer {
private final CQueryEngine cqueryEngine;
//@Deprecated
private DdlGenerator ddlGenerator;
private final ExpressionFactory expressionFactory;
@@ -157,6 +156,11 @@ public final class DefaultServer implements SpiEbeanServer {
*/
private String mbeanName;
/**
* The default PersistenceContextScope used if it is not explicitly set on a query.
*/
private final PersistenceContextScope defaultPersistenceContextScope;
/**
* The MBeanServer Ebean is registered with.
*/
@@ -215,6 +219,7 @@ public final class DefaultServer implements SpiEbeanServer {
this.cqueryEngine = config.getCQueryEngine();
this.expressionFactory = config.getExpressionFactory();
this.encryptKeyManager = serverConfig.getEncryptKeyManager();
this.defaultPersistenceContextScope = serverConfig.getPersistenceContextScope();
this.beanDescriptorManager = config.getBeanDescriptorManager();
beanDescriptorManager.setEbeanServer(this);
@@ -1131,8 +1136,8 @@ public final class DefaultServer implements SpiEbeanServer {
t = getCurrentServerTransaction();
}
PersistenceContext context = null;
if (t != null) {
// first look in the persistence context
if (t != null && useTransactionPersistenceContext(query)) {
// first look in the transaction scoped persistence context
context = t.getPersistenceContext();
if (context != null) {
WithOption o = context.getWithOption(beanDescriptor.getBeanType(), query.getId());
@@ -1155,7 +1160,22 @@ public final class DefaultServer implements SpiEbeanServer {
// Hit the L2 bean cache
return beanDescriptor.cacheBeanGet(query, context);
}
/**
* Return true if transactions PersistenceContext should be used.
*/
private <T> boolean useTransactionPersistenceContext(SpiQuery<T> query) {
return PersistenceContextScope.TRANSACTION.equals(getPersistenceContextScope(query));
}
/**
* Return the PersistenceContextScope to use defined at query or server level.
*/
public PersistenceContextScope getPersistenceContextScope(SpiQuery<?> query) {
PersistenceContextScope scope = query.getPersistenceContextScope();
return (scope != null) ? scope : defaultPersistenceContextScope;
}
@SuppressWarnings("unchecked")
private <T> T findId(Query<T> query, Transaction t) {
@@ -28,6 +28,7 @@ import com.avaje.ebeaninternal.server.loadcontext.DLoadContext;
import com.avaje.ebeaninternal.server.query.CQueryPlan;
import com.avaje.ebeaninternal.server.query.CancelableQuery;
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
import com.avaje.ebeaninternal.server.transaction.NoopPersistenceContext;
/**
* Wraps the objects involved in executing a Query.
@@ -53,7 +54,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
private HashQuery cacheKey;
private HashQueryPlan queryPlanHash;
/**
* Create the InternalQueryRequest.
*/
@@ -69,7 +70,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
this.readOnly = query.isReadOnly();
}
/**
* Return the database platform like clause.
*/
@@ -183,18 +184,32 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
persistenceContext = new DefaultPersistenceContext();
loadContext.setPersistenceContext(persistenceContext);
}
/**
* Get the TransactionContext either explicitly set on the query or
* transaction scoped.
*/
private PersistenceContext getPersistenceContext(SpiQuery<?> query, SpiTransaction t) {
// check if there is already a persistence context set which is the case
// when lazy loading or query joins are executed
PersistenceContext ctx = query.getPersistenceContext();
if (ctx == null) {
ctx = t.getPersistenceContext();
if (ctx != null) return ctx;
// determine the scope (from the query and then server)
PersistenceContextScope scope = ebeanServer.getPersistenceContextScope(query);
switch (scope) {
case QUERY:
// Create a new PersistenceContext for this query
return new DefaultPersistenceContext();
case NONE:
// Effectively don't use a PersistenceContext
return new NoopPersistenceContext();
default: {
// Use the transaction scoped PersistenceContext
return t.getPersistenceContext();
}
}
return ctx;
}
/**
@@ -725,6 +725,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
return inheritInfo == null || inheritInfo.isRoot();
}
/**
* Set the bean caching on or off.
*/
public void setUseCache(boolean useCache) {
cacheHelp.setUseCache(useCache);
}
/**
* Return true if there is currently query caching for this type of bean.
*/
@@ -105,7 +105,16 @@ public final class BeanDescriptorCacheHelp<T> {
}
}
}
public void setUseCache(boolean useCache) {
if (useCache) {
getBeanCache();
} else {
beanCacheClear();
beanCache = null;
}
}
/**
* Return true if there is currently query caching for this type of bean.
*/
@@ -167,6 +167,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private Boolean readOnly;
private PersistenceContextScope persistenceContextScope;
private boolean sqlSelect;
/**
@@ -459,6 +461,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
if (havingExpressions != null) {
copy.havingExpressions = havingExpressions.copy(copy);
}
copy.persistenceContextScope = persistenceContextScope;
copy.usageProfiling = usageProfiling;
copy.autoFetch = autoFetch;
copy.parentNode = parentNode;
@@ -468,6 +471,16 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return copy;
}
@Override
public Query<T> setPersistenceContextScope(PersistenceContextScope scope) {
this.persistenceContextScope = scope;
return this;
}
public PersistenceContextScope getPersistenceContextScope() {
return persistenceContextScope;
}
public Type getType() {
return type;
}
@@ -0,0 +1,60 @@
package com.avaje.ebeaninternal.server.transaction;
import com.avaje.ebean.bean.PersistenceContext;
/**
* PersistenceContext used with scope of NONE.
* <p/>
* When used effectively means no PersistenceContext is used at all. This is not expected to be used much and
* actually is not recommended.
*/
public class NoopPersistenceContext implements PersistenceContext {
@Override
public void put(Object id, Object bean) {
// do nothing
}
@Override
public Object putIfAbsent(Object id, Object bean) {
// do nothing
return null;
}
@Override
public Object get(Class<?> beanType, Object uid) {
// do nothing, return null
return null;
}
@Override
public WithOption getWithOption(Class<?> beanType, Object uid) {
// do nothing, return null
return null;
}
@Override
public void clear() {
// do nothing
}
@Override
public void clear(Class<?> beanType) {
// do nothing
}
@Override
public void clear(Class<?> beanType, Object uid) {
// do nothing
}
@Override
public void deleted(Class<?> beanType, Object id) {
// do nothing
}
@Override
public int size(Class<?> beanType) {
return 0;
}
}