SpringBoot cache management

Default cache management

The Spring framework supports transparently adding caches to applications to manage caches. The core of managing caches is to apply caches to methods of manipulating data to reduce the number of times the data is executed without causing any interference to the program itself.

Spring Boot inherits the cache management function of the Spring framework. By using the @enablecaching annotation to enable the annotated cache support, Spring Boot can enable automatic configuration of cache management.

Basic environment construction

1. Data preparation

Create databaseCREATEDATABASE springbootdata; USE springBootData; Create table T_article and insert related dataDROP TABLE IF EXISTS t_article;
  CREATE TABLE t_article (
      id INT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT 'the article id',
      title VARCHAR ( 200 ) DEFAULT NULL COMMENT 'Article Title',
      content LONGTEXT COMMENT 'Article content'.PRIMARY KEY ( id ) 
  ) ENGINE = INNODB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8;
  INSERT INTO t_article VALUES ('1'.'Spring Boot Basics'.'From entry to mastery... ');
  INSERT INTO t_article VALUES ('2'.'Spring Cloud Basics'.'From entry to mastery... '); Create table T_COMMENT and insert related dataDROP TABLE IF EXISTS t_comment;
  CREATE TABLE t_comment (
      id INT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT 'comment id',
      content LONGTEXT COMMENT 'Comment content',
      author VARCHAR ( 200 ) DEFAULT NULL COMMENT 'Review writer',
      a_id INT ( 20 ) DEFAULT NULL COMMENT 'Associated article ID'.PRIMARY KEY ( id ) 
  ) ENGINE = INNODB AUTO_INCREMENT = 3 DEFAULT CHARSET = utf8;
  INSERT INTO t_comment VALUES ('1'.'Full and detailed'.'luccy'.'1');
  INSERT INTO t_comment VALUES ('2'.'Like one'.'tom'.'1');
  INSERT INTO t_comment VALUES ('3'.'Very detailed'.'eric'.'1');
  INSERT INTO t_comment VALUES ('4'.'Very good. Very detailed.'.'Joe'.'1');
  INSERT INTO t_comment VALUES ('5'.'Very good'.'bill'.'2');
Copy the code

2. Create projects and write features

You need to select web-Spring Web and SQL-mysql Driver

Add JPA Dependencies in SQL module, MySQL Dependencies, and Web Dependencies in Web module

(2) Compile entity classes corresponding to database tables and configure mapping relationships using JPA annotations

@Entity(name = "t_comment") // Set the ORM entity class and specify the table name for the mapping
public class Comment {
    @Id // Indicates the primary key ID of the mapping
    @GeneratedValue(strategy = GenerationType.IDENTITY) // Set the policy for increasing the primary key
    private Integer id;
    private String content;
    private String author;
    @Column(name = "a_id") // Specify the table field name for the mapping
    private Integer aId;
  // get/set/toString()
}
Copy the code

(3) Compile the Repository interface file for database operations

public interface CommentRepository extends JpaRepository<Comment.Integer> {
    // Modify the comment author according to the comment ID
    @Transactional
    @Modifying
    @Query(value = "update t_comment c set c.author = ? 1 where c.id=? 2",nativeQuery = true)
    public int updateComment(String author,Integer id);
}
Copy the code

(4) Write the Service layer

For convenience, instead of writing the interface, write the Service class directly

@Service
public class CommentService {
    @Autowired
    private CommentRepository commentRepository;

    public Comment findCommentById(Integer id) {
        Optional<Comment> comment = commentRepository.findById(id);
        if (comment.isPresent()) {
            Comment comment1 = comment.get();
            return comment1;
        }
        return null; }}Copy the code

(5) Write the Controller layer

@RestController
public class CommentController {
    @Autowired
    private CommentService commentService;

    // http://localhost:8080/findCommentById? id=1
    @RequestMapping(value = "/findCommentById")
    public Comment findCommentById(Integer id) {
        Comment comment = commentService.findCommentById(id);
        returncomment; }}Copy the code

(6) Write configuration files

MySQL database connection configuration
spring.datasource.url=jdbc:mysql://localhost:3306/springbootdata? serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
# display SQL statements using JPA for database queries
spring.jpa.show-sql=true
# Enable camel name matching mapping
mybatis.configuration.map-underscore-to-camel-case=true
# Resolve garbled code
spring.http.encoding.force-response=true
Copy the code

(7) test

To access the url: http://localhost:8080/findCommentById? If id=1, the database will be queried on each access

Default Cache experience

(1) Enable annotation-based caching support using the @enablecaching annotation

@EnableCaching // Enable Spring Boot's annotation-based cache management support
@SpringBootApplication
public class Springboot05CacheApplication {

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

(2) Use the @cacheable annotation to manage caching of data manipulation methods.

// @cacheable: Store the comment of this method in the default springboot cache
//cacheNames: create a cache namespace with a unique cache identifier
@Cacheable(cacheNames = "comment")
public Comment findCommentById(Integer id) {
    Optional<Comment> comment = commentRepository.findById(id);
    if (comment.isPresent()) {
        Comment comment1 = comment.get();
        return comment1;
    }
    return null;
}
Copy the code

(3) Test access

At this point, the console will only query the database once after multiple visits

Underlying structure: In many automatic cache configuration class is SimpleCacheConfiguration SpringBoot default assembly, he USES a CacheManager is ConcurrentMapCacheManager, Use ConcurrentMap as the underlying data structure to query caches based on their names. Each Cache contains multiple k-V key-value pairs

(4) Introduction to cache annotations

@ EnableCaching annotations

@Enablecaching is provided by the Spring framework, and the SpringBoot framework inherits this annotation, which needs to be configured on the class (usually on the project startup class in Java) to enable annotation-based caching support

@ Cacheable annotations

The @cacheable annotation is also provided by the Spring framework and can be applied to classes or methods (typically data query methods).

The @cacheable annotation provides several attributes

The property name instructions
value/cacheNames Specifies the name of the cache space. This attribute is mandatory. Use either of these attributes
key Specifies the key to cache data. The default is the method parameter value. SpEL expressions can be used
keyGenerator Generator for the key that specifies the cache data, used either with the key property
cacheManager Specify the cache manager
cacheResolver Specifies the cache resolver, which can be used either with the cacheManager property
condition Specifies that data is cached if a condition is met
unless Specifies that data is not cached if a condition is met
sync Specifies whether to use asynchronous caching. The default false

If the cacheNames method is used to query the Cache(Cache component), the Cache component is obtained by the name specified by cacheNames. If the Cache component is not obtained for the first time, the Cache component is automatically created.

If multiple or no parameters are generated according to a certain policy, the default is KeyGenerator. SimpleKeyGenerator is used to generate the key. SimpleKeyGenerator’s default strategy for generating keys:

Number of parameters key
No parameters new SimpleKey()
There is one parameter The parameter value
Multiple parameters new SimpleKey(params)

@ CachePut annotations

The @cacheput annotation is used mostly for modification operations, even if the target value already exists in the cache, but it ensures that the method will still be executed and the result of the execution will be stored in the cache

@ CacheEvict annotations

The @cacheevict annotation is provided by the Spring framework and can be applied to classes or methods (typically data deletion methods) to delete cached data. The default execution order for @cacheevict annotations is to make method calls first and then clear the cache.

Second, integrate Redis cache implementation

Caching components supported by Spring Boot

In Spring Boot, Data storage is dependent on the Spring framework cache cache management related org. Springframework. Cache. The cache and org., springframework. Cache. CacheManager cache manager interface.

If the program does not define a Bean component of type CacheManager or a cacheResolver cache parser named cacheResolver, Spring Boot attempts to select and enable the following cache components (in the order specified):

(1) Generic

(2) JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, etc.) (3)EhCache 2.x

(4) Hazelcast

(5) Infinispan

(6) Couchbase

(7) Redis

(8) Caffeine

(9) Simple

Annotation-based Redis cache implementation

(1) Add Spring Data Redis dependent initiator.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Copy the code

(2)Redis service connection configuration

#Redis service address
spring.redis.host=127.0.0.1
#Redis server connection port
spring.redis.port=6379
Redis server connection password
spring.redis.password=
Copy the code

3. Modify methods in the CommentService class to customize cache management using @cacheable, @cacheput, and @cacheevict annotations to demonstrate cache storage, cache update, and cache deletion, respectively

@Service
public class CommentService {
    @Autowired
    private CommentRepository commentRepository;

    // @cacheable: Store the comment of this method in the default springboot cache
    //cacheNames: create a cache namespace with a unique cache identifier

    // value: cache result key: default If there is only one parameter, the default key is the method parameter if there is no parameter or more than one parameter: simpleKeyGenerate
    @Cacheable(cacheNames = "comment" ,unless = "#result == null")
    public Comment findCommentById(Integer id) {
        Optional<Comment> comment = commentRepository.findById(id);
        if (comment.isPresent()) {
            Comment comment1 = comment.get();
            return comment1;
        }
        return null;
    }

    // Update the method
    @CachePut(cacheNames = "comment",key = "#result.id")
    public Comment updateComment(Comment comment){
        commentRepository.updateComment(comment.getAuthor(),comment.getId());
        return comment;
    }

    // Delete method
    @CacheEvict(cacheNames = "comment")
    public void deleteComment(Integer id){ commentRepository.deleteById(id); }}Copy the code

In the @cacheable annotation, “Unless = “#result==null” is defined to indicate that the query result is empty and will not be cached

(4) Serialize the cache object.

(5) Start the test and query the test

After the query, the cache value is stored in Redis

(6) Redis cache update test based on annotations.

http://localhost:8080/updateComment?id=1&author=aaabb, database changes

There are only update statements in log printing, not query statements.

FindById () : findById() : findById() : findById() : findById() : findById() : findById() : findById The @cachePUT cache update configuration is successful

(7) Annotation-based Redis cache deletion test

Visit: http://localhost:8080/deleteComment? Id =1 data is deleted, and the Redis cache is also deleted

In addition, you can set the cache validity period

# set a uniform validity period of 1 minute in milliseconds for annotated Redis cache data
spring.cache.redis.time-to-live=60000
Copy the code

In the above code, the “spring.cache.redis. Time-to-live” property is added to the Spring Boot global configuration file to uniformly set the validity period (in milliseconds) of redis data, but this approach is relatively flexible

Redis cache implementation based on API

(1) Use Redis API for business data cache management.

Modifying the Service method

@Service
public class ApiCommentService {
    @Autowired
    private CommentRepository commentRepository;

    @Autowired
    private RedisTemplate redisTemplate;

    // Use the API to cache the database
    public Comment findCommentById(Integer id) {
        Object o = redisTemplate.opsForValue().get("comment_" + id);
        if(o ! =null) {// Return if found in cache
            return (Comment) o;
        }
        // If the cache does not exist, go to the database
        Optional<Comment> comment = commentRepository.findById(id);
        if (comment.isPresent()) {
            Comment comment1 = comment.get();
            // Save the query result to the cache. You can also set the validity period to 1 day
            redisTemplate.opsForValue().set("comment_" + id,comment1,1, TimeUnit.DAYS);
            return comment1;
        }
        return null;
    }

   / / update
    public Comment updateComment(Comment comment){
        commentRepository.updateComment(comment.getAuthor(),comment.getId());
        redisTemplate.opsForValue().set("comment_"+comment.getId(),comment);
        return comment;
    }

    public void deleteComment(Integer id){
        commentRepository.deleteById(id);
        redisTemplate.delete("comment_"+id); }}Copy the code

(2) Write the Web access layer Controller file

@RestController
@RequestMapping("api")
public class ApiCommentController {
    @Autowired
    private ApiCommentService commentService;

    // http://localhost:8080/api/findCommentById? id=1
    @RequestMapping(value = "/findCommentById")
    public Comment findCommentById(Integer id) {

        Comment comment = commentService.findCommentById(id);
        return comment;
    }

    @RequestMapping(value = "/updateComment")
    public Comment updateComment(Comment comment) {
        Comment commentById = commentService.findCommentById(comment.getId());
        commentById.setAuthor(comment.getAuthor());
        Comment res = commentService.updateComment(commentById);
        return res;
    }

    @RequestMapping(value = "/deleteComment")
    public void deleteComment(Integer id) { commentService.deleteComment(id); }}Copy the code

Api-based Redis cache implementation related configuration. The apI-based Redis caching implementation does not require the @Enablecaching annotation to enable annotation based caching support, so the @Enablecaching added to the project startup class can be deleted or commented out here

Custom cache serialization mechanism

Custom RedisTemplate

The Redis API default serialization mechanism

If you open the RedisTemplate class and look at the source information for the class, you can draw two conclusions:

(1) using RedisTemplate Redis data cache operation, internal default is JdkSerializationRedisSerializer serialization way, So entity classes that cache data must implement the JDK’s built-in serialization interfaces (such as Serializable);

(2) When RedisTemplate is used for Redis data cache operation, if defaultSerializer is customized, the customized serialization method will be used.

Each of the serialization types for cache data keys and values is RedisSerializer. The default implementation classes are as follows:

Custom RedisTemplate serialization mechanism

When you introduce Redis dependencies into your project, the RedisAutoConfiguration provided by Spring Boot automatically takes effect. Open the RedisAutoConfiguration class to see how RedisTemplate is defined in the internal source code

@Bean
@ConditionalOnMissingBean( name = {"redisTemplate"} )
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}
Copy the code

In the Redis auto-configuration class, a RedisTemplate is initialized by the Redis connection factory RedisConnectionFactory. The @conditionalonmissingBean annotation (which, as the name implies, works when a Bean does not exist) has been added above the class to indicate that if the developer custom a Bean named redisTemplate, the default redisTemplate initialization will not take effect.

Customize a Bean component named redisTemplate along these lines

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        // Use JSON format to serialize objects and convert cached data keys and values
        Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);

        // Resolve the query cache conversion exception
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jacksonSeial.setObjectMapper(om);

        / / set API RedisTemplate template serialization to JSON template. SetDefaultSerializer (jacksonSeial);
        returntemplate; }}Copy the code

The @Configuration annotation defines a RedisConfig Configuration class and uses the @Bean annotation to inject a redisTemplate component whose default name is the method name (note that the Bean component name must be redisTemplate). In the Bean of the definition of group a, the custom of a RedisTemplate, using custom Jackson2JsonRedisSerializer data serialization way; In the custom serialization mode, an ObjectMapper is defined for the data transformation Settings

The test results

Up and visit: http://localhost:8080/api/findCommentById? Id =3 The result is cached in Redis as follows:

Custom RedisCacheManager

For annotated templates, the following fixed templates are generally used:

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    // Create serialized objects in String format and JSON format respectively to convert cached data key and value
    RedisSerializer<String> strSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jacksonSeial =
            new Jackson2JsonRedisSerializer(Object.class);

    // Resolve the query cache conversion exception
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jacksonSeial.setObjectMapper(om);

    // Customize the cache data serialization method and duration
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofDays(1))
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(strSerializer))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                    .fromSerializer(jacksonSeial))
            .disableCachingNullValues();
    RedisCacheManager cacheManager = RedisCacheManager
            .builder(redisConnectionFactory).cacheDefaults(config).build();
    return cacheManager;
}
Copy the code

Up and visit: http://localhost:8080/findCommentById? id=4