SpringBoot 與緩存並整合 Redis


緩存

對於 JSR107 規範:
Java Caching (使用需要引入JCache)定義了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設置。

關係圖如下:
在這裏插入圖片描述


Spring緩存抽象

由於 JSR107 使用過程比較複雜,所以 Spring 定義了 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager 接口來統一不同的緩存技術;並支持使用JCache(JSR-107)註解簡化我們開發。
注:Cache接口下Spring提供了各種xxxCache的實現,如:RedisCache,EhCacheCache ,
ConcurrentMapCache等;

主要的概念與緩存註解:
在這裏插入圖片描述

示例
  1. 開啓緩存註解
		@SpringBootApplication
		@MapperScan("com.moke.cache.mapper")
		@EnableCaching
		public class SpringbootCacheApplication {
		
		    public static void main(String[] args) {
		        SpringApplication.run(SpringbootCacheApplication.class, args);
		    }
		
		}

2.使用緩存

		@Service
		public class EmployeeService {
		    @Autowired
		    private EmployeeMapper employeeMapper;
		    /*
		    @Cacheable的幾個屬性
		        cacheNmaes/value:指定緩存組件的名字,將方法的返回結果放到指定緩存中,可以以數組方式指定多個緩存
		        key:緩存數據使用的key,默認是使用方法參數的值【SpEL表達式】
		        keyGenerator:key的生成器,也可以自己指定
		        cacheManager/cacheResolver:指定緩存管理器/緩存解析器
		        condition:指定符合條件的情況下,才進行緩存
		            condition = "#id>0",
		        unless:否定緩存,當unless指定的條件爲true,不會被緩存,可以獲取到結果進行判斷
		            unless = "#result==null"
		        sync:是由使用異步模式
		     */
		    @Cacheable(cacheNames = "emp")
		    public Employee getEmp(Integer id){
		        return employeeMapper.getEmpById(id);
		    }
		}

緩存原理

相關自動配置類:CacheAutoConfiguration

	@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class})

由上面的註解會根據我們使用的緩存類型選擇相應的緩存配置類:
在這裏插入圖片描述
默認使用的是:org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration

SimpleCacheConfiguration 在容器中註冊了一個緩存管理器 ConcurrentMapCacheManager ,而它可以獲取和創建 ConcurrentMapCache 類型的緩存組件,並將數據保存在一個 ConcurrentMap 中:

		//SimpleCacheConfiguration
		@Bean
	    public ConcurrentMapCacheManager cacheManager() {
	        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
	        List<String> cacheNames = this.cacheProperties.getCacheNames();
	        if (!cacheNames.isEmpty()) {
	            cacheManager.setCacheNames(cacheNames);
	        }
	
	        return (ConcurrentMapCacheManager)this.customizerInvoker.customize(cacheManager);
	    }
		//ConcurrentMapCacheManager 
		public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
    		private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);
			...
			public Cache getCache(String name) {
		        Cache cache = (Cache)this.cacheMap.get(name);
		        if (cache == null && this.dynamic) {
		            synchronized(this.cacheMap) {
		                cache = (Cache)this.cacheMap.get(name);
		                if (cache == null) {
		                    cache = this.createConcurrentMapCache(name);
		                    this.cacheMap.put(name, cache);
		                }
		            }
		        }
		
		        return cache;
		    }
		    ...
		}
		//ConcurrentMapCache 
		public class ConcurrentMapCache extends AbstractValueAdaptingCache {
		    private final String name;
		    private final ConcurrentMap<Object, Object> store;
		    ...
		}

運行流程
@Cacheable:

  1. 方法運行之前,先去查詢 ConcurrentMapCache (緩存組件),按照 cacheNames 指定的名字獲取;第一次獲取緩存如果沒有會自動創建緩存組件。
  2. 去 ConcurrentMap 中查找緩存的內容,使用一個key,默認就是方法的參數;
    (1)key 默認是使用keyGenerator(SimpleKeyGenerator)生成的;
    (2)SimpleKeyGenerator生成key的默認策略;
    • 如果沒有參數;key=new SimpleKey();
    • 如果有一個參數:key=參數的值
    • 如果有多個參數:key=new SimpleKey(params);
  3. 沒有查到緩存就調用目標方法;
  4. 將目標方法返回的結果,放進緩存中

@Cacheable 標註的方法執行之前先來檢查緩存中有沒有這個數據,默認按照參數的值作爲key去查詢緩存;如果沒有就運行方法並將結果放入緩存;以後再來調用就可以直接使用緩存中的數據;

@CachePut:

		@CachePut(value = "emp",key = "#employee.id")//"#result.id"
	    public Employee updateEmp(Employee employee){
	        employeeMapper.updateEmp(employee);
	        return employee;
	    }

@CachePut,既調用方法,又更新緩存數據,用於同步更新緩存;不同於 @Cacheable ,它是先調用目標方法,將目標方法的結果緩存起來。
注:緩存使用的 key 默認是方法參數列表,所以我們需要統一緩存的 key 的格式。

@CacheEvict:

		/*  相關屬性:
		 *  allEntries = true:指定清除這個緩存中所有的數據
	     *  beforeInvocation = false:緩存的清除是否在方法之前執行
	     *      默認代表緩存清除操作是在方法執行之後執行;如果出現異常緩存就不會清除
	     *
	     *  beforeInvocation = true:
	     *      代表清除緩存操作是在方法運行之前執行,無論方法是否出現異常,緩存都清除
		*/
		@CacheEvict(value = "emp",key = "#id")
	    public void deleteEmp(Integer id){
	        employeeMapper.deleteById(id);
	    }

@CacheEvict 用於緩存清除,可以用 key 指定要清除的數據。

@Caching:

		@Caching(
	         cacheable = {
	             @Cacheable(/*value="emp",*/key = "#lastName")
	         },
	         put = {
	             @CachePut(/*value="emp",*/key = "#result.id"),
	             @CachePut(/*value="emp",*/key = "#result.email")
	         }
	    )
	    public Employee getEmpByLastName(String lastName){
	        return employeeMapper.getEmpByLastName(lastName);
	    }

@Caching 用於自定義複雜的緩存規則。

@CacheConfig:

		@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) //抽取緩存的公共配置
		@Service
		public class EmployeeService {...}

@CacheConfig 用於定義公共配置。


RedisCache

如果我們不使用 Sping 給我們提供的緩存,而是使用 Redis 緩存,則查閱官方文檔:
在這裏插入圖片描述
引入依賴:

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

增加配置:
在這裏插入圖片描述
而由自動配置原理,我們來看 RedisAutoConfiguration 中給我們的容器添加了哪些組件:
在這裏插入圖片描述
可以看到爲我們容器中自動添加了兩個 redistemplate 模板,使用時我們只需要自動注入即可。

使用:

		@Autowired
    	private RedisTemplate redisTemplatel;

更多的 redis 的使用可以參考,本人之前的總結:再見 Redis

在xml中配置 redistemplate 以及序列化方式:
在這裏插入圖片描述
而在 SpringBoot 中,改變默認的序列化規則:

		@Configuration
		public class RedisConfig {
		    @Bean
		    public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		        RedisTemplate<Object, Employee> template = new RedisTemplate();
		        template.setConnectionFactory(redisConnectionFactory);
		        Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
		        template.setDefaultSerializer(ser);
		        return template;
		    }
		}

原理:

  • 我們知道 SpringBoot 默認使用的是 SimpleCacheConfiguration 創建 ConcurrentMapCacheManager,使用 ConcurrentMapCache 作爲緩存組件,且使用 map 來保存數據
  • 而在配置完 Redis 後使用 RedisCacheConfiguration 來創建RedisCacheManager,使用 RedisCache 作爲緩存組件,其不會自己保存數據,而是會操作 redis 中數據

自定義 RedisCacheManager:
上面只是修改了 redistemplate 的序列化規則,那如果也想修改註解方式的序列化規則就得修改RedisCacheManager。

		@Bean
	    public RedisCacheManager empCacheManager(RedisConnectionFactory redisConnectionFactory) {
	        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
	                .defaultCacheConfig()
	                .serializeValuesWith( RedisSerializationContext
	                        .SerializationPair
	                        .fromSerializer(new Jackson2JsonRedisSerializer<Employee>(Employee.class)));
	
	        return RedisCacheManager
	                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
	                .cacheDefaults(redisCacheConfiguration).build();
	
	    }

對於多個 CacheManager ,需要使用 @Primary 指定默認的,並可以在註解屬性中指定,例如:

		@Cacheable(cacheNames = "emp",cacheManager = "empCacheManager")
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章