SpringBoot integrates Redis and Cache to realize paging cache

Last time I received a request to optimize the loading pages and speed of the old system. At the beginning, I wanted to optimize SQL. Later, the boss mentioned that I used the cache to check multiple forums. I didn’t find an article that introduced cache paging. I took a moment to share with you. share

name explain
Cache Cache interface, defines cache operations. Implementations include: RedisCache, EhCacheCache, ConcurrentMapCache, etc.
CacheManager Cache manager, manages various cache (cache) components
@Cacheable Mainly for method configuration, it can be cached according to the request parameters of the method
@CacheEvict Empty the cache
@CachePut The method is guaranteed to be called, and the result is expected to be cached.
The difference from @Cacheable is whether the method is called every time, which is often used for updating
@EnableCaching Enable annotation-based caching
key The key of the cache can be empty. If it is specified, it should be written according to the SpEL expression.
If not specified, it will be combined according to all parameters of the method by default.
For example:
@Cacheable(value=”testcache”,key=”#id”)
value

The name of the cache, defined in the spring configuration file, must specify at least one such as:

@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}

allEntries
(@CacheEvict )
Whether to clear all cache contents, the default is false, if specified as true,
all caches will be cleared immediately after the method is called
For example:
@CachEvict(value=”testcache”,allEntries=true)

//If you want to know more annotations, you can read the link of this article first. I feel that the annotations are well organized

spring-boot-starter-cache of Spring boot (integrating redis)_MrLee's Blog-CSDN Blog_spring-boot-starter-cache

      <!-- Redis Client 3版本以上会报错与spring-boot-starter-data-redis冲突 -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

 1- yml file configuration method

spring:
  thymeleaf:
    cache: false
  redis:
    #REDIS 数据库索引(默认为0)
    database: 1
    #redis服务地址
    host: 127.0.0.1
    #redis服务连接端口
    port: 6379
    #redis密码(没有密码默认为空)
    password:
    #连接池最大连接数(使用负值表示没有限制)
    jedis:
      pool:
        max-active: 8
    lettuce:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 8
        # 连接池中的最大空闲连接
        max-idle: 500
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 2000
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 10000
        #连接超时时间(毫秒)
    timeout: 5000


#打印sql日志
logging:
  level:
    root: debug

2-Comments added to SpringBoot startup

@SpringBootApplication
//扫描容器
@ComponentScan(basePackages = {"com.lwq.demo.*"})
//扫描mapper层
@MapperScan("com.lwq.demo.dao")
//启动缓存
@EnableCaching
public class DemoApplication {

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

}

 3- coifg configuration If configured in this way alone, it can also run normally. In this case, the data cached in redis is binary 

/**
 * @author redis 配置
 * 继承缓存配置
 */
@Configuration
public class redisConfig extends CachingConfigurerSupport {


}

 

  3-1 The coifg configuration means that there is no serialization and deserialization, although it does not affect the normal use, but I always feel uncomfortable. This point should be noted that if your project used cache before and you deserialize it, if you do not clear the cache It will report an error of 500, then just clear the cache 

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.CacheKeyPrefix;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.*;

import java.time.Duration;

/**
 * @author redis 配置
 * 继承缓存配置
 */
@Configuration
public class redisConfig extends CachingConfigurerSupport {


    /**
     * 二进制转成json 数据
     * 配置文件的配置没有用上
     *
     * @return
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        //过期时间 可有可无 如果不写就是永久保存 看需求 这里是缓存一个小时 
        //建议是永久保存   
        config = config.entryTtl(Duration.ofHours(1));
        //序列化
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        //反序列化
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        CacheProperties.Redis redisProperties = cacheProperties.getRedis();
        //将配置文件中所有的配置都生效
        if (redisProperties.getTimeToLive() != null) {
            config = config.entryTtl(redisProperties.getTimeToLive());
        }
        if (redisProperties.getKeyPrefix() != null) {
            config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());;
        }
        if (!redisProperties.isCacheNullValues()) {
            config = config.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            config = config.disableKeyPrefix();
        }
        return config;
    }

}

 4- Demo paging query cache controller layer

    /**
     * 分页缓存测试 分页携带数据
     */
    //@RequestMapping("/test5")
    //@GetMapping("/test5")
    @PostMapping("/test5")
    @ResponseBody
    public List<Daliy> test5( QueryRequest request,String  test) {
        List<Daliy> daliys = daliyService.paGeTwo(request,test);
        System.out.println(daliys);
        return daliys;
    }

 4-1 Paging query service Note that if one of the key values ​​is null, the null will be stored in the cache. The official explanation is easy to cause penetration through the avalanche cache. The official solution cannot be null, but it can be empty. I have verified If it is empty, this value will not be stored in the cache. This will demonstrate it to everyone and go directly to the topic.

 //优先级最低的  这个表示在redis 创建一个文件夹 DaliyService 在这个文件夹做缓存
@CacheConfig(cacheNames = "DaliyService")
public interface DaliyService extends IService<Daliy> {


    /**
     * 分页缓存2携带多一个参数
     * @param request
     * @param test
     * @return
     */
    //表示是否缓存空值,一般情况下是允许的。因为这涉及到缓存的三大问题:缓存穿透、缓存雪崩、缓存击穿。官方是怎么说的 
    @Cacheable(key = "#p0.toString() + (#p1 != null ? # p1.toString() : '')")
    List<Daliy> paGeTwo(QueryRequest request, String test);

​​​​​​​}

 

  4-2 Demo paged query cache deletion

   /**
     * 根据id 删除数据
     */
    @PostMapping("/test6")
    @ResponseBody
    public Boolean test6( String  id) {
        int bool = daliyService.deletes(id);
        System.out.println(bool);
        return bool >0 ? true:false;
    }

4-3 Pagination delete service @CacheEvict(key = "#p0",allEntries = true) Note that this allEntries = true means to delete all the data of the DaliyService file If @CacheEvict(key = "#p0") alone deletes the cache no effect

 //优先级最低的  这个表示在redis 创建一个文件夹 DaliyService 在这个文件夹做缓存
@CacheConfig(cacheNames = "DaliyService")
public interface DaliyService extends IService<Daliy> {

    //根据id 删除数据 缓存也跟着删除  allEntries 默认为 false 只会删除数据库得不会删除缓存这里要改成true
    //@CacheEvict(key = "#p0")
    @CacheEvict(key = "#p0",allEntries = true)
    int deletes(String id);

​​​​​​​}

 

4-4 There are still data in pagination query, but after deleting the data, all the data in the DliyService folder will be deleted

 

 4-5 The two methods of adding data and updating data controller layer by paging are the same, so I won’t make too much introduction here

 /**
     * 根据id 更新缓存
     */
    @PostMapping("/test7")
    @ResponseBody
    public Boolean test7( String  id,String projectNo) {
        int bool = daliyService.updataTest(id,projectNo);
        System.out.println(bool);
        return bool >0 ? true:false;
    }


    /**
     * 创建数据
     */
    @PostMapping("/test8")
    @ResponseBody
    public Daliy test8( @RequestBody Daliy daliy) {
        int bool = daliyService.createDayliy(daliy);
        System.out.println(bool);
        return daliy;
    }

 4-6 Paging to add data and update data Service

    
 //优先级最低的  这个表示在redis 创建一个文件夹 DaliyService 在这个文件夹做缓存
@CacheConfig(cacheNames = "DaliyService")
public interface DaliyService extends IService<Daliy> {

   /**
     * 更新数据
     * @param id id
     * @param projectNo 更新得值
     * @return
     */
    // 无论使用更新或添加 都把缓存删除
    @CacheEvict(key = "#p0",allEntries = true)
    int updataTest(String id, String projectNo);

    /**
     * 创建数据
     * @param daliy 前端返回的数据
     * @return
     */
    // 无论使用更新或添加 都把缓存删除
    @CacheEvict(key = "#p0",allEntries = true)
    int createDayliy(Daliy daliy);

}
   

4-7 This is the same result allEntries = true delete all caches in your current folder

 

  4-8 Paging cache is just like this, it’s not difficult to get it done, but I didn’t use the official @CachePut annotation because the usage of the above cache paging is my personal recommendation  

 5 The update, deletion and addition above are not used @CachePut by the way to show you

5-1 cache query data controller
   /**
     * 根据id查询数据 缓存没有就在数据库获取
     * @param id 根据id 获取数据源
     * @return
     */
    @PostMapping("/test9")
    @ResponseBody
    public Daliy test9(String id) {
        Daliy daliy = daliyService.selectByid(id);
        return daliy;
    }

 5-2 The cache query data server value means to recreate a folder called text. Before storing the query data in the cache according to the id, we first judge whether the id passed from the front end is null. If it is null, it is prone to avalanche penetration and breakdown. If it is empty, it will not be stored in the cache  

/**
     * 根据id 的查到的数据
     * @param id
     * @return
     */
    //这里重新创建文件夹text 这里重新定义key
    @Cacheable(value = "text",key = "#id.toString() != null ? 'user_'+#id.toString() : ''")
    Daliy selectByid(String id);

 

 

 5-3 @CachePut Usage Update Data Cache

 /**
     * 更改缓存里面的数据
     * @param daliy 根据id 更改的数据
     * @return
     */
    @PostMapping("/test10")
    @ResponseBody
    public Daliy test10(@RequestBody Daliy daliy) {
        System.out.println(daliy);
        Daliy updataDaliy = daliyService.updataByid(daliy);
        return updataDaliy;
    }

 5-4 Update data cache value As mentioned above, it means creating a folder. This is to replace the data in the cache according to the id. @CachePut is to replace the returned result set in the cache.  

    /**
     * 更新数据
     * @param daliy
     * @return
     */
    //CachePut 返回的数据的结果集 替换之前的缓存数据
    //不能直接这样 这样容易出现 穿透 跟 雪崩 要先判断不为null 的时候  官网提供的是可以为空但不能为null
    // @CachePut(value = "text",key = "#daliy.id")
    //如果更新全部字段的可以忽略这句话 更新某个字段的注意这边特别容易踩坑这是将结果集返回做缓存的 我们要先跟id把这条数据全部查出来 把要改的数据替换 在返回结果集,
    @CachePut(value = "text",key = "#daliy.id.toString() != null ? 'user_'+#daliy.id.toString() : ''")
    Daliy updataByid(Daliy daliy);

 The original data found the id22 data 

 

 Changing data @CachePut means replacing the cache

 

 Take a look at the data compared to the previous picture  

 5-5 @CachePut The principle of adding data is the same as updating data. If there is a cache to replace the cache, then add the cache

  /**
     * 添加数据缓存 有就替换 没有就添加
     * @param daliy 根据id 更改的数据
     * @return
     */
    @PostMapping("/test11")
    @ResponseBody
    public Daliy test11(@RequestBody Daliy daliy) {
        System.out.println(daliy);
        Daliy daliyAdd = daliyService.daliyAdd(daliy);
        return daliyAdd;
    }

 5-6 Here we should pay attention to when we add data without id @CachePut is to store the result set in the cache. After we add data, there will be primary key backfill. How to return the entity class?

  /**
     * 添加数据
     * @param daliy 要添加得数据
     * @return
     */
    //注意 daliy.id 现在实体类添加的数据id 是为空的  添加完数据以后为会主键回填生成id的 缓存存的是结果集
    @CachePut(value = "text", key = "#daliy.id.toString() != null ? 'user_'+#daliy.id.toString() : ''")
    Daliy daliyAdd(Daliy daliy);

dao layer 

   /**
     * 添加数据
     *
     * @param daliy 要添加得数据
     * @return
     */
    @Override
    public Daliy daliyAdd(Daliy daliy) {
        //进行了主键回填
        this.daliyMapper.insert(daliy);
        return  daliy;
    }

 If there is no primary key, use the primary key to backfill

 

 6- Delete data cache

   /**
     * 根据id删除数据
     * @param id 根据id 删除数据
     * @return
     */
    @PostMapping("/test12")
    @ResponseBody
    public void test12(String id) {
        System.out.println(id);
        int states = daliyService.deleteByid(id);
    }

 6-1 Delete the cache according to the id allEntries = true To delete all the data in the text folder, I only need to delete according to the id

    /**
     * 根据id删除数据
     * @param id
     * @return
     */
    //allEntries = true 这里不可以加这个哦 加上这个表示这个文件夹的text 会全部删除
    //下面这样写只是根据user_id 删除的
    @CacheEvict(value = "text",key = "'user_'+#id")
    int deleteByid(String id);

 //Delete the data cached with the id of 127 is also deleted

 

 7- The above demonstration is over, but there is still one point. Some companies have detailed division of labor and created many folders. At this time, you can use combined annotations. You can delete, add, and replace caches according to your files.

 @Caching(
                       //这是替换文件夹的 userCache, key id 的结果集
                 put = {@CachePut(value = "userCache", key = "#user.id")},
                       //这是删除 文件夹 allUsersCache 所有的缓存的数据
                 evict = {@CacheEvict(value = "allUsersCache", allEntries = true)}
          )
          public AppUser createUser(AppUser user) {
             logger.info("创建用户start..., user.id=" + user.getId());
             appuserRepository.save(user);
             return user;
         }

 The principle of this demonstration is the same as the one above.

Guess you like

Origin blog.csdn.net/qq_38062838/article/details/122449116