地址:
https://www.cnblogs.com/nfcm/p/7833032.html
第一步,需要加上springboot的redis jar包
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-data-redisartifactId> dependency>
然后我们写一个配置类,创建了一个redis连接的工厂的spring bean。(Redis连接工厂会生成到Redis数据库服务器的连接)
@Configuration public class RedisConfig { @Bean public RedisConnectionFactory redisCF(){ //如果什么参数都不设置,默认连接本地6379端口 JedisConnectionFactory factory = new JedisConnectionFactory(); factory.setPort(6379); factory.setHostName("localhost"); return factory; } }
单元测试,看看这个工厂方法的使用
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = Application.class) public class RedisTest { @Autowired RedisConnectionFactory factory; @Test public void testRedis(){ //得到一个连接 RedisConnection conn = factory.getConnection(); conn.set("hello".getBytes(), "world".getBytes()); System.out.println(new String(conn.get("hello".getBytes()))); } }
输出结果 :world,说明已经成功获取到连接,并且往redis获取添加数据,
但是我们发现每次添加的key和value都是byte数组类型(使用很麻烦),于是spring为我们带来了redis template(模版)
Spring Data Redis提供了两个模板:
RedisTemplate
StringRedisTemplate
首先我们先创建一个RedisTemplate模板类,类型的key是String类型,value是Object类型(如果key和value都是String类型,建议使用StringRedisTemplate)
@Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory){ //创建一个模板类 RedisTemplatetemplate = new RedisTemplate (); //将刚才的redis连接工厂设置到模板类中 template.setConnectionFactory(factory); return template; }
单元测试
@Autowired RedisTemplatetemplate; @Test public void testRedisTemplate(){ template.opsForValue().set("key1", "value1"); System.out.println(template.opsForValue().get("key1")); }
得到结果输出value1,是不是很方便了呢。
如果是操作集合呢,也很方便的哈。
@Test public void testRedisTemplateList(){ Pruduct prud = new Pruduct(1, "洗发水", "100ml"); Pruduct prud2 = new Pruduct(2, "洗面奶", "200ml"); //依次从尾部添加元素 template.opsForList().rightPush("pruduct", prud); template.opsForList().rightPush("pruduct", prud); //查询索引0到商品总数-1索引(也就是查出所有的商品) List
当保存一条数据的时候,key和value都要被序列化成json数据,取出来的时候被序列化成对象,key和value都会使用序列化器进行序列化,spring data redis提供多个序列化器
RedisTemplate会默认使用JdkSerializationRedisSerializer,这意味着key和value都会通过Java进行序列化。StringRedisTemplate默认会使用StringRedisSerializer
例如,假设当使用RedisTemplate的时候,我们希望将Product类型的value序列化为JSON,而key是String类型。RedisTemplate的setKeySerializer()和setValueSerializer()方法就需要如下所示:
@Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { // 创建一个模板类 RedisTemplatetemplate = new RedisTemplate (); // 将刚才的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; }
到这里,大家肯定会对springboot使用redis有了简单的了解。
在某些时候,我们可能有这样的需求,用户登录的时候,我们会从数据库中读取用户所有的权限,部门等信息。而且每次刷新页面都需要判断该用户有没有这个权限,如果不停的从数据库中读并且计算,
是非常耗性能的,所以我们这个时候就要用到了springboot为我们带来的缓存管理器
首先在我们的RedisConfig这个类上加上@EnableCaching这个注解。
/** * 申明缓存管理器,会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut) * 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值 * @return */ @Bean public RedisCacheManager cacheManager(RedisTemplate redisTemplate) { return new RedisCacheManager(redisTemplate); }
当然,缓存管理器除了RedisCacheManager还有一些其他的。例如
ConcurrentMapCacheManager,这个简单的缓存管理器使用java.util.concurrent.ConcurrentHashMap作为其缓存存储。它非常简单,因此对于开发、测试或基础的应用来讲,这是一个很不错的选择.
接下来我们在controller层的方法内加上注解,然后启动我们的项目。
@RequestMapping("/getPrud") @Cacheable("prudCache") public Pruduct getPrud(@RequestParam(required=true)String id){ System.out.println("如果第二次没有走到这里说明缓存被添加了"); return pruductDao.getPrud(Integer.parseInt(id)); }
发现打印的这段话只被打印一次,说明在走到这个方法的时候触发了一个切面,并且查找返回缓存中的数据。
当然@Cacheable注解也可以放到这个dao层的方法里面,但是这里会报一个错,Integer无法转成String,因为我们dao层方法的参数类型是int,而RedisTemplate的key类型是String,这里是要注意的。
打开redis的客户端发现redis对应的key就是我们的参数1,这个时候就会出问题,比如说我在其他要缓存的方法的参数也是1,就会重复。后面我们会将自定义这个key的值。
除了@Cacheable添加缓存外,springboot还为我们带了了其他几个注解
在delete的时候用@CacheEvict清楚这条缓存。
@RequestMapping("/deletePrud") @CacheEvict("pruddeleteCache") public String deletePrud(@RequestParam(required=true)String id){ return "SUCCESS"; }
@CachePut将这个方法的返回值放到缓存,如果我们放一个Pruduct对象,他会将这个对象作为key,这显然不是我们想要的。这个时候就需要自定义我们的key。
@Cacheable和@CachePut都有一个名为key属性,这个属性能够替换默认的key,它是通过一个表达式(Spel表达式,spring提供的,很简单)计算得到的。
例如下面的就是将返回对象的id当作key来存储(但是Pruduct的id是int类型,所以需要将数字转化成String类型)
@RequestMapping("/savePrud") @CachePut(value="prudsaveCache",key="#result.id +''") public Pruduct savePrud(Pruduct prud){ return prud; }
另外除了#result是代表函数的返回值,spring还为我们带来了其他的一些元数据
通过为方法添加Spring的缓存注解,Spring就会围绕着这个方法创建一个缓存切面。但是,在有些场景下我们可能希望将缓存功能关闭。
@Cacheable和@CachePut提供了两个属性用以实现条件化缓存:unless和condition,这两个属性都接受一个SpEL表达式。如果unless属性的SpEL表达式计算结
果为true,那么缓存方法返回的数据就不会放到缓存中。与之类似,如果condition属性的SpEL表达式计算结果为false,那么对于这个方法缓存就会被禁用掉
表面上来看,unless和condition属性做的是相同的事情。但是,这里有一点细微的差别。
unless属性只能阻止将对象放进缓存,但是在这个方法调用的时候,依然会去缓存中进行查找,如果找到了匹配的值,就会返回找到的值。
与之不同,如果condition的表达式计算结果为false,那么在这个方法调用的过程中,缓存是被禁用的。就是说,不会去缓存进行查找,同时返回值也不会放进缓存中。
@RequestMapping("/getPrud2") @CachePut(value ="prudCache",unless="#result.desc.contains('nocache')") public Pruduct getPrud2(@RequestParam(required=true)String id){ System.out.println("如果走到这里说明,说明缓存没有生效!"); Pruduct p = new Pruduct(Integer.parseInt(id), "name_nocache"+id, "nocache"); return p; }
上面的代码中,如果返回的对象desc中包含nocache字符串,则不进行缓存。
将类名方法名和pruduct的id作为key
@RequestMapping("/getPrud3") @Cacheable(value ="prudCache",key="#root.targetClass.getName() + #root.methodName + #id") public Pruduct getPrud3(@RequestParam(required=true)String id){ System.out.println("如果第二次没有走到这里说明缓存被添加了"); return pruductDao.getPrud(Integer.parseInt(id)); }
最后注意
#result 方法返回值不能用在@Cacheable上,只能用在@CachePut
当然上面的配置只是为了了解原理的哈,实际上我们使用会更简单点。我们重写了RedisConfig
@Configuration @EnableCaching//开启缓存 public class RedisConfig extends CachingConfigurerSupport { @Bean public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder sb = new StringBuilder(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } /** * 申明缓存管理器,会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut) * 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值 * @return */ @Bean public RedisCacheManager cacheManager(RedisTemplate redisTemplate) { return new RedisCacheManager(redisTemplate); } @Bean @Primary public RedisTemplate redisTemplate(RedisConnectionFactory factory) { // 创建一个模板类 RedisTemplatetemplate = new RedisTemplate (); // 将刚才的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; } }
然后在resources下的application.properties下配置
# REDIS (RedisProperties) # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=127.0.0.1 # Redis服务器连接端口 spring.redis.port=6379 # Redis服务器连接密码(默认为空) spring.redis.password= # 连接池最大连接数(使用负值表示没有限制) spring.redis.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=-1 # 连接池中的最大空闲连接 spring.redis.pool.max-idle=8 # 连接池中的最小空闲连接 spring.redis.pool.min-idle=0 # 连接超时时间(毫秒) spring.redis.timeout=0
大家发现我们并没有注册RedisConnectionFactory,那是因为spring默认帮我们读取application.properties文件并且注册了一个factorybean
keyGenerator方法帮我们注册了一个key的生成规则,就不用我们写spel表达式了,根据反射的原理读取类名+方法名+参数。但是我们有时候还是需要结合spel的。
然后在controller上加上@Cacheable("cachename"),之后就可以在redis看到保存了并且key的值是keyGenerator生成的名字