檔案:https://docs.spring.io/spring-boot/docs/2.3.12.RELEASE/reference/html/spring-boot-features.html
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
支援以下幾種快取實現,預設自動根據程式碼依賴判斷使用哪種實現,也可以在組態檔中強制指定使用哪種快取實現。
當然也可以自實現 cacheManager,隨便參照simple或者redis如何實現的即可。
在設定類中新增@EnableCaching
來啟用快取功能
@Configuration
@EnableCaching
public class CachingConfig {
}
預設情況下,如果我們沒有明確指定任何其他快取,會自動根據依賴環境判斷,如果上圖的依賴都沒有,它預設使用ConcurrentHashMap作為底層快取。即
Simple
的型別,參考程式碼SimpleCacheConfiguration.java
、ConcurrentMapCacheManager.java
也可以通過組態檔直接指定:
spring:
cache:
type: simple
Cacheable
等。CacheManager
。@Cacheable(value = "user", key = "#id", unless="#result == null")
public User getUser(long id) {...}
cacheNames/value:指定快取元件的名字;將方法的返回結果放在哪個快取中,是陣列的方式,可以指定多個快取。必填
key:快取的 key,可以為空,如果指定要按照 SpEL 表示式編寫,如果不指定,則預設按照方法的所有引數進行組合。
keyGenerator:key的生成器;可以自己指定key的生成器的元件id,key/keyGenerator:二選一使用。
cacheManager:當自己設定了CacheManager後可以指定使用哪個快取管理器,預設使用的是Springboot自動設定的快取管理器;或者cacheResolver指定獲取解析器。
condition:快取的條件,可以為空,使用 SpEL 編寫,返回 true 或者 false,只有為 true 才進行快取.
condition="#userName.length()>2"
。unless:否定快取;當unless指定的條件為true,方法的返回值就不會被快取;可以獲取到結果進行判斷。
sync:是否開啟同步功能,預設不開啟。開啟後unless屬性將不能使用。
getUser()
將首先檢查快取是否存在,不存在則呼叫實際的方法,最後快取結果;存在則直接返回快取結果,跳過呼叫實際方法。
或者使用快取管理器。
@Service
public class UserService {
@Autowired
CacheManager cacheManager;
public User getUser(long id) {
if(cacheManager.containsKey(id)) {
return cacheManager.get(id);
}
// lookup address, cache result, and return it
}
}
指定刪除一個或多個或所有值,以便可以再次將新值載入到快取中。
@CacheEvict(value="user", allEntries=true)
public User updateUser(long id) {...}
allEntries
:是否刪除所有值。預設false,預設情況下,只刪除關聯鍵下的值。注意,不允許將這個引數設定為true並指定一個鍵。
beforeInvocation
:預設false,是否應該在呼叫方法之前發生驅逐。將此屬性設定為true,將導致驅逐發生,而不考慮方法的結果(即,是否丟擲異常)。 預設值為false,意味著快取回收操作將在被建議的方法被成功呼叫後發生(也就是說,只有在呼叫沒有丟擲異常的情況下)。
或者使用快取管理器
@Autowired
CacheManager cacheManager;
public void evictSingleCacheValue(String cacheName, String cacheKey) {
cacheManager.getCache(cacheName).evict(cacheKey);
}
public void evictAllCacheValues(String cacheName) {
cacheManager.getCache(cacheName).clear();
}
public void evictAllCaches() {
cacheManager.getCacheNames().stream()
.forEach(cacheName -> cacheManager.getCache(cacheName).clear());
}
實際我們不用這個,高並行下會導致更新丟失問題或者鎖問題。這裡僅做介紹哈
@CachePut(value="addresses")
public String getAddress(Customer customer) {...}
@Caching(evict = {
@CacheEvict("addresses"),
@CacheEvict(value="directory", key="#customer.name") })
public String getAddress(Customer customer) {...}
可以使用@Caching
將多個快取註解組合,使用它來實現我們自己的自定義快取邏輯。
使用@CacheConfig
註解,我們可以在類級別將一些快取設定簡化到一個地方。
@CacheConfig(cacheNames={"addresses"})
public class CustomerDataService {
@Cacheable
public String getAddress(Customer customer) {...}
Spring Cache提供了一些供我們使用的SpEL上下文資料,下表直接摘自Spring官方檔案:
名稱 | 位置 | 描述 | 範例 |
---|---|---|---|
methodName | root物件 | 當前被呼叫的方法名 | #root.methodname |
method | root物件 | 當前被呼叫的方法 | #root.method.name |
target | root物件 | 當前被呼叫的目標物件範例 | #root.target |
targetClass | root物件 | 當前被呼叫的目標物件的類 | #root.targetClass |
args | root物件 | 當前被呼叫的方法的參數列 | #root.args[0] |
caches | root物件 | 當前方法呼叫使用的快取列表 | #root.caches[0].name |
Argument Name | 執行上下文 | 當前被呼叫的方法的引數,如findArtisan(Artisan artisan),可以通過#artsian.id獲得引數 | #artsian.id |
result | 執行上下文 | 方法執行後的返回值(僅當方法執行後的判斷有效,如 unless cacheEvict的beforeInvocation=false) | #result |
注意:
1.當我們要使用root物件的屬性作為key時我們也可以將「#root」省略,因為Spring預設使用的就是root物件的屬性。 如
@Cacheable(key = "targetClass + methodName +#p0")
2.使用方法引數時我們可以直接使用「#引數名」或者「#p引數index」。 如:
@Cacheable(value="users", key="#id")
@Cacheable(value="users", key="#p0")
SpEL提供了多種運運算元
型別 | 運運算元 |
---|---|
關係 | <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne |
算術 | +,- ,* ,/,%,^ |
邏輯 | &&,||,!,and,or,not,between,instanceof |
條件 | ?: (ternary),?: (elvis) |
正規表示式 | matches |
其他型別 | ?.,?[…],![…],1,$[…] |
檔案:https://spring.io/projects/spring-data-redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
需要引入commons-pool2作為連線池 必須!!!
<!--spring2.0整合redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
版本依賴如下:
使用的JavaRedis使用者端是Lettuce因為 Spring Boot 預設使用它。
在application.yaml
檔案中設定屬性:
spring:
cache:
# 指定使用redis
type: redis
redis:
# reids的連線ip
host: 127.0.0.1
port: 6379
password: laker123
# Redis預設情況下有16個分片,這裡設定具體使用的分片,預設是0
database: 0
# 連線超時時間(毫秒)
timeout: 10000ms
# redis client設定,使用lettuce
lettuce:
pool:
# 連線池中的最小空閒連線 預設 0
min-idle: 0
# 連線池最大阻塞等待時間(使用負值表示沒有限制) 預設 -1
max-wait: 1000ms
# 連線池最大連線數(使用負值表示沒有限制) 預設 8
max-active: 8
# 連線池中的最大空閒連線 預設 8
max-idle: 8
注意注意注意!!!,這裡redis預設的序列化為JDK序列化,所以上面的User
實體類一定要實現序列化public class User implements Serializable
,否則會報java.io.NotSerializableException
異常。
@Cacheable(value = "user", key = "#id", unless="#result == null")
public User getUser(long id) {...}
getUser(1)
其對應結果如下圖:
這裡我們後邊會修改其預設的序列化為json格式。
框架會自動生成一個RedisTemplate
範例 ,我們也可以直接使用RedisTemplate
操作快取。
@Autowired
private RedisTemplate redisTemplate;
public void save(User user) {
redisTemplate.opsForValue().set(user.getId(), user);
}
public User findById(Long id) {
return (User)redisTemplate.opsForValue().get(id);
}
RedisTemplate 是執行緒安全的哦
預設情況下,Lettuce 會為我們管理序列化和反序列化,我們來自定義設定RedisTemplate。
@Configuration
@EnableCaching
public class CachingConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setKeySerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return redisTemplate;
}
}
Jackson2JsonRedisSerializer和GenericJackson2JsonRedisSerializer的區別
使用Jackson2JsonRedisSerializer需要指明序列化的類Class,可以使用Obejct.class
使用GenericJacksonRedisSerializer比Jackson2JsonRedisSerializer效率低,佔用記憶體高。
GenericJacksonRedisSerializer反序列化帶泛型的陣列類會報轉換異常,解決辦法儲存以JSON字串儲存。
GenericJacksonRedisSerializer和Jackson2JsonRedisSerializer都是以JSON格式去儲存資料,都可以作為Redis的序列化方式。
來自:https://blog.csdn.net/bai_bug/article/details/81222519
redisTemplate.opsForValue().set(id,user);
迴歸Cache中RedisCacheManager
自定義設定如下:
@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.disableCachingNullValues()
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
@Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration("itemCache",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)))
.withCacheConfiguration("customerCache",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5)));
}
分別為itemCache
和customerCache
設定了 10 分鐘和 5 分鐘的 TTL 值。
擴充套件CachingConfigurerSupport
類並覆蓋cacheManager
() 方法。此方法返回一個 bean,它將成為我們應用程式的預設快取管理器:
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Override
public CacheManager cacheManager() {...}
@Override
public CacheResolver cacheResolver() {...}
@Override
public KeyGenerator keyGenerator() {...}
@Override
public CacheErrorHandler errorHandler() {...}
}
在某些情況下,我們可能需要在應用程式中使用多個快取管理器。
設定類中建立兩個快取管理器 bean。設定其中的一個 bean為@Primary
@Configuration
@EnableCaching
public class MultipleCacheManagerConfig {
@Bean
@Primary
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("customers", "orders");
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(200)
.maximumSize(500)
.weakKeys()
.recordStats());
return cacheManager;
}
@Bean
public CacheManager alternateCacheManager() {
return new ConcurrentMapCacheManager("customerOrders", "orderprice");
}
}
現在,Spring Boot 將使用CaffeineCacheManager
作為所有快取方法的預設值,直到我們為一個方法明確指定了AlternateCacheManager
@Cacheable(cacheNames = "customers")
public Customer getCustomerDetail(Integer customerId) {
return customerDetailRepository.getCustomerDetail(customerId);
}
@Cacheable(cacheNames = "customerOrders", cacheManager = "alternateCacheManager")
public List<Order> getCustomerOrders(Integer customerId) {
return customerDetailRepository.getCustomerOrders(customerId);
}
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
redisTemplate.setKeySerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
return redisTemplate;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) //設定所有快取有效時間 為 1個小時。
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer))
.disableCachingNullValues(); // 禁用快取空值.
RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();
return cacheManager;
}
}
CaffeineCacheManager
由spring-boot-starter-cache starter 提供。如果依賴存在Caffeine,它將由 Spring 自動設定, Caffeine是一個用 Java 8 編寫的快取庫。
增加依賴Caffeine
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
可以自己客製化設定,控制快取行為的主要設定,例如過期、快取大小限制等
@Bean
public Caffeine caffeineConfig() {
return Caffeine.newBuilder()
.expireAfterWrite(60, TimeUnit.MINUTES);
}
@Bean
public CacheManager cacheManager(Caffeine caffeine) {
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(caffeine);
return caffeineCacheManager;
}
參考:
… ↩︎