Spring注解式缓存

2022-11-22 15:45:10 浏览数 (1)

目录

前言: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 配置缓存管理器

代码语言:javascript复制
    <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
        <constructor-arg name="redisOperations" ref="redisTemplate" />
        <!--redis缓存数据过期时间单位秒-->
        <property name="defaultExpiration" value="${redis.expiration}" />
        <property name="usePrefix" value="true"/>

        <property name="cachePrefix">
            <bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix">
                <constructor-arg index="0" value="-cache-"/>
            </bean>
        </property>

    </bean>

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

代码语言:javascript复制
 <bean id="cacheKeyGenerator" class="com.zking.ssm.redis.CacheKeyGenerator"></bean>

  1.3 启用缓存注解功能

代码语言:javascript复制
     <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 基本形式

代码语言:javascript复制
 @Cacheable(value="cacheName", key="#id")
      public ResultDTO method(int id);

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

代码语言:javascript复制
 @Cacheable(value = "service", key = "#service.serviceId.toString()", unless = "#result == null")
           @Cacheable(value = "service", keyGenerator = RedisKeys.KEY_GENERATOR, unless = "#result.size() == 0")

  3.2 组合形式

代码语言:javascript复制
  @Cacheable(value="cacheName", key="T(String).valueOf(#name).concat('-').concat(#password))
      public ResultDTO method(int name, String password);

  3.3 对象形式

代码语言:javascript复制
      @Cacheable(value="cacheName", key="#user.id)
      public ResultDTO method(User user);

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

  3.4 自定义Key生成器

代码语言:javascript复制
@Cacheable(value="gomeo2oCache", keyGenerator = "keyGenerator")
      public ResultDTO method(User user);

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

代码语言:javascript复制
  @Component
      public class CacheTestImpl implements CacheTest {
        @Cacheable("databaseCache")
        public Long test1()
        { return 1L; }
 
        @Cacheable("databaseCache")
        public Long test2()
        { return 2L; }
 
        @Cacheable("databaseCache")
        public Long test3()
        { return 3L; }
 
        @Cacheable("databaseCache")
        public String test4()
        { return "4"; }//注意返回的是字符串“4”
      }

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

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

CacheKeyGenerator.java:

代码语言:javascript复制
package com.zking.ssm.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Method;

@Slf4j
public class CacheKeyGenerator implements KeyGenerator {
    // custom cache key
    public static final int NO_PARAM_KEY = 0;
    public static final int NULL_PARAM_KEY = 53;

    @Override
    public Object generate(Object target, Method method, Object... params) {
        StringBuilder key = new StringBuilder();
        key.append(target.getClass().getSimpleName()).append(".").append(method.getName()).append(":");
        if (params.length == 0) {
            key.append(NO_PARAM_KEY);
        } else {
            int count = 0;
            for (Object param : params) {
                if (0 != count) {//参数之间用,进行分隔
                    key.append(',');
                }
                if (param == null) {
                    key.append(NULL_PARAM_KEY);
                } else if (ClassUtils.isPrimitiveArray(param.getClass())) {
                    int length = Array.getLength(param);
                    for (int i = 0; i < length; i  ) {
                        key.append(Array.get(param, i));
                        key.append(',');
                    }
                } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
                    key.append(param);
                } else {//Java一定要重写hashCode和eqauls
                    key.append(param.hashCode());
                }
                count  ;
            }
        }

        String finalKey = key.toString();
        log.debug("using cache key={}", finalKey);
        return finalKey;
    }
}

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

代码语言:javascript复制
  <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>27.0.1-jre</version>
      </dependency>

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

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

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

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

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

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

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

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

代码语言:javascript复制
 @Transactional(readOnly = true)
   @Cacheable(value = "service 'By' service.userId", unless = "#result.size() == 0")
   List<Service> listByUserId(Service service, PageBean pageBean);

0 人点赞