mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Fix for #207: Add support for PeristenceContextScope QUERY and NONE (in addition to the existing TRANSACTION scope)
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
+7
-4
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user