spring缓存的使用

Spring缓存使用

缓存注解

对于Spring,缓存组件例如EhCache是可拔插的,而缓存注解是通用的。

@Cacheable

标记在方法或者类上,标识该方法或类支持缓存。Spring调用注解标识方法后会将返回值缓存到redis,以保证下次同条件调用该方法时直接从缓存中获取返回值。这样就不需要再重新执行该方法的业务处理过程,提高效率。

@Cacheable常用的三个参数如下:

cacheNames 缓存名称

key 缓存的key,需要注意key的写法哈

condition 缓存执行的条件,返回true时候执行

cacheManager 使用的cacheManager的bean名称

sync 如果多个请求同时来访问同一个key的数据,则sync表示加锁同步,等第一个请求返回数据后,其他请求直接获取缓存里的数据。

java 复制代码
@Cacheable(value = "DICT", key = "'getDictById'+#id", sync = true)
@CachePut

标记在方法或者类上,标识该方法或类支持缓存。每次都会执行目标方法,并将执行结果以键值对的形式存入指定的缓存中。

java 复制代码
@CachePut(value = "DICT", key = "'getDictById'+#model.getId()")
@CacheEvict

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即

value表示清除操作是发生在哪些Cache上的(对应Cache的名称);

key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;

condition表示清除操作发生的条件。

allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。

beforeInvocation 清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

java 复制代码
@CacheEvict(value = "DICT",key = "'getDictById'+#model.getId()",beforeInvocation=false)

使用Ehcache作为缓存

pom坐标
xml 复制代码
  <dependency>
   <groupId>net.sf.ehcache</groupId>
   <artifactId>ehcache</artifactId>
   <version>2.8.3</version>
  </dependency>
   <!-- Ehcache依赖此组件创建bean -->
   <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context-support</artifactId>
       <version>5.1.5.RELEASE</version>
   </dependency>
ehcache.xml 配置文件
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
    updateCheck="false">
    <diskStore path="java.io.tmpdir/Tmp_EhCache" />
    
    <!-- defaultCache,是默认的缓存策略 -->
    <!-- 如果你指定的缓存策略没有找到,那么就用这个默认的缓存策略 -->
    <!-- external:缓存对象是否一直存在,如果设置为true的话,那么timeout就没有效果,缓存就会一直存在,一般默认就是false -->
    <!-- maxElementsInMemory:内存中可以缓存多少个缓存条目 -->
    <!-- overflowToDisk:如果内存不够的时候,是否溢出到磁盘 -->
    <!-- diskPersistent:是否启用磁盘持久化的机制,在jvm崩溃的时候和重启之间 -->
    <!-- timeToIdleSeconds:对象最大的闲置的时间,如果超出闲置的时间,可能就会过期  单位:秒 当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大-->
    <!-- timeToLiveSeconds:对象最多存活的时间  单位:秒 当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是存活时间无穷大-->
    <!-- memoryStoreEvictionPolicy:当缓存数量达到了最大的指定条目数的时候,需要采用一定的算法,从缓存中清除一批数据,LRU,最近最少使用算法,最近一段时间内,最少使用的那些数据,就被干掉了 -->
    <defaultCache
        eternal="false"
        maxElementsInMemory="1000"
        overflowToDisk="false"
        diskPersistent="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        memoryStoreEvictionPolicy="LRU" />
 
  <!-- 手动指定的缓存策略 -->
  <!-- 对不同的数据,缓存策略可以在这里配置多种 -->
    <cache
        name="local"  
        eternal="false"
        maxElementsInMemory="1000"
        overflowToDisk="false"
        diskPersistent="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        memoryStoreEvictionPolicy="LRU" />    
</ehcache>
EhcacheConfiguration.java
java 复制代码
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
 
@Configuration
@EnableCaching
public class EhcacheConfiguration {
 @Bean
 public EhCacheManagerFactoryBean cacheManagerFactoryBean(){
  EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();
  bean.setConfigLocation(new ClassPathResource("ehcache.xml"));
  bean.setShared(true);
  return bean;
 }
 @Bean
    public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean){
      return new EhCacheCacheManager(bean.getObject());
    } 
}
使用
java 复制代码
@Service
public class EhcahceServiceImpl implements EhcahceService {
 
 private static final String CACHE_STRATEGY = "local";
 
 @CachePut(value=CACHE_STRATEGY,key="#root.methodName+#info.getProduct_id()")
 @Override
 public ProductInfo saveProductInfo(ProductInfo info) throws Exception {
  return info;
 }

 @Cacheable(value=CACHE_STRATEGY,key="#root.methodName+#id")
 @Override
 public ProductInfo getProductInfoById(Long id) throws Exception {
  return null;
 }}

使用redis作为缓存

pom坐标
xml 复制代码
        <!--redis 配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
yml配置
yml 复制代码
spring:
  # redis配置
  redis:
    password: 123456
    # 集群模式
    cluster:
      nodes: 127.0.0.1:1000,127.0.0.1:2000
    # 哨兵模式
    #sentinel:
      #master: sentinel #哨兵的名字 #下面是所有哨兵集群节点
      #nodes: 11.11.11.111:26379,11.11.11.111:26380
    jedis:
      pool:
        #最大连接数
        max-active: 200
        #最大阻塞等待时间(负数表示没限制)
        max-wait: -1
        #最大空闲
        max-idle: 8
        #最小空闲
        min-idle: 0
        #连接超时时间
    expireSecond: 604800 #7天
redisCacheManage配置

此处设计了两种不同cacheName的redis缓存

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableCaching
@Slf4j
public class CacheConfig {
    @Value("${spring.redis.expireSecond}")
    private Integer expireSecond;

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                this.getRedisCacheConfigurationWithTtl(600), // 默认策略,未配置的 key 会使用这个
                this.getRedisCacheConfigurationMap() // 指定 key 策略
        );
    }

    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        redisCacheConfigurationMap.put("Dict", this.getRedisCacheConfigurationWithTtl(600));
        redisCacheConfigurationMap.put("COL_KEY", this.getRedisCacheConfigurationWithTtl(expireSecond));

        return redisCacheConfigurationMap;
    }

    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        RedisCacheConfiguration config = RedisCacheConfiguration
                .defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofSeconds(seconds));

        return config;
    }
}
使用同上

同时使用两种缓存

配置
java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
@EnableCaching
@Slf4j
public class CacheConfig {
    @Value("${spring.redis.expireSecond}")
    private Integer expireSecond;

    @Bean
    public EhCacheManagerFactoryBean cacheManagerFactoryBean() {
        EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();
        bean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        bean.setShared(true);
        return bean;
    }

    @Bean
    @Primary
    public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean) {
        return new EhCacheCacheManager(bean.getObject());
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 使用缓存的默认配置
        RedisCacheConfiguration config = RedisCacheConfiguration
                .defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofSeconds(expireSecond));
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config);
        return builder.build();
    }
}
指定缓存使用
java 复制代码
    @Cacheable(value = CACHE_NAME, key = "#root.methodName+#code",cacheManager = "redisCacheManager")

当redis出现问题时,如何禁用redis,直连数据库

java 复制代码
1.yml配置文件忽略掉redis配置 
  spring.autoconfigure.exclude:org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
2.根据条件加载CacheConfig类,当spring.redis.useRedis=true时加载,否则不加载
      @ConditionalOnProperty(prefix = "spring.redis", value = "useRedis", havingValue = "true", matchIfMissing = true)
public class CacheConfig{}
Redis工具类
java 复制代码
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;

import javax.annotation.Resource;

@Service
@ConditionalOnProperty(prefix = "spring.redis", value = "useRedis", havingValue = "true", matchIfMissing = true)
public class RedisService {

    @Resource
    private RedisConnectionFactory redisConnectionFactory;
    //会出现循环依赖---Circular reference
    //RedisService引用JedisPool--JedisPool在RedisService,只有创建RedisService的实例才可以获取JedisPool的bean
    //所以需要单独拿出JedisPool的bean

    /**
     * 获取单个对象
     *
     * @param prefix
     * @param key
     * @param data
     * @return
     */
    public <T> T get(KeyPrefix prefix, String key, Class<T> data) {
        JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
        Jedis jedis = null;
        try {
            jedis = jedisConnection.getNativeConnection();
            //生成真正的key  className+":"+prefix;  BasePrefix:id1
            String realKey = prefix.getPrefix() + key;
            String sval = jedis.get(realKey);
            //将String转换为Bean入后传出
            T t = stringToBean(sval, data);
            return t;
        } finally {
            returnToPool(jedisConnection);
        }
    }

    /**
     * 移除对象,删除
     *
     * @param prefix
     * @param key
     * @return
     */
    public boolean delete(KeyPrefix prefix, String key) {
        JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
        Jedis jedis = null;
        try {
            jedis = jedisConnection.getNativeConnection();
            String realKey = prefix.getPrefix() + key;
            long ret = jedis.del(realKey);
            return ret > 0;//删除成功,返回大于0
        } finally {
            returnToPool(jedisConnection);
        }
    }

    /**
     * 设置单个、多个对象
     *
     * @param prefix
     * @param key
     * @param value
     * @return
     */
    public <T> boolean set(KeyPrefix prefix, String key, T value) {
        JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
        Jedis jedis = null;
        try {
            jedis = jedisConnection.getNativeConnection();
            String realKey = prefix.getPrefix() + key;
            String s = beanToString(value);
            if (s == null || s.length() <= 0) {
                return false;
            }
            int seconds = prefix.getExpireSeconds();
            if (seconds <= 0) { //永不过期
                jedis.set(realKey, s);
            } else {
                jedis.setex(realKey, seconds, s);
            }
            return true;
        } finally {
            returnToPool(jedisConnection);
        }
    }

    /**
     * 减少值
     *
     * @param prefix
     * @param key
     * @return
     */
    public <T> Long decr(KeyPrefix prefix, String key) {
        JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
        Jedis jedis = null;
        try {
            jedis = jedisConnection.getNativeConnection();
            String realKey = prefix.getPrefix() + key;
            return jedis.decr(realKey);
        } finally {
            returnToPool(jedisConnection);
        }
    }

    /**
     * 增加值
     *
     * @param prefix
     * @param key
     * @return
     */
    public <T> Long incr(KeyPrefix prefix, String key) {
        JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
        Jedis jedis = null;
        try {
            jedis = jedisConnection.getNativeConnection();
            String realKey = prefix.getPrefix() + key;
            return jedis.incr(realKey);
        } finally {
            returnToPool(jedisConnection);
        }
    }

    /**
     * 获取key的过期时间
     */
    public <T> Long ttl(KeyPrefix prefix, String key) {
        JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
        Jedis jedis = null;
        try {
            jedis = jedisConnection.getNativeConnection();
            String realKey = prefix.getPrefix() + key;
            return jedis.ttl(realKey);
        } finally {
            returnToPool(jedisConnection);
        }
    }

    /**
     * 设置key的过期时间
     */
    public <T> void setExpire(KeyPrefix prefix, String key, int expire) {
        JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
        Jedis jedis = null;
        try {
            jedis = jedisConnection.getNativeConnection();
            String realKey = prefix.getPrefix() + key;
            jedis.expire(realKey, expire);
        } finally {
            returnToPool(jedisConnection);
        }
    }

    /**
     * 检查key是否存在
     *
     * @param prefix
     * @param key
     * @return
     */
    public <T> boolean exitsKey(KeyPrefix prefix, String key) {
        JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
        Jedis jedis = null;
        try {
            jedis = jedisConnection.getNativeConnection();
            String realKey = prefix.getPrefix() + key;
            return jedis.exists(realKey);
        } finally {
            returnToPool(jedisConnection);
        }
    }

    /**
     * 将字符串转换为Bean对象
     * <p>
     * parseInt()返回的是基本类型int 而valueOf()返回的是包装类Integer
     * Integer是可以使用对象方法的  而int类型就不能和Object类型进行互相转换 。
     * int a=Integer.parseInt(s);
     * Integer b=Integer.valueOf(s);
     */
    public static <T> T stringToBean(String s, Class<T> clazz) {
        if (s == null || s.length() == 0 || clazz == null) {
            return null;
        }
        if (clazz == int.class || clazz == Integer.class) {
            return ((T)Integer.valueOf(s));
        } else if (clazz == String.class) {
            return (T)s;
        } else if (clazz == long.class || clazz == Long.class) {
            return (T)Long.valueOf(s);
        } else {
            JSONObject json = JSON.parseObject(s);
            return JSON.toJavaObject(json, clazz);
        }
    }

    /**
     * 将Bean对象转换为字符串类型
     *
     * @param <T>
     */
    public static <T> String beanToString(T value) {
        //如果是null
        if (value == null) {
            return null;
        }
        //如果不是null
        Class<?> clazz = value.getClass();
        if (clazz == int.class || clazz == Integer.class) {
            return "" + value;
        } else if (clazz == String.class) {
            return "" + value;
        } else if (clazz == long.class || clazz == Long.class) {
            return "" + value;
        } else {
            return JSON.toJSONString(value);
        }
    }

    private void returnToPool(JedisConnection jedisConnection) {
        if (jedisConnection != null) {
            RedisConnectionUtils.releaseConnection(jedisConnection, redisConnectionFactory);
        }
    }

    public <T> boolean set(String key, T value) {
        JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
        Jedis jedis = null;
        try {
            jedis = jedisConnection.getNativeConnection();
            //将T类型转换为String类型
            String s = beanToString(value);
            if (s == null) {
                return false;
            }
            jedis.set(key, s);
            return true;
        } finally {
            returnToPool(jedisConnection);
        }
    }

    public <T> T get(String key, Class<T> data) {
        JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
        Jedis jedis = null;
        try {
            jedis = jedisConnection.getNativeConnection();
            String sval = jedis.get(key);
            //将String转换为Bean入后传出
            T t = stringToBean(sval, data);
            return t;
        } finally {
            returnToPool(jedisConnection);
        }
    }
}
相关推荐
BillKu1 小时前
Java + Spring Boot + Mybatis 插入数据后,获取自增 id 的方法
java·tomcat·mybatis
全栈凯哥1 小时前
Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解
java·算法·leetcode·链表
chxii1 小时前
12.7Swing控件6 JList
java
全栈凯哥1 小时前
Java详解LeetCode 热题 100(27):LeetCode 21. 合并两个有序链表(Merge Two Sorted Lists)详解
java·算法·leetcode·链表
YuTaoShao1 小时前
Java八股文——集合「List篇」
java·开发语言·list
PypYCCcccCc1 小时前
支付系统架构图
java·网络·金融·系统架构
华科云商xiao徐1 小时前
Java HttpClient实现简单网络爬虫
java·爬虫
扎瓦2 小时前
ThreadLocal 线程变量
java·后端
BillKu2 小时前
Java后端检查空条件查询
java·开发语言
jackson凌2 小时前
【Java学习笔记】String类(重点)
java·笔记·学习