From c0341458c3a1274b99c3a9291f1339ddaa3858b3 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 29 Nov 2016 01:34:45 +1300 Subject: [PATCH] #493 - ENH: Multi-tenant support --- .../com/avaje/ebean/cache/ServerCache.java | 7 - .../avaje/ebean/cache/ServerCacheManager.java | 5 - .../avaje/ebean/cache/ServerCacheType.java | 18 ++- .../ebean/config/CurrentTenantProvider.java | 12 ++ .../com/avaje/ebean/config/ServerConfig.java | 109 ++++++++++++--- .../config/TenantDataSourceProvider.java | 19 +++ .../com/avaje/ebean/config/TenantMode.java | 27 ++++ .../ebean/config/TenantSchemaProvider.java | 15 +++ .../java/com/avaje/ebean/plugin/BeanType.java | 20 +++ .../ebeaninternal/api/ScopedTransaction.java | 10 ++ .../ebeaninternal/api/SpiEbeanServer.java | 5 + .../ebeaninternal/api/SpiTransaction.java | 10 ++ .../server/cache/CacheChangeSet.java | 2 +- .../server/cache/DefaultCacheAdapter.java | 49 +++++++ .../server/cache/DefaultCacheHolder.java | 124 +++++++++++------- .../cache/DefaultServerCacheManager.java | 64 +++------ .../server/cache/SpiCacheManager.java | 46 +++++++ .../server/core/DefaultContainer.java | 24 ++-- .../server/core/DefaultServer.java | 24 ++-- .../server/core/InternalConfiguration.java | 41 +++++- .../core/MultiTenantDbSchemaSupplier.java | 111 ++++++++++++++++ .../server/core/MultiTenantDbSupplier.java | 32 +++++ .../server/core/OrmQueryRequest.java | 4 +- .../server/core/PersistRequestBean.java | 4 + .../server/core/SimpleDataSourceProvider.java | 30 +++++ .../server/deploy/BeanDescriptor.java | 37 +++++- .../deploy/BeanDescriptorCacheHelp.java | 45 +++---- .../server/deploy/BeanDescriptorManager.java | 8 +- .../server/deploy/BeanDescriptorMap.java | 4 +- .../server/deploy/BeanProperty.java | 10 ++ .../deploy/meta/DeployBeanProperty.java | 12 ++ .../deploy/meta/DeployBeanPropertyLists.java | 8 ++ .../server/deploy/parse/AnnotationFields.java | 3 + .../AutoCommitTransactionManager.java | 11 +- .../server/transaction/BeanPersistIds.java | 2 +- .../transaction/DataSourceSupplier.java | 25 ++++ .../DocStoreTransactionManager.java | 12 +- .../ExplicitTransactionManager.java | 14 +- .../server/transaction/JdbcTransaction.java | 12 ++ .../transaction/TransactionManager.java | 68 +++++----- .../TransactionManagerOptions.java | 36 +++++ .../ebeaninternal/api/TDSpiEbeanServer.java | 5 + ...faultCacheHolder_getCacheOptions_Test.java | 10 +- .../tests/cache/TestCacheCollectionIds.java | 1 - .../cache/TestCacheReferenceLazyLoad.java | 33 ----- .../tests/query/TestQueryFindIterate.java | 6 +- 46 files changed, 878 insertions(+), 296 deletions(-) create mode 100644 src/main/java/com/avaje/ebean/config/CurrentTenantProvider.java create mode 100644 src/main/java/com/avaje/ebean/config/TenantDataSourceProvider.java create mode 100644 src/main/java/com/avaje/ebean/config/TenantMode.java create mode 100644 src/main/java/com/avaje/ebean/config/TenantSchemaProvider.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/DefaultCacheAdapter.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/cache/SpiCacheManager.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/MultiTenantDbSchemaSupplier.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/MultiTenantDbSupplier.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/SimpleDataSourceProvider.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/DataSourceSupplier.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManagerOptions.java delete mode 100644 src/test/java/com/avaje/tests/cache/TestCacheReferenceLazyLoad.java 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)