• spring注解式缓存+Redis


    spring注解式缓存+Redis:

    spring注解式缓存+Redis的前提条件:spring+redis集成

    1. 导入依赖
    <!-- ********************** redis相关依赖 ********************** -->
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>${jedis.version}</version>
                <exclusions>
                    <exclusion>
                        <artifactId>commons-pool2</artifactId>
                        <groupId>org.apache.commons</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <!-- spring-data-redis 依赖-->
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-redis</artifactId>
                <version>${spring-data-redis.version}</version>
            </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. 将两个配置文件复制到resources
      redis.properties:
      文件乱码,只需要在idea里面设置一下utf-8就可以了
    #ip\u5730\u5740
    redis.hostName=192.168.199.128
    #\u7AEF\u53E3\u53F7
    redis.port=6379
    #\u5982\u679C\u6709\u5BC6\u7801
    redis.password=123456
    #\u5BA2\u6237\u7AEF\u8D85\u65F6\u65F6\u95F4\u5355\u4F4D\u662F\u6BEB\u79D2 \u9ED8\u8BA4\u662F2000
    redis.timeout=10000
    
    #redis\u7F13\u5B58\u6570\u636E\u8FC7\u671F\u65F6\u95F4\u5355\u4F4D\u79D2
    redis.expiration=3600
    
    ##################### redis\u8FDE\u63A5\u6C60\u914D\u7F6E ###########################################
    #\u6700\u5927\u7A7A\u95F2\u6570
    redis.maxIdle=300
    #\u8FDE\u63A5\u6C60\u7684\u6700\u5927\u6570\u636E\u5E93\u8FDE\u63A5\u6570\u3002\u8BBE\u4E3A0\u8868\u793A\u65E0\u9650\u5236,\u5982\u679C\u662Fjedis 2.4\u4EE5\u540E\u7528redis.maxTotal
    #redis.maxActive=600
    #\u63A7\u5236\u4E00\u4E2Apool\u53EF\u5206\u914D\u591A\u5C11\u4E2Ajedis\u5B9E\u4F8B,\u7528\u6765\u66FF\u6362\u4E0A\u9762\u7684redis.maxActive,\u5982\u679C\u662Fjedis 2.4\u4EE5\u540E\u7528\u8BE5\u5C5E\u6027
    redis.maxTotal=1000
    #\u6700\u5927\u5EFA\u7ACB\u8FDE\u63A5\u7B49\u5F85\u65F6\u95F4\u3002\u5982\u679C\u8D85\u8FC7\u6B64\u65F6\u95F4\u5C06\u63A5\u5230\u5F02\u5E38\u3002\u8BBE\u4E3A-1\u8868\u793A\u65E0\u9650\u5236\u3002
    redis.maxWaitMillis=1000
    #\u8FDE\u63A5\u7684\u6700\u5C0F\u7A7A\u95F2\u65F6\u95F4 \u9ED8\u8BA41800000\u6BEB\u79D2(30\u5206\u949F)
    redis.minEvictableIdleTimeMillis=300000
    #\u6BCF\u6B21\u91CA\u653E\u8FDE\u63A5\u7684\u6700\u5927\u6570\u76EE,\u9ED8\u8BA43
    redis.numTestsPerEvictionRun=1024
    #\u9010\u51FA\u626B\u63CF\u7684\u65F6\u95F4\u95F4\u9694(\u6BEB\u79D2) \u5982\u679C\u4E3A\u8D1F\u6570,\u5219\u4E0D\u8FD0\u884C\u9010\u51FA\u7EBF\u7A0B, \u9ED8\u8BA4-1
    redis.timeBetweenEvictionRunsMillis=30000
    #\u662F\u5426\u5728\u4ECE\u6C60\u4E2D\u53D6\u51FA\u8FDE\u63A5\u524D\u8FDB\u884C\u68C0\u9A8C,\u5982\u679C\u68C0\u9A8C\u5931\u8D25,\u5219\u4ECE\u6C60\u4E2D\u53BB\u9664\u8FDE\u63A5\u5E76\u5C1D\u8BD5\u53D6\u51FA\u53E6\u4E00\u4E2A
    redis.testOnBorrow=true
    #\u5728\u7A7A\u95F2\u65F6\u68C0\u67E5\u6709\u6548\u6027, \u9ED8\u8BA4false
    redis.testWhileIdle=true
    
    #redis\u96C6\u7FA4\u914D\u7F6E
    #redis.sentinel.host1=172.20.1.230
    #redis.sentinel.port1=26379
    
    #redis.sentinel.host2=172.20.1.231
    #redis.sentinel.port2=26379
    
    #redis.sentinel.host3=172.20.1.232
    #redis.sentinel.port3=26379
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    spring-redis.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx" xmlns:cache="http://www.springframework.org/schema/cache"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
    		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
    		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
    
        <!-- redis的相关配置已经在applicationContext.xml导入了,因为spring只允许有一个context:property-placeholder -->
        <!-- 所以下面的配置会注释掉了 -->
        <!-- 1. 引入properties配置文件 -->
        <!--<context:property-placeholder ignore-unresolvable="true" location="classpath:redis.properties" />-->
    
        <!-- 2. redis连接池配置-->
        <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
            <!--最大空闲数-->
            <property name="maxIdle" value="${redis.maxIdle}"/>
            <!--连接池的最大数据库连接数  -->
            <property name="maxTotal" value="${redis.maxTotal}"/>
            <!--最大建立连接等待时间-->
            <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
            <!--逐出连接的最小空闲时间 默认1800000毫秒(30分钟)-->
            <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
            <!--每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3-->
            <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
            <!--逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1-->
            <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
            <!--是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个-->
            <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
            <!--在空闲时检查有效性, 默认false  -->
            <property name="testWhileIdle" value="${redis.testWhileIdle}"/>
        </bean>
    
        <!-- 3. redis连接工厂 -->
        <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
              destroy-method="destroy">
            <property name="poolConfig" ref="poolConfig"/>
            <!--IP地址 -->
            <property name="hostName" value="${redis.hostName}"/>
            <!--端口号  -->
            <property name="port" value="${redis.port}"/>
            <!--如果Redis设置有密码  -->
            <property name="password" value="${redis.password}"/>
            <!--客户端超时时间单位是毫秒  -->
            <property name="timeout" value="${redis.timeout}"/>
        </bean>
    
        <!-- 4. redis操作模板,使用该对象可以操作redis  -->
        <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
            <property name="connectionFactory" ref="connectionFactory"/>
            <!--如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!!  -->
            <property name="keySerializer">
                <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
            </property>
            <property name="valueSerializer">
                <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
            </property>
            <property name="hashKeySerializer">
                <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
            </property>
            <property name="hashValueSerializer">
                <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
            </property>
            <!--开启事务  -->
            <property name="enableTransactionSupport" value="false"/>
        </bean>
    
    
    
    </beans>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    1. spring注解式缓存使用步骤
      1.1 配置缓存管理器 spring-redis.xml文件
          <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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    1.2 配置自定义Key生成器CacheKeyGenerator    spring-redis.xml文件
    缓存的Java对象一定要重写hashCode和eqauls

     <bean id="cacheKeyGenerator" class="com.zking.ssm.redis.CacheKeyGenerator"></bean>
    
    • 1
      注:CacheKeyGenerator 源码在下面
    
    • 1

    1.3 启用缓存注解功能 spring-redis.xml文件

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

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

    1. 缓存注解
      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

    2.5使用实例:

    @CacheEvict(value = "selectByPrimaryKey",key = "'selectOne-'+#id",allEntries = true)
        @Override
        public int deleteByPrimaryKey(Integer id) {
            return bookMapper.deleteByPrimaryKey(id);
        }
        
        @Cacheable(value = "selectByPrimaryKey",key = "'selectOne-'+#id",condition = "#id<10")
        @Override
        public Book selectByPrimaryKey(Integer id) {
            return bookMapper.selectByPrimaryKey(id);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. Spring-Cache key设置
      3.1 基本形式
      @Cacheable(value=“cacheName”, key=“#id”)
      public ResultDTO method(int id);

      注1:Spring Cacheable注解不缓存null值
      用Cacheable注解时,发现空值,也会被缓存下来。下次另一个系统如果更新了值,这边从缓存取,还是空值,会有问题。
      解决方案:
      @Cacheable(value = “service”, key = “#service.serviceId.toString()”, unless = “#result == null”)
      @Cacheable(value = “service”, keyGenerator = RedisKeys.KEY_GENERATOR, unless = “#result.size() == 0”)

    3.2 组合形式
    @Cacheable(value=“cacheName”, key="T(String).valueOf(#name).concat(‘-’).concat(#password))
    public ResultDTO method(int name, String password);

    3.3 对象形式
    @Cacheable(value=“cacheName”, key="#user.id)
    public ResultDTO method(User user);

    3.4 自定义Key生成器
    @Cacheable(value=“gomeo2oCache”, keyGenerator = “keyGenerator”)
    public ResultDTO method(User user);

    spring注解式缓存中的巨坑~~~~~~~
    没有指定key,默认情况下spirng会使用SimpleKeyGenerator生成key,
    而Spring默认的SimpleKeyGenerator是不会将函数名组合进key中的,举个例子:
    @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
    ClassCastException: java.lang.Long cannot be cast to java.lang.String
    此外,原子类型的数组,直接作为key使用也是不会生效的,为了解决上述2个问题,只能通过自定义KeyGenerator解决

    自定义Key生成器CacheKeyGenerator:源码:

    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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    ,另外此类使用非加密哈希算法MurmurHash
    (源码46行: Hashing.murmur3_128().hashString),需要引入google guava项目,其pom如下:

      <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>27.0.1-jre</version>
          </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. redis缓存的一些注意事项
      只应将热数据放到缓存中
      所有缓存信息都应设置过期时间
      缓存过期时间应当分散以避免集中过期
      缓存key应具备可读性
      应避免不同业务出现同名缓存key
      可对key进行适当的缩写以节省内存空间
      选择合适的数据结构
      确保写入缓存中的数据是完整且正确的
      避免使用耗时较长的操作命令,如:keys *
      Redis默认配置中操作耗时超过10ms即视为慢查询
      一个key对应的数据不应过大

      对于string类型,一个key对应的value大小应控制在10K以内,1K左右更优hash类型,不应超过5000行

      避免缓存穿透
      数据库中未查询到的数据,可在Redis中设置特殊标识,以避免因缓存中无数据而导致每次请求均达到数据库

      缓存层不应抛出异常

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

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

      读的顺序是先缓存,后数据库;写的顺序是先数据库,后缓存

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

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

  • 相关阅读:
    MySQL 主要线程
    2022前端常考手写面试题总结
    京东数据平台:2023年服饰行业销售数据分析
    ROS2——DDS(十三)
    python基础专栏12-python基础篇-复合数据类型-字典
    java基于springboot+jsp鲜活农产品销售商城系统
    (八) 共享模型之管程【活跃性】
    四、《任务列表案例》后端程序实现和测试
    IF 22.1,中科院1区TOP,顶级期刊更名!
    【算法思想】排序
  • 原文地址:https://blog.csdn.net/weixin_63719049/article/details/127799525