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. 将两个配置文件复制到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

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. 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 配置自定义Key生成器CacheKeyGenerator    spring-redis.xml文件
缓存的Java对象一定要重写hashCode和eqauls

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

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

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

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. 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;
    }
}

,另外此类使用非加密哈希算法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. redis缓存的一些注意事项
    只应将热数据放到缓存中
    所有缓存信息都应设置过期时间
    缓存过期时间应当分散以避免集中过期
    缓存key应具备可读性
    应避免不同业务出现同名缓存key
    可对key进行适当的缩写以节省内存空间
    选择合适的数据结构
    确保写入缓存中的数据是完整且正确的
    避免使用耗时较长的操作命令,如:keys *
    Redis默认配置中操作耗时超过10ms即视为慢查询
    一个key对应的数据不应过大

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

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

    缓存层不应抛出异常

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

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

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

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

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

你可能感兴趣的:(面对对象,Java,redis,缓存,spring)