一、springboot與緩存介紹&使用緩存
1、JSR107
JAVA Cahing定義了5個核心接口,分別是CachingProvider、CacheManager、Cache、Entry、Expiry。
CachingProvider:定義創建、配置、獲取、管理、和控制多個CacheManager.一個應用在運行期間可以訪問多個CacheManager;
CacheManager:定義了創建、配置、獲取、管理、和控制多個唯一命名的Cache,這些Cache存在於CacheManager的上下文中,一個CacheManager僅被一個CachingProvider所擁有;
Cache:是一個類似Map的數據結構並臨時存儲以key爲索引的值,一個Cache僅被一個CacheManager所擁有;
Entry:是一個存儲在Cache中的key-value對;
Expiry:每一個存儲在Cache中的條目有一個定義的有效期。一旦超過這個時間,條目爲過期的狀態,一旦過期,條目將不可訪問,更新和刪除。緩存有效期可以通過ExpriyPolicy設置;
由於整合系統是使用JSR107難度較大,因此spring提供了自己的緩存抽象、
2、Spring緩存抽象
Spring從3.1開始定義了org.springframework.cache.Cache| org.springframework.cache.CacheManager接口來統一不同的緩存技術;並支持使用JCache(JSR-107)註解簡化我們開發.
-
Cache接口爲緩存的組件規範定義,包含緩存的各種操作集合;
-
Cache接口下Spring提供了各種xxxCache的實現;如RedisCache,EhCacheCache , ConcurrentMapCache等;
-
每次調用需要緩存功能的方法時,Spring會檢查檢查指定參數的指定的目標方法是否已經被調用過;如果有就直接從緩存中獲取方法調用後的結果,如果沒有就調用方法並緩存結果後返回給用戶。下次調用直接從緩存中獲取;
-
使用Spring緩存抽象時我們需要關注以下兩點
1、確定方法需要被緩存以及他們的緩存策略
2、從緩存中讀取之前緩存存儲的數據
3、幾個重要概念&緩存註解
1、幾個重要的註解
Cache | 緩存接口,定義緩存操作。實現有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
---|---|
CacheManager | 緩存管理器,管理各種緩存(Cache)組件 |
@Cacheable | 主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存 |
@CacheEvict | 清空緩存 |
@CachePut | 保證方法被調用,又希望結果被緩存。 |
@EnableCaching | 開啓基於註解的緩存 |
keyGenerator | 緩存數據時key生成策略 |
serialize | 緩存數據時value序列化策略 |
2、@Cacheable/@CachePut/@CacheEvict主要的參數
3、Cache SpEL available metadata
4、緩存的使用
一、搭建基本環境,
1**、導入基本環境,創建出department和employee**
2、創建javabean
3、整合mybatis操作數據庫
1)、配置數據源對象
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_cache?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone = GMT
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.initialization-mode=always
logging.level.com.springboot.springboot.mapper
debug=true
2)、註解版的mybatis
@Mapper
public interface EmployeeMapper {
@Select("select * from employee where id=#{id}")
public Employee getEmpById(Integer id);
@Update("update employee set lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id=#{id}")
public void updateEmp(Employee employee);
@Insert("insert into employee(lastName,email,gender,d_id) values(#{lastName},#{email},#{gender},#{id}")
public void insertEmp(Employee employee);
@Delete("delete from employee where id=#{id}")
public void deleteEmp(Integer id);
@Select("select * from employee where lastName=#{lastName}")
public Employee getEmpByLastName(String lastName);
}
1、@MapperScan需要掃描的mapper接口
可以新建一個mybatis配置類
@EnableCaching //開啓基於緩存的註解
@MapperScan("com.springboot.springboot.mapper")
@org.springframework.context.annotation.Configuration
public class MybatisConfig {
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(Configuration configuration) {
configuration.setMapUnderscoreToCamelCase(true);
}
};
}
}
這裏開啓駝峯命名也可以在properties中配置
二、快速體驗緩存
步驟:
1、開啓基於註解的緩存*
在mybatis配置類中添加:
@EnableCaching //開啓基於緩存的註解
2、標註緩存註解*
在EmployeeService類中標註緩存註解進行測試,將方法的運行結果進行緩存,以後獲取相同的數據,直接從緩存找,不用調用方法
1)@EnableCaching 開啓基於註解的緩存*
2)@Cacheable 主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存**
CacheManager管理多個Cache組件,對緩存的真正的CRUD操作在Cache組件中,每一個緩存組件有自己的名字。
屬性介紹:
-
cacheNames/value: 指定緩存組件的名字,將方法的返回結果放在緩存中,是數組的方式,可以指定多個緩存
-
key:緩存數據使用的key,可以用它進行指定默認是使用方法參數的值1,值爲方法的返回值*
編寫可使用spEL: #id參數id的值 #a0 #p0 #root.args[0]*
自定義key:*
1、實例:key顯示爲getEmp[2]*
@Cacheable(cacheNames = {"emp"},key = "#root.methodName+'['+#id+']'
2、自定義keyGenerator生成器,在配置類中配置
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params)+"]";
}
};
}
}
- keyGenerator:key的生成器,可以指定key的生成器組件id
注意:keyGenerator/key二選一*
-
cacheManager:指定緩存管理器,或者cacheResolver指定獲取解析器*
-
condition:指定符合條件的情況向下才進行緩存*
-
unless:否定緩存,當unless指定的條件爲true時,方法的返回值纔不會緩存,可以獲取結果進行判斷* unless=’#resultnull’* unless=’#a02’ 如果第一個參數值是2,結果不緩存*
-
sync:是否使用異步模式
-
@CacheEvict 清空緩存
-
@CachePut 保證方法被調用,又希望結果被緩存。
默認使用的是:ConcurrentMapCacheManager===ConcurrentMapCache(private final ConcurrentMap<Object, Object> store;)
實際開發:使用緩存中間件:redis|memcached|ehcache
EmployeeService
// @Cacheable(cacheNames = {"emp"},key = "#root.methodName+'['+#id+']'")
@Cacheable(cacheNames = {"emp"}/*keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2"*/)
public Employee getEmp(Integer id){
System.out.println("查詢員工"+id);
Employee emp =employeeMapper.getEmpById(id);
return emp;
}
Controller:
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getEmp(@PathVariable("id") Integer id){
Employee emp= employeeService.getEmp(id);
return emp;
}
3、原理介紹
這裏以@Cacheable 爲例子介紹原理:
1、自動配置類 CacheAutoConfiguration*
@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {
/**
* {@link ImportSelector} to add {@link CacheType} configuration classes.
給容器中導入一些緩存配置類
*/
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
2、緩存的配置類*
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration* org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration* org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration* org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration* org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration* org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration* org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration* org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration* org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration* org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
3、哪個配置類會生效呢?
SimpleCacheConfiguration
4、給容器配置了一個CacheManager:ConcurrentMapCacheManager**
@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {
private final CacheProperties cacheProperties;
private final CacheManagerCustomizers customizerInvoker;
SimpleCacheConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
}
ConcurrentMapCacheManager
ConcurrentMapCacheManager
@Override
@Nullable
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
// 調用本類的
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
5、可以獲取和創建createConcurrentMapCache類型的緩存,
這裏會創建一個Cache組件
protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256),
isAllowNullValues(), actualSerialization);
}
ConcurrentMapCache:
作用將數據保存在ConcurrentMap中
private final ConcurrentMap<Object, Object> store;
4、運行流程
運行流程(以@Cacheable爲列子)
1、方法運行之前,先查詢Cache(緩存組件),按照cacheNames指定的名字進行獲取 (CacheManager先獲取相應的緩存)第一次獲取緩存如果沒有Cache組件會自動創建出來**
@Override
@Nullable
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
2、去Cache去查找緩存的內容,使用一個key,key默認就是方法的參數,
ConcurrentMapCache
@Override
@Nullable
protected Object lookup(Object key) {
return this.store.get(key);
}
key是按照某種策略生成的,默認使用SimpleKeyGenerator生成key ,默認使用SimpleKeyGenerator生成策略。
SimpleKeyGenerator生成策略:
-
如果沒有參數:key=new SimpleKey()
-
如果有一個參數:key=參數的值
-
如果有多個參數:key=new SimpleKey(params)
public static Object generateKey(Object... params) { if (params.length == 0) { return SimpleKey.EMPTY; } if (params.length == 1) { Object param = params[0]; if (param != null && !param.getClass().isArray()) { return param; } } return new SimpleKey(params); }
CacheAspectSupport
Object key = generateKey(context, result);
@Nullable
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
Cache.ValueWrapper cached = findInCaches(context, key);
if (cached != null) {
return cached;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
}
}
}
}
return null;
}
調用本類此方法
private Object generateKey(CacheOperationContext context, @Nullable Object result){
Object key = context.generateKey(result);
if (key == null) {
throw new IllegalArgumentException("Null key returned for cache operation (maybe you are " +
"using named params on classes without debug info?) " + context.metadata.operation);
}
if (logger.isTraceEnabled()) {
logger.trace("Computed cache key '" + key + "' for operation " + context.metadata.operation);
}
return key;
}
再調用本類:generateKey()方法,計算給定緩存操作的密鑰(key)
/**
* Compute the key for the given caching operation.
*/
@Nullable
protected Object generateKey(@Nullable Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
}
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
3、沒有查詢到緩存就調用目標方法
4、將目標方法返回的結果,放進緩存中
總結:@Cacheable標註的方法執行之前先來檢查緩存中有沒有這個數據,默認按照參數的值作爲key去查詢緩存,如果沒有就運行方法,並且將結果放到緩存中,以後再來調用就直接可以從緩存中拿到數據的值** *
核心: 1、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】組件*
2、key使用keyGenerator生成的,默認是SimpleKeyGenerator
5、其它註解說明
@CachePut 即調用方法,又更新緩存數據 達到同步更新的目的
修改了數據庫的某個數據,同時更新緩存;
運行時機: 1、先調用目標方法 2、將目標方法的結果緩存起來
測試步驟:
1、先查詢1號員工,查到結果會放到緩存中
key :1 , value:lastName
2、以後查詢還是之前的結果
3、更新1號員工的信息(lastName:zhangsan,gender=0) ,將方法的返回值也放進了緩存中
key:employee
value:返回的employee對象
4、查詢1號員工信息
預測:應該是更新後的信息,但是數據還是沒更新前的數據(緩存中的數據),這時數據庫已經更新了*
key應該是更新的員工信息:
key="#employee.id",使用傳入員工的參數id*
key="#result.id" 使用返回後的id*
注意:@Cacheable的key是不能用result* 爲什麼沒有更新?(1號員工沒有在緩存中進行更新)
EmployeeService
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp( Employee employee){
System.out.println("udapte..."+employee);
employeeMapper.updateEmp(employee);
return employee;
}
controller
@GetMapping("/emp")
public Employee updateEmp(Employee employee){
Employee emp = employeeService.updateEmp(employee);
return emp;
}
測試自行測試
@CacheEvict 清空緩存
**key:**指定要刪除的數據
allEntries:清空這個value中所有的緩存數據,
beforeInvocation:緩存的清除是否在方法之前執行,默認false ,false:代表方法之後執行,如果出現異常緩存就不會清除
beforeInvocation =true: 代表清除緩存是在方法執行之前清空,無論方法是否出現異常方法都會清空
@CacheEvict(value = "emp",allEntries = true,beforeInvocation =true)
public void deleteEmp(Integer id){
System.out.println("deleteEmp"+id);
int a=10/0;
//employeeMapper.deleteEmp(id);
}
@Caching:定義多個緩存規則
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
定義複雜的緩存規則
@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);
}
@CacheConfg:抽取緩存的公共配置,之後的其他緩存屬性可以不用寫
@Service
@CacheConfig(cacheNames = "emp")
public class EmployeeService {}