preface

Yesterday, when developing business, we planned to add a cache layer to improve the system response speed. I looked up some information and found that Spring cache is very powerful! You can easily cache the objects returned by the method with just a little extra code. This article introduces the limitations and considerations of Using Spring Cache by describing a practical example.

Environment to prepare

  • Redis 5+
  • JDK 1.8 +
  • Gradle 6+
  • An IDE that you love

Practice process

Add the dependent

Open the build.gradle file and add Spring Cache dependencies.

implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Copy the code

Create the model

@Data
@AllArgsConstructor
public class Post implements Serializable {

    private Long id;

    private String title;

    private String content;
}
Copy the code

PS: The Lombok plug-in is used here. If you are not familiar with it, check the relevant information first.

Create a model repository

public interface PostRepository {

    Post getById(Long id);

}
Copy the code
@Component public class PostRepositoryImpl implements PostRepository { @Override public Post getById(Long id) { // SimulateSlowService (); return new Post(100L, "title", "content"); } private void simulateSlowService() { try { Long time = 3000L; Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code

Writing controller

@RestController public class PostController { private final PostRepository postRepository; public PostController(PostRepository postRepository) { this.postRepository = postRepository; } @GetMapping("posts/{id}") public Post getPostById(@PathVariable("id") Long id) { return postRepository.getById(id); }}Copy the code

For some resources that are not easy to modify, if you need to query the persistent database every time, it will be very wasteful and poor experience.

Using the Spring Cache

@EnableCaching @SpringBootApplication public class CacheApplication { public static void main(String[] args) { SpringApplication.run(CacheApplication.class, args); }}Copy the code

Add the @enablecaching annotation to start Spring Cache.

Spring: cache: type: redis redis: host: 127.0.0.1 port: 6379Copy the code

Here, Redis is used as the cache engine. If you want to use other engines, you can consult the documentation for configuration.

@RestController public class PostController { private final PostRepository postRepository; public PostController(PostRepository postRepository) { this.postRepository = postRepository; } @Cacheable(cacheNames = "getPostById", key = "#id") @GetMapping("posts/{id}") public Post getPostById(@PathVariable("id") Long id) { return postRepository.getById(id); }}Copy the code

The @Cacheable annotation getPostById method is used with the cacheNames and key parameters. Instead of going into details, I will focus on several annotations and their parameter meanings.

Spring Cache annotations

There are five common Spring Cache annotations:

  • @EnableCaching
  • @Cacheable
  • @CachePut
  • @CacheEvict
  • @CacheConfig

@EnableCaching

Add the @enablecaching annotation to the startup class to EnableCaching.

@Cacheable

The function is to enable caching, which can be tagged on classes or methods. When a method is called, the result is fetched from the cache, and the method is executed if it does not exist. The main parameters include cacheNames, key, condition, and unless.

  • CacheNames: Specifies the collection name for cache storage. This parameter is mandatory.
  • Key: The key value of the cache object stored in the collection. The default key value is the combination of all the parameters of the function.
  • Condition: The condition of the cache object, using an SpEL expression. Only content that meets expression conditions is cached.
  • Unless: Conditions for caching objects, using SpEL expressions. It differs from the condition argument in its timing. The condition is judged after the function has been called, so it can be judged on the returned object.
  • KeyGenerator: Specifies the key generator. To customize a key generator, you need to implement the KeyGenerator interface and specify it using this parameter.
  • CacheManager: specifies which cacheManager to use.
  • CacheResolver: Specifies which cache parser to use.

@CachePut

With respect to method configuration, unlike @Cacheable, it triggers a call to a real method every time. Simply update cached data. The main parameters are the same as @cacheable.

@CacheEvict

For method configuration, used to remove data from the cache. In addition to the same argument as @cacheable, there are allEntries and beforeInvocation.

  • AllEntries are optional and default to false. When true, all data is removed.
  • BeforeInvocation is optional, defaults to false and removes data after the method invocation. When true, the data is removed before the method is called.

@CacheConfig

This annotation is a class-level annotation that lets the methods below the class share the cacheNames, keyGenerator, cacheManager, and cacheResolver parameters.

Custom cacheNames

The purpose here is to allow our cache annotations to support custom TTL expiration times, something like the following.

@cacheable (cacheNames = "getPostById#3600", key = "#id")Copy the code

To achieve this effect, we create a CustomRedisCacheManager custom class, as shown below.

public class CustomRedisCacheManager extends RedisCacheManager { public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) { super(cacheWriter, defaultCacheConfiguration); } @Override protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) { String[] array = StringUtils.delimitedListToStringArray(name, "#"); name = array[0]; if (array.length > 1) { long ttl = Long.parseLong(array[1]); cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl)); } return super.createRedisCache(name, cacheConfig); }}Copy the code

Use custom CustomRedisCacheManager to configure CacheConfig.

public class CacheConfig extends CachingConfigurerSupport { @Value("${spring.redis.host}") private String redisHost; @Value("${spring.redis.port}") private Integer redisPort; @Value("${spring.redis.database}") private Integer redisDatabase; @Override @Bean public CacheManager cacheManager() { RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofDays(1)) .computePrefixWith(cacheName -> "caching:" + cacheName); return new CustomRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory()), defaultCacheConfig); } @Bean public RedisConnectionFactory redisConnectionFactory() { RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); configuration.setHostName(redisHost); configuration.setPort(redisPort); configuration.setDatabase(redisDatabase); return new LettuceConnectionFactory(configuration); }}Copy the code

conclusion

This article introduces the basic usage of Spring Cache and common annotations. Future articles will delve into the underlying principles.