優雅的緩存解決方案--SpringCache和Redis集成(SpringBoot)

1. 前言

一個系統在於數據庫交互的過程當中,內存的速度遠遠快於硬盤速度,當咱們重複地獲取相同數據時,咱們一次又一次地請求數據庫或遠程服務,者無疑時性能上地浪費(這會致使大量時間被浪費在數據庫查詢或者遠程方法調用上導致程序性能惡化),因而有了「緩存」。 本文將介紹在spring boot項目開發中怎樣使用spring提供的Spring Cache 與最近很火的 Redis 數據庫來實現數據的緩存。html

2. SpringCache簡介

Spring CacheSpring框架提供的對緩存使用的抽象類,支持多種緩存,好比RedisEHCache等,集成很方便。同時提供了多種註解來簡化緩存的使用,可對方法進行緩存。java

2.1 關於SpringCache 註解的簡單介紹

  • @Cacheable:標記在一個方法上,也能夠標記在一個類上。主要是緩存標註對象的返回結果,標註在方法上緩存該方法的返回值,標註在類上,緩存該類全部的方法返回值。 參數: value緩存名、 key緩存鍵值、 condition知足緩存條件、unless否決緩存條件

@Cacheable

  • @CacheEvict:從緩存中移除相應數據。

@CacheEvict

  • @CachePut:方法支持緩存功能。與@Cacheable不一樣的是使用@CachePut標註的方法在執行前不會去檢查緩存中是否存在以前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。

@CachePut

  • @Caching:多個Cache註解使用,好比新增用戶時,刪除用戶屬性等須要刪除或者更新多個緩存時,集合以上三個註解。

2.2 SpEL上下文數據

Spring Cache提供了一些供咱們使用的SpEL上下文數據,下表直接摘自Spring官方文檔:git

名字 位置 描述 示例
methodName root對象 當前被調用的方法名 #root.methodName
method root對象 當前被調用的方法 #root.method.name
target root對象 當前被調用的目標對象 #root.target
targetClass root對象 當前被調用的目標對象類 #root.targetClass
args root對象 當前被調用的方法的參數列表 #root.args[0]
caches root對象 當前方法調用使用的緩存列表(如@Cacheable(value={"cache1", "cache2"})),則有兩個cache #root.caches[0].name
argument name 執行上下文 當前被調用的方法的參數,如findById(Long id),咱們能夠經過#id拿到參數 #user.id
result 執行上下文 方法執行後的返回值(僅當方法執行以後的判斷有效,如‘unless’,'cache evict'的beforeInvocation=false) #result

其餘關於 Cache 詳細配置或註解,請參考文章基於Redis的Spring cache 緩存介紹或spring官方文檔github

3. Redis簡介

Redis 是徹底開源免費的,遵照BSD協議,是一個高性能的key-value數據庫。web

Redis 與其餘 key - value 緩存產品有如下三個特色:redis

  • Redis支持數據的持久化,能夠將內存中的數據保存在磁盤中,重啓的時候能夠再次加載進行使用。
  • Redis不只僅支持簡單的key-value類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。
  • Redis支持數據的備份,即master-slave模式的數據備份。

Redis 的安裝和使用請自行Google 。spring

4. 實踐--SpringCache和Redis集成

4.1 步驟

咱們要把一個查詢函數加入緩存功能,大體須要三步。數據庫

  • 1、在函數執行前,咱們須要先檢查緩存中是否存在數據,若是存在則返回緩存數據。
  • 2、若是不存在,就須要在數據庫的數據查詢出來。
  • 3、最後把數據存放在緩存中,當下次調用此函數時,就能夠直接使用緩存數據,減輕了數據庫壓力。

本實例沒有存入MySQL數據庫,主要是爲了方便實踐,實際使用中你們能夠把service層中的方法改成數據庫操做代碼便可。json

4.2 具體操做

添加依賴

<!-- springboot redis依賴-->
        <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
複製代碼

注: 其實咱們從官方文檔能夠看到spring-boot-starter-data-redis 已經包含了jedis客戶端,咱們在使用jedis鏈接池的時候沒必要再添加jedis依賴。緩存

jedis

配置SpringCache,Redis鏈接等信息

  • SpringCache配置--RedisConfig.java
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.EnableCaching;
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.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/** * @Author: MaoLin * @Date: 2019/3/26 17:04 * @Version 1.0 */

@Configuration
@EnableCaching
public class RedisConfig {

    /** * 申明緩存管理器,會建立一個切面(aspect)並觸發Spring緩存註解的切點(pointcut) * 根據類或者方法所使用的註解以及緩存的狀態,這個切面會從緩存中獲取數據,將數據添加到緩存之中或者從緩存中移除某個值 * @return */
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        return RedisCacheManager.create(redisConnectionFactory);
    }

    @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;
    } 
}

複製代碼
  • redis配置--application.yml
server:
  port: 8080
spring:
  # redis相關配置
  redis:
    database: 0
    host: localhost
    port: 6379
    password: 123456
    jedis:
      pool:
        # 鏈接池最大鏈接數(使用負值表示沒有限制)
        max-active: 8
        # 鏈接池最大阻塞等待時間(使用負值表示沒有限制)
        max-wait: -1ms
        # 鏈接池中的最大空閒鏈接
        max-idle: 8
        # 鏈接池中的最小空閒鏈接
        min-idle: 0
    # 鏈接超時時間(毫秒)默認是2000ms
    timeout: 2000ms
  cache:
    redis:
      ## Entry expiration in milliseconds. By default the entries never expire.
      time-to-live: 1d
      #寫入redis時是否使用鍵前綴。
      use-key-prefix: true
複製代碼

編寫實體類

import lombok.Data;
import java.io.Serializable;

/** * @Author: MaoLin * @Date: 2019/3/24 14:36 * @Version 1.0 */

@Data
//lombok依賴,可省略get set方法
public class User implements Serializable {

    private int userId;

    private String userName;

    private String userPassword;

    public User(int userId, String userName, String userPassword) {
        this.userId = userId;
        this.userName = userName;
        this.userPassword = userPassword;
    }
}

複製代碼

service簡單操做

import com.ml.demo.entity.User;
import org.springframework.stereotype.Service;

/** * @Author: MaoLin * @Date: 2019/3/24 14:38 * @Version 1.0 */
@Service
public class UserDao {

    public User getUser(int userId) {
        System.out.println("執行此方法,說明沒有緩存,若是沒有走到這裏,就說明緩存成功了");
        User user = new User(userId, "沒有緩存_"+userId, "password_"+userId);
        return user;
    }

    public User getUser2(int userId) {
        System.out.println("執行此方法,說明沒有緩存,若是沒有走到這裏,就說明緩存成功了");
        User user = new User(userId, "name_nocache"+userId, "nocache");
        return user;
    }
}
複製代碼

控制層

  • 在方法上添加相應的方法便可操做緩存了,SpringCache 對象能夠對redis自行操做,減小了不少工做啊,仍是那個開箱即用的Spring
import com.ml.demo.dao.UserDao;
import com.ml.demo.entity.User;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

/** * @Author: MaoLin * @Date: 2019/3/26 17:03 * @Version 1.0 */


@RestController
public class testController {
    @Resource
    private UserDao userDao;

    /** * 查詢出一條數據而且添加到緩存 * * @param userId * @return */
    @RequestMapping("/getUser")
    @Cacheable("userCache")
    public User getPrud(@RequestParam(required = true) String userId) {
        System.out.println("若是沒有緩存,就會調用下面方法,若是有緩存,則直接輸出,不會輸出此段話");
        return userDao.getUser(Integer.parseInt(userId));
    }

    /** * 刪除一個緩存 * * @param userId * @return */
    @RequestMapping(value = "/deleteUser")
    @CacheEvict("userCache")
    public String deleteUser(@RequestParam(required = true) String userId) {
        return "刪除成功";
    }

    /** * 添加一條保存的數據到緩存,緩存的key是當前user的id * * @param user * @return */
    @RequestMapping("/saveUser")
    @CachePut(value = "userCache", key = "#result.userId +''")
    public User saveUser(User user) {
        return user;
    }


    /** * 返回結果userPassword中含有nocache字符串就不緩存 * * @param userId * @return */
    @RequestMapping("/getUser2")
    @CachePut(value = "userCache", unless = "#result.userPassword.contains('nocache')")
    public User getUser2(@RequestParam(required = true) String userId) {
        System.out.println("若是走到這裏說明,說明緩存沒有生效!");
        User user = new User(Integer.parseInt(userId), "name_nocache" + userId, "nocache");
        return user;
    }


    @RequestMapping("/getUser3")
    @Cacheable(value = "userCache", key = "#root.targetClass.getName() + #root.methodName + #userId")
    public User getUser3(@RequestParam(required = true) String userId) {
        System.out.println("若是第二次沒有走到這裏說明緩存被添加了");
        return userDao.getUser(Integer.parseInt(userId));
    }
}

複製代碼

接下來最重要的工做:跑起來

運行結果

  • 存入數據:

  • 從緩存讀取數據:

  • 刪除緩存:

  • 再讀取:

  • 此時沒有緩存,調用方法,並存入緩存

  • 此爲cache中的條件:含有nocache字符時不存入緩存。本身去探索就好。

5. 小結&參考資料

小結

爲了實現緩存,在網上參考了不少博客、資料,可是都不盡人意,後來通過幾天的學習,發現Spring提供了緩存對象,我結合redis,優雅地實現了緩存。學習代碼是個艱辛的過程,我在學習這部分時看了好多書,找了好多博客資料,終於找到了合適的緩存方案,很開心,不過這還只是一小步啊,加油!!!

參考資料

相關文章
相關標籤/搜索