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);
        }
    }
}
相关推荐
WaaTong14 分钟前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
m0_7430484414 分钟前
初识Java EE和Spring Boot
java·java-ee
AskHarries16 分钟前
Java字节码增强库ByteBuddy
java·后端
小灰灰__36 分钟前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
夜雨翦春韭40 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
程序媛小果1 小时前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林1 小时前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac
芒果披萨1 小时前
El表达式和JSTL
java·el
duration~2 小时前
Maven随笔
java·maven
zmgst2 小时前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql