zoukankan      html  css  js  c++  java
  • springBoot:缓存以及其工作原理

    前言

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

    JSR107、Spring缓存抽象等概念

    JSP107:

    Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, EntryExpiry

    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:

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

    Spring缓存抽象:

     可以很明显的看到,我们使用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序列化策略

    具体详细介绍参考:https://blog.csdn.net/xiaoyao2246/article/details/83995882

    三、 缓存使用

    要在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) + "]";
            }
        };
    }
  • 相关阅读:
    JVM基础(一)—— 运行时内存结构
    SQL if 和 case when查询示例
    SQL分组聚合查询
    My music
    DataX增量同步到ADB ADS时报错
    shell find的用法
    pycharm安装
    在两个库中查找出差异明细
    docker下安装centos
    升级RDS8.0遇到的问题
  • 原文地址:https://www.cnblogs.com/mengY/p/11760562.html
Copyright © 2011-2022 走看看