Hi all,
I work on a project use spring boot 2.1, hibernate 5.3.14.Final.
I have a problem with the second level caching, i change the cache used from EHcache to infinispan for Multitenancy and distributed cache for invalidating entity.
My problem appeared when an second tenant use the application.
I start the server and i call a WS (Tenant1) to load from BDD some entity
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Entityimplements Serializable { … }
I see the first request and subselect request, at the second call of WS i don’t see any request, i modify an entity, the cache for this entity is invalidate, at the call, i see a request -> OK
After that when i use a WS to an other tenant, i see the request and result -> OK
and at this time the second cache level doesn’t seem works. On any tenant, if i load some entity, nothing is cached, every time the request is execute on BDD.
CacheFactoryConfiguration .java
@Component
public class CacheFactoryConfiguration extends InfinispanRegionFactory {
private static final long serialVersionUID = 1L;
private EmbeddedCacheManager cacheManager;
public CacheFactoryConfiguration(EmbeddedCacheManager cacheManager) {
super();
this.cacheManager = cacheManager;
}
@Override
protected EmbeddedCacheManager createCacheManager(Properties properties, ServiceRegistry serviceRegistry) {
System.out.println("create cache manager");
return cacheManager;
}
}
CacheFactoryConfiguration .java
@Configuration
@EnableCaching
public class CacheConfiguration{
private final Logger log = LoggerFactory.getLogger(CacheConfiguration.class);
// Initialize the cache in a non Spring-managed bean
private static EmbeddedCacheManager cacheManager;
public static EmbeddedCacheManager getCacheManager() {
System.out.println("getCacheManager");
return cacheManager;
}
public static void setCacheManager(EmbeddedCacheManager cacheManager) {
System.out.println("setCacheManager");
CacheConfiguration.cacheManager = cacheManager;
}
@Bean
public InfinispanGlobalConfigurer globalConfiguration(JHipsterProperties jHipsterProperties) {
log.info("Defining Infinispan Global Configuration");
return () -> GlobalConfigurationBuilder.defaultClusteredBuilder()
.defaultCacheName("infinispan-test-cluster-cache").transport().defaultTransport()
.addProperty("configurationFile", jHipsterProperties.getCache().getInfinispan().getConfigFile())
.clusterName("infinispan-test-cluster").globalJmxStatistics()
.enabled(jHipsterProperties.getCache().getInfinispan().isStatsEnabled()).allowDuplicateDomains(true)
.build();
}
@Bean
public InfinispanCacheConfigurer cacheConfigurer(JHipsterProperties jHipsterProperties) {
log.info("Defining {} configuration", "app-data for local, replicated and distributed modes");
JHipsterProperties.Cache.Infinispan cacheInfo = jHipsterProperties.getCache().getInfinispan();
return manager -> {
// initialize application cache
manager.defineConfiguration("local-app-data",
new ConfigurationBuilder().clustering().cacheMode(CacheMode.LOCAL).jmxStatistics()
.enabled(cacheInfo.isStatsEnabled()).memory().evictionType(EvictionType.COUNT)
.size(cacheInfo.getLocal().getMaxEntries()).expiration()
.lifespan(cacheInfo.getLocal().getTimeToLiveSeconds(), TimeUnit.SECONDS).build());
manager.defineConfiguration("dist-app-data",
new ConfigurationBuilder().clustering().cacheMode(CacheMode.DIST_SYNC).hash()
.numOwners(cacheInfo.getDistributed().getInstanceCount()).jmxStatistics()
.enabled(cacheInfo.isStatsEnabled()).memory().evictionType(EvictionType.COUNT)
.size(cacheInfo.getDistributed().getMaxEntries()).expiration()
.lifespan(cacheInfo.getDistributed().getTimeToLiveSeconds(), TimeUnit.SECONDS).build());
manager.defineConfiguration("repl-app-data",
new ConfigurationBuilder().clustering().cacheMode(CacheMode.REPL_SYNC).jmxStatistics()
.enabled(cacheInfo.isStatsEnabled()).memory().evictionType(EvictionType.COUNT)
.size(cacheInfo.getReplicated().getMaxEntries()).expiration()
.lifespan(cacheInfo.getReplicated().getTimeToLiveSeconds(), TimeUnit.SECONDS).build());
// initialize Hibernate L2 cache configuration templates
manager.defineConfiguration("entity",
new ConfigurationBuilder().clustering().cacheMode(CacheMode.INVALIDATION_SYNC).jmxStatistics()
.enabled(cacheInfo.isStatsEnabled()).locking().concurrencyLevel(1000)
.lockAcquisitionTimeout(15000).template(true).build());
manager.defineConfiguration("replicated-entity",
new ConfigurationBuilder().clustering().cacheMode(CacheMode.REPL_SYNC).jmxStatistics()
.enabled(cacheInfo.isStatsEnabled()).locking().concurrencyLevel(1000)
.lockAcquisitionTimeout(15000).template(true).build());
manager.defineConfiguration("local-query",
new ConfigurationBuilder().clustering().cacheMode(CacheMode.LOCAL).jmxStatistics()
.enabled(cacheInfo.isStatsEnabled()).locking().concurrencyLevel(1000)
.lockAcquisitionTimeout(15000).template(true).build());
manager.defineConfiguration("replicated-query",
new ConfigurationBuilder().clustering().cacheMode(CacheMode.REPL_ASYNC).jmxStatistics()
.enabled(cacheInfo.isStatsEnabled()).locking().concurrencyLevel(1000)
.lockAcquisitionTimeout(15000).template(true).build());
manager.defineConfiguration("timestamps",
new ConfigurationBuilder().clustering().cacheMode(CacheMode.REPL_ASYNC).jmxStatistics()
.enabled(cacheInfo.isStatsEnabled()).locking().concurrencyLevel(1000)
.lockAcquisitionTimeout(15000).template(true).build());
manager.defineConfiguration("pending-puts",
new ConfigurationBuilder().clustering().cacheMode(CacheMode.LOCAL).jmxStatistics()
.enabled(cacheInfo.isStatsEnabled()).simpleCache(true).transaction()
.transactionMode(TransactionMode.NON_TRANSACTIONAL).expiration().maxIdle(60000)
.template(true).build());
Stream.of(com.mycompany.repository.campagne.CampagneRepository.CAMPAGNE_BY_DATE_CACHE)
.forEach(cacheName -> manager.defineConfiguration(cacheName,
new ConfigurationBuilder().clustering().cacheMode(CacheMode.INVALIDATION_SYNC)
.jmxStatistics().enabled(cacheInfo.isStatsEnabled()).locking()
.concurrencyLevel(1000).lockAcquisitionTimeout(15000).build()));
setCacheManager(manager);
};
}
configuration.yml
spring:
jpa:
database-platform: io.github.jhipster.domain.util.FixedPostgreSQL95Dialect
database: POSTGRESQL
show-sql: true
properties:
hibernate.id.new_generator_mappings: true
hibernate.connection.provider_disables_autocommit: true
hibernate.cache.use_second_level_cache: true
hibernate.cache.use_query_cache: false
hibernate.generate_statistics: true
hibernate.cache.infinispan.statistics: true
hibernate.cache.use_minimal_puts: true
hibernate.cache.infinispan.entity.expiration.lifespan: 3600000
hibernate.cache.infinispan.entity.memory.size: 1000
hibernate.cache.infinispan.jgroups_cfg: default-configs/default-jgroups-tcp.xml
jhipster:
cache:
infinispan:
config-file: default-configs/default-jgroups-tcp.xml
statsEnabled: true
local:
time-to-live-seconds: 3600
max-entries: 1000
distributed:
time-to-live-seconds: 3600
max-entries: 1000
instance-count: 2
replicated:
time-to-live-seconds: 3600
max-entries: 1000
Configuration of multitenancy for schema based
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
Map<String, Object> hibernateProps = new LinkedHashMap<>();
hibernateProps.putAll(hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(),
new HibernateSettings()));
hibernateProps.putAll(jpaProperties.getProperties());
hibernateProps.put(AvailableSettings.CACHE_REGION_FACTORY, cacheFactoryConfiguration);
hibernateProps.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
hibernateProps.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
hibernateProps.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
hibernateProps.put(Environment.DIALECT, jpaProperties.getDatabasePlatform());
return builder.dataSource(dataSource).packages("com.mycompany.domain").properties(hibernateProps).jta(false)
.build();
}
Switching schema :
@Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 6246085840652870138L;
private static final Logger log = LoggerFactory.getLogger(MultiTenantConnectionProviderImpl.class);
@Autowired
private transient DataSource dataSource;
@Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.setSchema(tenantIdentifier);
} catch (SQLException e) {
log.warn("Use default schema, only in test mode");
}
return connection;
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try {
connection.setSchema(RequestContextHolderUtils.getCurrentTenantIdentifier());
} catch (SQLException e) {
log.warn("Use default schema, only in test mode");
}
connection.close();
}
@SuppressWarnings("rawtypes")
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
@Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
@Override
public boolean supportsAggressiveRelease() {
return true;
}
}
i read inside the documentation that the tenant is used to create key to store cache, i don’t see where is this problem, someone have an idea ? how can i see the current tenant of hibernate ?