在数据量比较大时,持续的访问数据库,数据库的性能就成为系统的瓶颈。在读多写少的场景中,可以利用缓存来提高系统的性能。

在Spring Boot中对于缓存的支持,提供了一系列的自动化配置,使我们可以非常方便的使用缓存。

Cache

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设置。

spring boot分布缓存 springboot缓存原理_ide

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、从缓存中读取之前缓存存储的数据

Cache

缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、 ConcurrentMapCache等

CacheManager

缓存管理器,管理各种缓存(Cache)组件

@Cacheable

主要针对方法配置,能够根据方法的请求参数对其结果进行缓存

@CacheEvict

清空缓存

@CachePut

保证方法被调用,又希望结果被缓存。

@EnableCaching

开启基于注解的缓存

keyGenerator

缓存数据时key生成策略

serialize

缓存数据时value序列化策略

@Cacheable/@CachePut/@CacheEvict 主要的参数

value

缓存的名称,在 spring 配置文件中定义,必须指定 至少一个

例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}

key

缓存的 key,可以为空,如果指定要按照 SpEL 表达 式编写,如果不指定,则缺省按照方法的所有参数 进行组合

例如: @Cacheable(value=”testcache”,key=”#userName”

condition

缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在 调用方法之前之后都能判断

例如: @Cacheable(value=”testcache”,condition=”#userNam e.length()>2”)

allEntries (@CacheEvict )

是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存

例如:@CachEvict(value=”testcache”,allEntries=true)

beforeInvocation (@CacheEvict)

是否在方法执行前就清空,缺省为 false,如果指定 为 true,则在方法还没有执行的时候就清空缓存, 缺省情况下,如果方法执行抛出异常,则不会清空 缓存

例如:@CachEvict(value=”testcache”, beforeInvocation=true)

unless (@CachePut) (@Cacheable)

用于否决缓存的,不像condition,该表达式只在方 法执行之后判断,此时可以拿到返回值result进行判 断。条件为true不会缓存,fasle才缓存

例如:@Cacheable(value=”testcache”,unless=”#result == null”)

Cache SpEL available metadata

名字

位置

描述

实例

methodName

root object

当前被调用的方法名

#root.methodName

method

root object

当前被调用的方法

#root.method.name

target

root object

当前被调用的目标对象

#root.target

targetClass

root object

当前被调用的目标对象类

#root.targetClass

args

root object

当前被调用的方法的参数列表

#root.args[0]

caches

root object

当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cache。

#root.caches[0].name

argument name

evaluation context

方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的 形式,0代表参数的索引。

#a0、#p0

result

evaluation context

方法执行后的返回值(仅当方法执行之后的判断有效,如 ‘unless’,’cache put’的表达式 ’cache evict’的表达式 beforeInvocation=false)

#result

自动配置原理

spring cache 的自动配置和之前的mongo 一样。
参考:

配置文件添加:
application.properties

debug=true

将 spring 的 debug 结果打印出来:

CacheAutoConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.cache.CacheManager' (OnClassCondition)
      - @ConditionalOnBean (types: org.springframework.cache.interceptor.CacheAspectSupport; SearchStrategy: all) found bean 'cacheInterceptor'; @ConditionalOnMissingBean (names: cacheResolver; types: org.springframework.cache.CacheManager; SearchStrategy: all) did not find any beans (OnBeanCondition)
CacheAutoConfiguration#cacheManagerCustomizers matched:
      - @ConditionalOnMissingBean (types: org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers; SearchStrategy: all) did not find any beans (OnBeanCondition)
CacheAutoConfiguration.CacheManagerJpaDependencyConfiguration matched:
      - @ConditionalOnClass found required class 'org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean' (OnClassCondition)
      - @ConditionalOnBean (types: org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; SearchStrategy: all) found bean '&entityManagerFactory' (OnBeanCondition)
-------------------------分割线---------------------------------------
SimpleCacheConfiguration matched:
      - Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration automatic cache type (CacheCondition)
      - @ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) did not find any beans (OnBeanCondition)

最终注入的是 SimpleCacheConfiguration 这个配置类。
那么我们看一下,为什么会注入 SimpleCacheConfiguration 这个配置类,而不是 CacheAutoConfiguration。

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 {

	@Bean
	@ConditionalOnMissingBean
	public CacheManagerCustomizers cacheManagerCustomizers(
			ObjectProvider<CacheManagerCustomizer<?>> customizers) {
		return new CacheManagerCustomizers(
				customizers.orderedStream().collect(Collectors.toList()));
	}
	...
}

注意这三个注解:

  • @ConditionalOnClass(CacheManager.class)
  • @ConditionalOnBean(CacheAspectSupport.class)
  • @ConditionalOnMissingBean(value = CacheManager.class, name = “cacheResolver”)

再来看 SimpleCacheConfiguration

@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);
	}

}
  • @ConditionalOnMissingBean(CacheManager.class)
  • @Conditional(CacheCondition.class)

如果没有 Bean CacheManager,那么默认会使用 SimpleCacheConfiguration 这个配置类。
然后会创建一个 ConcurrentMapCacheManager 对象,使用期对 Cache 进行管理。

ConcurrentMapCacheManager

public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {

private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);

	private boolean dynamic = true;

	private boolean allowNullValues = true;

	private boolean storeByValue = false;

	@Nullable
	private SerializationDelegate serialization;
	public void setCacheNames(@Nullable Collection<String> cacheNames) {
		if (cacheNames != null) {
			for (String name : cacheNames) {
				this.cacheMap.put(name, createConcurrentMapCache(name));
			}
			this.dynamic = false;
		}
		else {
			this.dynamic = true;
		}
	}
	@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;
	}
	...
}
springboot 使用 cache
pom 依赖
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<!-- h2 java 写的内存数据库 -->
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
@EnableCaching 开启注解缓存
@SpringBootApplication
@EnableJpaRepositories
@EnableCaching
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}
Service 层使用 @Cache
@Service
@Slf4j
@CacheConfig
// @CacheConfig主要用于配置该类中会用到的一些共用的缓存配置
// @CacheConfig的作用:抽取@Cacheable、@CachePut、@CacheEvict的公共属性值
// @CacheConfig的属性
public class CoffeeService {

    @Autowired
    private CoffeeRepository coffeeRepository;

    // 缓存用在更新/插入比较少的情况
    @Cacheable(cacheNames = "coffeeList") // 缓存结果
    public List<Coffee> getAllCoffee() {
        return coffeeRepository.findAll(Sort.by("id"));
    }

    /*1. @Cacheable的几个属性详解:
     *       cacheNames/value:指定缓存组件的名字
     *       key:缓存数据使用的key,可以用它来指定。默认使用方法参数的值,一般不需要指定
     *       keyGenerator:作用和key一样,二选一
     *       cacheManager和cacheResolver作用相同:指定缓存管理器,二选一
     *       condition:指定符合条件才缓存,比如:condition="#id>3"
     *                   也就是说传入的参数id>3才缓存数据
     *      unless:否定缓存,当unless为true时不缓存,可以获取方法结果进行判断
     *      sync:是否使用异步模式*/
    // @Cacheable(cacheNames= "coffee")
    // @Cacheable(cacheNames= "coffee",key="#name",condition="#name != 'admin'")
    @Cacheable(cacheNames = "coffee", key = "#name")
    public Coffee getCoffeeByName(String name) {
        return coffeeRepository.findCoffeeByName(name);
    }

    // @CachePut必须结合@Cacheable一起使用,否则没什么意义
    // @CachePut的作用:即调用方法,又更新缓存数据 ,修改了数据库中的数据,同时又更新了缓存!
    
    /**
     * @CachePut:即调用方法,又更新缓存数据
     * 修改了数据库中的数据,同时又更新了缓存
     *
     *运行时机:
     * 1.先调用目标方法
     * 2.将目标方法返回的结果缓存起来
     *
     * 测试步骤:
     * 1.查询name的咖啡信息
     * 2.以后查询还是之前的结果
     * 3.更新name的咖啡信息
     * 4.查询name=?返回的结果是什么?
     *     应该是更新后的咖啡信息
     *     但只更新了数据库,但没有更新缓存是什么原因?
     * 5.如何解决缓存和数据库同步更新?
     * 这样写:@CachePut(cacheNames = "coffee",key = "#coffee.name")
     *         @CachePut(cacheNames = "coffee",key = "#result.name")
     */
    @CachePut(cacheNames = "coffee", key = "#result.name")
    public Coffee updateCoffee(Coffee coffee) {
        return coffeeRepository.save(coffee);
    }
    
    /**
     *   @Caching是 @Cacheable、@CachePut、@CacheEvict注解的组合
     *   以下注解的含义:
     *   1.当使用指定名字查询数据库后,数据保存到缓存
     *   2.现在使用id、name就会直接查询缓存,而不是查询数据库
     */
    @Caching(
            cacheable = {@Cacheable(cacheNames="coffee",key="#name")},
            put={ @CachePut(key = "#result.id"),
                  @CachePut(key = "#result.name")
                }
    )
    public Coffee queryCoffeeByName(String name){
        return coffeeRepository.findCoffeeByName(name);
    }

    /**
     * @CacheEvict:清除缓存
     *    1.key:指定要清除缓存中的某条数据
     *    2.allEntries=true:删除缓存中的所有数据
     *    beforeInvocation=false:默认是在方法之后执行清除缓存
     *    3.beforeInvocation=true:现在是在方法执行之前执行清除缓存,
     *                          作用是:只清除缓存、不删除数据库数据
     */
    //@CacheEvict(cacheNames = "coffee",key = "#name")
    @CacheEvict(cacheNames = "coffee")
    public void delete(String name) {
    };

    @CacheEvict(cacheNames = "coffeeList")
    public void reload() {

    }

    public List<Coffee> getCoffeeByName(List<String> names) {
        return coffeeRepository.findByNameInOrderById(names);
    }
}
Controller 层调用 service 层的方法
@Controller
@RequestMapping("/coffee")
public class CoffeeController {
    @Autowired
    private CoffeeService coffeeService;

    @GetMapping("/list")
    @ResponseBody
    public List<Coffee> getAll() {
        return coffeeService.getAllCoffee();
    }

    @GetMapping("/get/{name}")
    @ResponseBody
    public Coffee getCoffeeByName(@PathVariable(value="name") String name){
        return coffeeService.getCoffeeByName(name);
    }

    @PostMapping("/update")
    @ResponseBody
    public Coffee updateCoffeeByName(@RequestBody Coffee coffee){
        return coffeeService.updateCoffee(coffee);
    }

    @GetMapping("/delete/{name}")
    @ResponseBody
    public String deleteCoffeeByName(@PathVariable(value="name") String name){
         coffeeService.delete(name);
         return "success";
    }

    @GetMapping("/reload")
    @ResponseBody
    public String delete(){
        coffeeService.reload();
        return "success";
    }
}
测试

使用 vscode restClient 插件测试:

### GET
GET http://localhost:8080/coffee/list

### GET
GET http://localhost:8080/coffee/reload

### GET
GET http://localhost:8080/coffee/get/latte

### GET
GET http://localhost:8080/coffee/delete/latte

### Coffee Post
POST http://localhost:8080/coffee/update
Content-Type: application/json

{
    "id": 2,
    "name": "latte",
    "price": 1000
}