I'm migrating my application from spring boot 1.5.x to 3.0.x. I want to keep jedis but I have a problem with the instantiation of RedisCacheManager.
Now constructor signature is
RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration)
But before it was:
RedisCacheManager(RedisOperations redisOperations)
I define this bean having only RedisTemplate in scope:
#Bean
public CacheManager cacheManager1(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setDefaultExpiration(300);
HashMap<String, Long> expires = new HashMap<>();
expires.put("DELIVERED_DLR_MESSAGE_PART_COUNT_MAP", new Long(1000));
expires.put("FAILED_DLR_MESSAGE_PART_COUNT_MAP", new Long(1000));
cacheManager.setExpires(expires);
cacheManager.setUsePrefix(true);
return cacheManager;
}
How is it supposed to be created now?
Related
I am using #Cacheable of Spring annotation to cache the data and Redis as the cache manager.
I created the cache with name xyx on one war, now I want to access/update/delete the same cache on another war.
Below is the code I have used to create the cache manager
#Bean
public JedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
// Defaults
redisConnectionFactory.setHostName("127.0.0.1");
redisConnectionFactory.setPort(6379);
return redisConnectionFactory;
}
Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory cf) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(cf);
return redisTemplate;
}
#Primary
#Bean(name = "cManager")
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cm= new RedisCacheManager(redisTemplate);
return cm;
}
Below is the method to cache the data in war 1
#Cacheable(value = "xyz" , cacheManager = "cManager")
public Map<String, Map<String, List<DTO>>> cachingData()
throws CustomException {
//logic
}
As long as both web applications are connecting to the same Redis instance, and using the same cacheName and cache key, this should work transparently, as if it was in the same war.
Example annotation
#Cacheable(cacheNames = "myCache", key = "'myKey'")
public String myCacheableMethod(){
return "some value";
}
I'm migrating my Spring application from Spring-boot 1.5.9 to Spring-boot 2.0.0.
With this new Spring bundle, I have some issues with caching data in Redis.
In my Configuration, I have 3 CacheManager with differents TTL (long, medium and short) :
#Bean(name = "longLifeCacheManager")
public CacheManager longLifeCacheManager() {
RedisCacheConfiguration cacheConfiguration =
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(redisExpirationLong))
.disableCachingNullValues();
return RedisCacheManager.builder(jedisConnectionFactory()).cacheDefaults(cacheConfiguration).build();
}
I also have a custom RestTemplate :
#Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<?, ?> template = new RedisTemplate<>();
template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
With the previous Spring version, every data that is cached use this RestTemplate and was serialized with the GenericJackson2JsonRedisSerializer.
With the new Spring version, the CacheManager don't use the RestTemplate but use its own SerializationPair. This result to everything beeing serialized with the default JdkSerializationRedisSerializer.
Is it possible to configure the CacheManager to use the RestTemplate and how ?
If it is not possible, what can I do to use the JacksonSerializer instead of the JdkSerializer ?
I finally found a working solution.
I can't configure the CacheManager to use my RedisTemplate, but I can set the Serializer like this :
#Bean(name = "longLifeCacheManager")
public CacheManager longLifeCacheManager(JedisConnectionFactory jedisConnectionFactory) {
RedisCacheConfiguration cacheConfiguration =
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(redisExpirationLong))
.disableCachingNullValues()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(jedisConnectionFactory).cacheDefaults(cacheConfiguration).build();
}
The serializeValuesWith method is the key.
I have my Configuration Class with some Dependent Beans
public class WebConfig{
#Bean
#Qualifier("geojedis")
public StringRedisTemplate geoJedisTemplate(
#Qualifier("geographyJedisConnectionFactory") final JedisConnectionFactory connectionFactory) {
// Create a RedisTemplate implementation which is basically of string
// data structure.
StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory);
return redisTemplate;
}
#Bean
#Qualifier("capacityStringRedisTemplate")
public StringRedisTemplate capacityStringRedisTemplate(
#Qualifier("capacityJedisConnectionFactory") final JedisConnectionFactory connectionFactory) {
// Create a RedisTemplate implementation which is basically of string
// data structure.
StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory);
return redisTemplate;
}
#Bean
public JedisConnectionFactory geographyJedisConnectionFactory() {
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
return connectionFactory;
}
#Bean
public JedisConnectionFactory capacityJedisConnectionFactory() {
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
return connectionFactory;
}
}
But I am getting the below error. When i checked the configurations all are fine and I have also defined the Qualifier for mapping the correct dependencies. Any help is much appreciated.
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'redisTemplate' defined in class path
resource
[org/springframework/boot/autoconfigure/redis/RedisAutoConfiguration$RedisConfiguration.class]:
Unsatisfied dependency expressed through constructor argument with
index 0 of type
[org.springframework.data.redis.connection.RedisConnectionFactory]: :
No qualifying bean of type
[org.springframework.data.redis.connection.RedisConnectionFactory] is
defined: expected single matching bean but found 2:
geographyJedisConnectionFactory,capacityJedisConnectionFactory; nested
exception is
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No
qualifying bean of type
[org.springframework.data.redis.connection.RedisConnectionFactory] is
defined: expected single matching bean but found 2:
geographyJedisConnectionFactory,capacityJedisConnectionFactory
There is a bean inside RedisAutoConfiguration that is created if there is no default "redisTemplate" in Spring Context.
#Bean
#ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
This one needs a single RedisConnectionFactory, but finds two.
As a work-around you can create a dummy RedisTemplate with the name "redisTemplate" and not use it.
Since it checks by bean name, the following could be enough as long as nothing tries to #Autowire it:
#Bean
public Object redisTemplate() {
return new Object();
}
You can simply call the connection factory bean creation method instead of injection:
#Bean
public StringRedisTemplate capacityStringRedisTemplate() {
// Create a RedisTemplate implementation which is basically of string
// data structure.
StringRedisTemplate redisTemplate =
new StringRedisTemplate(capacityJedisConnectionFactory());
return redisTemplate;
}
This will point directly to the one your looking for
use #EnableAutoConfiguration(exclude = RedisAutoConfiguration.class) above your config class and provide the custom connection properties
How can I configure Redis caching with Spring Boot. From what I have heard, it's just some changes in the application.properties file, but don't know exactly what.
To use Redis caching in your Spring boot application all you need to do is set these in your application.properties file
spring.cache.type=redis
spring.redis.host=localhost //add host name here
spring.redis.port=6379
Add this dependency in your pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Additionally, you have to use the #EnableCaching on your main application class and use the #Cacheable annotation on the methods to use the Cache. That is all is needed to use redis in a Spring boot application. You can use it in any class by autowiring the CacheManager in this case RedisCacheManager.
#Autowired
RedisCacheManager redisCacheManager;
You can mention all the required properties that is hostname, port etc. in the application.properties file and then read from it.
#Configuration
#PropertySource("application.properties")
public class SpringSessionRedisConfiguration {
#Value("${redis.hostname}")
private String redisHostName;
#Value("${redis.port}")
private int redisPort;
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(redisHostName);
factory.setPort(redisPort);
factory.setUsePool(true);
return factory;
}
#Bean
RedisTemplate<Object, Object> redisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
#Bean
RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate());
return redisCacheManager;
}
}
I am trying to configure multiple JPA entity/transaction managers within the same application context using Spring's #Configuration class.
When the context loads, Spring is having difficulties auto-wiring the beans because they implement the same interfaces.
Unfortunately, I'm using legacy code so I can't auto-wire the beans directly and use the #Qualifier annotations, which is why I'm trying to do it using the configuration class.
Within a #Bean declaration, is there any way to qualify which bean should be injected? I thought that using a direct method call would be enough, but it typically results in errors such as
NoUniqueBeanDefinitionException: No qualifying bean of type
[javax.sql.DataSource] is defined: expected single matching bean but
found 4
Here's an example of what I'm trying to do below:
#Configuration
public class ApplicationConfig {
#Bean(name = "transactionManager1")
public PlatformTransactionManager transactionManager1() {
return new JpaTransactionManager(entityManagerFactory1());
}
#Bean(name = "entityManagerFactory1")
public EntityManagerFactory entityManagerFactory1() {
...
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource1());
...
}
#Bean(destroyMethod = "")
#ConfigurationProperties(prefix = "datasource.test1")
public JndiObjectFactoryBean jndiObjectFactoryBean1() {
return new JndiObjectFactoryBean();
}
#Bean(name = "dataSource1")
public DataSource dataSource1() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiObjectFactoryBean1().getJndiName());
}
#Bean(name = "transactionManager2")
public PlatformTransactionManager transactionManager2() {
return new JpaTransactionManager(entityManagerFactory2());
}
#Bean(name = "entityManagerFactory2")
public EntityManagerFactory entityManagerFactory2() {
...
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource2());
...
}
#Bean(destroyMethod = "")
#ConfigurationProperties(prefix = "datasource.test2")
public JndiObjectFactoryBean jndiObjectFactoryBean2() {
return new JndiObjectFactoryBean();
}
#Bean(name = "dataSource2")
public DataSource dataSource2() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiObjectFactoryBean2().getJndiName());
}
I suppose I could try to inject the beans directly via the Spring context's getBean() method, but is there a cleaner way of doing this?
I'm not too familiar with the #Primary annotation, but based on what I've read I don't know how spring would autowire the secondary data source in this case since it looks like it would always pick the beans with #Primary first.
If you cannot change the injection sites to add qualifiers, then you're going to have to create a delegating DataSource based on some logic (which you haven't detailed in the question).
Something like this.
#Primary #Bean
public DelegatingDataSource delegatingDataSource(List<DataSource> sources) {
return new DelegatingDataSource() {
#Override
public DataSource getTargetDataSource() {
// decide which dataSource to delegate to
return sources.get(0);
}
}
}
I've used DelegatingDataSource, but that may not be able to provide what you need. You may need to get more advanced with some kind of interceptor/aspect to get details of the caller on which to base the DataSource selection.
However it's implemented, you need to specify a #Primary bean and use it as a proxy.