很多時候,我們會在springboot中配置redis,但是就那么幾個配置就配好了,沒辦法知道為什么,這里就詳細的講解一下
這里假設已經成功創建了一個springboot項目。
redis連接工廠類
第一步,需要加上springboot的redis jar包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </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獲取添加數據,
template(模版)
但是我們發現每次添加的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){ //創建一個模板類 RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); //將剛才的redis連接工廠設置到模板類中 template.setConnectionFactory(factory); return template; }
單元測試
@Autowired RedisTemplate<String, Object> template; @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<Object> prodList = template.opsForList().range("pruduct", 0,template.opsForList().size("pruduct")-1); for(Object obj:prodList){ System.out.println((Pruduct)obj); } System.out.println("產品數量:"+template.opsForList().size("pruduct")); }
key和value序列化
當保存一條數據的時候,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的時候,我們希望將Product類型的value序列化為JSON,而key是String類型。RedisTemplate的setKeySerializer()和setValueSerializer()方法就需要如下所示:
@Bean 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; }
到這里,大家肯定會對springboot使用redis有了簡單的了解。
springboot緩存某個方法
申明緩存管理器
在某些時候,我們可能有這樣的需求,用戶登錄的時候,我們會從數據庫中讀取用戶所有的權限,部門等信息。而且每次刷新頁面都需要判斷該用戶有沒有這個權限,如果不停的從數據庫中讀並且計算,
是非常耗性能的,所以我們這個時候就要用到了springboot為我們帶來的緩存管理器
首先在我們的RedisConfig這個類上加上@EnableCaching這個注解。
/** * 申明緩存管理器,會創建一個切面(aspect)並觸發Spring緩存注解的切點(pointcut) * 根據類或者方法所使用的注解以及緩存的狀態,這個切面會從緩存中獲取數據,將數據添加到緩存之中或者從緩存中移除某個值 * @return */ @Bean public RedisCacheManager cacheManager(RedisTemplate redisTemplate) { return new RedisCacheManager(redisTemplate); }
當然,緩存管理器除了RedisCacheManager還有一些其他的。例如
- SimpleCacheManager
- NoOpCacheManager
- ConcurrentMapCacheManager
- CompositeCacheManager
- EhCacheCacheManager
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。
自定義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字符串,則不進行緩存。
總結demo:
將類名方法名和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
springboot配置升級簡單化
當然上面的配置只是為了了解原理的哈,實際上我們使用會更簡單點。我們重寫了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) { // 創建一個模板類 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; } }
然后在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生成的名字