Closed spring-projects-issues closed 9 years ago
Thomas Darimont commented
Hello Prashant,
I gave your example a spin but I couldn't reproduce your problem.
When do you get the exception?
package demo;
import java.util.Map;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.SimpleCacheResolver;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootApplication
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(App.class, args);
Map<String, CacheManager> cms = ctx.getBeansOfType(CacheManager.class);
System.out.println(cms);
}
@Bean
RedisConnectionFactory rcf() {
return new JedisConnectionFactory();
}
@Bean
RedisTemplate<String, BusinessObject> foo(RedisConnectionFactory rcf) {
RedisTemplate<String, BusinessObject> redisTemplate = new RedisTemplate<String, App.BusinessObject>();
redisTemplate.setConnectionFactory(rcf);
return redisTemplate;
}
@Bean
RedisTemplate<String, String> bar(RedisConnectionFactory rcf) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
redisTemplate.setConnectionFactory(rcf);
return redisTemplate;
}
@Bean
CacheManager cacheManagerToID(@Qualifier("foo") RedisTemplate redisTemplateToID) {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplateToID);
redisCacheManager.setUsePrefix(true);
return redisCacheManager;
}
@Bean
CacheManager cacheManagerFromID(@Qualifier("bar") RedisTemplate redisTemplateFromID) {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplateFromID);
redisCacheManager.setUsePrefix(true);
return redisCacheManager;
}
@Bean
CacheResolver cacheResolver(CacheManager cacheManagerToID) {
return new SimpleCacheResolver(cacheManagerToID);
}
public static class BusinessObject {}
}
Cheers, Thomas
Thomas Darimont commented
Ah... too fast - I see your problem.
It seems that org.springframework.cache.interceptor.CacheAspectSupport
only supports a single CacheManager
.
Why do you need two CacheManager
s in the first place? Is this because of DATAREDIS-390?
Cheers, Thomas
Prashant Deva commented
yes i did think this could be (an extremely clunky) solution to DATAREDIS-390
the @Cacheable
annotation specifically has an attribute to specify which cache manager to use.
By not allowing multiple cache managers, you are breaking existing, documented functionality
Thomas Darimont commented
Hello Prashant,
I think that this is not a Problem of Spring Data Redis but rather incomplete caching configuration.
As the exception message indicates: No CacheResolver specified, and no unique bean of type CacheManager found. Mark one as primary or declare a specific CacheManager to use.
You need to declare one of the cache managers as the default one (by adding @Primary
or giving it the bean name cacheManager
) that will be used in case no cacheManager or cacheResolver is referenced explicitly.
The following example demonstrates the use of multiple CacheManagers
:
package demo;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootApplication
@EnableCaching
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(App.class, args);
BusinessService service = ctx.getBean(BusinessService.class);
System.out.println(service.computeWithRedisCache("redis"));
System.out.println(service.computeWithChmCache("chm"));
System.out.println(service.computeWithRedisCache("redis"));
System.out.println(service.computeWithChmCache("chm"));
}
@Bean
public RedisConnectionFactory rcf() {
return new JedisConnectionFactory();
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
redisTemplate.setConnectionFactory(cf);
return redisTemplate;
}
@Bean
public CacheManager redisCacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setDefaultExpiration(100);
return cacheManager;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
@Bean
public BusinessService businessService() {
return new DefaultBusinessService();
}
public static interface BusinessService {
BusinessObjectRedis computeWithRedisCache(String param);
BusinessObjectCHM computeWithChmCache(String param);
}
static class DefaultBusinessService implements BusinessService {
@Override
@Cacheable(value = "businessObjects", cacheManager = "redisCacheManager")
public BusinessObjectRedis computeWithRedisCache(String param) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new BusinessObjectRedis(param);
}
@Override
@Cacheable(value = "businessObjects")
public BusinessObjectCHM computeWithChmCache(String param) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new BusinessObjectCHM(param);
}
}
static class BusinessObject implements Serializable {
private final String value;
public BusinessObject(String value) {
this.value = value + System.currentTimeMillis();
}
@Override
public String toString() {
return getClass().getSimpleName() + "@" + System.identityHashCode(this) + " value: " + value;
}
}
public static class BusinessObjectRedis extends BusinessObject {
public BusinessObjectRedis(String value) {
super(value);
}
}
public static class BusinessObjectCHM extends BusinessObject {
public BusinessObjectCHM(String value) {
super(value);
}
}
}
Cheers, Thomas
Thomas Darimont commented
If you want to use multiple CacheManager
's with your own CacheResolver
you could do something like this:
package demo;
import java.util.Arrays;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@SpringBootApplication
@EnableCaching
public class CacheResolverConfigExample {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(CacheResolverConfigExample.class, args);
BusinessService service = ctx.getBean(BusinessService.class);
System.out.println(service.computeWithCacheA("a"));
System.out.println(service.computeWithCacheB("b"));
System.out.println(service.computeWithCacheA("a"));
System.out.println(service.computeWithCacheB("b"));
}
@Bean
public CacheManager cacheManagerA() {
return new ConcurrentMapCacheManager();
}
@Bean
@Primary
public CacheManager cacheManagerB() {
return new ConcurrentMapCacheManager();
}
@Configuration
static class CachingConfig extends CachingConfigurerSupport {
@Autowired CacheManager cacheManagerA;
@Autowired CacheManager cacheManagerB;
public CachingConfig() {}
@Override
public CacheResolver cacheResolver() {
return new CacheResolver() {
@Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
Class<?> returnType = context.getMethod().getReturnType();
if (BusinessObjectA.class.equals(returnType)) {
return Arrays.asList(cacheManagerA.getCache(returnType.getSimpleName()));
} else if (BusinessObjectB.class.equals(returnType)) {
return Arrays.asList(cacheManagerB.getCache(returnType.getSimpleName()));
}
return null;
}
};
}
}
@Bean
public BusinessService businessService() {
return new DefaultBusinessService();
}
public static interface BusinessService {
BusinessObjectA computeWithCacheA(String param);
BusinessObjectB computeWithCacheB(String param);
}
@CacheConfig(cacheNames = { "BusinessObjectA", "BusinessObjectB" })
static class DefaultBusinessService implements BusinessService {
@Override
@Cacheable
public BusinessObjectA computeWithCacheA(String param) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new BusinessObjectA(param);
}
@Override
@Cacheable
public BusinessObjectB computeWithCacheB(String param) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return new BusinessObjectB(param);
}
}
static class BusinessObject {
private final String value;
public BusinessObject(String value) {
this.value = value + System.currentTimeMillis();
}
@Override
public String toString() {
return super.toString() + " value: " + value;
}
}
public static class BusinessObjectA extends BusinessObject {
public BusinessObjectA(String value) {
super(value);
}
}
public static class BusinessObjectB extends BusinessObject {
public BusinessObjectB(String value) {
super(value);
}
}
}
Prashant Deva commented
??declare one of the cache managers as the default one (by adding @Primary
or giving it the bean name cacheManager)??
The error message should mention the above since this is not mentioned in the docs. (the docs should mention this too)
Thomas Darimont commented
I agree with you, that's why I created SPR-12898 to make this more clear
Thomas Darimont commented
Closing as invalid since this is a configuration issue that has nothing to do with Spring Data Redis
Prashant Deva opened DATAREDIS-391 and commented
Sample code:
results in this exception:
How is one supposed to have multiple cache managers then?
Affects: 1.5 GA (Fowler)