diff --git a/src/main/java/com/avaje/ebean/cache/ServerCache.java b/src/main/java/com/avaje/ebean/cache/ServerCache.java
index 40439ac0f..ca6e84e3e 100644
--- a/src/main/java/com/avaje/ebean/cache/ServerCache.java
+++ b/src/main/java/com/avaje/ebean/cache/ServerCache.java
@@ -10,8 +10,6 @@ package com.avaje.ebean.cache;
* which holds beans of a given type. The other is the 'query cache' holding
* query results for a given type.
*
- *
- * @author rbygrave
*/
public interface ServerCache {
@@ -32,11 +30,6 @@ public interface ServerCache {
/**
* Clear all entries from the cache.
- *
- * NOTE: Be careful using this method in that most of the time application
- * code should clear BOTH the bean and query caches at the same time. This can
- * be done via {@link ServerCacheManager#clear(Class)}.
- *
*/
void clear();
diff --git a/src/main/java/com/avaje/ebean/cache/ServerCacheManager.java b/src/main/java/com/avaje/ebean/cache/ServerCacheManager.java
index 90d2e8cc5..36d586601 100644
--- a/src/main/java/com/avaje/ebean/cache/ServerCacheManager.java
+++ b/src/main/java/com/avaje/ebean/cache/ServerCacheManager.java
@@ -14,11 +14,6 @@ public interface ServerCacheManager {
*/
boolean isLocalL2Caching();
- /**
- * Return true if there is an active bean cache for this type of bean.
- */
- boolean isBeanCaching(Class> beanType);
-
/**
* Return the cache for mapping natural keys to id values.
*/
diff --git a/src/main/java/com/avaje/ebean/cache/ServerCacheType.java b/src/main/java/com/avaje/ebean/cache/ServerCacheType.java
index bdead0c6e..c5e27ef22 100644
--- a/src/main/java/com/avaje/ebean/cache/ServerCacheType.java
+++ b/src/main/java/com/avaje/ebean/cache/ServerCacheType.java
@@ -8,20 +8,30 @@ public enum ServerCacheType {
/**
* Bean cache.
*/
- BEAN,
+ BEAN("_B"),
/**
* Natural key cache.
*/
- NATURAL_KEY,
+ NATURAL_KEY("_N"),
/**
* Collection Ids for Many properties.
*/
- COLLECTION_IDS,
+ COLLECTION_IDS("_C"),
/**
* Query cache.
*/
- QUERY
+ QUERY("_Q");
+
+ private String code;
+
+ ServerCacheType(String code) {
+ this.code = code;
+ }
+
+ public String code(){
+ return code;
+ }
}
diff --git a/src/main/java/com/avaje/ebean/config/CurrentTenantProvider.java b/src/main/java/com/avaje/ebean/config/CurrentTenantProvider.java
new file mode 100644
index 000000000..29c2bd33c
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/config/CurrentTenantProvider.java
@@ -0,0 +1,12 @@
+package com.avaje.ebean.config;
+
+/**
+ * Provides the Tenant Id for the current request based on the current user.
+ */
+public interface CurrentTenantProvider {
+
+ /**
+ * Return the Tenant Id for the current user.
+ */
+ String currentId();
+}
diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java
index e594400d5..9c83fb8ac 100644
--- a/src/main/java/com/avaje/ebean/config/ServerConfig.java
+++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java
@@ -24,6 +24,7 @@ import com.avaje.ebean.event.readaudit.ReadAuditPrepare;
import com.avaje.ebean.meta.MetaInfoManager;
import com.fasterxml.jackson.core.JsonFactory;
import org.avaje.datasource.DataSourceConfig;
+import org.avaje.dbmigration.MigrationRunner;
import javax.sql.DataSource;
import java.util.ArrayList;
@@ -115,6 +116,16 @@ public class ServerConfig {
*/
private boolean disableClasspathSearch;
+ private TenantMode tenantMode = TenantMode.NONE;
+
+ private String tenantPartitionColumn = "tenant_id";
+
+ private CurrentTenantProvider currentTenantProvider;
+
+ private TenantDataSourceProvider tenantDataSourceProvider;
+
+ private TenantSchemaProvider tenantSchemaProvider;
+
/**
* List of interesting classes such as entities, embedded, ScalarTypes,
* Listeners, Finders, Controllers etc.
@@ -368,8 +379,6 @@ public class ServerConfig {
private ServerCachePlugin serverCachePlugin;
- private ServerCacheManager serverCacheManager;
-
private boolean collectQueryStatsByNode = true;
private boolean collectQueryOrigins = true;
@@ -612,6 +621,76 @@ public class ServerConfig {
this.currentUserProvider = currentUserProvider;
}
+ /**
+ * Return the tenancy mode used.
+ */
+ public TenantMode getTenantMode() {
+ return tenantMode;
+ }
+
+ /**
+ * Set the tenancy mode to use.
+ */
+ public void setTenantMode(TenantMode tenantMode) {
+ this.tenantMode = tenantMode;
+ }
+
+ /**
+ * Return the column name used for TenantMode.PARTITION.
+ */
+ public String getTenantPartitionColumn() {
+ return tenantPartitionColumn;
+ }
+
+ /**
+ * Set the column name used for TenantMode.PARTITION.
+ */
+ public void setTenantPartitionColumn(String tenantPartitionColumn) {
+ this.tenantPartitionColumn = tenantPartitionColumn;
+ }
+
+ /**
+ * Return the current tenant provider.
+ */
+ public CurrentTenantProvider getCurrentTenantProvider() {
+ return currentTenantProvider;
+ }
+
+ /**
+ * Set the current tenant provider.
+ */
+ public void setCurrentTenantProvider(CurrentTenantProvider currentTenantProvider) {
+ this.currentTenantProvider = currentTenantProvider;
+ }
+
+ /**
+ * Return the tenancy datasource provider.
+ */
+ public TenantDataSourceProvider getTenantDataSourceProvider() {
+ return tenantDataSourceProvider;
+ }
+
+ /**
+ * Set the tenancy datasource provider.
+ */
+ public void setTenantDataSourceProvider(TenantDataSourceProvider tenantDataSourceProvider) {
+ this.tenantDataSourceProvider = tenantDataSourceProvider;
+ }
+
+ /**
+ * Return the tenancy schema provider.
+ */
+ public TenantSchemaProvider getTenantSchemaProvider() {
+ return tenantSchemaProvider;
+ }
+
+ /**
+ * Set the tenancy schema provider.
+ */
+ public void setTenantSchemaProvider(TenantSchemaProvider tenantSchemaProvider) {
+ this.tenantSchemaProvider = tenantSchemaProvider;
+ }
+
/**
* Return the PersistBatch mode to use by default at the transaction level.
*
@@ -1023,20 +1102,6 @@ public class ServerConfig {
this.serverCachePlugin = serverCachePlugin;
}
- /**
- * Return the ServerCacheManager.
- */
- public ServerCacheManager getServerCacheManager() {
- return serverCacheManager;
- }
-
- /**
- * Set the ServerCacheManager to use.
- */
- public void setServerCacheManager(ServerCacheManager serverCacheManager) {
- this.serverCacheManager = serverCacheManager;
- }
-
/**
* Return true if LOB's should default to fetch eager.
* By default this is set to false and LOB's must be explicitly fetched.
@@ -2440,7 +2505,6 @@ public class ServerConfig {
encryptor = createInstance(p, Encryptor.class, "encryptor", encryptor);
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt", dbEncrypt);
serverCachePlugin = createInstance(p, ServerCachePlugin.class, "serverCachePlugin", serverCachePlugin);
- serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager", serverCacheManager);
if (packages != null) {
String packagesProp = p.get("search.packages", p.get("packages", null));
@@ -2646,6 +2710,17 @@ public class ServerConfig {
this.disableL2Cache = disableL2Cache;
}
+ /**
+ * Run the DB migration against the DataSource.
+ */
+ public DataSource runDbMigration(DataSource dataSource) {
+ if (migrationConfig.isRunMigration()) {
+ MigrationRunner runner = migrationConfig.createRunner(getClassLoadConfig().getClassLoader());
+ runner.run(dataSource);
+ }
+ return dataSource;
+ }
+
/**
* Specify how UUID is stored.
*/
diff --git a/src/main/java/com/avaje/ebean/config/TenantDataSourceProvider.java b/src/main/java/com/avaje/ebean/config/TenantDataSourceProvider.java
new file mode 100644
index 000000000..7ef7c4cd1
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/config/TenantDataSourceProvider.java
@@ -0,0 +1,19 @@
+package com.avaje.ebean.config;
+
+import javax.sql.DataSource;
+
+/**
+ * For multi-tenancy via DB supply the DataSource given the tenantId.
+ */
+public interface TenantDataSourceProvider {
+
+ /**
+ * Return the DataSource to use for the given current tenant.
+ */
+ DataSource dataSource(String tenantId);
+
+ /**
+ * Shutdown all the DataSources.
+ */
+ void shutdown(boolean deregisterDriver);
+}
diff --git a/src/main/java/com/avaje/ebean/config/TenantMode.java b/src/main/java/com/avaje/ebean/config/TenantMode.java
new file mode 100644
index 000000000..2552fb029
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/config/TenantMode.java
@@ -0,0 +1,27 @@
+package com.avaje.ebean.config;
+
+/**
+ * The mode to use for multi-tenancy.
+ */
+public enum TenantMode {
+
+ /**
+ * No multi-tenancy.
+ */
+ NONE,
+
+ /**
+ * Each Tenant has their own Database (javax.sql.DataSource)
+ */
+ DB,
+
+ /**
+ * Each Tenant has their own Database schema.
+ */
+ SCHEMA,
+
+ /**
+ * Tenants share tables but have a discriminator/partition column that partitions the data.
+ */
+ PARTITION
+}
diff --git a/src/main/java/com/avaje/ebean/config/TenantSchemaProvider.java b/src/main/java/com/avaje/ebean/config/TenantSchemaProvider.java
new file mode 100644
index 000000000..b51f70641
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/config/TenantSchemaProvider.java
@@ -0,0 +1,15 @@
+package com.avaje.ebean.config;
+
+/**
+ * For multi-tenancy via DB SCHEMA supply the schema given the tenantId.
+ */
+public interface TenantSchemaProvider {
+
+ /**
+ * Return the DB schema for the given tenantId.
+ *
+ * @param tenantId The current tenant id.
+ * @return The DB schema to use for the given tenant
+ */
+ String schema(String tenantId);
+}
diff --git a/src/main/java/com/avaje/ebean/plugin/BeanType.java b/src/main/java/com/avaje/ebean/plugin/BeanType.java
index fb2afc74f..53cb26cc8 100644
--- a/src/main/java/com/avaje/ebean/plugin/BeanType.java
+++ b/src/main/java/com/avaje/ebean/plugin/BeanType.java
@@ -73,6 +73,26 @@ public interface BeanType {
*/
boolean isValidExpression(String property);
+ /**
+ * Return true if bean caching is on for this bean type.
+ */
+ boolean isBeanCaching();
+
+ /**
+ * Return true if query caching is on for this bean type.
+ */
+ boolean isQueryCaching();
+
+ /**
+ * Clear the bean cache.
+ */
+ void clearBeanCache();
+
+ /**
+ * Clear the query cache.
+ */
+ void clearQueryCache();
+
/**
* Return true if the type is document store only.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java
index b5a4d1fd5..8351f8efc 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java
@@ -80,6 +80,16 @@ public class ScopedTransaction implements SpiTransaction {
}
}
+ @Override
+ public void setTenantId(Object tenantId) {
+ transaction.setTenantId(tenantId);
+ }
+
+ @Override
+ public Object getTenantId() {
+ return transaction.getTenantId();
+ }
+
@Override
public DocStoreTransaction getDocStoreTransaction() {
return transaction.getDocStoreTransaction();
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java
index 09e02d717..b5d7f90a2 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiEbeanServer.java
@@ -40,6 +40,11 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
boolean isUpdateAllPropertiesInBatch();
+ /**
+ * Return the current Tenant Id.
+ */
+ Object currentTenantId();
+
/**
* Return the server configuration.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java
index bab1cb769..a06f4bf02 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java
@@ -271,4 +271,14 @@ public interface SpiTransaction extends Transaction {
* Return a document store transaction.
*/
DocStoreTransaction getDocStoreTransaction();
+
+ /**
+ * Set the current Tenant Id.
+ */
+ void setTenantId(Object tenantId);
+
+ /**
+ * Return the current Tenant Id.
+ */
+ Object getTenantId();
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeSet.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeSet.java
index 282c9c8d7..43f485e38 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeSet.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeSet.java
@@ -42,7 +42,7 @@ public class CacheChangeSet {
*/
public Set apply() {
for (BeanDescriptor> entry : queryCaches) {
- entry.queryCacheClear();
+ entry.clearQueryCache();
}
for (CacheChange entry : entries) {
entry.apply();
diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheAdapter.java b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheAdapter.java
new file mode 100644
index 000000000..465bcf60b
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheAdapter.java
@@ -0,0 +1,49 @@
+package com.avaje.ebeaninternal.server.cache;
+
+import com.avaje.ebean.cache.ServerCache;
+import com.avaje.ebean.cache.ServerCacheManager;
+
+public class DefaultCacheAdapter implements ServerCacheManager {
+
+ private final SpiCacheManager cacheManager;
+
+ public DefaultCacheAdapter(SpiCacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+ }
+
+ @Override
+ public boolean isLocalL2Caching() {
+ return cacheManager.isLocalL2Caching();
+ }
+
+ @Override
+ public ServerCache getNaturalKeyCache(Class> beanType) {
+ return cacheManager.getNaturalKeyCache(beanType).get();
+ }
+
+ @Override
+ public ServerCache getBeanCache(Class> beanType) {
+ return cacheManager.getBeanCache(beanType).get();
+ }
+
+ @Override
+ public ServerCache getCollectionIdsCache(Class> beanType, String propertyName) {
+ return cacheManager.getCollectionIdsCache(beanType, propertyName).get();
+ }
+
+ @Override
+ public ServerCache getQueryCache(Class> beanType) {
+ return cacheManager.getQueryCache(beanType).get();
+ }
+
+ @Override
+ public void clear(Class> beanType) {
+ cacheManager.getBeanCache(beanType).get().clear();
+ cacheManager.getQueryCache(beanType).get().clear();
+ }
+
+ @Override
+ public void clearAll() {
+ cacheManager.clearAll();
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder.java b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder.java
index cae96c6a5..1b2addfcb 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder.java
@@ -6,74 +6,64 @@ import com.avaje.ebean.cache.ServerCache;
import com.avaje.ebean.cache.ServerCacheFactory;
import com.avaje.ebean.cache.ServerCacheOptions;
import com.avaje.ebean.cache.ServerCacheType;
+import com.avaje.ebean.config.CurrentTenantProvider;
-import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
/**
* Manages the construction of caches.
*/
-public class DefaultCacheHolder {
+class DefaultCacheHolder {
- private final ConcurrentHashMap concMap = new ConcurrentHashMap<>();
-
- private final HashMap synchMap = new HashMap<>();
-
- private final Object monitor = new Object();
+ private final ConcurrentHashMap allCaches = new ConcurrentHashMap<>();
private final ServerCacheFactory cacheFactory;
private final ServerCacheOptions defaultOptions;
+ private final CurrentTenantProvider tenantProvider;
+
/**
* Create with a cache factory and default cache options.
*
* @param cacheFactory the factory for creating the cache
* @param defaultOptions the default options for tuning the cache
*/
- public DefaultCacheHolder(ServerCacheFactory cacheFactory, ServerCacheOptions defaultOptions) {
+ DefaultCacheHolder(ServerCacheFactory cacheFactory, ServerCacheOptions defaultOptions, CurrentTenantProvider tenantProvider) {
this.cacheFactory = cacheFactory;
this.defaultOptions = defaultOptions;
+ this.tenantProvider = tenantProvider;
+ }
+
+ Supplier getCache(Class> beanType, String cacheKey, ServerCacheType type) {
+
+ if (tenantProvider == null) {
+ return new SimpleSupplier(getCacheInternal(beanType, cacheKey, type));
+ }
+ return new TenantSupplier(beanType, cacheKey, type);
+ }
+
+ private String key(String cacheKey, ServerCacheType type) {
+ return cacheKey + type.code();
}
/**
* Return the cache for a given bean type.
*/
- public ServerCache getCache(String cacheKey, ServerCacheType type) {
+ private ServerCache getCacheInternal(Class> beanType, String cacheKey, ServerCacheType type) {
- ServerCache cache = concMap.get(cacheKey);
- if (cache != null) {
- return cache;
- }
- synchronized (monitor) {
- cache = synchMap.get(cacheKey);
- if (cache == null) {
- ServerCacheOptions options = getCacheOptions(cacheKey, type);
- cache = cacheFactory.createCache(type, cacheKey, options);
- synchMap.put(cacheKey, cache);
- concMap.put(cacheKey, cache);
- }
- return cache;
- }
+ String fullKey = key(cacheKey, type);
+ return allCaches.computeIfAbsent(fullKey, s -> createCache(beanType, type, fullKey));
}
- public void clearCache(String cacheKey) {
-
- ServerCache cache = concMap.get(cacheKey);
- if (cache != null) {
- cache.clear();
- }
+ private ServerCache createCache(Class> beanType, ServerCacheType type, String key) {
+ ServerCacheOptions options = getCacheOptions(beanType, type);
+ return cacheFactory.createCache(type, key, options);
}
- /**
- * Return true if there is an active cache for this bean type.
- */
- public boolean isCaching(String beanType) {
- return concMap.containsKey(beanType);
- }
-
- public void clearAll() {
- for (ServerCache serverCache : concMap.values()) {
+ void clearAll() {
+ for (ServerCache serverCache : allCaches.values()) {
serverCache.clear();
}
}
@@ -81,21 +71,17 @@ public class DefaultCacheHolder {
/**
* Return the cache options for a given bean type.
*/
- ServerCacheOptions getCacheOptions(String beanType, ServerCacheType type) {
+ ServerCacheOptions getCacheOptions(Class> beanType, ServerCacheType type) {
- try {
- Class> cls = Class.forName(beanType);
- switch (type) {
- case QUERY:
- return getQueryOptions(cls);
- default:
- return getBeanOptions(cls);
- }
- } catch (ClassNotFoundException e) {
- // ignore
+ if (beanType == null) {
+ return defaultOptions.copy();
+ }
+ switch (type) {
+ case QUERY:
+ return getQueryOptions(beanType);
+ default:
+ return getBeanOptions(beanType);
}
-
- return defaultOptions.copy();
}
private ServerCacheOptions getQueryOptions(Class> cls) {
@@ -118,4 +104,40 @@ public class DefaultCacheHolder {
return defaultOptions.copy();
}
+ /**
+ * Multi-Tenant based cache supplier.
+ */
+ private class TenantSupplier implements Supplier {
+
+ final Class> beanType;
+ final String key;
+ final ServerCacheType type;
+
+ private TenantSupplier(Class> beanType, String key, ServerCacheType type) {
+ this.beanType = beanType;
+ this.key = key;
+ this.type = type;
+ }
+
+ @Override
+ public ServerCache get() {
+ String fullKey = key + "_" + tenantProvider.currentId();
+ return getCacheInternal(beanType, fullKey, type);
+ }
+ }
+
+ private static class SimpleSupplier implements Supplier {
+
+ final ServerCache underlying;
+
+ private SimpleSupplier(ServerCache underlying) {
+ this.underlying = underlying;
+ }
+
+ @Override
+ public ServerCache get() {
+ return underlying;
+ }
+ }
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheManager.java b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheManager.java
index 7f93d27d6..45cc3ac35 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCacheManager.java
@@ -2,97 +2,73 @@ package com.avaje.ebeaninternal.server.cache;
import com.avaje.ebean.cache.ServerCache;
import com.avaje.ebean.cache.ServerCacheFactory;
-import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.cache.ServerCacheOptions;
import com.avaje.ebean.cache.ServerCacheType;
+import com.avaje.ebean.config.CurrentTenantProvider;
+
+import java.util.function.Supplier;
/**
* Manages the bean and query caches.
*/
-public class DefaultServerCacheManager implements ServerCacheManager {
+public class DefaultServerCacheManager implements SpiCacheManager {
- private final DefaultCacheHolder beanCache;
-
- private final DefaultCacheHolder queryCache;
-
- private final DefaultCacheHolder naturalKeyCache;
-
- private final DefaultCacheHolder collectionIdsCache;
+ private final DefaultCacheHolder cacheHolder;
private final boolean localL2Caching;
/**
* Create with a cache factory and default cache options.
*/
- public DefaultServerCacheManager(boolean localL2Caching, ServerCacheFactory cacheFactory, ServerCacheOptions defaultBeanOptions, ServerCacheOptions defaultQueryOptions) {
+ public DefaultServerCacheManager(boolean localL2Caching, CurrentTenantProvider tenantProvider, ServerCacheFactory cacheFactory,
+ ServerCacheOptions defaultBeanOptions, ServerCacheOptions defaultQueryOptions) {
this.localL2Caching = localL2Caching;
- this.beanCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
- this.queryCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions);
- this.naturalKeyCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
- this.collectionIdsCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
+ this.cacheHolder = new DefaultCacheHolder(cacheFactory, defaultBeanOptions, tenantProvider);
}
/**
* Construct when l2 cache is disabled.
*/
public DefaultServerCacheManager() {
- this(true, new DefaultServerCacheFactory(), new ServerCacheOptions(), new ServerCacheOptions());
+ this(true, null, new DefaultServerCacheFactory(), new ServerCacheOptions(), new ServerCacheOptions());
}
public boolean isLocalL2Caching() {
return localL2Caching;
}
- /**
- * Clear both the bean cache and the query cache for a
- * given bean type.
- */
- public void clear(Class> beanType) {
- String beanName = beanType.getName();
- beanCache.clearCache(beanName);
- naturalKeyCache.clearCache(beanName);
- collectionIdsCache.clearCache(beanName);
- queryCache.clearCache(beanName);
- }
-
/**
* Clear all caches.
*/
public void clearAll() {
- beanCache.clearAll();
- queryCache.clearAll();
- naturalKeyCache.clearAll();
- collectionIdsCache.clearAll();
+ cacheHolder.clearAll();
}
- public ServerCache getCollectionIdsCache(Class> beanType, String propertyName) {
- return collectionIdsCache.getCache(beanType.getName() + "." + propertyName, ServerCacheType.COLLECTION_IDS);
+ public Supplier getCollectionIdsCache(Class> beanType, String propertyName) {
+ return cacheHolder.getCache(beanType, name(beanType) + "." + propertyName, ServerCacheType.COLLECTION_IDS);
}
- public ServerCache getNaturalKeyCache(Class> beanType) {
- return naturalKeyCache.getCache(beanType.getName(), ServerCacheType.NATURAL_KEY);
+ public Supplier getNaturalKeyCache(Class> beanType) {
+ return cacheHolder.getCache(beanType, name(beanType), ServerCacheType.NATURAL_KEY);
}
/**
* Return the query cache for a given bean type.
*/
- public ServerCache getQueryCache(Class> beanType) {
- return queryCache.getCache(beanType.getName(), ServerCacheType.QUERY);
+ public Supplier getQueryCache(Class> beanType) {
+ return cacheHolder.getCache(beanType, name(beanType), ServerCacheType.QUERY);
}
/**
* Return the bean cache for a given bean type.
*/
- public ServerCache getBeanCache(Class> beanType) {
- return beanCache.getCache(beanType.getName(), ServerCacheType.BEAN);
+ public Supplier getBeanCache(Class> beanType) {
+ return cacheHolder.getCache(beanType, name(beanType), ServerCacheType.BEAN);
}
- /**
- * Return true if there is an active cache for the given bean type.
- */
- public boolean isBeanCaching(Class> beanType) {
- return beanCache.isCaching(beanType.getName());
+ private String name(Class> beanType) {
+ return beanType.getName();
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/SpiCacheManager.java b/src/main/java/com/avaje/ebeaninternal/server/cache/SpiCacheManager.java
new file mode 100644
index 000000000..3889a1eb8
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/cache/SpiCacheManager.java
@@ -0,0 +1,46 @@
+package com.avaje.ebeaninternal.server.cache;
+
+import com.avaje.ebean.cache.ServerCache;
+
+import java.util.function.Supplier;
+
+/**
+ * The cache service for server side caching of beans and query results.
+ */
+public interface SpiCacheManager {
+
+ /**
+ * Return true if the L2 caching is local.
+ *
+ * Local L2 caching means that the cache updates should occur in foreground
+ * rather than background processing.
+ *
+ */
+ boolean isLocalL2Caching();
+
+ /**
+ * Return the cache for mapping natural keys to id values.
+ */
+ Supplier getNaturalKeyCache(Class> beanType);
+
+ /**
+ * Return the cache for beans of a particular type.
+ */
+ Supplier getBeanCache(Class> beanType);
+
+ /**
+ * Return the cache for associated many properties of a bean type.
+ */
+ Supplier getCollectionIdsCache(Class> beanType, String propertyName);
+
+ /**
+ * Return the cache for query results of a particular type of bean.
+ */
+ Supplier getQueryCache(Class> beanType);
+
+ /**
+ * Clear all the caches.
+ */
+ void clearAll();
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java
index 98ece7bad..f73f94925 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultContainer.java
@@ -2,13 +2,13 @@ package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.BackgroundExecutor;
import com.avaje.ebean.cache.ServerCacheFactory;
-import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.cache.ServerCacheOptions;
import com.avaje.ebean.cache.ServerCachePlugin;
import com.avaje.ebean.common.SpiContainer;
import com.avaje.ebean.config.ContainerConfig;
import com.avaje.ebean.config.PropertyMap;
import com.avaje.ebean.config.ServerConfig;
+import com.avaje.ebean.config.TenantMode;
import com.avaje.ebean.config.UnderscoreNamingConvention;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.config.dbplatform.H2Platform;
@@ -17,6 +17,7 @@ import com.avaje.ebeaninternal.api.SpiBackgroundExecutor;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.cache.DefaultServerCacheManager;
import com.avaje.ebeaninternal.server.cache.DefaultServerCachePlugin;
+import com.avaje.ebeaninternal.server.cache.SpiCacheManager;
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
import com.avaje.ebeaninternal.server.core.bootup.BootupClassPathSearch;
import com.avaje.ebeaninternal.server.core.bootup.BootupClasses;
@@ -99,9 +100,11 @@ public class DefaultContainer implements SpiContainer {
if (serverConfig.isDocStoreOnly()) {
serverConfig.setDatabasePlatform(new H2Platform());
} else {
- setDataSource(serverConfig);
- // check the autoCommit and Transaction Isolation
- online = checkDataSource(serverConfig);
+ if (!TenantMode.DB.equals(serverConfig.getTenantMode())) {
+ setDataSource(serverConfig);
+ // check the autoCommit and Transaction Isolation
+ online = checkDataSource(serverConfig);
+ }
}
// determine database platform (Oracle etc)
@@ -116,11 +119,11 @@ public class DefaultContainer implements SpiContainer {
// executor and l2 caching service setup early (used during server construction)
SpiBackgroundExecutor executor = createBackgroundExecutor(serverConfig);
- ServerCacheManager cacheManager = getCacheManager(online, serverConfig, executor);
+ SpiCacheManager cacheManager = getCacheManager(online, serverConfig, executor);
InternalConfiguration c = new InternalConfiguration(clusterManager, cacheManager, executor, serverConfig, bootupClasses);
- DefaultServer server = new DefaultServer(c, cacheManager);
+ DefaultServer server = new DefaultServer(c, c.cache());
// generate and run DDL if required
// if there are any other tasks requiring action in their plugins, do them as well
@@ -146,18 +149,13 @@ public class DefaultContainer implements SpiContainer {
/**
* Create and return the CacheManager.
*/
- private ServerCacheManager getCacheManager(boolean online, ServerConfig serverConfig, BackgroundExecutor executor) {
+ private SpiCacheManager getCacheManager(boolean online, ServerConfig serverConfig, BackgroundExecutor executor) {
if (!online || serverConfig.isDisableL2Cache()) {
// use local only L2 cache implementation as placeholder
return new DefaultServerCacheManager();
}
- ServerCacheManager serverCacheManager = serverConfig.getServerCacheManager();
- if (serverCacheManager != null) {
- return serverCacheManager;
- }
-
// reasonable default settings are for a cache per bean type
ServerCacheOptions beanOptions = new ServerCacheOptions();
beanOptions.setMaxSize(serverConfig.getCacheMaxSize());
@@ -187,7 +185,7 @@ public class DefaultContainer implements SpiContainer {
}
ServerCacheFactory factory = plugin.create(serverConfig, executor);
- return new DefaultServerCacheManager(localL2Caching, factory, beanOptions, queryOptions);
+ return new DefaultServerCacheManager(localL2Caching, serverConfig.getCurrentTenantProvider(), factory, beanOptions, queryOptions);
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
index f6e548ba6..fc4b47149 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
@@ -9,9 +9,11 @@ import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.bean.PersistenceContext.WithOption;
import com.avaje.ebean.cache.ServerCacheManager;
+import com.avaje.ebean.config.CurrentTenantProvider;
import com.avaje.ebean.config.DbMigrationConfig;
import com.avaje.ebean.config.EncryptKeyManager;
import com.avaje.ebean.config.ServerConfig;
+import com.avaje.ebean.config.TenantMode;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.dbmigration.DdlGenerator;
import com.avaje.ebean.event.BeanPersistController;
@@ -155,6 +157,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final MetaInfoManager metaInfoManager;
+ private final CurrentTenantProvider currentTenantProvider;
+
/**
* The default PersistenceContextScope used if it is not explicitly set on a query.
*/
@@ -205,6 +209,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.expressionFactory = config.getExpressionFactory();
this.encryptKeyManager = serverConfig.getEncryptKeyManager();
this.defaultPersistenceContextScope = serverConfig.getPersistenceContextScope();
+ this.currentTenantProvider = serverConfig.getCurrentTenantProvider();
this.beanDescriptorManager = config.getBeanDescriptorManager();
beanDescriptorManager.setEbeanServer(this);
@@ -282,6 +287,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return queryBatchSize;
}
+ @Override
+ public Object currentTenantId() {
+ return currentTenantProvider.currentId();
+ }
+
public ServerConfig getServerConfig() {
return serverConfig;
}
@@ -346,16 +356,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Start any services after registering with the ClusterManager.
*/
public void start() {
- DbMigrationConfig migrationConfig = serverConfig.getMigrationConfig();
- if (migrationConfig != null) {
+ if (!TenantMode.DB.equals(serverConfig.getTenantMode())) {
+ DbMigrationConfig migrationConfig = serverConfig.getMigrationConfig();
migrationConfig.generateOnStart(this);
-
- if (migrationConfig.isRunMigration()) {
- // classLoader used to load resources
- ClassLoader classLoader = serverConfig.getClassLoadConfig().getClassLoader();
- MigrationRunner runner = migrationConfig.createRunner(classLoader);
- runner.run(serverConfig.getDataSource());
- }
+ serverConfig.runDbMigration(serverConfig.getDataSource());
}
}
@@ -408,7 +412,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
for (Plugin plugin : serverPlugins) {
try {
plugin.shutdown();
- } catch (Throwable e) {
+ } catch (Exception e) {
logger.error("Error when shutting down plugin", e);
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
index 83d931b79..4592d88f8 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
@@ -18,6 +18,8 @@ import com.avaje.ebeaninternal.api.SpiBackgroundExecutor;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.server.autotune.AutoTuneService;
import com.avaje.ebeaninternal.server.autotune.service.AutoTuneServiceFactory;
+import com.avaje.ebeaninternal.server.cache.DefaultCacheAdapter;
+import com.avaje.ebeaninternal.server.cache.SpiCacheManager;
import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogListener;
import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogPrepare;
import com.avaje.ebeaninternal.server.changelog.DefaultChangeLogRegister;
@@ -42,12 +44,14 @@ import com.avaje.ebeaninternal.server.readaudit.DefaultReadAuditLogger;
import com.avaje.ebeaninternal.server.readaudit.DefaultReadAuditPrepare;
import com.avaje.ebeaninternal.server.text.json.DJsonContext;
import com.avaje.ebeaninternal.server.transaction.AutoCommitTransactionManager;
+import com.avaje.ebeaninternal.server.transaction.DataSourceSupplier;
import com.avaje.ebeaninternal.server.transaction.DefaultTransactionScopeManager;
import com.avaje.ebeaninternal.server.transaction.DocStoreTransactionManager;
import com.avaje.ebeaninternal.server.transaction.ExplicitTransactionManager;
import com.avaje.ebeaninternal.server.transaction.ExternalTransactionScopeManager;
import com.avaje.ebeaninternal.server.transaction.JtaTransactionManager;
import com.avaje.ebeaninternal.server.transaction.TransactionManager;
+import com.avaje.ebeaninternal.server.transaction.TransactionManagerOptions;
import com.avaje.ebeaninternal.server.transaction.TransactionScopeManager;
import com.avaje.ebeaninternal.server.type.DefaultTypeManager;
import com.avaje.ebeaninternal.server.type.TypeManager;
@@ -96,7 +100,7 @@ public class InternalConfiguration {
private final ClusterManager clusterManager;
- private final ServerCacheManager cacheManager;
+ private final SpiCacheManager cacheManager;
private final ExpressionFactory expressionFactory;
@@ -112,7 +116,7 @@ public class InternalConfiguration {
private final List plugins = new ArrayList<>();
public InternalConfiguration(ClusterManager clusterManager,
- ServerCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
+ SpiCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses) {
this.docStoreFactory = initDocStoreFactory(serverConfig.service(DocStoreFactory.class));
@@ -275,7 +279,7 @@ public class InternalConfiguration {
return new DefaultPersister(server, binder, beanDescriptorManager);
}
- public ServerCacheManager getCacheManager() {
+ public SpiCacheManager getCacheManager() {
return cacheManager;
}
@@ -340,16 +344,35 @@ public class InternalConfiguration {
public TransactionManager createTransactionManager(DocStoreUpdateProcessor indexUpdateProcessor) {
boolean localL2 = cacheManager.isLocalL2Caching();
+
+ TransactionManagerOptions options =
+ new TransactionManagerOptions(localL2, serverConfig, clusterManager, backgroundExecutor,
+ indexUpdateProcessor, beanDescriptorManager, dataSource());
+
if (serverConfig.isExplicitTransactionBeginMode()) {
- return new ExplicitTransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager);
+ return new ExplicitTransactionManager(options);
}
if (isAutoCommitMode()) {
- return new AutoCommitTransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager);
+ return new AutoCommitTransactionManager(options);
}
if (serverConfig.isDocStoreOnly()) {
- return new DocStoreTransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager);
+ return new DocStoreTransactionManager(options);
+ }
+ return new TransactionManager(options);
+ }
+
+ /**
+ * Return the DataSource supplier based on the tenancy mode.
+ */
+ private DataSourceSupplier dataSource() {
+ switch (serverConfig.getTenantMode()) {
+ case DB:
+ return new MultiTenantDbSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getTenantDataSourceProvider());
+ case SCHEMA:
+ return new MultiTenantDbSchemaSupplier(serverConfig.getCurrentTenantProvider(), serverConfig.getDataSource(), serverConfig.getTenantSchemaProvider());
+ default:
+ return new SimpleDataSourceProvider(serverConfig.getDataSource());
}
- return new TransactionManager(localL2, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, beanDescriptorManager);
}
/**
@@ -401,4 +424,8 @@ public class InternalConfiguration {
public DataTimeZone getDataTimeZone() {
return dataTimeZone;
}
+
+ public ServerCacheManager cache() {
+ return new DefaultCacheAdapter(cacheManager);
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/MultiTenantDbSchemaSupplier.java b/src/main/java/com/avaje/ebeaninternal/server/core/MultiTenantDbSchemaSupplier.java
new file mode 100644
index 000000000..cd23b034a
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/MultiTenantDbSchemaSupplier.java
@@ -0,0 +1,111 @@
+package com.avaje.ebeaninternal.server.core;
+
+import com.avaje.ebean.config.CurrentTenantProvider;
+import com.avaje.ebean.config.TenantSchemaProvider;
+import com.avaje.ebeaninternal.server.transaction.DataSourceSupplier;
+import org.avaje.datasource.DataSourcePool;
+
+import javax.sql.DataSource;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.logging.Logger;
+
+/**
+ * DataSource supplier that changes DB schema based on current Tenant Id.
+ */
+class MultiTenantDbSchemaSupplier implements DataSourceSupplier {
+
+ private final CurrentTenantProvider tenantProvider;
+
+ private final DataSource dataSource;
+
+ private final TenantSchemaProvider schemaProvider;
+
+ private final SchemaDataSource schemaDataSource;
+
+ MultiTenantDbSchemaSupplier(CurrentTenantProvider tenantProvider, DataSource dataSource, TenantSchemaProvider schemaProvider) {
+ this.tenantProvider = tenantProvider;
+ this.dataSource = dataSource;
+ this.schemaProvider = schemaProvider;
+ this.schemaDataSource = new SchemaDataSource();
+ }
+
+ @Override
+ public DataSource getDataSource() {
+ return schemaDataSource;
+ }
+
+ @Override
+ public void shutdown(boolean deregisterDriver) {
+ if (dataSource instanceof DataSourcePool) {
+ ((DataSourcePool) dataSource).shutdown(deregisterDriver);
+ }
+ }
+
+ /**
+ * Returns the DB schema for the current user Tenant Id.
+ */
+ private String tenantSchema() {
+ return schemaProvider.schema(tenantProvider.currentId());
+ }
+
+ private class SchemaDataSource implements DataSource {
+
+ SchemaDataSource() {
+ }
+
+ /**
+ * Return the connection with the appropriate DB schema set.
+ */
+ @Override
+ public Connection getConnection() throws SQLException {
+
+ Connection connection = dataSource.getConnection();
+ connection.setSchema(tenantSchema());
+ return connection;
+ }
+
+ @Override
+ public Connection getConnection(String username, String password) throws SQLException {
+ return dataSource.getConnection(username, password);
+ }
+
+ @Override
+ public T unwrap(Class iface) throws SQLException {
+ return dataSource.unwrap(iface);
+ }
+
+ @Override
+ public boolean isWrapperFor(Class> iface) throws SQLException {
+ return dataSource.isWrapperFor(iface);
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException {
+ return dataSource.getLogWriter();
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter out) throws SQLException {
+ dataSource.setLogWriter(out);
+ }
+
+ @Override
+ public void setLoginTimeout(int seconds) throws SQLException {
+ dataSource.setLoginTimeout(seconds);
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException {
+ return dataSource.getLoginTimeout();
+ }
+
+ @Override
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+ return dataSource.getParentLogger();
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/MultiTenantDbSupplier.java b/src/main/java/com/avaje/ebeaninternal/server/core/MultiTenantDbSupplier.java
new file mode 100644
index 000000000..7f8d377fc
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/MultiTenantDbSupplier.java
@@ -0,0 +1,32 @@
+package com.avaje.ebeaninternal.server.core;
+
+import com.avaje.ebean.config.CurrentTenantProvider;
+import com.avaje.ebean.config.TenantDataSourceProvider;
+import com.avaje.ebeaninternal.server.transaction.DataSourceSupplier;
+
+import javax.sql.DataSource;
+
+/**
+ * DataSource supplier based on DataSource per Tenant.
+ */
+class MultiTenantDbSupplier implements DataSourceSupplier {
+
+ private final CurrentTenantProvider tenantProvider;
+
+ private final TenantDataSourceProvider dataSourceProvider;
+
+ MultiTenantDbSupplier(CurrentTenantProvider tenantProvider, TenantDataSourceProvider dataSourceProvider) {
+ this.tenantProvider = tenantProvider;
+ this.dataSourceProvider = dataSourceProvider;
+ }
+
+ @Override
+ public DataSource getDataSource() {
+ return dataSourceProvider.dataSource(tenantProvider.currentId());
+ }
+
+ @Override
+ public void shutdown(boolean deregisterDriver) {
+ dataSourceProvider.shutdown(deregisterDriver);
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
index 0d78c41d3..f9a1160db 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
@@ -155,9 +155,7 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe
* Prepare the query and calculate the query plan key.
*/
public void prepareQuery() {
- if (beanDescriptor.isDocStoreOnly()) {
- query.setUseDocStore(true);
- }
+ beanDescriptor.prepareQuery(query);
adapterPreQuery();
this.secondaryQueries = query.convertJoins();
this.queryPlanKey = query.prepare(this);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
index 49e78a971..a67c0562e 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java
@@ -1072,6 +1072,10 @@ public final class PersistRequestBean extends PersistRequest implements BeanP
}
public void executeInsert() {
+ Object tenantId = transaction.getTenantId();
+ if (tenantId != null) {
+ beanDescriptor.setTenantId(entityBean, tenantId);
+ }
if (controller == null || controller.preInsert(this)) {
beanManager.getBeanPersister().insert(this);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/SimpleDataSourceProvider.java b/src/main/java/com/avaje/ebeaninternal/server/core/SimpleDataSourceProvider.java
new file mode 100644
index 000000000..c1ca76ee7
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/SimpleDataSourceProvider.java
@@ -0,0 +1,30 @@
+package com.avaje.ebeaninternal.server.core;
+
+import com.avaje.ebeaninternal.server.transaction.DataSourceSupplier;
+import org.avaje.datasource.DataSourcePool;
+
+import javax.sql.DataSource;
+
+/**
+ * Simple DataSource supplier when no multi-tenancy used.
+ */
+class SimpleDataSourceProvider implements DataSourceSupplier {
+
+ private final DataSource dataSource;
+
+ SimpleDataSourceProvider(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ @Override
+ public DataSource getDataSource() {
+ return dataSource;
+ }
+
+ @Override
+ public void shutdown(boolean deregisterDriver) {
+ if (dataSource instanceof DataSourcePool){
+ ((DataSourcePool) dataSource).shutdown(deregisterDriver);
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
index d11fe0494..8229353c7 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -215,6 +215,8 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
private final BeanProperty unmappedJson;
+ private final BeanProperty tenant;
+
private final BeanProperty draft;
private final BeanProperty draftDirty;
@@ -465,6 +467,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
this.idProperty = listHelper.getId();
this.versionProperty = listHelper.getVersionProperty();
this.unmappedJson = listHelper.getUnmappedJson();
+ this.tenant = listHelper.getTenant();
this.draft = listHelper.getDraft();
this.draftDirty = listHelper.getDraftDirty();
this.propMap = listHelper.getPropertyMap();
@@ -1076,9 +1079,25 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
return draftDirty;
}
+ /**
+ * Prepare the query for multi-tenancy check for document store only use.
+ */
+ public void prepareQuery(SpiQuery query) {
+ if (tenant != null) {
+ Object tenantId = ebeanServer.currentTenantId();
+ if (tenantId != null) {
+ query.where().eq(tenant.getName(), tenantId);
+ }
+ }
+ if (isDocStoreOnly()) {
+ query.setUseDocStore(true);
+ }
+ }
+
/**
* Return true if there is currently bean caching for this type of bean.
*/
+ @Override
public boolean isBeanCaching() {
return cacheHelp.isBeanCaching();
}
@@ -1086,6 +1105,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
/**
* Return true if there is query caching for this type of bean.
*/
+ @Override
public boolean isQueryCaching() {
return cacheHelp.isQueryCaching();
}
@@ -1105,10 +1125,16 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
return cacheHelp.isCacheNotify(type);
}
+ @Override
+ public void clearBeanCache() {
+ cacheHelp.beanCacheClear();
+ }
+
/**
* Clear the query cache.
*/
- public void queryCacheClear() {
+ @Override
+ public void clearQueryCache() {
cacheHelp.queryCacheClear();
}
@@ -2476,6 +2502,15 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
}
}
+ /**
+ * Set the Tenant Id value to the bean.
+ */
+ public void setTenantId(EntityBean entityBean, Object tenantId) {
+ if (tenantId != null) {
+ tenant.setValue(entityBean, tenantId);
+ }
+ }
+
/**
* Set the draft to true for this entity bean instance.
* This bean is being loaded via asDraft() query.
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java
index 888ad0d71..fc0025e33 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java
@@ -5,7 +5,6 @@ import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.cache.ServerCache;
-import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
import com.avaje.ebeaninternal.server.cache.CacheChangeSet;
@@ -13,6 +12,7 @@ import com.avaje.ebeaninternal.server.cache.CachedBeanData;
import com.avaje.ebeaninternal.server.cache.CachedBeanDataFromBean;
import com.avaje.ebeaninternal.server.cache.CachedBeanDataToBean;
import com.avaje.ebeaninternal.server.cache.CachedManyIds;
+import com.avaje.ebeaninternal.server.cache.SpiCacheManager;
import com.avaje.ebeaninternal.server.core.CacheOptions;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
@@ -26,6 +26,7 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
/**
* Helper for BeanDescriptor that manages the bean, query and collection caches.
@@ -43,7 +44,7 @@ final class BeanDescriptorCacheHelp {
private final BeanDescriptor desc;
- private final ServerCacheManager cacheManager;
+ private final SpiCacheManager cacheManager;
private final CacheOptions cacheOptions;
@@ -59,9 +60,9 @@ final class BeanDescriptorCacheHelp {
private final BeanPropertyAssocOne>[] propertiesOneImported;
private final String naturalKeyProperty;
- private final ServerCache beanCache;
- private final ServerCache naturalKeyCache;
- private final ServerCache queryCache;
+ private final Supplier beanCache;
+ private final Supplier naturalKeyCache;
+ private final Supplier queryCache;
/**
* Set to true if all persist changes need to notify the cache.
@@ -73,7 +74,7 @@ final class BeanDescriptorCacheHelp {
*/
private boolean cacheNotifyOnDelete;
- BeanDescriptorCacheHelp(BeanDescriptor desc, ServerCacheManager cacheManager, CacheOptions cacheOptions,
+ BeanDescriptorCacheHelp(BeanDescriptor desc, SpiCacheManager cacheManager, CacheOptions cacheOptions,
boolean cacheSharableBeans, BeanPropertyAssocOne>[] propertiesOneImported) {
this.desc = desc;
@@ -107,7 +108,7 @@ final class BeanDescriptorCacheHelp {
/**
* Derive the cache notify flags.
*/
- public void deriveNotifyFlags() {
+ void deriveNotifyFlags() {
cacheNotifyOnAll = (beanCache != null || queryCache != null);
cacheNotifyOnDelete = !cacheNotifyOnAll && isNotifyOnDeletes();
@@ -167,7 +168,7 @@ final class BeanDescriptorCacheHelp {
if (queryLog.isDebugEnabled()) {
queryLog.debug(" CLEAR {}", cacheName);
}
- queryCache.clear();
+ queryCache.get().clear();
}
}
@@ -188,7 +189,7 @@ final class BeanDescriptorCacheHelp {
if (queryCache == null) {
throw new IllegalStateException("No query cache enabled on " + desc + ". Need explicit @Cache(enableQueryCache=true)");
}
- BeanCollection list = (BeanCollection) queryCache.get(id);
+ BeanCollection list = (BeanCollection) queryCache.get().get(id);
if (queryLog.isDebugEnabled()) {
if (list == null) {
queryLog.debug(" GET {}({}) - cache miss", cacheName, id);
@@ -209,12 +210,12 @@ final class BeanDescriptorCacheHelp {
if (queryLog.isDebugEnabled()) {
queryLog.debug(" PUT {}({})", cacheName, id);
}
- queryCache.put(id, query);
+ queryCache.get().put(id, query);
}
void manyPropRemove(String propertyName, Object parentId) {
- ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName);
+ ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName).get();
if (manyLog.isTraceEnabled()) {
manyLog.trace(" REMOVE {}({}).{}", cacheName, parentId, propertyName);
}
@@ -222,7 +223,7 @@ final class BeanDescriptorCacheHelp {
}
void manyPropClear(String propertyName) {
- ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName);
+ ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName).get();
if (manyLog.isDebugEnabled()) {
manyLog.debug(" CLEAR {}(*).{} ", cacheName, propertyName);
}
@@ -233,7 +234,7 @@ final class BeanDescriptorCacheHelp {
* Return the CachedManyIds for a given bean many property. Returns null if not in the cache.
*/
private CachedManyIds manyPropGet(Object parentId, String propertyName) {
- ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName);
+ ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName).get();
CachedManyIds entry = (CachedManyIds) collectionIdsCache.get(parentId);
if (entry == null) {
if (manyLog.isTraceEnabled()) {
@@ -284,7 +285,7 @@ final class BeanDescriptorCacheHelp {
void cachePutManyIds(Object parentId, String manyName, CachedManyIds entry) {
- ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, manyName);
+ ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, manyName).get();
if (manyLog.isDebugEnabled()) {
manyLog.debug(" PUT {}({}).{} - ids:{}", cacheName, parentId, manyName, entry);
}
@@ -324,7 +325,7 @@ final class BeanDescriptorCacheHelp {
}
// try to lookup the id using the natural key
- Object id = naturalKeyCache.get(keyBindParam.getValue());
+ Object id = naturalKeyCache.get().get(keyBindParam.getValue());
if (natLog.isTraceEnabled()) {
natLog.trace(" LOOKUP {}({}) - id:{}", cacheName, keyBindParam.getValue(), id);
}
@@ -363,18 +364,18 @@ final class BeanDescriptorCacheHelp {
if (beanCache == null) {
throw new IllegalStateException("No bean cache enabled for " + desc + ". Add the @Cache annotation.");
}
- return beanCache;
+ return beanCache.get();
}
/**
* Clear the bean cache.
*/
- private void beanCacheClear() {
+ void beanCacheClear() {
if (beanCache != null) {
if (beanLog.isDebugEnabled()) {
beanLog.debug(" CLEAR {}", cacheName);
}
- beanCache.clear();
+ beanCache.get().clear();
}
}
@@ -413,7 +414,7 @@ final class BeanDescriptorCacheHelp {
if (natLog.isDebugEnabled()) {
natLog.debug(" PUT {}({}, {})", cacheName, naturalKey, id);
}
- naturalKeyCache.put(naturalKey, id);
+ naturalKeyCache.get().put(naturalKey, id);
}
}
}
@@ -542,7 +543,7 @@ final class BeanDescriptorCacheHelp {
if (beanLog.isDebugEnabled()) {
beanLog.debug(" REMOVE {}({})", cacheName, id);
}
- beanCache.remove(id);
+ beanCache.get().remove(id);
}
for (BeanPropertyAssocOne> aPropertiesOneImported : propertiesOneImported) {
aPropertiesOneImported.cacheClear();
@@ -675,7 +676,7 @@ final class BeanDescriptorCacheHelp {
void cacheNaturalKeyPut(Object id, Object newKey) {
if (newKey != null) {
- naturalKeyCache.put(newKey, id);
+ naturalKeyCache.get().put(newKey, id);
}
}
@@ -710,7 +711,7 @@ final class BeanDescriptorCacheHelp {
if (natLog.isDebugEnabled()) {
natLog.debug(".. update {} REMOVE({}) - old key for ({})", cacheName, oldKey, id);
}
- naturalKeyCache.remove(oldKey);
+ naturalKeyCache.get().remove(oldKey);
}
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java
index e70fe2ced..378df82f6 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java
@@ -5,7 +5,6 @@ import com.avaje.ebean.Model;
import com.avaje.ebean.RawSqlBuilder;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.EntityBean;
-import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.EncryptKey;
import com.avaje.ebean.config.EncryptKeyManager;
import com.avaje.ebean.config.NamingConvention;
@@ -23,6 +22,7 @@ import com.avaje.ebean.plugin.BeanType;
import com.avaje.ebeaninternal.api.ConcurrencyMode;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.TransactionEventTable;
+import com.avaje.ebeaninternal.server.cache.SpiCacheManager;
import com.avaje.ebeaninternal.server.core.InternString;
import com.avaje.ebeaninternal.server.core.InternalConfiguration;
import com.avaje.ebeaninternal.server.core.Message;
@@ -159,7 +159,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final DatabasePlatform databasePlatform;
- private final ServerCacheManager cacheManager;
+ private final SpiCacheManager cacheManager;
private final BackgroundExecutor backgroundExecutor;
@@ -278,7 +278,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return serverName;
}
- public ServerCacheManager getCacheManager() {
+ public SpiCacheManager getCacheManager() {
return cacheManager;
}
@@ -457,7 +457,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
List> list = tableToViewDescMap.get(depTable.toLowerCase());
if (list != null) {
for (BeanDescriptor> aList : list) {
- aList.queryCacheClear();
+ aList.clearQueryCache();
}
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java
index 80b65b6f9..6fee345f9 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorMap.java
@@ -1,8 +1,8 @@
package com.avaje.ebeaninternal.server.deploy;
-import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.EncryptKey;
import com.avaje.ebean.config.ServerConfig;
+import com.avaje.ebeaninternal.server.cache.SpiCacheManager;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import com.avaje.ebeanservice.docstore.api.DocStoreBeanAdapter;
@@ -28,7 +28,7 @@ public interface BeanDescriptorMap {
/**
* Return the Cache Manager.
*/
- ServerCacheManager getCacheManager();
+ SpiCacheManager getCacheManager();
/**
* Return the BeanDescriptor for a given class.
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
index fa8b70bc5..00efd2115 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
@@ -244,6 +244,7 @@ public class BeanProperty implements ElPropertyValue, Property {
final boolean jsonSerialize;
final boolean jsonDeserialize;
final boolean unmappedJson;
+ final boolean tenantId;
final boolean draft;
@@ -280,6 +281,7 @@ public class BeanProperty implements ElPropertyValue, Property {
this.dbUpdatable = deploy.isDbUpdateable();
this.excludedFromHistory = deploy.isExcludedFromHistory();
this.unmappedJson = deploy.isUnmappedJson();
+ this.tenantId = deploy.isTenantId();
this.draft = deploy.isDraft();
this.draftDirty = deploy.isDraftDirty();
this.draftOnly = deploy.isDraftOnly();
@@ -379,6 +381,7 @@ public class BeanProperty implements ElPropertyValue, Property {
this.aggregation = null;
this.excludedFromHistory = source.excludedFromHistory;
+ this.tenantId = source.tenantId;
this.draft = source.draft;
this.draftDirty = source.draftDirty;
this.draftOnly = source.draftOnly;
@@ -1210,6 +1213,13 @@ public class BeanProperty implements ElPropertyValue, Property {
return unmappedJson;
}
+ /**
+ * Return true if this is the tenantId property (for multi-tenant partitioning).
+ */
+ public boolean isTenantId() {
+ return tenantId;
+ }
+
/**
* Return true if this property only exists on the draft table.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
index 9cb0532c0..cc429768b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
@@ -197,6 +197,8 @@ public class DeployBeanProperty {
private boolean excludedFromHistory;
+ private boolean tenantId;
+
private boolean draft;
private boolean draftOnly;
private boolean draftDirty;
@@ -977,4 +979,14 @@ public class DeployBeanProperty {
return dbColumnDefault;
}
+ public void setTenantId() {
+ this.tenantId = true;
+ this.nullable = false;
+ this.dbInsertable = true;
+ this.dbUpdateable = false;
+ }
+
+ public boolean isTenantId() {
+ return tenantId;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java
index cdee51e94..041644af6 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java
@@ -33,6 +33,8 @@ public class DeployBeanPropertyLists {
private BeanProperty draftDirty;
+ private BeanProperty tenant;
+
private final BeanDescriptor> desc;
private final LinkedHashMap propertyMap;
@@ -214,6 +216,8 @@ public class DeployBeanPropertyLists {
}
} else if (prop.isDraftDirty()) {
draftDirty = prop;
+ } else if (prop.isTenantId()) {
+ tenant = prop;
}
if (prop instanceof BeanPropertyCompound) {
baseCompound.add((BeanPropertyCompound) prop);
@@ -348,6 +352,10 @@ public class DeployBeanPropertyLists {
return null;
}
+ public BeanProperty getTenant() {
+ return tenant;
+ }
+
/**
* Mode used to determine which BeanPropertyAssoc to include.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java
index 92334ace4..86cc0f4b8 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java
@@ -165,6 +165,9 @@ public class AnnotationFields extends AnnotationParser {
util.setLobType(prop);
}
+ if (get(prop, TenantId.class) != null) {
+ prop.setTenantId();
+ }
if (get(prop, Draft.class) != null) {
prop.setDraft();
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java
index 934d5fabc..a00d1df54 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/AutoCommitTransactionManager.java
@@ -1,11 +1,6 @@
package com.avaje.ebeaninternal.server.transaction;
-import com.avaje.ebean.BackgroundExecutor;
-import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiTransaction;
-import com.avaje.ebeaninternal.server.cluster.ClusterManager;
-import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
-import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import java.sql.Connection;
@@ -16,10 +11,8 @@ import java.sql.Connection;
*/
public class AutoCommitTransactionManager extends TransactionManager {
- public AutoCommitTransactionManager(boolean localL2Caching, ServerConfig serverConfig, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
- DocStoreUpdateProcessor indexUpdateProcessor, BeanDescriptorManager descMgr) {
-
- super(localL2Caching, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, descMgr);
+ public AutoCommitTransactionManager(TransactionManagerOptions options) {
+ super(options);
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java
index 4bc583d9b..6a109fe7e 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java
@@ -219,7 +219,7 @@ public class BeanPersistIds {
void notifyCacheAndListener() {
// any change invalidates the query cache
- beanDescriptor.queryCacheClear();
+ beanDescriptor.clearQueryCache();
if (updateIds != null) {
for (Object id : updateIds) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DataSourceSupplier.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DataSourceSupplier.java
new file mode 100644
index 000000000..5989cb439
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DataSourceSupplier.java
@@ -0,0 +1,25 @@
+package com.avaje.ebeaninternal.server.transaction;
+
+import javax.sql.DataSource;
+
+/**
+ * Supply the DataSource to the transaction manager.
+ *
+ * Implementations can support multi-tenancy via DB or SCHEMA.
+ *
+ */
+public interface DataSourceSupplier {
+
+ /**
+ * Return the DataSource to use for the current request.
+ *
+ * This should take into account multi-tenancy and the current tenantId.
+ *
+ */
+ DataSource getDataSource();
+
+ /**
+ * Shutdown the datasource de-registering the JDBC driver if requested.
+ */
+ void shutdown(boolean deregisterDriver);
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DocStoreTransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DocStoreTransactionManager.java
index 98de8bebd..a361e36af 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/DocStoreTransactionManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DocStoreTransactionManager.java
@@ -1,11 +1,6 @@
package com.avaje.ebeaninternal.server.transaction;
-import com.avaje.ebean.BackgroundExecutor;
-import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebeaninternal.api.SpiTransaction;
-import com.avaje.ebeaninternal.server.cluster.ClusterManager;
-import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
-import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import java.sql.Connection;
@@ -16,14 +11,11 @@ import java.sql.Connection;
*/
public class DocStoreTransactionManager extends TransactionManager {
- private final String prefix = "";
-
/**
* Create the TransactionManager
*/
- public DocStoreTransactionManager(boolean localL2Caching, ServerConfig config, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
- DocStoreUpdateProcessor docStoreUpdateProcessor, BeanDescriptorManager descMgr) {
- super(localL2Caching, config, clusterManager, backgroundExecutor, docStoreUpdateProcessor, descMgr);
+ public DocStoreTransactionManager(TransactionManagerOptions options) {
+ super(options);
}
@Override
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java
index 3875f5a8c..9dde0d7fb 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/ExplicitTransactionManager.java
@@ -1,14 +1,8 @@
package com.avaje.ebeaninternal.server.transaction;
-import com.avaje.ebean.BackgroundExecutor;
-import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebeaninternal.api.SpiTransaction;
-import com.avaje.ebeaninternal.server.cluster.ClusterManager;
-import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
-import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
-import javax.sql.DataSource;
import java.sql.Connection;
/**
@@ -16,10 +10,8 @@ import java.sql.Connection;
*/
public class ExplicitTransactionManager extends TransactionManager {
- public ExplicitTransactionManager(boolean localL2Caching, ServerConfig serverConfig, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
- DocStoreUpdateProcessor indexUpdateProcessor, BeanDescriptorManager descMgr) {
-
- super(localL2Caching, serverConfig, clusterManager, backgroundExecutor, indexUpdateProcessor, descMgr);
+ public ExplicitTransactionManager(TransactionManagerOptions options) {
+ super(options);
}
/**
@@ -35,7 +27,7 @@ public class ExplicitTransactionManager extends TransactionManager {
* Override the initialise of OnQueryOnly with the intention not to use CLOSE with ExplicitJdbcTransaction.
*/
@Override
- protected DatabasePlatform.OnQueryOnly initOnQueryOnly(DatabasePlatform.OnQueryOnly dbPlatformOnQueryOnly, DataSource ds) {
+ protected DatabasePlatform.OnQueryOnly initOnQueryOnly(DatabasePlatform.OnQueryOnly dbPlatformOnQueryOnly) {
// first check for a system property 'override'
String systemPropertyValue = System.getProperty("ebean.transaction.onqueryonly");
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java
index a2d217847..1b9d04c0b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java
@@ -121,6 +121,8 @@ public class JdbcTransaction implements SpiTransaction {
protected String logPrefix;
+ private Object tenantId;
+
/**
* The depth used by batch processing to help the ordering of statements.
*/
@@ -811,6 +813,16 @@ public class JdbcTransaction implements SpiTransaction {
return id;
}
+ @Override
+ public void setTenantId(Object tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public Object getTenantId() {
+ return tenantId;
+ }
+
/**
* Return the underlying connection for internal use.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
index 89dbb3b16..985c4b0d7 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
@@ -1,8 +1,8 @@
package com.avaje.ebeaninternal.server.transaction;
import com.avaje.ebean.BackgroundExecutor;
+import com.avaje.ebean.config.CurrentTenantProvider;
import com.avaje.ebean.config.PersistBatch;
-import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
import com.avaje.ebean.event.changelog.ChangeLogListener;
import com.avaje.ebean.event.changelog.ChangeLogPrepare;
@@ -16,7 +16,6 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeanservice.docstore.api.DocStoreTransaction;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
-import org.avaje.datasource.DataSourcePool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,7 +57,7 @@ public class TransactionManager {
/**
* The dataSource of connections.
*/
- protected final DataSource dataSource;
+ protected final DataSourceSupplier dataSourceSupplier;
/**
* Flag to indicate the default Isolation is READ COMMITTED. This enables us
@@ -107,37 +106,38 @@ public class TransactionManager {
private final boolean skipCacheAfterWrite;
+ private final CurrentTenantProvider currentTenantProvider;
+
/**
* Create the TransactionManager
*/
- public TransactionManager(boolean localL2Caching, ServerConfig config, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
- DocStoreUpdateProcessor docStoreUpdateProcessor, BeanDescriptorManager descMgr) {
-
- this.skipCacheAfterWrite = config.isSkipCacheAfterWrite();
- this.localL2Caching = localL2Caching;
- this.persistBatch = config.getPersistBatch();
- this.persistBatchOnCascade = config.appliedPersistBatchOnCascade();
- this.beanDescriptorManager = descMgr;
- this.viewInvalidation = descMgr.requiresViewEntityCacheInvalidation();
- this.changeLogPrepare = descMgr.getChangeLogPrepare();
- this.changeLogListener = descMgr.getChangeLogListener();
- this.clusterManager = clusterManager;
- this.serverName = config.getName();
- this.backgroundExecutor = backgroundExecutor;
- this.dataSource = config.getDataSource();
- this.docStoreActive = config.getDocStoreConfig().isActive();
- this.docStoreUpdateProcessor = docStoreUpdateProcessor;
- this.bulkEventListenerMap = new BulkEventListenerMap(config.getBulkTableEventListeners());
+ public TransactionManager(TransactionManagerOptions options) {
+ this.skipCacheAfterWrite = options.config.isSkipCacheAfterWrite();
+ this.localL2Caching = options.localL2Caching;
+ this.persistBatch = options.config.getPersistBatch();
+ this.persistBatchOnCascade = options.config.appliedPersistBatchOnCascade();
+ this.beanDescriptorManager = options.descMgr;
+ this.viewInvalidation = options.descMgr.requiresViewEntityCacheInvalidation();
+ this.changeLogPrepare = options.descMgr.getChangeLogPrepare();
+ this.changeLogListener = options.descMgr.getChangeLogListener();
+ this.clusterManager = options.clusterManager;
+ this.serverName = options.config.getName();
+ this.backgroundExecutor = options.backgroundExecutor;
+ this.dataSourceSupplier = options.dataSourceSupplier;
+ this.docStoreActive = options.config.getDocStoreConfig().isActive();
+ this.docStoreUpdateProcessor = options.docStoreUpdateProcessor;
+ this.bulkEventListenerMap = new BulkEventListenerMap(options.config.getBulkTableEventListeners());
+ this.currentTenantProvider = options.config.getCurrentTenantProvider();
this.prefix = "";
this.externalTransPrefix = "e";
- this.onQueryOnly = initOnQueryOnly(config.getDatabasePlatform().getOnQueryOnly(), dataSource);
+ this.onQueryOnly = initOnQueryOnly(options.config.getDatabasePlatform().getOnQueryOnly());
}
public void shutdown(boolean shutdownDataSource, boolean deregisterDriver) {
- if (shutdownDataSource && (dataSource instanceof DataSourcePool)) {
- ((DataSourcePool) dataSource).shutdown(deregisterDriver);
+ if (shutdownDataSource) {
+ dataSourceSupplier.shutdown(deregisterDriver);
}
}
@@ -181,7 +181,7 @@ public class TransactionManager {
* just for queries do need to be committed or rollback after the query.
*
*/
- protected OnQueryOnly initOnQueryOnly(OnQueryOnly dbPlatformOnQueryOnly, DataSource ds) {
+ protected OnQueryOnly initOnQueryOnly(OnQueryOnly dbPlatformOnQueryOnly) {
// first check for a system property 'override'
String systemPropertyValue = System.getProperty("ebean.transaction.onqueryonly");
@@ -198,7 +198,7 @@ public class TransactionManager {
}
public DataSource getDataSource() {
- return dataSource;
+ return dataSourceSupplier.getDataSource();
}
/**
@@ -235,10 +235,10 @@ public class TransactionManager {
public SpiTransaction createTransaction(boolean explicit, int isolationLevel) {
Connection c = null;
try {
- c = dataSource.getConnection();
+ c = getDataSource().getConnection();
long id = transactionCounter.incrementAndGet();
- SpiTransaction t = createTransaction(explicit, c, id);
+ SpiTransaction t = init(createTransaction(explicit, c, id));
if (isolationLevel > -1) {
c.setTransactionIsolation(isolationLevel);
}
@@ -262,13 +262,19 @@ public class TransactionManager {
}
}
+ private SpiTransaction init(SpiTransaction transaction) {
+ if (currentTenantProvider != null) {
+ transaction.setTenantId(currentTenantProvider.currentId());
+ }
+ return transaction;
+ }
+
public SpiTransaction createQueryTransaction() {
Connection c = null;
try {
- c = dataSource.getConnection();
+ c = getDataSource().getConnection();
long id = transactionCounter.incrementAndGet();
-
- return createTransaction(false, c, id);
+ return init(createTransaction(false, c, id));
} catch (PersistenceException ex) {
// close the connection and re-throw the exception
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManagerOptions.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManagerOptions.java
new file mode 100644
index 000000000..8dad95f74
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManagerOptions.java
@@ -0,0 +1,36 @@
+package com.avaje.ebeaninternal.server.transaction;
+
+import com.avaje.ebean.BackgroundExecutor;
+import com.avaje.ebean.config.ServerConfig;
+import com.avaje.ebeaninternal.server.cluster.ClusterManager;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
+import com.avaje.ebeanservice.docstore.api.DocStoreUpdateProcessor;
+
+/**
+ * Objects supplied for constructing the TransactionManager.
+ */
+public class TransactionManagerOptions {
+
+ final boolean localL2Caching;
+ final ServerConfig config;
+ final ClusterManager clusterManager;
+ final BackgroundExecutor backgroundExecutor;
+
+ final DocStoreUpdateProcessor docStoreUpdateProcessor;
+ final BeanDescriptorManager descMgr;
+ final DataSourceSupplier dataSourceSupplier;
+
+
+ public TransactionManagerOptions(boolean localL2Caching, ServerConfig config, ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
+ DocStoreUpdateProcessor docStoreUpdateProcessor, BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier) {
+
+ this.localL2Caching = localL2Caching;
+ this.config = config;
+ this.clusterManager = clusterManager;
+ this.backgroundExecutor = backgroundExecutor;
+ this.docStoreUpdateProcessor = docStoreUpdateProcessor;
+ this.descMgr = descMgr;
+ this.dataSourceSupplier = dataSourceSupplier;
+ }
+
+}
diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
index 92418b571..c2abbc4f8 100644
--- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
+++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
@@ -49,6 +49,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
}
+ @Override
+ public Object currentTenantId() {
+ return null;
+ }
+
@Override
public DataTimeZone getDataTimeZone() {
return null;
diff --git a/src/test/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder_getCacheOptions_Test.java b/src/test/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder_getCacheOptions_Test.java
index 45f99062a..74526498b 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder_getCacheOptions_Test.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/cache/DefaultCacheHolder_getCacheOptions_Test.java
@@ -19,34 +19,34 @@ public class DefaultCacheHolder_getCacheOptions_Test {
defaultOptions.setMaxSize(10000);
defaultOptions.setMaxSecsToLive(120);
- this.cacheHolder = new DefaultCacheHolder(null, defaultOptions);
+ this.cacheHolder = new DefaultCacheHolder(null, defaultOptions, null);
}
@Test
public void beanOptions_when_set() {
- ServerCacheOptions options = cacheHolder.getCacheOptions(Article.class.getName(), ServerCacheType.BEAN);
+ ServerCacheOptions options = cacheHolder.getCacheOptions(Article.class, ServerCacheType.BEAN);
assertEquals(options.getMaxSecsToLive(), 45);
}
@Test
public void beanOptions_when_notSet_expect_default() {
- ServerCacheOptions options = cacheHolder.getCacheOptions(Order.class.getName(), ServerCacheType.BEAN);
+ ServerCacheOptions options = cacheHolder.getCacheOptions(Order.class, ServerCacheType.BEAN);
assertEquals(options.getMaxSecsToLive(), 120);
}
@Test
public void queryOptions_when_set() {
- ServerCacheOptions options = cacheHolder.getCacheOptions(Product.class.getName(), ServerCacheType.QUERY);
+ ServerCacheOptions options = cacheHolder.getCacheOptions(Product.class, ServerCacheType.QUERY);
assertEquals(options.getMaxSecsToLive(), 15);
}
@Test
public void queryOptions_when_notSet_expect_default() {
- ServerCacheOptions options = cacheHolder.getCacheOptions(Order.class.getName(), ServerCacheType.QUERY);
+ ServerCacheOptions options = cacheHolder.getCacheOptions(Order.class, ServerCacheType.QUERY);
assertEquals(options.getMaxSecsToLive(), 120);
}
}
diff --git a/src/test/java/com/avaje/tests/cache/TestCacheCollectionIds.java b/src/test/java/com/avaje/tests/cache/TestCacheCollectionIds.java
index 8272c3ab3..31fb3ee27 100644
--- a/src/test/java/com/avaje/tests/cache/TestCacheCollectionIds.java
+++ b/src/test/java/com/avaje/tests/cache/TestCacheCollectionIds.java
@@ -27,7 +27,6 @@ public class TestCacheCollectionIds extends BaseTestCase {
awaitL2Cache();
ServerCache custCache = cacheManager.getBeanCache(Customer.class);
- ServerCache contactCache = cacheManager.getBeanCache(Contact.class);
ServerCache custManyIdsCache = cacheManager.getCollectionIdsCache(Customer.class, "contacts");
// cacheManager.setCaching(Customer.class, true);
diff --git a/src/test/java/com/avaje/tests/cache/TestCacheReferenceLazyLoad.java b/src/test/java/com/avaje/tests/cache/TestCacheReferenceLazyLoad.java
deleted file mode 100644
index 5249a48f3..000000000
--- a/src/test/java/com/avaje/tests/cache/TestCacheReferenceLazyLoad.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.avaje.tests.cache;
-
-import com.avaje.ebean.BaseTestCase;
-import com.avaje.ebean.Ebean;
-import com.avaje.ebean.cache.ServerCacheManager;
-import com.avaje.tests.model.basic.Customer;
-import com.avaje.tests.model.basic.ResetBasicData;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class TestCacheReferenceLazyLoad extends BaseTestCase {
-
- @Test
- public void testLazyLoadOnRef() {
-
- ResetBasicData.reset();
-
- ServerCacheManager serverCacheManager = Ebean.getServerCacheManager();
- boolean originallyBeanCaching = serverCacheManager.isBeanCaching(Customer.class);
-
- Customer customer = Ebean.getReference(Customer.class, 1);
-
- // invoke lazy loading
- customer.getName();
-
- if (!originallyBeanCaching) {
- // the lazy loading shouldn't start the L2 bean cache
- boolean beanCachingAfter = serverCacheManager.isBeanCaching(Customer.class);
- Assert.assertFalse(beanCachingAfter);
- }
- }
-
-}
diff --git a/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java b/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java
index a6d4d4858..96e8aba5a 100644
--- a/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java
+++ b/src/test/java/com/avaje/tests/query/TestQueryFindIterate.java
@@ -154,10 +154,8 @@ public class TestQueryFindIterate extends BaseTestCase {
LoggedSqlCollector.start();
// make sure we don't hit the L2 cache for order shipments
- Ebean.getServerCacheManager().getBeanCache(Order.class).clear();
- Ebean.getServerCacheManager().getQueryCache(Order.class).clear();
- Ebean.getServerCacheManager().getBeanCache(OrderShipment.class).clear();
- Ebean.getServerCacheManager().getQueryCache(OrderShipment.class).clear();
+ Ebean.getServerCacheManager().clear(Order.class);
+ Ebean.getServerCacheManager().clear(OrderShipment.class);
Ebean.find(Order.class)
.setLazyLoadBatchSize(10)