Redis——NoSQL与缓存

使用NoSQL

有了RedisTemplate之后,我们就可以开始保存获取以及删除键值对了。
在这里插入图片描述
假设我们想通过RedisTemplate<String,Product>保存Product,其中key是num属性的值,如下代码展示了如何借助opsForValue()来完成该功能:

redis.opsForValue().set(product.getNum(),product);//存储num-对象的键值对

如果想获得该对象Product,且num=123456:

Product product=redis.opsForValue().get("123456");

那我们使用List类型的value与之类似,只需要使用opsForList()即可:(假设我们希望在List类型的条目尾部添加一个值)

redis.opsForList().rightPush("cart",product);

使用rightPush()在列的尾部添加了一个Product,所使用这个列表在存储时,key为cart。如果这个key尚未存在,将会创建一个。同理leftPush()会在头部添加。
我们也有方法获取单个元素:

Product first=redis.opsForList().leftPop("cart");
Product last=redis.opsForList().rightPop("cart");

这两个方法有个副作用是获取的同时,也出队列了。我们如果只想获取值的话,甚至可能是在中间获取,那么我们可以使用range()方法:

List<Product> products=redis.opsForList().range("cart",2,12);

除了操作列表外,我们还可以使用opsForSet()操作Set,最为常用的操作就是Set中添加一个元素:

redis.opsForSet().add("cart",product);

在我们有多个Set并填充值之后,我们就可以对这些Set进行一些操作,比如获取其差异,求交集,求并集:

List<Product> diff=redis.opsForSet().difference("cart","cart1");
List<Product> union=redis.opsForSet().union("cart","cart1");
List<Product> isect=redis.opsForSet().isect("cart","cart1");

当然还可以移除它的元素:

redis.opsForSet().remove(product);

Redis缓存

其实缓存的条目无非就是一个键值对,很自然的想到Redis缓存。
Redis可以用来为Spring缓存抽象机制存储缓存条目。RedisCacheManager是一个CacheManager的实现。RedisCacheManager会与一个Redis服务器写作,并通过RedisTemplate将缓存条目存储到Redis中。
为了使用RedisCacheManager,我们需要RedisTemplate bean以及RedisConnectionFactory实现类的一个bean。

最后配置:

package com.promusician.config;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@PropertySource({"classpath:redis.properties"})
public class RedisConfig {
    //log
    private static final Logger log=Logger.getLogger(RedisConfig.class.getName());

    @Value("${redis.host}")
    private String host;
    @Value("${redis.port}")
    private int port;
    @Value("${redis.password}")
    private String password;
//    @Value("${redis.maxIdle}")
//    private int maxIndle;
//    @Value("${redis.testOnBorrow}")
//    private boolean testOnBorrow;
//    @Value("${redis.maxWait}")
//    private int maxWait;
//    @Value("${redis.maxActive}")
//    private int maxActive;

    @Bean
    public JedisConnectionFactory jedisConnectionFactory(){
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
        jedisConnectionFactory.setHostName(host);
        jedisConnectionFactory.setPort(port);
        jedisConnectionFactory.setPassword(password);
//        jedisConnectionFactory.afterPropertiesSet();

        return jedisConnectionFactory;
    }

    @Bean
    public RedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory){
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(jedisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        return redisTemplate;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate){
        RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
        return redisCacheManager;
    }

}

在配置完缓存管理器并启动缓存后,就可以在bean方法上应用缓存规则了。

为方法添加注解以支持缓存
以下的注解能用在方法级别或者类级别上:
在这里插入图片描述
我们可以看到@Cacheable@CachePut注解都可以填充缓存,但是他们的工作方式略有差异。
@Cacheable首先在缓存中查找条目,如果找到了匹配的条目,那么就不会对方法进行调用了。如果没有找到匹配的条目,方法会被调用并且返回值放到缓存之中。
@CachePut并不会在缓存中检查匹配的值,目标方法总是会调用,并将返回值添加到缓存中。
他们俩属性有一些是共有的:
在这里插入图片描述
在最简单的情况下,只需要使用value属性指定一个或多个缓存即可。例如考虑SpittleRepository中的findOne()方法。在初始保存之后,Spittle就不会再发生变化了。如果有的Spittle比较热门并且会被频繁的请求,我们可以在findOne()上添加@Cacheable注解,确保将Spittle保存在缓存中,从而避免对数据库的不必要的访问。

    @Cacheable("spittleCache")
    public Spittle findOne(long id) {
        return jdbc.queryForObject(
                "select id, message, created_at, latitude, longitude" +
                        " from Spittle" +
                        " where id = ?",
                new SpittleRowMapper(), id);
    }

使用@Cacheable(“spittleCache”),当findOne()被调用时,缓存切面会拦截调用并在缓存中查找之前以名"spittleCache"存储的返回值,缓存的Key是传递到findOne方法中的id参数。

将值放到缓存之中
@CachePut采用了一种更为直接的流程。带有这个注解的方法始终会被调用,而且它的返回值也会放到缓存中。这提供了很便利机制,让我们在请求之前预先加载缓存。
例如,当一个全新的Spittle通过SpittleRepository的save()方法保存之后,很可能马上就会请求这条记录。所以当save()方法调用后,立即将Spittle塞到缓存中是很有意义的。当其他人通过findOne()查找时,它已经准备就绪。

@Cacheable("spittleCache")
Spittle save(Spittle spittle);

这里唯一的问题就是,缓存的key。前文说过,缓存的key是基于方法的参数决定的。因为save的参数是Spittle,那么它会做缓存的key。然而,诡异的是,Spittle的键值对都是spittle,更不幸的是,我们希望用id作为他的key。
让我们看一下怎么自定义缓存的key。

自定义缓存Key
@Cacheable@CachePut都有一个名为key属性,这个属性能够替换默认的key,它是通过一个SpEL表达式计算得到的。
具体到我们现在的场景,我们需要将Key设置为所保存Spittle的ID,以参数形式传递给save()返回的Spittle得到的id属性。幸好,为缓存编写SpEL表达式的时候,Spring暴露了一些很有用的元数据。
在这里插入图片描述
对于Save()方法, 我们需要的键是所返回的Spittle对象的id,表达式#result能够得到方法调动的返回值Spittle对象。我们可以将key属性设置为#result.id来引用id属性。

@CachePut(value="spittleCache",key="#result.id")
Spittle save(Spittle spittle);

条件化缓存
通过为方法添加Spring的缓存注解,Spring就会围绕这个方法创建一个缓存切面。但是,在有些场景下我们可能希望将缓存功能关闭。
@Cacheable@CachePut提供了两个属性以实现条件化缓存:unless和condition。这两个属性都接受一个SpEL表达式。如果unless属性的SpEL表达式计算结果为true,那么缓存方法返回的数据就不会放到缓存中。与之类似,如果condition计算结果为false,那么这个方法缓存就会被禁用掉。

表面上看unless和condition都是做的相同的事情,但是实际上,unless属性只能组织对象放进缓存,但是在这个方法调用的时候,依然先去缓存中查找,没找到才调用方法。而condition的表达结果为false时,不会去缓存中查找,直接调用方法,同时返回值也不会放进缓存中。

作为样例(尽管有些牵强),假设对于message属性包含"NoCache"的Spittle对象不进行缓存。为了阻止这样的对象被缓存起来,我们使用unless:

@Cacheable(value="spittleCache"
	unless="#result.message.contains('NoCache')")
Spittle findOne(long id);

假如我们对于ID小于10的Spittle对象不使用缓存,也不希望从缓存中获取数据:

@Cacheable(value="spittleCache"
	unless="#result.message.contains('NoCache')"
	condition="#id>=10")
Spittle findOne(long id);

移除缓存条目

@CacheEvict并不往缓存中添加任何东西,相反,它还会移除一个或更多的在缓存中的条目。
在什么场景需要从缓存中移除内容呢?当缓存值不再合法时,我们应该确保将其从缓存中移除,这样的话,后续的缓存命中就不会返回旧的或者已经不存在的值。

这样的话,SpittleRepository的remove()方法就是使用@CacheEvict的绝佳选择:

@CacheEvict("spittleCache")
void remove(long spittleId);

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/No_Game_No_Life_/article/details/84336144