• Spring注解式缓存


    目录

    前言:spring+redis集成已完成的前提下​编辑Spring注解式缓存

    1. spring注解式缓存使用步骤

      1.1 配置缓存管理器 

      1.2 配置自定义Key生成器CacheKeyGenerator           缓存的Java对象一定要重写hashCode和eqauls

      1.3 启用缓存注解功能

      1.4 在需要的地方进行注解缓存   

    2. 缓存注解

      2.1 @CacheConfig        它是一个类级别的注解,允许共享缓存的名称、KeyGenerator、CacheManager和CacheResolver

       2.2 @Cacheable  

            2.3 @CachePut

      2.4 @CacheEvict

    3. Spring-Cache key设置

      3.1 基本形式     

      3.2 组合形式

      3.3 对象形式

      3.4 自定义Key生成器

    5. redis缓存的一些注意事项

    6. 根据用户ID或公司ID进行查询(此想法未测试)


     

    前言:spring+redis集成已完成的前提下Spring注解式缓存

     Redis是key-value存储的非关系型数据库。Spring Data Redis包含了多个模板实现,用来完成Redis数据库的数据存取功能



    1. spring注解式缓存使用步骤

      1.1 配置缓存管理器
     

    1.     "redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
    2.         <constructor-arg name="redisOperations" ref="redisTemplate" />
    3.        
    4.         <property name="defaultExpiration" value="${redis.expiration}" />
    5.         <property name="usePrefix" value="true"/>
    6.         <property name="cachePrefix">
    7.             <bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix">
    8.                 <constructor-arg index="0" value="-cache-"/>
    9.             bean>
    10.         property>
    11.    

      1.2 配置自定义Key生成器CacheKeyGenerator

               缓存的Java对象一定要重写hashCode和eqauls

     "cacheKeyGenerator" class="com.zking.ssm.redis.CacheKeyGenerator">

      1.3 启用缓存注解功能

         <cache:annotation-driven cache-manager="redisCacheManager" key-generator="cacheKeyGenerator"/>

      1.4 在需要的地方进行注解缓存
       

    2. 缓存注解


      2.1 @CacheConfig  
          它是一个类级别的注解,允许共享缓存的名称、KeyGenerator、CacheManager和CacheResolver

          value:缓存位置的一段名称,不能为空
          key:缓存的key,默认为空,表示使用方法的参数类型及参数值作为key,支持SpEL

     
      2.2 @Cacheable  


          配置在方法或类上,作用:本方法执行后,先去缓存看有没有数据,如果没有,从数据库中查找出来,给缓存中存一份,返回结果,
          下次本方法执行,在缓存未过期情况下,先在缓存中查找,有的话直接返回,没有的话从数据库查找
          value:缓存位置的一段名称,不能为空
          key:缓存的key,默认为空,表示使用方法的参数类型及参数值作为key,支持SpEL
          keyGenerator:指定key的生成策略
          condition:触发条件,满足条件就加入缓存,默认为空,表示全部都加入缓存,支持SpEL

        
          注1:condition是在方法执行前评估, unless是在方法执行后评估. 


          
      2.3 @CachePut


          类似于更新操作,即每次不管缓存中有没有结果,都从数据库查找结果,并将结果更新到缓存,并返回结果
           value    缓存的名称,在 spring 配置文件中定义,必须指定至少一个
          key    缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合
          condition    缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存

      2.4 @CacheEvict


          用来清除用在本方法或者类上的缓存数据(用在哪里清除哪里)
          value:缓存位置的一段名称,不能为空
          key:缓存的key,默认为空,表示使用方法的参数类型及参数值作为key,支持SpEL
          condition:触发条件,满足条件就加入缓存,默认为空,表示全部都加入缓存,支持SpEL
          allEntries:true表示清除value中的全部缓存,默认为false


    3. Spring-Cache key设置


      3.1 基本形式

    1. @Cacheable(value="cacheName", key="#id")
    2.       public ResultDTO method(int id);

          注1:Spring Cacheable注解不缓存null值
               用Cacheable注解时,发现空值,也会被缓存下来。下次另一个系统如果更新了值,这边从缓存取,还是空值,会有问题。
               解决方案:

    1.  @Cacheable(value = "service", key = "#service.serviceId.toString()", unless = "#result == null")
    2.            @Cacheable(value = "service", keyGenerator = RedisKeys.KEY_GENERATOR, unless = "#result.size() == 0")

      3.2 组合形式

    1.   @Cacheable(value="cacheName", key="T(String).valueOf(#name).concat('-').concat(#password))
    2.       public ResultDTO method(int name, String password);

      3.3 对象形式

    1.       @Cacheable(value="cacheName", key="#user.id)
    2.       public ResultDTO method(User user);

          注1:以上三种配置方式中,使用了spEL表达式

      3.4 自定义Key生成器

    1. @Cacheable(value="gomeo2oCache", keyGenerator = "keyGenerator")
    2.       public ResultDTO method(User user);

          spring注解式缓存中的巨坑~~~~~~~
          没有指定key,默认情况下spirng会使用SimpleKeyGenerator生成key,
          而Spring默认的SimpleKeyGenerator是不会将函数名组合进key中的,举个例子:
       

    1.   @Component
    2.       public class CacheTestImpl implements CacheTest {
    3.         @Cacheable("databaseCache")
    4.         public Long test1()
    5.         { return 1L; }
    6.  
    7.         @Cacheable("databaseCache")
    8.         public Long test2()
    9.         { return 2L; }
    10.  
    11.         @Cacheable("databaseCache")
    12.         public Long test3()
    13.         { return 3L; }
    14.  
    15.         @Cacheable("databaseCache")
    16.         public String test4()
    17.         { return "4"; }//注意返回的是字符串“4”
    18.       }


          我们期望的输出是:
          1
          2
          3
          4
          而实际上的输出是:
          1
          1
          1
          此外,原子类型的数组,直接作为key使用也是不会生效的,为了解决上述2个问题,只能通过自定义KeyGenerator解决


          自定义Key生成器CacheKeyGenerator:源码见资料“CacheKeyGenerator.java”,另外此类使用非加密哈希算法MurmurHash

    CacheKeyGenerator.java:

    1. package com.zking.ssm.redis;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.cache.interceptor.KeyGenerator;
    4. import org.springframework.util.ClassUtils;
    5. import java.lang.reflect.Array;
    6. import java.lang.reflect.Method;
    7. @Slf4j
    8. public class CacheKeyGenerator implements KeyGenerator {
    9. // custom cache key
    10. public static final int NO_PARAM_KEY = 0;
    11. public static final int NULL_PARAM_KEY = 53;
    12. @Override
    13. public Object generate(Object target, Method method, Object... params) {
    14. StringBuilder key = new StringBuilder();
    15. key.append(target.getClass().getSimpleName()).append(".").append(method.getName()).append(":");
    16. if (params.length == 0) {
    17. key.append(NO_PARAM_KEY);
    18. } else {
    19. int count = 0;
    20. for (Object param : params) {
    21. if (0 != count) {//参数之间用,进行分隔
    22. key.append(',');
    23. }
    24. if (param == null) {
    25. key.append(NULL_PARAM_KEY);
    26. } else if (ClassUtils.isPrimitiveArray(param.getClass())) {
    27. int length = Array.getLength(param);
    28. for (int i = 0; i < length; i++) {
    29. key.append(Array.get(param, i));
    30. key.append(',');
    31. }
    32. } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
    33. key.append(param);
    34. } else {//Java一定要重写hashCode和eqauls
    35. key.append(param.hashCode());
    36. }
    37. count++;
    38. }
    39. }
    40. String finalKey = key.toString();
    41. log.debug("using cache key={}", finalKey);
    42. return finalKey;
    43. }
    44. }


          (源码46行: Hashing.murmur3_128().hashString),需要引入google guava项目,其pom如下:
       

    1.  
    2.         <groupId>com.google.guavagroupId>
    3.         <artifactId>guavaartifactId>
    4.         <version>27.0.1-jreversion>
    5.      


    5. redis缓存的一些注意事项


       只应将热数据放到缓存中
       所有缓存信息都应设置过期时间
       缓存过期时间应当分散以避免集中过期
       缓存key应具备可读性
       应避免不同业务出现同名缓存key
       可对key进行适当的缩写以节省内存空间
       选择合适的数据结构
       确保写入缓存中的数据是完整且正确的
       避免使用耗时较长的操作命令,如:keys *
       Redis默认配置中操作耗时超过10ms即视为慢查询   
       一个key对应的数据不应过大

       对于string类型,一个key对应的value大小应控制在10K以内,1K左右更优hash类型,不应超过5000行 避免缓存穿透
       数据库中未查询到的数据,可在Redis中设置特殊标识,以避免因缓存中无数据而导致每次请求均达到数据库缓存层不应抛出异常

       缓存应有降级处理方案,缓存出了问题要能回源到数据库进行处理

       可以进行适当的缓存预热
       对于上线后可能会有大量读请求的应用,在上线之前可预先将数据写入缓存中读的顺序是先缓存,后数据库;写的顺序是先数据库,后缓存

       数据一致性问题
       数据源发生变更时可能导致缓存中数据与数据源中数据不一致,应根据实际业务需求来选择适当的缓存更新策略:

       主动更新:在数据源发生变更时同步更新缓存数据或将缓存数据过期。一致性高,维护成本较高。
       被动删除:根据缓存设置的过期时间有Redis负责数据的过期删除。一致性较低,维护成本较低。

    6. 根据用户ID或公司ID进行查询(此想法未测试)

    1.  @Transactional(readOnly = true)
    2.    @Cacheable(value = "service+'By'+service.userId", unless = "#result.size() == 0")
    3.    List listByUserId(Service service, PageBean pageBean);

     


     

  • 相关阅读:
    MIT 开源 ScalarGui 图形化搞定超大 Git 仓库克隆,支持断点续传与稀疏检出
    linux下打包qt程序
    新型海上风电机组及压缩空气储能系统的建模与控制(Matlab代码实现)
    Linux基础组件之分布式锁的实现
    用DIV+CSS技术设计的凤阳旅游网站(web前端网页制作课作业)HTML+CSS+JavaScript
    利用Python turtle绘制中国结附源码
    30-33、SpringBoot项目部署\属性配置方式\多环境开发(一个文件)\多环境分组(多个文件)
    Kubernetes Namespace
    C++QT开发——TCP&UDP网络编程
    Python实现聚类分析和数据降维
  • 原文地址:https://blog.csdn.net/m0_62019369/article/details/127835895