文章目录
1. SpringBoot对缓存的支持
JSR-107
定义了5个核心接口来实现缓存操作,分别是CachingProvider, CacheManager, Cache, Entry和Expiry。
Spring从3.1开始定义了org.springframework.cache.Cache
和 org.springframework.cache.CacheManager
接口来统一不同的缓存技术;并支持使用 JSR-107
注解简化我们开发;
其中,Spring支持多种缓存的实现方式,如下图所示:
2. 核心接口与缓存注解
Spring 中关于缓存的核心接口主要有两个:
org.springframework.cache.Cache
:用于定义缓存的各种操作org.springframework.cache.CacheManager
:用于管理各个cache缓存组件
Spring中常用的关于缓存的注解有以下几个:
注解 | 作用 |
---|---|
@Cacheable | 通常用于配置方法,将方法的返回结果注入到缓存对象中 |
@CacheConfig | 用于对类进行配置,对整个类的缓存进行配置,可用 @Cacheable 取代 |
@CacheEvict | 可用于类或方法,用于清空缓存 |
@CachePut | 强制执行方法并将返回结果放入缓存,而不是像 @Cacheable 那样首先从缓存中寻找方法返回结果是否存在缓存 |
@EnableCaching | 用于SpringBoot的启动类,开启注解功能 |
3. 缓存的基本使用
下面用一个小demo来实现一下Springboot中的缓存使用。
- 创建一个数据库和数据表,创建对应好实体类:
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;
private Integer dId;
//getter和setter方法省略
}
- 本例采用Mybatis框架,采用注解的方式编写crud的mapper接口实现DAO层:
@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} WHRER id=#{id}")
public void updateEmp(Employee emp);
@Delete("DELETE FROM employee WHERE id=#{id}")
public void deleteEmpById(Integer id);
@Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName}, #{email}, #{gender}, #{dId})")
public void insertEmployee(Employee employee);
}
- 编写相应的service类和Controller类实现数据库的查询:
//Service
@Service
public class EmployeeService {
@Resource //用Autowired会出现无法获取mapper类的报错,可忽略
EmployeeMapper employeeMapper;
@Cacheable(cacheNames = {"emp"} ) //开启缓存,将查询结果放入到名为emp的cache组件中
public Employee getEmp(Integer id){
Employee employee = employeeMapper.getEmpById(id);
return employee;
}
}
//-------------------------------------------------------------------
//Controller
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@RequestMapping("/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id){
Employee employee = employeeService.getEmp(id);
return employee;
}
}
- 在启动类中加入注解开启缓存功能;
@MapperScan("com.erving.springboot01cache.mapper")
@SpringBootApplication
@EnableCaching
public class Springboot01CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot01CacheApplication.class, args);
}
}
下面来看一下缓存开启的效果:
当没有开启缓存时,多次向服务器发送相同的查询请求,可以看到返回的json数据:
此时,每次请求都会导致服务器执行一次数据库查询,严重影响效率:
当开启缓存后,只有第一次向服务器发送请求时,服务器执行了查询,并将查询结果放入缓存,此后再次执行查询便从缓存中获取查询结果。
4. Springboot缓存的基本原理
1. CacheConfigurationImportSelector
该方法导入了所有缓存类型的配置类,默认启用 SimpleCacheConfiguration
,
2. SimpleCacheConfiguration
SimpleCacheConfiguration
配置类向容器中注入了一个 ConcurrentMapCacheManager
实例 。
3. ConcurrentMapCacheManager
该类用于管理缓存对象的实例,主要有如下几个方法:
//根据缓存对象的名称获取缓存对象
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;
}
//创建一个底层为ConcurrentMap的缓存实例对象
protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256),
isAllowNullValues(), actualSerialization);
}
//设置缓存名称
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;
}
}
//...
4. ConcurrentMapCache
ConcurrentMapCacheManager 类会调用一个 ConcurrentMapCache 构造方法来创建一个 ConcurrentMapCache 缓存实例对象,这个类就是用来实现缓存的底层类了,看一下这个类的内容:
public class ConcurrentMapCache extends AbstractValueAdaptingCache {
private final String name;
//store对象是一个ConcorentMap实例,用来直接存放缓存对象
private final ConcurrentMap<Object, Object> store;
//从外部获取store对象
@Override
public final ConcurrentMap<Object, Object> getNativeCache() {
return this.store;
}
//根据传入的key值查询缓存对象
@Override
@Nullable
protected Object lookup(Object key) {
return this.store.get(key);
}
//放入缓存对象和对应的key值
@Override
public void put(Object key, @Nullable Object value) {
this.store.put(key, toStoreValue(value));
}
//...等等
}
上面列举了该类的几个基本方法。
综上所述,Springboot的默认缓存实现主要由两个ConcurrentHashMap实现,一个是 ConcurrentMapCacheManager
类中的 cacheMap
,该对象存放着 不同名称的 Cache 对象。而另一个是 ConcurrentMapCache
类中的 store
对象,该对象存放着被放入到缓存中的各种类的对象。
5. 获取缓存中的所有对象
在第3节代码的基础上,新增以下代码:
//Department对象的pojo实体类代码忽略,只给出相关的查询代码
@RestController
public class DepartmentController {
@Autowired
DepartmentService departmentService;
@RequestMapping("/dep/{id}")
public Department getDepById(@PathVariable("id") Integer id){
return departmentService.getDepById(id);
}
}
@Service
public class DepartmentService {
@Resource
DepartmentMapper departmentMapper;
@Cacheable(cacheNames = {"dep"} )
public Department getDepById(Integer id){
return departmentMapper.getDepById(id);
}
}
@Mapper
public interface DepartmentMapper {
@Select("SELECT * FROM department WHERE id = #{id}")
public Department getDepById(Integer id);
}
在一个controller类中加入以下代码,可以实现对缓存数据的遍历:
@Autowired //注入缓存管理器
CacheManager cacheManager;
@RequestMapping("/showcache")
public void showCache(){
//获取cacheMap中所有缓存对象的名称
Collection<String> e= cacheManager.getCacheNames();
for (String s : e) {
//遍历所有缓存对象
Cache cache = cacheManager.getCache(s);
System.out.println(cache.getName());
//获取ConcurrentMapCache对象中的store实例对象,以ConcurrentMap形式返回
ConcurrentMap<Object, Object> map = (ConcurrentMap<Object, Object>) cache.getNativeCache();
Collection<Object> collection = map.values();
//遍历缓存对象中的所有元素
for (Object o : collection) {
System.out.println(o);
}
}
}
通过http请求在缓存中放入两个名为emp和dep的缓存对象,每个缓存对象都放入对应的Employee类对象和Department对象,再访问上述的路径"…/showcache",可以看到控制台中输出一下信息:
5. 基本注解
1. @Cacheable
该注解用于方法上,将方法返回值放入缓存中。
1. value/cachenames
用于指定缓存名称,为数组形式,可以给一个返回结果声明多个缓存对象,即放入多个缓存对象中。
2. key / keyGenerator
用于指定方法返回结果在缓存中存放时所对应的key值,默认情况下使用方法的参数生成。可以使用spel表达式自己配置,也可以创建一个keyGenerator类来生成自己的key值。
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uuWzEJbU-1586441822869)(img/Snipaste_2020-04-09_20-53-10.png)]
3. condition / unless
用于带条件地创建缓存,使用spel表达式,利用方法参数或返回结果进行条件设置,当满足条件时,才将结果放入缓存。
当unless指定条件为true时,不执行缓存。
4. sync
在多线程的情况下,可以设置sync=true来实现缓存的线程安全。当开启同步后,unless
关键字不能使用。
2. CachePut
使用该注解后,无论方法的返回值是否被缓存,方法都会执行,而且每次执行后的返回结果都会被更新到缓存中,因此常用于更新数据库内容的方法中。
其内置属性与Cacheable基本一致。
注意使用该注解时,key值需要与被更新的对象在缓存中的原有key值相对应,若key值不一致,将会创建一个新的缓存,无法实现更新效果。
3. CacheEvict
通过制定的key值清除对应的缓存,或者使用allEntries=true属性删除所有的缓存。
默认情况下,方法执行完毕后,才进行缓存清除,从而保证缓存与数据库的状态保持一致。若缓存不依赖于方法的执行结果,可以使用 beforeInvocation=true
来配置为方法执行前执行缓存清除。
4. Caching
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
如代码所示,该注解用于组合三个常用的注解。
5. CacheConfig
该注解用于类中,当一个类中的多个方法中的缓存注解使用某些相同的属性时,为了简化代码,可以将这些属性在类的层级使用该注解进行配置,从而省略方法层级的配置。