Spring: Multiple Cache Managers

2020-07-27 04:42发布

问题:

I have a problem implementing a secong cache managers. At the moment I'm using EhCache, which is working fine. Additionally I would like to implement Java Simple Cache.

My CacheConfiguration looks like this:

CacheConfiguration.java

@Configuration
@EnableCaching
@AutoConfigureAfter(value = { MetricsConfiguration.class })
@AutoConfigureBefore(value = { WebConfigurer.class, DatabaseConfiguration.class })
public class CacheConfiguration {

private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;

public CacheConfiguration(JHipsterProperties jHipsterProperties) {
    JHipsterProperties.Cache.Ehcache ehcache =
        jHipsterProperties.getCache().getEhcache();

    jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
        CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class,
            ResourcePoolsBuilder.heap(ehcache.getMaxEntries()))
            .withExpiry(Expirations.timeToLiveExpiration(Duration.of(ehcache.getTimeToLiveSeconds(), TimeUnit.SECONDS)))
            .build());
}

/**
 * EhCache configuration
 * 
 * @return
 */
@Bean
@Primary
public JCacheManagerCustomizer cacheManagerCustomizer() {
    return cm -> {          
        cm.createCache(com.david.coinlender.domain.News.class.getName(), jcacheConfiguration);

// ...More caches
}

/**
 * Java Simple Cache configuration
 * @return
 */
@Bean
@Qualifier("simpleCacheManager")
public CacheManager simpleCacheManager() {

    SimpleCacheManager simpleCacheManager = new SimpleCacheManager();       
    simpleCacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("bitfinexAuthCache")));

    return simpleCacheManager;

}

}

With Simple Cache I want to cache objects. I.e.:

@Cacheable(cacheManager = "simpleCacheManager", cacheNames = "bitfinexAuthCache", key = "#apiKey.apiKey")
private Exchange createBitfinexAuthenticatedExchange(ApiKeys apiKey) {

    ExchangeSpecification exSpec = new BitfinexExchange().getDefaultExchangeSpecification();
    exSpec.setApiKey(apiKey.getApiKey());
    exSpec.setSecretKey(apiKey.getSecret());

    Exchange bfx = ExchangeFactory.INSTANCE.createExchange(BitfinexExchange.class.getName());
    bfx.applySpecification(exSpec);

    return bfx;
}

However, on Server startup liquibase gives me an error stating:

Caused by: java.lang.IllegalStateException: All Hibernate caches should be created upfront. Please update CacheConfiguration.java to add com.david.coinlender.domain.News
at io.github.jhipster.config.jcache.NoDefaultJCacheRegionFactory.createCache(NoDefaultJCacheRegionFactory.java:37)
at org.hibernate.cache.jcache.JCacheRegionFactory.getOrCreateCache(JCacheRegionFactory.java:190)
at org.hibernate.cache.jcache.JCacheRegionFactory.buildEntityRegion(JCacheRegionFactory.java:113)
at org.hibernate.cache.spi.RegionFactory.buildEntityRegion(RegionFactory.java:132)
at org.hibernate.internal.CacheImpl.determineEntityRegionAccessStrategy(CacheImpl.java:439)
at org.hibernate.metamodel.internal.MetamodelImpl.initialize(MetamodelImpl.java:120)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:297)
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:445)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:889)
... 25 common frames omitted

I'm using the Jhipster framework for my appication. I googled this issue for hours now and haven't found a solution yet.

Is this error due to wrong configuration? Can someone please point me in the right direction?

回答1:

In JHipster (I did the code), there is in fact two layers of cache. You have Spring Cache and Hibernate Second Level Cache. Both are using the same actual Ehcache CacheManager.

In your case, you've replaced Ehcache with a simple cache for Spring. But since the NoDefaultJCacheRegionFactory is still configured on Hibernate, it is still Ehcache that is used for Hibernate. But the customizer isn't used anymore. So it fails.

You would like to have a simple cache for Spring and Ehcache for Hibernate. This means that in fact to don't need to have bean registered for Ehcache in Spring.

The easiest is then to do the following.

First, configure Ehcache in DatabaseConfiguration. So when the hibernate JCache factory will retrieve it, it will be correctly configured.

public DatabaseConfiguration(Environment env, JHipsterProperties jHipsterProperties) {
    this.env = env;

    JHipsterProperties.Cache.Ehcache ehcache =
        jHipsterProperties.getCache().getEhcache();

    CachingProvider provider = Caching.getCachingProvider();
    javax.cache.CacheManager cacheManager = provider.getCacheManager();

    javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
        CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class,
            ResourcePoolsBuilder.heap(ehcache.getMaxEntries()))
            .withExpiry(Expirations.timeToLiveExpiration(Duration.of(ehcache.getTimeToLiveSeconds(), TimeUnit.SECONDS)))
            .build());

    cacheManager.createCache(com.mycompany.myapp.domain.User.class.getName(), jcacheConfiguration);
    cacheManager.createCache(com.mycompany.myapp.domain.Authority.class.getName(), jcacheConfiguration);
    cacheManager.createCache(com.mycompany.myapp.domain.User.class.getName() + ".authorities", jcacheConfiguration);
}

Then, configure Spring Cache.

public class CacheConfiguration {

    public CacheConfiguration() {
    }

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        Collection<Cache> caches = Arrays.asList(
            new ConcurrentMapCache("mycache")
            // ...
        ); 
        cacheManager.setCaches(caches);
        return cacheManager;
    }
}


回答2:

@EnableCaching Doc

For those that wish to establish a more direct relationship between @EnableCaching and the exact cache manager bean to be used, the CachingConfigurer callback interface may be implemented. Notice the @Override-annotated methods below:

There is an additional alternative using CachingConfigurer and taking the approach that is implemented on JCacheCacheConfiguration.

1- Implements the CachingConfigurer interface in CacheConfiguration class. One option is extends from JCacheConfigurerSupport.

public class CustomCacheConfiguration extends JCacheConfigurerSupport {

2- Create the default cacheManager bean, since the bean is configured through CachingConfigurer it will be retrieved as a default cacheManager and in this case it can be used by hibernate second level cache, for this approach the cacheManagerCustomizer will be only a method. Like this :

   @Bean
    @Override
    public CacheManager cacheManager() {
        javax.cache.CacheManager jCacheCacheManager = createCacheManager();
        cacheManagerCustomizer().customize(jCacheCacheManager);
        return new JCacheCacheManager(jCacheCacheManager);
    }

    private javax.cache.CacheManager createCacheManager()  {
        CachingProvider cachingProvider = Caching.getCachingProvider();
        return cachingProvider.getCacheManager();
    }

    private JCacheManagerCustomizer cacheManagerCustomizer() {
        return cm -> {

            cm.createCache(org.demo.app.domain.User.class.getName(), jcacheConfiguration);
        ......

      };
}

3- Create the simpleCacheManager bean

@Bean("simpleCacheManager")
    public SimpleCacheManager simpleCacheManager() {
        SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
        simpleCacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("bitfinexAuthCache")));

        return simpleCacheManager;

    }
  1. In order to define which cacheManager will be used then define it through @Cacheable(cacheManager = "simpleCacheManager", cacheNames = "bitfinexAuthCache")

Here is the complete customization of CacheConfiguration.

@Configuration
@EnableCaching
@AutoConfigureAfter(value = { MetricsConfiguration.class })
@AutoConfigureBefore(value = { WebConfigurer.class, DatabaseConfiguration.class})
public class CacheConfiguration extends JCacheConfigurerSupport {

    private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;


    public CustomCacheConfiguration(JHipsterProperties jHipsterProperties) {
        JHipsterProperties.Cache.Ehcache ehcache =
            jHipsterProperties.getCache().getEhcache();




        jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
            CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class,
                ResourcePoolsBuilder.heap(ehcache.getMaxEntries()))
                .withExpiry(Expirations.timeToLiveExpiration(Duration.of(ehcache.getTimeToLiveSeconds(), TimeUnit.SECONDS)))
                .build());
    }


    @Bean
    @Override
    public CacheManager cacheManager() {
        javax.cache.CacheManager jCacheCacheManager = createCacheManager();
        cacheManagerCustomizer().customize(jCacheCacheManager);
        return new JCacheCacheManager(jCacheCacheManager);
    }

    private javax.cache.CacheManager createCacheManager()  {
        CachingProvider cachingProvider = Caching.getCachingProvider();
        return cachingProvider.getCacheManager();
    }


    private JCacheManagerCustomizer cacheManagerCustomizer() {
        return cm -> {

            cm.createCache(org.demo.app.domain.User.class.getName(), jcacheConfiguration);
            cm.createCache(org.demo.app.domain.Authority.class.getName(), jcacheConfiguration);
            cm.createCache(org.demo.app.domain.User.class.getName() + ".authorities", jcacheConfiguration);
            cm.createCache(org.demo.app.domain.Company.class.getName(), jcacheConfiguration);
            //cm.createCache(org.demo.app.domain.News.class.getName(), jcacheConfiguration);

        };
    }

    @Bean("simpleCacheManager")
    public SimpleCacheManager simpleCacheManager() {
        SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
        simpleCacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("bitfinexAuthCache")));

        return simpleCacheManager;

    }

}