前言

我们都知道,一个程序的瓶颈通常都在数据库,很多场景需要获取相同的数据。比如网站页面数据等,需要一次次的请求数据库,导致大部分时间都浪费在数据库查询和方法调用上,这时就可以利用到缓存来缓解这个问题。

JSR107、Spring缓存抽象等概念

JSP107:

Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。

  1. CachingProvider:定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
  2. CacheManager:定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
  3. Cache:是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
  4. Entry:是一个存储在Cache中的key-value对.
  5. Expiry:每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

 首先是原生的JCache:

 

spring boot 缓存管理 springboot缓存原理_缓存

 不得不说,使用java原生的缓存规范来实现我们的需求是很麻烦的,所有spring才对JSR-107进行了抽象,简化为CacheCacheManager来帮助我们开发,我们可以通过两张图来对比一下使用了spring缓存抽象和使用Java Caching的区别:

Spring缓存抽象:

spring boot 缓存管理 springboot缓存原理_System_02

仅仅只需要操作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等

spring boot 缓存管理 springboot缓存原理_缓存_03

这些缓存配置类 都会有规则判断

@Configuration(proxyBeanMethods = false)  //声明这是一个配置类
@ConditionalOnMissingBean({CacheManager.class})  //如果容器中没有CacheManager 才会实例化这个Bean
@Conditional({CacheCondition.class}) 
class SimpleCacheConfiguration {

  

现在我并没有在SpringBoot中配置其他缓存管理器,但CacheAutoConfiguration在启动的时候加载了10个缓存配置类,那么到底哪个配置起作用了呢?

首先在配置文件中添加:

debug: true   #打开自动配置类报告

然后启动项目:

spring boot 缓存管理 springboot缓存原理_spring_04

spring boot 缓存管理 springboot缓存原理_缓存_05

spring boot 缓存管理 springboot缓存原理_spring boot 缓存管理_06

在启动日志中,我们可以看到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());
    }

测试结果:

spring boot 缓存管理 springboot缓存原理_System_07

 

 

 

测试:@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());
    }

测试结果

spring boot 缓存管理 springboot缓存原理_System_08

 

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