前言
我們都知道,一個程序的瓶頸通常都在數據庫,很多場景需要獲取相同的數據。比如網站頁面數據等,需要一次次的請求數據庫,導致大部分時間都浪費在數據庫查詢和方法調用上,這時就可以利用到緩存來緩解這個問題。
JSR107、Spring緩存抽象等概念
JSP107:
Java Caching定義了5個核心接口,分別是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
- CachingProvider:定義了創建、配置、獲取、管理和控制多個CacheManager。一個應用可以在運行期訪問多個CachingProvider。
- CacheManager:定義了創建、配置、獲取、管理和控制多個唯一命名的Cache,這些Cache存在於CacheManager的上下文中。一個CacheManager僅被一個CachingProvider所擁有。
- Cache:是一個類似Map的數據結構並臨時存儲以Key為索引的值。一個Cache僅被一個CacheManager所擁有。
- Entry:是一個存儲在Cache中的key-value對.
- Expiry:每一個存儲在Cache中的條目有一個定義的有效期。一旦超過這個時間,條目為過期的狀態。一旦過期,條目將不可訪問、更新和刪除。緩存有效期可以通過ExpiryPolicy設置。
首先是原生的JCache:
不得不說,使用java原生的緩存規范來實現我們的需求是很麻煩的,所有spring才對JSR-107進行了抽象,簡化為Cache
和CacheManager
來幫助我們開發,我們可以通過兩張圖來對比一下使用了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) + "]"; } }; }