1.1_springboot2.x與緩存原理介紹&使用緩存

一、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 緩存接口,定義緩存操作。實現有:RedisCacheEhCacheCacheConcurrentMapCache
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 {}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章