Caching in Spring Boot with Caffeine

1.9k Views Asked by At

I have this configuration class:

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    @Primary
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("dogsInHouse");
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .initialCapacity(200)
                .expireAfterAccess(Duration.ofDays(30))
                .maximumSize(500));
        return cacheManager;
    }
}

in the properties file:

spring.jpa.show-sql=true

in the service:

@Service
@Transactional(readOnly = true)
@Slf4j
@CacheConfig(cacheNames = {"dogsInHouse"})
public class DogsInHouseService {

    @Cacheable("dogsInHouse")
    public DogsInHouse findDogHouseEnFromDB (String key) {
        return dogsEnRepository.findByNameAndLangIs(key);
    }
}

The only thing that I can always see in the console is select query but I don't see the cache logs.

3

There are 3 best solutions below

0
Anish B. On BEST ANSWER

Use logging.level.org.springframework.cache=TRACE in your application.properties to see whether or not the value is picked from the cache.

NOTE: When you hit the service for the first time, it will bring data from database and will put the data into the cache and next time, it will bring from the cache.

Please remove @CacheConfig you don't need that in Spring Boot App.


I've used spring-data-jpa with MySQL for demonstration.

Same Config used. Just added for reference.

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    @Primary
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("dogsInHouse");
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .initialCapacity(200)
                .expireAfterAccess(Duration.ofDays(30))
                .maximumSize(500));
        return cacheManager;
    }
}

DogInHouse for testing:

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DogInHouse {

    @Id
    @GeneratedValue
    private int id;

    @Column(name = "name")
    private String name;

}

DogInHouseRepository for testing:

public interface DogInHouseRepository extends JpaRepository<DogInHouse, Integer> {
}

My Service class for testing:

@Service
@Slf4j
public class DogsInHouseService {

    @Autowired
    private DogInHouseRepository dogsEnRepository;

    @Cacheable(value = "dogsInHouse")
    public Optional<DogInHouse> findDogHouseById(int key) {
        return dogsEnRepository.findById(key);
    }

}

application.properties for testing:

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=Anish@123
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.springframework.cache=TRACE

My pom.xml had these dependencies:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
         <groupId>com.github.ben-manes.caffeine</groupId>
         <artifactId>caffeine</artifactId>
    </dependency>
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>
</dependencies>

My Application Log:

First Time Service Hit:

2023-10-21T22:22:16.575+05:30 TRACE 14989 --- [nio-8080-exec-3] o.s.cache.interceptor.CacheInterceptor   : Computed cache key '1' for operation Builder[public java.util.Optional com.example.springbootmysql.DogsInHouseService.findDogHouseById(int)] caches=[dogsInHouse] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'
2023-10-21T22:22:16.576+05:30 TRACE 14989 --- [nio-8080-exec-3] o.s.cache.interceptor.CacheInterceptor   : No cache entry for key '1' in cache(s) [dogsInHouse]
2023-10-21T22:22:16.577+05:30 TRACE 14989 --- [nio-8080-exec-3] o.s.cache.interceptor.CacheInterceptor   : Computed cache key '1' for operation Builder[public java.util.Optional com.example.springbootmysql.DogsInHouseService.findDogHouseById(int)] caches=[dogsInHouse] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'
2023-10-21T22:22:16.587+05:30 DEBUG 14989 --- [nio-8080-exec-3] org.hibernate.SQL                        : 
    select
        d1_0.id,
        d1_0.name 
    from
        dog_in_house d1_0 
    where
        d1_0.id=?

Currently there is no value in the cache with the key that is passed, So, a select query will be executed via Hibernate to bring the value from DB and the value is put into the cache by that key.

Second Time Service Hit:

2023-10-21T22:23:33.485+05:30 TRACE 14989 --- [nio-8080-exec-6] o.s.cache.interceptor.CacheInterceptor   : Computed cache key '1' for operation Builder[public java.util.Optional com.example.springbootmysql.DogsInHouseService.findDogHouseById(int)] caches=[dogsInHouse] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'
2023-10-21T22:23:33.490+05:30 TRACE 14989 --- [nio-8080-exec-6] o.s.cache.interceptor.CacheInterceptor   : Cache entry for key '1' found in cache 'dogsInHouse'

This time the value exists in the cache by the same key that was passed earlier and the value is fetched from the cache.

Postman Output:

enter image description here

0
VonC On

If you see a select query in the console every time findDogHouseEnFromDB() method is called, that should mean the caching might not be working as expected.

After reading "Caffeine Cache with Spring Boot" from Lokesh Gupta, for debugging purposes, you could add an endpoint or method to inspect the contents of the cache to make sure entries are being cached and evicted as expected.

@GetMapping(value = "/inspectCache")
public void inspectCache() {
    CaffeineCache caffeineCache = (CaffeineCache) cacheManager.getCache("dogsInHouse");
    Cache<Object, Object> nativeCache = caffeineCache.getNativeCache();
    for (Map.Entry<Object, Object> entry : nativeCache.asMap().entrySet()) {
        System.out.println("Key = " + entry.getKey());
        System.out.println("Value = " + entry.getValue());
    }
}

Note: Instead of configuring cache properties within the Java configuration file, you could move some or all of these configurations to the application.properties file. That could simplify the configuration and make it more manageable. See Configuration library for JVM languages

spring.cache.cache-names=dogsInHouse
spring.cache.caffeine.spec=initialCapacity=200,maximumSize=500,expireAfterAccess=720h

And if there is a need for custom cache key generation, you might consider implementing a custom KeyGenerator or using the key attribute on the @Cacheable annotation to specify how the cache key should be generated.

@Cacheable(value = "dogsInHouse", key = "#key")
public DogsInHouse findDogHouseEnFromDB (String key) {
    return dogsEnRepository.findByNameAndLangIs(..);
}
0
Dirk Deyne On

With your configuration, the caching should work. It is due to something other than the provided code if it fails.

Created a working demo with your code here: caffeine_cache