How to use Spring Cache Redis with a custom RestTemplate? - java

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.

Related

How to create RedisCacheManager in spring-data 3.0.x

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?

How we can access the same cache between two war files?

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";
}

spring boot assign specific DataSource to JpaRepository

I have two datasources that I'm trying to assign a specific datasource to each JpaRepositories. I'm using the spring boot framework. The primary datasource is used about 90% of the time while the secondary datasource is used about 10% so it would be nice to default to the primary and only assign the secondary datasource when required. I tried using the docs here https://docs.spring.io/spring-boot/docs/current/reference/html/howto-data-access.html , but I dont think its exactly what I need. Any tips would be great!
spring.datasource.configuration.url=jdbc:postgresql://localhost:5432/mydatabase
spring.datasource.configuration.username=dockerusername
spring.datasource.configuration.password=dockerpassword
spring.datasource.configuration.driver-class-name=org.postgresql.Driver
spring.datasource.cached.url=jdbc:hsqldb:mem:main
spring.datasource.cached.driver-class-name=org.hsqldb.jdbc.JDBCDriver
spring.datasource.initialize=false
spring.jpa.database=default
spring.jpa.hibernate.ddl-auto=update
config file
#Configuration
#ComponentScan({"com.praeses.gov"})
public class Config {
#Bean
#ConfigurationProperties("spring.datasource.configuration")
public DataSourceProperties configDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("spring.datasource.configuration")
public DataSource configDataSource() {
return configDataSourceProperties().initializeDataSourceBuilder().build();
}
#Bean
#Primary
#ConfigurationProperties("spring.datasource.cached")
public DataSourceProperties cachedDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("spring.datasource.cached")
public DataSource cachedDataSource() {
return cachedDataSourceProperties().initializeDataSourceBuilder().build();
}
repository that uses the primary datasource
#Qualifier("spring.datasource.cached")
#Repository("spring.datasource.cached")
public interface GeoitemRepository extends JpaRepository<Geoitem, String> {
}
repository that uses the secondary datasource
#Qualifier("spring.datasource.configuration")
#Repository("spring.datasource.configuration")
public interface GeoitemhistoryRepository extends JpaRepository<Geoitemhistory, String> {
}
You can do something on similar lines as below link. Just create two config files instead of one(one each for each datasource) and add both of them to your main application.
Also, in the properties file, give as below:
datasource.local.url=
datasource.local.driver-class-name=
datasource.local.username=
datasource.local.password=
datasource.primary.url=
datasource.primary.driver-class-name=
datasource.primary.username=
datasource.primary.password=
Also, give #Primary annotation to the LocalContainerEntityManagerFactoryBean and platformTransactionManager in the config file for primary datasource.
See this answer for more details:
https://stackoverflow.com/a/44971911/6775742

How to configure Redis caching in Spring Boot?

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;
}
}

How do I configure multiple JPA data sources using Spring #Configuration class?

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.

Categories