springboot(7)集成缓存技术之Redis

添加jar包依赖

<dependency>
     <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Redis配置文件

spring.redis.database=0
spring.redis.port=6379
spring.redis.host=172.21.13.24
spring.redis.timeout=5000
#spring.redis.password=123
 
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=1
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=6000

创建配置类

package net.thinktrader.fundta.cache;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;

import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class RedisConfig {
 
 /**
  * redis连接池配置
  * @return
  */
 @Bean
    @ConfigurationProperties(prefix="spring.redis.pool")
    public JedisPoolConfig getRedisConfig(){
        JedisPoolConfig config = new JedisPoolConfig();
        return config;
    }
    /**
     * redis连接工厂配置
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix="spring.redis")
    public JedisConnectionFactory getConnectionFactory(){
        JedisConnectionFactory factory = new JedisConnectionFactory();
        JedisPoolConfig config = getRedisConfig();
        factory.setPoolConfig(config);
        return factory;
    }

   
    /**
     * Spring Data Redis提供了两个模板:
     * RedisTemplate
     * StringRedisTemplate
     * 这里创建一个RedisTemplate模板类,类型的key是String类型,value是Object类型(如果key和value都是String类型,建议使用StringRedisTemplate)
     *
     * 当保存一条数据的时候,key和value都要被序列化成json数据,取出来的时候反序列化成对象,key和value都会使用序列化器进行序列化,spring data redis提供多个序列化器
     * GenericToStringSerializer:使用Spring转换服务进行序列化;
     * JacksonJsonRedisSerializer:使用Jackson 1,将对象序列化为JSON;
     * Jackson2JsonRedisSerializer:使用Jackson 2,将对象序列化为JSON;
     * JdkSerializationRedisSerializer:使用Java序列化;
     * OxmSerializer:使用Spring O/X映射的编排器和解排器(marshaler和unmarshaler)实现序列化,用于XML序列化;
     * StringRedisSerializer:序列化String类型的key和value。
     * RedisTemplate会默认使用JdkSerializationRedisSerializer,这意味着key和value都会通过Java进行序列化。StringRedisTemplate默认会使用StringRedisSerializer
     * 例如,假设当使用RedisTemplate的时候,我们希望将value序列化为JSON,而key是String类型。需要用RedisTemplate的setKeySerializer()和setValueSerializer()方法进行设置
     *
     *
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
 @Bean("redisTemplate")
    public RedisTemplate redisTemplate(RedisConnectionFactory factory){
     // 创建一个模板类
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        // 将刚才的redis连接工厂设置到模板类中
        template.setConnectionFactory(factory);
        // 设置key的序列化器
        template.setKeySerializer(new StringRedisSerializer());
        // 设置value的序列化器
        //使用Jackson 2,将对象序列化为JSON
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //json转对象类,不设置默认的会将json转成hashmap
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);

        return template;
    }
   
    /**
     * 申明缓存管理器,会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut)
     * 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值
     * 当然,缓存管理器除了RedisCacheManager还有一些其他的。例如
     * SimpleCacheManager
     * NoOpCacheManager
     * ConcurrentMapCacheManager:这个简单的缓存管理器使用java.util.concurrent.ConcurrentHashMap作为其缓存存储。它非常简单,因此对于开发、测试或基础的应用来讲,这是一个很不错的选择.
     * CompositeCacheManager
     * EhCacheCacheManager
     * @return
     */
    @SuppressWarnings("rawtypes")
 @Bean
    public RedisCacheManager cacheManager(RedisTemplate redisTemplate) {
        return new RedisCacheManager(redisTemplate);
    }
   
}

Spring Data Redis提供了两个模板:

  • RedisTemplate
  • StringRedisTemplate

上述代码中,创建一个RedisTemplate模板类,类型的key是String类型,value是Object类型(如果key和value都是String类型,建议使用StringRedisTemplate)。

 

key和value序列化:

当保存一条数据的时候,key和value都要被序列化,取出来的时候反序列化成对象,key和value都会使用序列化器进行序列化,spring data redis提供多个序列化器

  • GenericToStringSerializer:使用Spring转换服务进行序列化;
  • JacksonJsonRedisSerializer:使用Jackson 1,将对象序列化为JSON;
  • Jackson2JsonRedisSerializer:使用Jackson 2,将对象序列化为JSON;
  • JdkSerializationRedisSerializer:使用Java序列化;
  • OxmSerializer:使用Spring O/X映射的编排器和解排器(marshaler和unmarshaler)实现序列化,用于XML序列化;
  • StringRedisSerializer:序列化String类型的key和value。

RedisTemplate会默认使JdkSerializationRedisSerializer,即缓存的键值都通过Java进行序列化。StringRedisTemplate默认会使用StringRedisSerializer。上述代码中使用了RedisTemplate的setKeySerializer()和setValueSerializer()方法设置key和value的序列化器。

 

Redis测试代码如下

springboot缓存某个方法

向上面那样使用Redis,是一件比较麻烦的事,springboot为了能减少开发的工作量,允许对某个方法进行缓存操作。但必须配置缓存管理器。

缓存管理器代码如上面RedisConfig类中的cacheManager方法,且为了让缓存管理器生效,还需要在系统启动类(或者单元测试启动类)上添加@EnableCaching注解。这个注解会在系统启动时被springboot发现,并且在执行有CachePut、CacheEvict、Cacheable注解修饰的方法时,进行缓存操作。

内部原理应该是系统在启动时,使用动态代理初始化bean,在执行bean的相关方法时,看这个方法是否有CachePut等注解,如果有,则执行CacheInterceptor拦截器,该拦截器会根据注解的名称以及注解的配置进行相关的操作,并决定最终是否需要调用bean的目标方法,以及调用该方法后该如何处理。

springboot缓存管理器有如下几种:

  • RedisCacheManager:Redis缓存管理器
  • SimpleCacheManager
  • NoOpCacheManager
  • ConcurrentMapCacheManager:使用java中的 ConcurrentHashMap作为其缓存存储。
  • CompositeCacheManager
  • EhCacheCacheManager

@Cacheable:缓存方法返回值,主要用在查询方法中,先看缓存中是否有查询的数据,如果没有,则执行方法,并将方法执行的结果数据缓存到缓存中,示例代码如下:

@Cacheable(cacheNames="userCache15",key="'user_'+#id",condition="#id>10")
 public User getUser(Long id){
        System.err.println("如果第二次没有走到这里说明缓存被添加了");
        User u=new User();
        u.setDataSourceName(DefaultDBConfigContent.DB1);
        User result=findById(id);
        return result;
    }

value/cacheNames:缓存名称。

key:缓存的key,#id表示为id的参数,也可以写成#p0,表示第一个参数,#p1表示第二个参数,如果参数为对象,还可以写成#p0.id这种形式。

condition:缓存的条件,如果条件为true,则缓存,否则不缓存。

注意:

@Cacheable不能缓存所有数据,即代码如下所示:

@Cacheable(cacheNames="userCache15")
 public List<User> getUsers(){
        System.out.println("如果第二次没有走到这里说明缓存被添加了");
        User u=new User();
        u.setDataSourceName(DefaultDBConfigContent.DB1);
        return findAll(u);
    }

可能与我们常规的理解不一样。如果执行顺序这样:第一次向数据库插入一条数据(同时插入缓存),调用这个方法,第二次向数据库插入一条数据(同时插入缓存),再调用这个方法,常规理解,第二次调用这个方法时应该返回2条数据,但结果是只返回第一次插入的数据。可能是springboot在第一次调用这个方法后,将结果进行本地缓存,以后再调用这个方法,直接将本地缓存的结果返回给用户。至于要如何获得缓存中的所有数据,可能需要通过工具类直接操作Redis。

 

@CachePut:缓存方法返回值,主要用在新增和修改方法上,即方法先保存到数据库中,方法结束后,将返回值保存和更新到缓存中。示例代码如下:

@CachePut(cacheNames="userCache15",key="'user_'+#result.id")
 public User saveUser(User vo) {
        super.save(vo);
        return vo;
    }
 
 @CachePut(cacheNames="userCache15",key="'user_'+#result.id")
 public User updateUser(User vo) {
        super.update(vo);
        return vo;
    }

#result为方法的返回值,不能用在@Cacheable中。

其他参数如value、key和@Cacheable意义一致。

 

@CacheEvict:删除缓存中的值。示例代码如下:

/**
  * 删除缓存中所有数据
  * @return
  */
 @CacheEvict(cacheNames="userCache15",allEntries=true,beforeInvocation=false)
    public String deleteUser(){
        return "SUCCESS";
    }
 /**
  * 删除缓存中id为指定id的数据,注意:如果这里的allEntries为true,则会删除所有值,而不是只删除id对应的值
  * @param id
  * @return
  */
 @CacheEvict(cacheNames="userCache15",key="'user_'+#id",allEntries=false,beforeInvocation=false)
    public String deleteUser(Long id){
        return "SUCCESS";
    }

allEntries:表示是否删除所有值,默认为false

beforeInvocation:表示是否在执行本方法前删除缓存中符合条件的值,默认为false。

其他参数如value、key和@Cacheable意义一致。

 

除了上面介绍到的#result外,Spring Cache提供了一些供我们使用的SpEL上下文数据,如下表所示:

表达式描述实例
#root.args缓存方法的参数,是一个数组key="'user_'+#root.args[0]"
#root.target当前被调用的目标对象condition = "#root.target.canCache()"
//目标对象的canCache方法的返回值为条件
#root.targetClass目标对象的类condition = "#root.targetClass.canCache()"
//目标对象的canCache静态方法返回值
#root.caches当前方法调用使用的缓存列表
(如@Cacheable(value=
{“cache1”, “cache2”}),
则有两个cache
condition = "#root.caches[0].get(#user.id)
.get().username ne #user.username"
#root.methodName当前被调用的方法名condition = "#root.methodName
 ne 'getUser'"
#root.method当前被调用的方法 
#result方法执行后的返回值
仅当方法执行之后且判断有效的
数据,如‘unless’过滤后的数据
key="'user_'+#result.id"
argument当前被调用的方法的参数,
如findById(Long id),
我们可以通过#id或者#p0或者#a0拿到参数

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值