前言
我们都知道,一个程序的瓶颈通常都在数据库,很多场景需要获取相同的数据。比如网站页面数据等,需要一次次的请求数据库,导致大部分时间都浪费在数据库查询和方法调用上,这时就可以利用到缓存来缓解这个问题。
JSR107、Spring缓存抽象等概念
JSP107:
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设置。
首先是原生的JCache:
不得不说,使用java原生的缓存规范来实现我们的需求是很麻烦的,所有spring才对JSR-107进行了抽象,简化为Cache
和CacheManager
来帮助我们开发,我们可以通过两张图来对比一下使用了spring缓存抽象和使用Java Caching的区别:
Spring缓存抽象:
仅仅只需要操作CacheManager就可以来方便进行开发.
SpringBoot缓存原理
SpringBoot使用缓存需要引入spring-boot-starter-cache
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
根据以往对SpringBoot的解析中,我们先找到缓存自动配置类:CacheAutoConfiguration
在代码中我们可以看到CacheAutoConfiguration在启动的时候,加载了10个缓存配置类,比如我们常用的:RedisCacheConfiguration,EhCacheCacheConfiguration等
这些缓存配置类 都会有规则判断
@Configuration(proxyBeanMethods = false) //声明这是一个配置类
@ConditionalOnMissingBean({CacheManager.class}) //如果容器中没有CacheManager 才会实例化这个Bean
@Conditional({CacheCondition.class})
class SimpleCacheConfiguration {
现在我并没有在SpringBoot中配置其他缓存管理器,但CacheAutoConfiguration在启动的时候加载了10个缓存配置类,那么到底哪个配置起作用了呢?
首先在配置文件中添加:
debug: true #打开自动配置类报告
然后启动项目:
在启动日志中,我们可以看到SpringBoot默认为我们匹配(matched)了一个SimpleCacheConfiguration,而其他的9个缓存配置都是Did not match(不匹配)。那么也就是说SpringBoot的默认缓存配置类是SimpleCacheConfiguration
(ps:如果我们使用了Redis组件,匹配的缓存配置类就是:RedisCacheConfiguration)
那么SimpleCacheConfiguration这个缓存配置类又为我们做了什么呢?
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class SimpleCacheConfiguration {
SimpleCacheConfiguration() {
}
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return (ConcurrentMapCacheManager)cacheManagerCustomizers.customize(cacheManager);
}
}
在代码中我们可以看到,SimpleCacheConfiguration给容器中注入了一个CacheManager
通过这CacheManager(缓存管理器)我们可以创建、配置、获取、管理和控制多个唯一命名的Cache
二、 缓存注解
- Cache:缓存接口,定义缓存操作。实现:RedisCache,EnCacheCache等
- CacheManager:缓存管理器,管理各种缓存(Cache)组件
- @Cacheable:触发缓存写入,主要针对方法配置。
- @CacheEvict:清除缓存。
- @CachePut:更新缓存(不会影响到方法的运行)。
- @Caching:开启基于缓存的注解
- @CacheConfig:设置类级别上共享的一些常见缓存设置。
- keyGenerator:缓存数据时Key的生产策略
- serialize:缓存数据时value序列化策略
三、 缓存使用
要在Springboot中使用缓存需要以下几步:
第一步: 导入spring-boot-starter-cache模块
第二步: @EnableCaching开启缓存
第三步: 使用缓存注解
1.首先在pom文件中引入缓存坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2.在主程序中开启缓存注解
@EnableCaching //开启缓存
@MapperScan("com.meng.demo.mapper")
@SpringBootApplication
public class DemoCacheApplication {
public static void main(String[] args) {
SpringApplication.run(DemoCacheApplication.class, args);
}
}
测试:@Cacheable
将方法的运行结果进行缓存,以后要相同的数据,直接从缓存中获取,不用调用方法
service:
@Cacheable(cacheNames = "user")
public UserPO getUser(Integer id){
System.out.println("访问数据库:"+id);
return userMapper.getOne(id);
}
测试方法:
@Test
void contextLoads() {
Integer id = 1;
UserPO user1 = userService.getUser(1);
System.out.println("第一次查询:"+user1.getUserName());
UserPO user2 = userService.getUser(1);
System.out.println("第二次查询:"+user2.getUserName());
}
测试结果:
测试:@CachePut
主要用于修改了数据库的某个数据,同时更新缓存,
运行时机:先调用目标方法,再将目标方法的结果缓存起来
service:
@Cacheable(cacheNames = "user")
public UserPO getUser(Integer id){
System.out.println("访问数据库:"+id);
return userJpaMapper.getOne(id);
}
@CachePut(value = "user",key = "#result.id")
public UserPO update(UserPO po) {
System.out.println("数据库更新");
userMapper.update(po);
return po;
}
测试方法:
@Test
void updataUser() {
Integer id = 2;
UserPO user1 = userService.getUser(id);
System.out.println("第一次查询:"+user1.getUserName()+", 年龄:"+user1.getAge());
UserPO po = new UserPO();
po.setId(id);
po.setAge(50);
userService.update(po);
UserPO user2 = userService.getUser(id);
System.out.println("第二次查询:"+user2.getUserName()+", 年龄:"+user2.getAge());
}
测试结果
@CacheEvict
该注解用于删除一个缓存
/**
* allEntries:删除所有的缓存 allEntries = true
* beforeInvocation:缓存的清除是否在方法之前执行,默认是在方法之后执行
* 其他参数与@Cacheable大致一样
*/
@CacheEvict(value = "user", key = "#id", allEntries = true)
public void delete(Integer id) {
userJpaMapper.deleteById(id);
}
@Caching
该注解用于存放多个缓存注解,有时候对于一个方法,想放置多个缓存,既想缓存又想更新时使用
/**
* 可以放多个注解
* @param lastName
* @return
*/
@Caching(
cacheable = {
@Cacheable(value = "user", key = "#id")
},
put = {
@CachePut(value = "user", key = "#result.id"),
@CachePut(value = "emp", key = "#result.email")
}
)
public UserPO getUser(Integer id){
System.out.println("访问数据库:"+id);
return userJpaMapper.getOne(id);
}
@CacheConfig
有的时候觉得每次在使用缓存注解的时候都要指定缓存的名字,或者指定缓存的cacheManager之类的,觉得很麻烦。那么就可以在类上使用@CacheConfig统一的配置缓存的名字
@CacheConfig(value = "user")
@Service
public class UserService {
自定义key的生成策略
对于key,我们可以让它自动生成,生成的策略可以有我们自己制定,之需要在配置类中将我们的定制规则加入到容器中即可
@Bean
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object o, Method method, Object... objects) {
return method.getName() + "[" + Arrays.asList(objects) + "]";
}
};
}