SpringBoot项目中怎么使用缓存Cache - 开发技术

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
    本文小编为大家详细介绍“SpringBoot项目中怎么使用缓存Cache”,内容详细,步骤清晰,细节处理妥当,希望这篇“SpringBoot项目中怎么使用缓存Cache”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    前言

    缓存可以通过将经常访问的数据存储在内存中,减少底层数据源如数据库的压力,从而有效提高系统的性能和稳定性。我想大家的项目中或多或少都有使用过,我们项目也不例外,但是最近在review公司的代码的时候写的很蠢且low, 大致写法如下:

    public User getById(String id) {
    	User user = cache.getUser();
        if(user != null) {
            return user;
        }
        // 从数据库获取
        user = loadFromDB(id);
        cahce.put(id, user);
    	return user;
    }

    其实Spring Boot 提供了强大的缓存抽象,可以轻松地向您的应用程序添加缓存。本文就讲讲如何使用 Spring 提供的不同缓存注解实现缓存的最佳实践。

    启用缓存@EnableCaching

    现在大部分项目都是是SpringBoot项目,我们可以在启动类添加注解@EnableCaching来开启缓存功能。

    @SpringBootApplication
    @EnableCaching
    public class SpringCacheApp {
    
        public static void main(String[] args) {
            SpringApplication.run(Cache.class, args);
        }
    }

    既然要能使用缓存,就需要有一个缓存管理器Bean,默认情况下,@EnableCaching 将注册一个ConcurrentMapCacheManager的Bean,不需要单独的 bean 声明。ConcurrentMapCacheManager将值存储在ConcurrentHashMap的实例中,这是缓存机制的最简单的线程安全实现。

    自定义缓存管理器

    默认的缓存管理器并不能满足需求,因为她是存储在jvm内存中的,那么如何存储到redis中呢?这时候需要添加自定义的缓存管理器。

    1.添加依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    2.配置Redis缓存管理器

    @Configuration
    @EnableCaching
    public class CacheConfig {
    
        @Bean
        public RedisConnectionFactory redisConnectionFactory() {
            return new LettuceConnectionFactory();
        }
    
        @Bean
        public CacheManager cacheManager() {
            RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .disableCachingNullValues()
                .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
    
            RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory())
                .cacheDefaults(redisCacheConfiguration)
                .build();
    
            return redisCacheManager;
        }
    }

    现在有了缓存管理器以后,我们如何在业务层面操作缓存呢?

    我们可以使用@Cacheable@CachePut 或@CacheEvict 注解来操作缓存了。

    @Cacheable

    该注解可以将方法运行的结果进行缓存,在缓存时效内再次调用该方法时不会调用方法本身,而是直接从缓存获取结果并返回给调用方。

    SpringBoot项目中怎么使用缓存Cache

    例子1:缓存数据库查询的结果。

    @Service
    public class MyService {
    
        @Autowired
        private MyRepository repository;
    
        @Cacheable(value = "myCache", key = "#id")
        public MyEntity getEntityById(Long id) {
            return repository.findById(id).orElse(null);
        }
    }

    在此示例中,@Cacheable 注解用于缓存 getEntityById()方法的结果,该方法根据其 ID 从数据库中检索 MyEntity 对象。

    但是如果我们更新数据呢?旧数据仍然在缓存中?

    @CachePut

    然后@CachePut 出来了, 与 @Cacheable 注解不同的是使用 @CachePut 注解标注的方法,在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式写入指定的缓存中。@CachePut 注解一般用于更新缓存数据,相当于缓存使用的是写模式中的双写模式。

    @Service
    public class MyService {
    
        @Autowired
        private MyRepository repository;
    
        @CachePut(value = "myCache", key = "#entity.id")
        public void saveEntity(MyEntity entity) {
            repository.save(entity);
        }
    }

    @CacheEvict

    标注了 @CacheEvict 注解的方法在被调用时,会从缓存中移除已存储的数据。@CacheEvict 注解一般用于删除缓存数据,相当于缓存使用的是写模式中的失效模式。

    SpringBoot项目中怎么使用缓存Cache

    @Service
    public class MyService {
    
        @Autowired
        private MyRepository repository;
    
         @CacheEvict(value = "myCache", key = "#id")
        public void deleteEntityById(Long id) {
            repository.deleteById(id);
        }
    }

    @Caching

    @Caching 注解用于在一个方法或者类上,同时指定多个 Spring Cache 相关的注解。

    SpringBoot项目中怎么使用缓存Cache

    例子1:@Caching注解中的evict属性指定在调用方法 saveEntity 时失效两个缓存。

    @Service
    public class MyService {
    
        @Autowired
        private MyRepository repository;
    
        @Cacheable(value = "myCache", key = "#id")
        public MyEntity getEntityById(Long id) {
            return repository.findById(id).orElse(null);
        }
    
        @Caching(evict = {
            @CacheEvict(value = "myCache", key = "#entity.id"),
            @CacheEvict(value = "otherCache", key = "#entity.id")
        })
        public void saveEntity(MyEntity entity) {
            repository.save(entity);
        }
    
    }

    例子2:调用getEntityById方法时,Spring会先检查结果是否已经缓存在myCache缓存中。如果是,Spring 将返回缓存的结果而不是执行该方法。如果结果尚未缓存,Spring 将执行该方法并将结果缓存在 myCache 缓存中。方法执行后,Spring会根据@CacheEvict注解从otherCache缓存中移除缓存结果。

    @Service
    public class MyService {
    
        @Caching(
            cacheable = {
                @Cacheable(value = "myCache", key = "#id")
            },
            evict = {
                @CacheEvict(value = "otherCache", key = "#id")
            }
        )
        public MyEntity getEntityById(Long id) {
            return repository.findById(id).orElse(null);
        }
    
    }

    例子3:当调用saveData方法时,Spring会根据@CacheEvict注解先从otherCache缓存中移除数据。然后,Spring 将执行该方法并将结果保存到数据库或外部 API。

    方法执行后,Spring 会根据@CachePut注解将结果添加到 myCachemyOtherCache 和 myThirdCache 缓存中。Spring 还将根据@Cacheable注解检查结果是否已缓存在 myFourthCache 和 myFifthCache 缓存中。如果结果尚未缓存,Spring 会将结果缓存在适当的缓存中。如果结果已经被缓存,Spring 将返回缓存的结果,而不是再次执行该方法。

    @Service
    public class MyService {
    
        @Caching(
            put = {
                @CachePut(value = "myCache", key = "#result.id"),
                @CachePut(value = "myOtherCache", key = "#result.id"),
                @CachePut(value = "myThirdCache", key = "#result.name")
            },
            evict = {
                @CacheEvict(value = "otherCache", key = "#id")
            },
            cacheable = {
                @Cacheable(value = "myFourthCache", key = "#id"),
                @Cacheable(value = "myFifthCache", key = "#result.id")
            }
        )
        public MyEntity saveData(Long id, String name) {
            // Code to save data to a database or external API
            MyEntity entity = new MyEntity(id, name);
            return entity;
        }
    
    }

    @CacheConfig

    通过@CacheConfig 注解,我们可以将一些缓存配置简化到类级别的一个地方,这样我们就不必多次声明相关值:

    @CacheConfig(cacheNames={"myCache"})
    @Service
    public class MyService {
    
        @Autowired
        private MyRepository repository;
    
        @Cacheable(key = "#id")
        public MyEntity getEntityById(Long id) {
            return repository.findById(id).orElse(null);
        }
    
        @CachePut(key = "#entity.id")
        public void saveEntity(MyEntity entity) {
            repository.save(entity);
        }
    
        @CacheEvict(key = "#id")
        public void deleteEntityById(Long id) {
            repository.deleteById(id);
        }
    }

    Condition & Unless

    • condition作用:指定缓存的条件(满足什么条件才缓存),可用 SpEL 表达式(如 #id>0,表示当入参 id 大于 0 时才缓存)

    • unless作用 : 否定缓存,即满足 unless 指定的条件时,方法的结果不进行缓存,使用 unless 时可以在调用的方法获取到结果之后再进行判断(如 #result == null,表示如果结果为 null 时不缓存)

    //when id >10, the @CachePut works. 
    @CachePut(key = "#entity.id", condition="#entity.id > 10")
    public void saveEntity(MyEntity entity) {
    	repository.save(entity);
    }
    
    
    //when result != null, the @CachePut works.
    @CachePut(key = "#id", condition="#result == null")
    public void saveEntity1(MyEntity entity) {
    	repository.save(entity);
    }

    清理全部缓存

    通过allEntriesbeforeInvocation属性可以来清除全部缓存数据,不过allEntries是方法调用后清理,beforeInvocation是方法调用前清理。

    //方法调用完成之后,清理所有缓存
    @CacheEvict(value="myCache",allEntries=true)
    public void delectAll() {
        repository.deleteAll();
    }
    
    //方法调用之前,清除所有缓存
    @CacheEvict(value="myCache",beforeInvocation=true)
    public void delectAll() {
        repository.deleteAll();
    }

    SpEL表达式

    Spring Cache注解中频繁用到SpEL表达式,那么具体如何使用呢?

    SpEL 表达式的语法

    SpringBoot项目中怎么使用缓存Cache

    Spring Cache可用的变量

    SpringBoot项目中怎么使用缓存Cache

    最佳实践

    通过Spring缓存注解可以快速优雅地在我们项目中实现缓存的操作,但是在双写模式或者失效模式下,可能会出现缓存数据一致性问题(读取到脏数据),Spring Cache 暂时没办法解决。最后我们再总结下Spring Cache使用的一些最佳实践。

    • 只缓存经常读取的数据:缓存可以显着提高性能,但只缓存经常访问的数据很重要。很少或从不访问的缓存数据会占用宝贵的内存资源,从而导致性能问题。

    • 根据应用程序的特定需求选择合适的缓存提供程序和策略。SpringBoot 支持多种缓存提供程序,包括 EhcacheHazelcast 和 Redis

    • 使用缓存时请注意潜在的线程安全问题。对缓存的并发访问可能会导致数据不一致或不正确,因此选择线程安全的缓存提供程序并在必要时使用适当的同步机制非常重要。

    • 避免过度缓存。缓存对于提高性能很有用,但过多的缓存实际上会消耗宝贵的内存资源,从而损害性能。在缓存频繁使用的数据和允许垃圾收集不常用的数据之间取得平衡很重要。

    • 使用适当的缓存逐出策略。使用缓存时,重要的是定义适当的缓存逐出策略以确保在必要时从缓存中删除旧的或陈旧的数据。

    • 使用适当的缓存键设计。缓存键对于每个数据项都应该是唯一的,并且应该考虑可能影响缓存数据的任何相关参数,例如用户 ID、时间或位置。

    • 常规数据(读多写少、即时性与一致性要求不高的数据)完全可以使用 Spring Cache,至于写模式下缓存数据一致性问题的解决,只要缓存数据有设置过期时间就足够了。

    • 特殊数据(读多写多、即时性与一致性要求非常高的数据),不能使用 Spring Cache,建议考虑特殊的设计(例如使用 Cancal 中间件等)。

    读到这里,这篇“SpringBoot项目中怎么使用缓存Cache”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注片云行业资讯频道。

  • 阿里云国际版折扣https://www.yundadi.com

  • 阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
    标签: Spring

    “SpringBoot项目中怎么使用缓存Cache - 开发技术” 的相关文章

    将本地master分支代码提交到远程main分支

    将master分支代码提交到main分支 VSCode写项目时默认分支为master而github创建repository默认为main如何将本地的master分支提交到远程main分支上。 第一种方式通过修改本地分支名称 修改本地分支名称 git branch -m master main...

    Docker:关于 Dockerfile 编写优化的一些笔记整理

    写在前面 分享一些 Dickerfile 构建镜像优化方式的笔记 理解不足小伙伴帮忙指正 对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整...

    (Python)判断输入的数字是否为素数

    # 素数只能被1和自身整除的正数 number = int(input("请输入一个整数")) if number <= 1:     print(str(number)+"不是素数") else:     # 判断2到number之间...

    scala iterator循环遍历

    感觉scala的循环遍历,有些时候像狗屎一样难用 你要想使用scala遍历 com.google.gson.JsonArray,刚开始估计感觉scala真他妈的不是东西,狗咬刺猬,无从下口。下面是我的解决办法:val array = JsonUtil.jsonArray(stepStr)...

    如何用post的方式进行eventSource请求

    我们平时的工作中可能需要和服务端建立链接从而来接受服务端推送的数据常用的就是eventSource我们平时常用的就是通过get的方式创建一个eventSource但是我们如何通过post的方式创建呢首先我们介绍一下eventSource还有他和websocket的区别 1.eventSourc...

    怎么修改Nextcloud中的PHP配置 - 编程语言

    这篇“怎么修改Nextcloud中的PHP配置”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么修改Nextcloud中的PHP配置”文章吧。...