• SpringBoot 整合缓存Cacheable实战详细使用


    前言

    我知道在接口api项目中,频繁的调用接口获取数据,查询数据库是非常耗费资源的,于是就有了缓存技术,可以把一些不常更新,或者经常使用的数据,缓存起来,然后下次再请求时候,就直接从缓存中获取,不需要再去查询数据,这样可以提供程序性能,增加用户体验,也节省服务资源浪费开销,

    springboot帮你我们做好了整合,有对应的场景启动器start,我们之间引入使用就好了,帮我们整合了各种缓存

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-cache</artifactId>
            </dependency>
        </dependencies>
    

    简介

    缓存介绍

    Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术。并支持使用 JCache(JSR-107)注解简化我们的开发。

    其使用方法和原理都类似于 Spring 对事务管理的支持。Spring Cache 是作用在方法上的,其核心思想是,当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存在缓存中。

    Cache 和 CacheManager 接口说明

    Cache 接口包含缓存的各种操作集合,你操作缓存就是通过这个接口来操作的。
    Cache 接口下 Spring 提供了各种 xxxCache 的实现,比如:RedisCache、EhCache、ConcurrentMapCache

    CacheManager 定义了创建、配置、获取、管理和控制多个唯一命名的 Cache。这些 Cache 存在于 CacheManager 的上下文中。

    小结

    每次调用需要缓存功能的方法时,Spring 会检查指定参数的指定目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

    使用Spring缓存抽象时我们需要关注以下两点;

    1. 确定方法需要被缓存以及他们的缓存策略
    2. 从缓存中读取之前缓存存储的数据

    快速开始

    1. 使用缓存我们需要开启基于注解的缓存,使用 @EnableCaching 标注在 springboot 主启动类上或者配置类上
    @SpringBootApplication
    @MapperScan(value = {"cn.soboys.kmall.mapper","cn.soboys.kmall.sys.mapper","cn.soboys.kmall.security.mapper"},nameGenerator = UniqueNameGenerator.class)
    @ComponentScan(value =  {"cn.soboys.kmall"},nameGenerator = UniqueNameGenerator.class)
    @EnableCaching  //开启缓存注解驱动,否则后面使用的缓存都是无效的
    public class WebApplication {
        private static ApplicationContext applicationContext;
    
        public static void main(String[] args) {
            applicationContext =
                    SpringApplication.run(WebApplication.class, args);
            //displayAllBeans();
        }
    
    
        /**
         * 打印所以装载的bean
         */
        public static void displayAllBeans() {
            String[] allBeanNames = applicationContext.getBeanDefinitionNames();
            for (String beanName : allBeanNames) {
                System.out.println(beanName);
            }
        }
    }
    

    或者配置类

    /**
     * @author kenx
     * @version 1.0
     * @date 2021/8/17 15:05
     * @webSite https://www.soboys.cn/
     * 自定义缓存配置
     */
    @Configuration
    @Slf4j
    @EnableCaching  //开启缓存注解驱动,否则后面使用的缓存都是无效的
    public class CacheConfig {
    
        //自定义配置类配置keyGenerator
    
        @Bean("myKeyGenerator")
        public KeyGenerator keyGenerator(){
            return new KeyGenerator() {
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    return method.getName()+"["+ Arrays.asList(params).toString() +"]";
                }
            };
        }
    }
    
    1. 标注缓存注解在需要缓存的地方使用@Cacheable注解
    @CacheConfig(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )
    public interface IMenuService extends IService<Menu> {
        /**
         * 获取用户菜单信息
         *
         * @param username 用户名
         * @return
         */
        @Cacheable(key = "#username")
        List<Menu> getUserMenus(String username);
    }
    

    @Cacheable注解有如下一些参数我们可以看到他源码

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Cacheable {
        @AliasFor("cacheNames")
        String[] value() default {};
    
        @AliasFor("value")
        String[] cacheNames() default {};
    
        String key() default "";
    
        String keyGenerator() default "";
    
        String cacheManager() default "";
    
        String cacheResolver() default "";
    
        String condition() default "";
    
        String unless() default "";
    
        boolean sync() default false;
    }
    

    下面介绍一下 @Cacheable 这个注解常用的几个属性:

    1. cacheNames/value :指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定 多个缓存;

    2. key :缓存数据时使用的 key,可以用它来指定。默认是使用方法参数的值。(这个 key 你可以使用 spEL 表达式来编写如 #i d;参数id的值 #a0 #p0 #root.args[0]

    3. keyGenerator :key的生成器;可以自己指定key的生成器的组件id 然后key 和 keyGenerator 二选一使用

    4. cacheManager :可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。或者cacheResolver指定获取解析器

    5. condition :可以用来指定符合条件的情况下才缓存

    condition = "#id>0"
    condition = "#a0>1":第一个参数的值》1的时候才进行缓存
    
    1. unless :否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存。当然你也可以获取到结果进行判断。(通过 #result 获取方法结果)
    unless = "#result == null"
    unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
    
    1. sync :是否使用异步模式。异步模式的情况下unless不支持 默认是方法执行完,以同步的方式将方法返回的结果存在缓存中

    cacheNames/value属性

    用来指定缓存组件的名字,将方法的返回结果放在哪个缓存中,可以是数组的方式,支持指定多个缓存

     /**
         * 获取用户菜单信息
         *
         * @param username 用户名
         * @return
         */
        @Cacheable(cacheNames = "menuCache") 或者// @Cacheable(cacheNames = {"menuCache","neCacge"})
        List<Menu> getUserMenus(String username);
    

    如果只有一个属性,cacheNames可忽略,直接是value属性默认

    key

    缓存数据时使用的 key。默认使用的是方法参数的值。可以使用 spEL 表达式去编写。

    Cache SpEL available metadata

    名称 位置 描述 示例
    methodName root对象 当前被调用的方法名 #root.methodname
    method root对象 当前被调用的方法 #root.method.name
    target root对象 当前被调用的目标对象实例 #root.target
    targetClass root对象 当前被调用的目标对象的类 #root.targetClass
    args root对象 当前被调用的方法的参数列表 #root.args[0]
    caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
    argumentName 执行上下文(avaluation context) 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
    result 执行上下文(evaluation context) 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result
    //key = "#username" 就是参数username
     @Cacheable(key = "#username" ,cacheNames = "menuCache")
    List<Menu> getUserMenus(String username);
    

    keyGenerator

    key 的生成器,可以自己指定 key 的生成器,通过这个生成器来生成 key

    定义一个@Bean类,将KeyGenerator添加到Spring容器

    @Configuration
    @Slf4j
    @EnableCaching  //开启缓存注解驱动,否则后面使用的缓存都是无效的
    public class CacheConfig {
    
        //自定义配置类配置keyGenerator
    
        @Bean("myKeyGenerator")
        public KeyGenerator keyGenerator(){
            return new KeyGenerator() {
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    return method.getName()+"["+ Arrays.asList(params).toString() +"]";
                }
            };
        }
    }
    

    在使用指定自己的@Cacheable(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )

    注意这样放入缓存中的 key 的生成规则就按照你自定义的 keyGenerator 来生成。不过需要注意的是:@Cacheable 的属性,key 和 keyGenerator 使用的时候,一般二选一。

    condition

    符合条件的情况下才缓存。方法返回的数据要不要缓存,可以做一个动态判断。

     /**
         * 获取用户菜单信息
         *
         * @param username 用户名
         * @return
         */
        //判断username 用户名是kenx开头才会被缓存
        @Cacheable(key = "#username" ,condition = "#username.startsWith('kenx')")
        List<Menu> getUserMenus(String username);
    

    unless

    否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存。

     /**
         * 获取用户菜单信息
         *
         * @param username 用户名
         * @return
         */
        //判断username 用户名是kenx开头不会被缓存
        @Cacheable(key = "#username" ,condition = "#username.startsWith('kenx')")
        List<Menu> getUserMenus(String username);
    

    spEL 编写 key

    当然我们可以全局去配置,cacheNames,keyGenerator属性通过@CacheConfig注解可以用于抽取缓存的公共配置,然后在类加上就可以,eg:如

    //全局配置,下面用到缓存方法,不配置默认使用全局的
    @CacheConfig(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )
    public interface IMenuService extends IService<Menu> {
        /**
         * 获取用户菜单信息
         *
         * @param username 用户名
         * @return
         */
        @Cacheabl
        List<Menu> getUserMenus(String username);
    }
    
    

    深入使用

    @CachePut

    @CachePut注解也是一个用来缓存的注解,不过缓存和@Cacheable有明显的区别是即调用方法,又更新缓存数据,也就是执行方法操作之后再来同步更新缓存,所以这个主键常用于更新操作,也可以用于查询,主键属性和@Cacheable有很多类似的参看 @CachePut源码

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface CachePut {
        @AliasFor("cacheNames")
        String[] value() default {};
    
        @AliasFor("value")
        String[] cacheNames() default {};
    
        String key() default "";
    
        String keyGenerator() default "";
    
        String cacheManager() default "";
    
        String cacheResolver() default "";
    
        String condition() default "";
    
        String unless() default "";
    }
    
    /**
         *  @CachePut:既调用方法,又更新缓存数据;同步更新缓存
         *  修改了数据,同时更新缓存
         */
        @CachePut(value = {"emp"}, key = "#result.id")
        public Employee updateEmp(Employee employee){
            employeeMapper.updateEmp(employee);
            LOG.info("更新{}号员工数据",employee.getId());
            return employee;
        }
    

    @CacheEvict

    清空缓存
    主要属性:

    1. key:指定要清除的数据
    2. allEntries = true:指定清除这个缓存中所有的数据
    3. beforeInvocation = false:默认代表缓存清除操作是在方法执行之后执行
    4. beforeInvocation = true:代表清除缓存操作是在方法运行之前执行
    @CacheEvict(value = {"emp"}, beforeInvocation = true,key="#id")
        public void deleteEmp(Integer id){
            employeeMapper.deleteEmpById(id);
            //int i = 10/0;
        }
    

    @Caching

    @Caching 用于定义复杂的缓存规则,可以集成@Cacheable和 @CachePut

    // @Caching 定义复杂的缓存规则
        @Caching(
                cacheable = {
                        @Cacheable(/*value={"emp"},*/key = "#lastName")
                },
                put = {
                        @CachePut(/*value={"emp"},*/key = "#result.id"),
                        @CachePut(/*value={"emp"},*/key = "#result.email")
                }
        )
        public Employee getEmpByLastName(String lastName){
            return employeeMapper.getEmpByLastName(lastName);
        }
    
    

    @CacheConfig

    @CacheConfig注解可以用于抽取缓存的公共配置,然后在类加上就可以

    //全局配置,下面用到缓存方法,不配置默认使用全局的
    @CacheConfig(cacheNames = "menuCache",keyGenerator ="myKeyGenerator" )
    public interface IMenuService extends IService<Menu> {
        /**
         * 获取用户菜单信息
         *
         * @param username 用户名
         * @return
         */
        @Cacheable(key = "#username" )
        List<Menu> getUserMenus(String username);
    }
    

    参考

    1. SpringBoot之缓存使用教程
    2. 缓存入门
  • 相关阅读:
    struts2-dojo-plugin-2.3.1.2.jar!/struts-plugin.xml:29:119
    谈论高并发(十二)分析java.util.concurrent.atomic.AtomicStampedReference看看如何解决源代码CAS的ABA问题
    linux安装QQ
    Android:创建耐磨应用
    僵尸网络
    几个比较好的网站
    几点基于Web日志的Webshell检测思路
    Redis异常JedisConnectionException:Read timed out解决笔记
    ELk 几篇好的文章
    深入了解linux下的last命令及其数据源
  • 原文地址:https://www.cnblogs.com/kenx/p/15155865.html
Copyright © 2020-2023  润新知