Pavan Dittakavi
Pavan Dittakavi

Reputation: 3181

SpringBoot connection to Redis : Does not appear to be caching data

I am using SpringBoot to connect to Redis. I have Web dependency on SpringBoot and my intention is to write Product information to a runtime datastructure i.e., a Map. I want to test the Cache annotations that Spring provides to understand the usage.

Here is the POM.xml file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>io.fireacademy</groupId>
    <artifactId>redisconnectivity</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>redisconnectivity</name>
    <description>Demo project for Spring Boot &amp; Redis connectivity</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Here is the Spring Application class

package io.fireacademy.redisconnectivity;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching
public class RedisconnectivityApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedisconnectivityApplication.class, args);
    }
}

Here is my main controller class

package io.fireacademy.redisconnectivity.controllers;

import io.fireacademy.redisconnectivity.model.Product;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/products")
public class WebAppController {

    private static final Logger logger = LoggerFactory.getLogger(WebAppController.class);

    // This will serve as the database
    private Map<String, Product> m_productDatabase = new HashMap<String, Product>();

    @Cacheable(value="my-product-cache", key="#productId")
    private Product getProductFromCacheOrDB(String productId)
    {
        logger.info("Loading the Product " + productId + " from the cache!.");
        return m_productDatabase.get(productId);
    }

    @CacheEvict(value="my-product-cache", key="#productId")
    private Product deleteFromCache(String productId)
    {
        logger.info("Remove the Product " + productId + " from the cache!.");
        return m_productDatabase.get(productId);
    }

    @GetMapping(path="/")
    public ResponseEntity<List<Product>> getProducts()
    {
        Collection<Product> allProducts = m_productDatabase.values();

        List<Product> allProductsAsList = allProducts.stream().collect(Collectors.toList());

        return new ResponseEntity<List<Product>>(allProductsAsList, HttpStatus.OK);
    }

    @GetMapping(path="/{productId}")
    public ResponseEntity<Product> getProducts(@PathVariable String productId)
    {
        // Either from the Cache or from the DB.
        Product product = getProductFromCacheOrDB(productId);

        return new ResponseEntity<Product>(product, HttpStatus.OK);
    }

    @PostMapping(consumes = {MediaType.APPLICATION_XML_VALUE,MediaType.APPLICATION_JSON_VALUE},
            produces = {MediaType.APPLICATION_XML_VALUE,MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Product> createProduct(@RequestBody Product product)
    {
        m_productDatabase.put(product.getId(), product);

        return new ResponseEntity<Product>(product, HttpStatus.CREATED);
    }

    @PutMapping(path="/{productId}",
                consumes = {MediaType.APPLICATION_XML_VALUE,MediaType.APPLICATION_JSON_VALUE},
                produces = {MediaType.APPLICATION_XML_VALUE,MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Product> updateProduct(@PathVariable String productId, @RequestBody Product product)
    {
        m_productDatabase.put(productId, product);
        return new ResponseEntity<Product>(product, HttpStatus.OK);
    }

    @DeleteMapping(path="/{productId}")
    public ResponseEntity<Product> deleteProduct(@PathVariable String productId)
    {
        Product deletedProduct = getProductFromCacheOrDB(productId);
        deleteFromCache(productId);

        return new ResponseEntity<Product>(deletedProduct, HttpStatus.OK);
    }
}

Here is my RedisConfig class

package io.fireacademy.redisconnectivity.configurations;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableRedisRepositories
public class RedisConfig {

    @Bean
    public JedisConnectionFactory connectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName("localhost");
        configuration.setPort(6379);
        return new JedisConnectionFactory(configuration);
    }

    @Bean
    public RedisTemplate<String, Object> template() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory());
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new JdkSerializationRedisSerializer());
        template.setValueSerializer(new JdkSerializationRedisSerializer());
        template.setEnableTransactionSupport(true);
        template.afterPropertiesSet();
        return template;
    }
}

My application.properties looks as below:

# Redis Config
spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379

And I am running Redis as a docker container using this:

docker run -d -p 6379:6379 --name my-redis redis

When I inspect the docker container's logs, I see nothing happening.

D:\Development\springboot\learn_redis>docker logs -f c376f1be9be35281b900c2943fbf8ea37e1563157efb57d46ca1c74fc880bc5c
1:C 04 Jun 2022 17:49:51.854 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 04 Jun 2022 17:49:51.854 # Redis version=7.0.0, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 04 Jun 2022 17:49:51.854 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 04 Jun 2022 17:49:51.854 * monotonic clock: POSIX clock_gettime
1:M 04 Jun 2022 17:49:51.858 * Running mode=standalone, port=6379.
1:M 04 Jun 2022 17:49:51.858 # Server initialized
1:M 04 Jun 2022 17:49:51.858 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
1:M 04 Jun 2022 17:49:51.858 * The AOF directory appendonlydir doesn't exist
1:M 04 Jun 2022 17:49:51.858 * Ready to accept connections

My understanding was that the Cache annotations would make the data be stored onto the Redis cache. For instance, the getProductFromCacheOrDB, should store the Product object onto the cache with the input productId as the key because of the @Cacheable annotation and a subsequent call to get this specific Product using the GET productId should not invoke the method again. But this is not happening.

Please show some pointers on what I could have missed...

Thanks, Pavan.

Edits:

  1. I do not see anything on Redis getting created. I enabled monitoring on Redis via the redis-cli, but see nothing.
  2. I tried removing the RedisConfig class and see no change.

Upvotes: 2

Views: 6611

Answers (1)

BSB
BSB

Reputation: 1713

Just created a simple working example at https://github.com/bsbodden/basic-caching-spring-data-redis

What you need:

  1. POM dependencies:
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    
  2. In the app class or configurer @EnableCaching:
    @SpringBootApplication
    @EnableCaching
    public class DemoApplication {
    
  3. In the app class RedisCacheManager bean:
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
      RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() //
          .prefixCacheNameWith(this.getClass().getPackageName() + ".") //
          .entryTtl(Duration.ofHours(1)) //
          .disableCachingNullValues();
    
      return RedisCacheManager.builder(connectionFactory) //
          .cacheDefaults(config) //
          .build();
    }
    
  4. @Cacheable in your controller:
    @GetMapping("/{id}")
    @Cacheable("some-cache")
    public SomeModel get(@PathVariable("id") String id) {
      return repo.findById(id).orElse(SomeModel.of("nope"));
    }
    

Upvotes: 1

Related Questions