1. An overview of the

This article describes how Spring Boot uses Redis for caching, how to customize the Redis cache (such as the expiration date of a key), and how Spring Boot initializes redis for caching. The @cacheable, @cacheevict, @cacheput, @Cacheconfig annotations and their attributes are explained in code.

2. Spring Boot integrates Redis

2.1. The application properties

Configure application.properties, which contains the following information:

  • Specifies the type of cache
  • Configure redis server information
  • Do not configure spring.cache.cache-names, for reasons explained later
Cache -names=book1,book2 spring.cache.type=REDIS # REDIS (RedisProperties) spring.redis Spring. Redis. Host = 192.168.188.7 spring. Redis. Password = spring. Redis. Port = 6379 spring. Redis. Pool. The Max - idle = 8 spring.redis.pool.min-idle=0 spring.redis.pool.max-active=100 spring.redis.pool.max-wait=-1Copy the code

2.2. Configure the startup class

  • @enablecaching: Starts the cache
  • Reconfigure RedisCacheManager to use the new configured value
@springBootApplication @enablecaching // Starting the cache Public class CacheApplication {private static final Logger log = LoggerFactory.getLogger(CacheApplication.class); public static void main(String[] args) { log.info("Start CacheApplication.. "); SpringApplication.run(CacheApplication.class, args); } /** * Reconfigure RedisCacheManager * @param rd */ @autoWired public void configRedisCacheManger(RedisCacheManager rd){ rd.setDefaultExpiration(100L); }}Copy the code

After the above configuration, the Redis cache managed object is generated. Here’s how Spring Boot initializes the Redis cache.

2.3. How does Spring Boot initialize Redis for caching

Cache management interface org. Springframework. Cache. CacheManager that spring boot is achieved by such a cache management. Redis corresponding to the interface implementation class is org. Springframework. Data. Redis. Cache. RedisCacheManager. Here’s how this class is generated.

First we configure the spring.redis.* property of application.properties @EnableCaching, spring will perform RedisAutoConfiguration, Initialize RedisTemplate and StringRedisTemplate

@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
/**
 * Standard Redis configuration.
 */
@Configuration
protected static class RedisConfiguration {
    ….
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
        RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
        RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

}

}
Copy the code

RedisCacheConfiguration will then inject the RedisTemplate method generated by RedisAutoConfiguration into RedisCacheManager.

@Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) @ConditionalOnBean(RedisTemplate.class) @ConditionalOnMissingBean(CacheManager.class) @Conditional(CacheCondition.class) class RedisCacheConfiguration { private  final CacheProperties cacheProperties; private final CacheManagerCustomizers customizerInvoker; RedisCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker) { this.cacheProperties = cacheProperties; this.customizerInvoker = customizerInvoker; } @Bean public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); cacheManager.setUsePrefix(true); List<String> cacheNames = this.cacheProperties.getCacheNames(); if (! cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); } return this.customizerInvoker.customize(cacheManager); }}Copy the code

Based on the above analysis, we know that spring has helped us generate and configure a RedisCacheManager. Finally, we can configure the RedisCacheManager again, where only the validity period of the configuration key is listed

/** * Reconfigure RedisCacheManager * @param rd */ @AutoWired public void configRedisCacheManger(RedisCacheManager rd){ rd.setDefaultExpiration(100L); }Copy the code

Note: Please do not set spring.cache.cache-names=book1,book2 in applicaion.properties, otherwise our new configuration will not apply to these caches. This is because a RedisCacheConfiguration initializes a RedisCacheConfiguration and immediately calls the initialization cache of a RedisCacheConfiguration, The configRedisCacheManger has not yet executed this method, making our configuration ineffective. Otherwise, if the cache configuration is not configured, the cache configuration will be used when the cache is created.

3. Use of Spring cache annotations

You learned how to configure the cache in the previous section. This section shows you how to use the cache.

3.1 the auxiliary class

Helper classes will be used below

Book:

public class Book implements Serializable { private static final long serialVersionUID = 2629983876059197650L; private String id; private String name; // private Integer price; // price private Date update; // public Book(String id, String name, Integer price, Date update) { super(); this.id = id; this.name = name; this.price = price; this.update = update; } // set/getCopy the code

BookQry: Encapsulates the request class

public class BookQry { private String id; private String name; // set/getCopy the code

AbstractService abstract class: initializes repositoryBook values to simulate database data. BookService and BookService2 both inherit from this class

public abstract class AbstractService { protected static Map<String, Book> repositoryBook = new HashMap<>(); public AbstractService() { super(); } @PostConstruct public void init() { // 1 Book book1 = new Book("1", "name_1", 11, new Date()); repositoryBook.put(book1.getId(), book1); // 2 Book book2 = new Book("2", "name_2", 11, new Date()); repositoryBook.put(book2.getId(), book2); // 3 Book book3 = new Book("3", "name_3", 11, new Date()); repositoryBook.put(book3.getId(), book3); // 4 Book book4 = new Book("4", "name_4", 11, new Date()); repositoryBook.put(book4.getId(), book4); }}Copy the code

3.2. @ Cacheable

Meaning of @cacheable property

  • CacheNames: Specifies the name of the cache
  • Key: Defines the key value. If not, use all parameters to calculate a key value. You can use spring El expressions
/** * cacheNames Sets the cache value * key: specifies the cache key, which is the parameter ID value. SpEl * @param id * @return */ @cacheable (cacheNames="book1", key="#id") public Book queryBookCacheable(String id){ logger.info("queryBookCacheable,id={}",id); return repositoryBook.get(id); } /** * another cache is used to store the cache ** @param id * @return */ @cacheable (cacheNames="book2", key="#id") public Book queryBookCacheable_2(String id){ logger.info("queryBookCacheable_2,id={}",id); return repositoryBook.get(id); } /** * The cached key can also specify the object's member variable * @param qry * @return */ @cacheable (cacheNames="book1", key="#qry.id") public Book queryBookCacheableByBookQry(BookQry qry){ logger.info("queryBookCacheableByBookQry,qry={}",qry); String id = qry.getId(); Assert.notNull(id, "id can't be null!" ); String name = qry.getName(); Book book = null; if(id ! = null){ book = repositoryBook.get(id); if(book ! = null && ! (name ! = null && book.getName().equals(name))){ book = null; } } return book; }Copy the code
  • KeyGenerator: defines the class generated by the key. The class and key cannot exist together
/** * We are using the default keyGenerator, which corresponds to Spring's SimpleKeyGenerator. We can also customize myKeyGenerator's generated key * * Key and keyGenerator are mutually exclusive, The key and keyGenerator parameters are SPECIFIED in parallel and an operation specifying both will result in an exception. * * @param id * @return */ @Cacheable(cacheNames="book3", keyGenerator="myKeyGenerator") public Book queryBookCacheableUseMyKeyGenerator(String id){ logger.info("queryBookCacheableUseMyKeyGenerator,id={}",id); return repositoryBook.get(id); } // Create a custom cache key.  @Component public class MyKeyGenerator implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... Params) {system.out.println (" Custom cache with key.params = "+ array.toString (params)); Return params[0] + "0"; }}Copy the code
  • Sync: if sync=true: a. If there is no data in the cache and multiple threads access the method at the same time, only one method will execute to the method, and the other methods need to wait; B. If there is already data in the cache, multiple threads can simultaneously fetch data from the cache
/*** * If sync=true, * If there is no data in the cache and multiple threads access the method at the same time, only one method will execute to the method, the others will have to wait * If there is already data in the cache, @param id @return */ @cacheable (cacheNames="book3", sync=true) public Book queryBookCacheableWithSync(String id) { logger.info("begin ... queryBookCacheableByBookQry,id={}",id); try { Thread.sleep(1000 * 2); } catch (InterruptedException e) { } logger.info("end ... queryBookCacheableByBookQry,id={}",id); return repositoryBook.get(id); }Copy the code
  • Condition and unless are cached only if certain conditions are met:

    • Condition: Before the method is executed, the value of condition is true and the data is cached
    • Unless: After executing the method, judge unless, and if the value is true, the data is not cached
    • Conditon and unless can be used together, so only records that satisfy both can be cached
/** * conditional cache: * Only requests that satisfy condition can be cached. If condition is not met, @cacheable (cacheNames="book11", condition="T(java.lang.Integer).parseInt(#id) < 3 ") public Book queryBookCacheableWithCondition(String id) { logger.info("queryBookCacheableByBookQry,id={}",id); return repositoryBook.get(id); } /** * Show time to evaluate the entries in order to show the most recent applications. Only cache records that do not return 'T(java.lang.INTEGER).parseint (#result.id) <3 '* @param id * @return */ @cacheable (cacheNames="book22",  unless = "T(java.lang.Integer).parseInt(#result.id) <3 ") public Book queryBookCacheableWithUnless(String id) { logger.info("queryBookCacheableByBookQry,id={}",id); return repositoryBook.get(id); }Copy the code

3.3. @ CacheEvict

Delete the cache

  • AllEntries = true: Empty all values in cache book1
  • AllEntries = false: The default value, in which only key values are deleted
/** * allEntries = true: */ @cacheevict (cacheNames="book1", allEntries=true) public void clearBook1All(){logger.info("clearAll"); @cacheevICT (cacheNames="book1", key="#id") public void updateBook(String id, String name){ logger.info("updateBook"); Book book = repositoryBook.get(id); if(book ! = null){ book.setName(name); book.setUpdate(new Date()); }}Copy the code

3.4. @ CachePut

Each execution executes the method, regardless of whether there is a value in the cache, and replaces the value in the cache with the new return value. This differs from @cacheable: @cacheable executes the method and caches the data if the cache has no value, or gets the value from the cache if the cache has a value

    @CachePut(cacheNames="book1", key="#id")
    public Book queryBookCachePut(String id){
        logger.info("queryBookCachePut,id={}",id);
        return repositoryBook.get(id);
    }Copy the code

3.5. @ CacheConfig

@Cacheconfig: Class-level annotation: If we define cacheNames in this annotation, cacheNames on @Cacheable defaults to this value on all methods in this class. Of course @cacheable can also redefine the value of cacheNames

@Component @CacheConfig(cacheNames="booksAll") public class BookService2 extends AbstractService { private static final Logger logger = LoggerFactory.getLogger(BookService2.class); /** * @cacheable for this method does not define cacheNames, CacheNames * @param ID * @return */ @cacheable (key="# ID ") Public Book queryBookCacheable(String id){ logger.info("queryBookCacheable,id={}",id); return repositoryBook.get(id); } /** * The @cacheable of this method defines cacheNames, CacheNames * * @param ID * @return */ @cacheable (cacheNames="books_custom", key="#id") public Book queryBookCacheable2(String id){ logger.info("queryBookCacheable2,id={}",id); return repositoryBook.get(id); }}Copy the code

4. Test

4.1. Ready to redis

You can install Redis via Docker, very convenient. See Docker Installation and Common commands for the usage of Docker

4.2. The test class

To verify each of the above methods, download the project and execute the test class CacheTest. I’m not going to demonstrate it here because it’s relatively simple.

5. The code

The detailed code for this article is Github