springBoot:緩存以及其工作原理


前言

我們都知道,一個程序的瓶頸通常都在數據庫,很多場景需要獲取相同的數據。比如網站頁面數據等,需要一次次的請求數據庫,導致大部分時間都浪費在數據庫查詢和方法調用上,這時就可以利用到緩存來緩解這個問題。

JSR107、Spring緩存抽象等概念

JSP107:

Java Caching定義了5個核心接口,分別是CachingProvider, CacheManager, Cache, EntryExpiry

  1. CachingProvider:定義了創建、配置、獲取、管理和控制多個CacheManager。一個應用可以在運行期訪問多個CachingProvider。
  2. CacheManager:定義了創建、配置、獲取、管理和控制多個唯一命名的Cache,這些Cache存在於CacheManager的上下文中。一個CacheManager僅被一個CachingProvider所擁有。
  3. Cache:是一個類似Map的數據結構並臨時存儲以Key為索引的值。一個Cache僅被一個CacheManager所擁有。
  4. Entry:是一個存儲在Cache中的key-value對.
  5. Expiry:每一個存儲在Cache中的條目有一個定義的有效期。一旦超過這個時間,條目為過期的狀態。一旦過期,條目將不可訪問、更新和刪除。緩存有效期可以通過ExpiryPolicy設置。

 首先是原生的JCache:

 

 不得不說,使用java原生的緩存規范來實現我們的需求是很麻煩的,所有spring才對JSR-107進行了抽象,簡化為CacheCacheManager來幫助我們開發,我們可以通過兩張圖來對比一下使用了spring緩存抽象和使用Java Caching的區別:

Spring緩存抽象:

 可以很明顯的看到,我們使用Spring以后,僅僅只需要操作CacheManager就可以來方便進行開發.

 SpringBoot緩存原理

SpringBoot使用緩存需要引入spring-boot-starter-cache

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

根據以往對SpringBoot的解析中,我們先找到緩存自動配置類:CacheAutoConfiguration

在代碼中我們可以看到CacheAutoConfiguration在啟動的時候,加載了10個緩存配置類,比如我們常用的:RedisCacheConfiguration,EhCacheCacheConfiguration等

這些緩存配置類 都會有規則判斷

@Configuration(proxyBeanMethods = false)  //聲明這是一個配置類
@ConditionalOnMissingBean({CacheManager.class})  //如果容器中沒有CacheManager 才會實例化這個Bean
@Conditional({CacheCondition.class}) 
class SimpleCacheConfiguration {

  

現在我並沒有在SpringBoot中配置其他緩存管理器,但CacheAutoConfiguration在啟動的時候加載了10個緩存配置類,那么到底哪個配置起作用了呢?

首先在配置文件中添加:

debug: true   #打開自動配置類報告

然后啟動項目:

在啟動日志中,我們可以看到SpringBoot默認為我們匹配(matched)了一個SimpleCacheConfiguration,而其他的9個緩存配置都是Did not match(不匹配)。那么也就是說SpringBoot的默認緩存配置類是SimpleCacheConfiguration

(ps:如果我們使用了Redis組件,匹配的緩存配置類就是:RedisCacheConfiguration)

那么SimpleCacheConfiguration這個緩存配置類又為我們做了什么呢?

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class SimpleCacheConfiguration {
    SimpleCacheConfiguration() {
    }

    @Bean
    ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers) {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        List<String> cacheNames = cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            cacheManager.setCacheNames(cacheNames);
        }

        return (ConcurrentMapCacheManager)cacheManagerCustomizers.customize(cacheManager);
    }
}

在代碼中我們可以看到,SimpleCacheConfiguration給容器中注入了一個CacheManager

通過這CacheManager(緩存管理器)我們可以創建、配置、獲取、管理和控制多個唯一命名的Cache

 

二、 緩存注解

  • Cache:緩存接口,定義緩存操作。實現:RedisCache,EnCacheCache等
  • CacheManager:緩存管理器,管理各種緩存(Cache)組件
  • @Cacheable:觸發緩存寫入,主要針對方法配置。
  • @CacheEvict:清除緩存。
  • @CachePut:更新緩存(不會影響到方法的運行)。
  • @Caching:開啟基於緩存的注解
  • @CacheConfig:設置類級別上共享的一些常見緩存設置。
  • keyGenerator:緩存數據時Key的生產策略
  • serialize:緩存數據時value序列化策略

具體詳細介紹參考:https://blog.csdn.net/xiaoyao2246/article/details/83995882

三、 緩存使用

要在Springboot中使用緩存需要以下幾步:

      第一步: 導入spring-boot-starter-cache模塊

      第二步: @EnableCaching開啟緩存

      第三步: 使用緩存注解

1.首先在pom文件中引入緩存坐標

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

2.在主程序中開啟緩存注解

@EnableCaching  //開啟緩存
@MapperScan("com.meng.demo.mapper")
@SpringBootApplication
public class DemoCacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoCacheApplication.class, args);
    }

}

 

測試:@Cacheable

將方法的運行結果進行緩存,以后要相同的數據,直接從緩存中獲取,不用調用方法

service:

@Cacheable(cacheNames = "user")
public UserPO getUser(Integer id){
  System.out.println("訪問數據庫:"+id);
    return userMapper.getOne(id);
}

測試方法:

  @Test
    void contextLoads() {
        Integer id = 1;
        UserPO user1 = userService.getUser(1);
        System.out.println("第一次查詢:"+user1.getUserName());

        UserPO user2 = userService.getUser(1);
        System.out.println("第二次查詢:"+user2.getUserName());
    }

測試結果:

 

 

 

測試:@CachePut

主要用於修改了數據庫的某個數據,同時更新緩存,運行時機:先調用目標方法,再將目標方法的結果緩存起來

service:

  @Cacheable(cacheNames = "user")
    public UserPO getUser(Integer id){
        System.out.println("訪問數據庫:"+id);
        return userJpaMapper.getOne(id);
    }

    @CachePut(value = "user",key = "#result.id")
    public UserPO update(UserPO po) {
        System.out.println("數據庫更新");
        userMapper.update(po);
        return po;
    }

測試方法:

    @Test
    void updataUser() {
        Integer id = 2;
        UserPO user1 = userService.getUser(id);
        System.out.println("第一次查詢:"+user1.getUserName()+", 年齡:"+user1.getAge());

        UserPO po = new UserPO();
        po.setId(id);
        po.setAge(50);
        userService.update(po);

        UserPO user2 = userService.getUser(id);
        System.out.println("第二次查詢:"+user2.getUserName()+", 年齡:"+user2.getAge());
    }

測試結果

 

@CacheEvict

該注解用於刪除一個緩存

  /**
     * allEntries:刪除所有的緩存   allEntries = true
     * beforeInvocation:緩存的清除是否在方法之前執行,默認是在方法之后執行
     * 其他參數與@Cacheable大致一樣
     */
    @CacheEvict(value = "user", key = "#id", allEntries = true)
    public void delete(Integer id) {
        userJpaMapper.deleteById(id);
    }

@Caching

該注解用於存放多個緩存注解,有時候對於一個方法,想放置多個緩存,既想緩存又想更新時使用

/**
     * 可以放多個注解
     * @param lastName
     * @return
     */
    @Caching(
            cacheable = {
                    @Cacheable(value = "user", key = "#id")
            },
            put = {
                    @CachePut(value = "user", key = "#result.id"),
                    @CachePut(value = "emp", key = "#result.email")
            }
    )
    public UserPO getUser(Integer id){
        System.out.println("訪問數據庫:"+id);
        return userJpaMapper.getOne(id);
    }

 

@CacheConfig

有的時候覺得每次在使用緩存注解的時候都要指定緩存的名字,或者指定緩存的cacheManager之類的,覺得很麻煩。那么就可以在類上使用@CacheConfig統一的配置緩存的名字

@CacheConfig(value = "user")
@Service
public class UserService {

 

自定義key的生成策略

對於key,我們可以讓它自動生成,生成的策略可以有我們自己制定,之需要在配置類中將我們的定制規則加入到容器中即可

@Bean
public KeyGenerator keyGenerator(){
    return new KeyGenerator(){
        @Override
        public Object generate(Object o, Method method, Object... objects) {
            return method.getName() + "[" + Arrays.asList(objects) + "]";
        }
    };
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM