1. 程式人生 > >springboot番外之redis

springboot番外之redis

目錄

正文

很多時候,我們會在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這個註解。

這個註解會被spring發現,並且會建立一個切面(aspect) 並觸發Spring快取註解的切點(pointcut) 。 根據所使用的註解以及快取的狀態, 這個切面會從快取中獲取資料, 將資料新增到快取之中或者從快取中移除某個值。 

接下來我們需要申明一個快取管理器的bean,這個作用就是 @EnableCaching這個切面在新增快取或者刪除快取的時候會呼叫這個快取管理器的方法 /**
     * 申明快取管理器,會建立一個切面(aspect)並觸發Spring快取註解的切點(pointcut)
     * 根據類或者方法所使用的註解以及快取的狀態,這個切面會從快取中獲取資料,將資料新增到快取之中或者從快取中移除某個值     
     * @return
     */
    @Bean
    public RedisCacheManager cacheManager(RedisTemplate redisTemplate) {
        return new RedisCacheManager(redisTemplate);
    }

 當然,快取管理器除了RedisCacheManager還有一些其他的。例如

  1. SimpleCacheManager
  2. NoOpCacheManager
  3. ConcurrentMapCacheManager
  4. CompositeCacheManager
  5. 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生成的名字

 本文程式碼github地址

分類:  springboot