当前位置: 首页 > news >正文

@cacheable 是否缓存成功_Spring Cache缓存注解

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

  作者 |  成猿手册

来源 | cnblogs.com/WangJpBlog/p/13389932.html

Spring Cache缓存注解

本篇文章代码示例在Spring Cache简单实现上的代码示例加以修改。

只有使用public定义的方法才可以被缓存,而private方法、protected 方法或者使用default 修饰符的方法都不能被缓存。 当在一个类上使用注解时,该类中每个公共方法的返回值都将被缓存到指定的缓存项中或者从中移除。

@Cacheable

@Cacheable注解属性一览:

属性名作用与描述
cacheNames/value指定缓存的名字,缓存使用CacheManager管理多个缓存Cache,这些Cache就是根据该属性进行区分。对缓存的真正增删改查操作在Cache中定义,每个缓存Cache都有自己唯一的名字。
key缓存数据时的key的值,默认是使用方法所有入参的值,可以使用SpEL表达式表示key的值。
keyGenerator缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。
cacheManager指定缓存管理器(例如ConcurrentHashMap、Redis等)。
cacheResolver和cacheManager作用一样,使用时二选一。
condition指定缓存的条件(对参数判断,满足什么条件时才缓存),可用SpEL表达式,例如:方法入参为对象user则表达式可以写为condition = "#user.age>18",表示当入参对象user的属性age大于18才进行缓存。
unless否定缓存的条件(对结果判断,满足什么条件时不缓存),即满足unless指定的条件时,对调用方法获取的结果不进行缓存,例如:unless = "result==null",表示如果结果为null时不缓存。
sync是否使用异步模式进行缓存,默认false。

@Cacheable指定了被注解方法的返回值是可被缓存的。其工作原理是Spring首先在缓存中查找数据,如果没有则执行方法并缓存结果,然后返回数据。

缓存名是必须提供的,可以使用引号、Value或者cacheNames属性来定义名称。下面的定义展示了users缓存的声明及其注解的使用:

@Cacheable(

键生成器

缓存的本质就是键/值对集合。在默认情况下,缓存抽象使用(方法签名及参数值)作为一个键值,并将该键与方法调用的结果组成键/值对。 如果在Cache注解上没有指定key,
则Spring会使用KeyGenerator来生成一个key。

package org.springframework.cache.interceptor;
import java.lang.reflect.Method;

@FunctionalInterface
public interface KeyGenerator {
    Object generate(Object var1, Method var2, Object... var3);
}

Sping默认提供了SimpleKeyGenerator生成器。Spring 3.x之后废弃了3.x 的DefaultKey
Generator而用SimpleKeyGenerator取代,原因是DefaultKeyGenerator在有多个入参时只是简单地把所有入参放在一起使用hashCode()方法生成key值,这样很容易造成key冲突。SimpleKeyGenerator使用一个复合键SimpleKey来解决这个问题。通过其源码可得知Spring生成key的规则。

/**
 * SimpleKeyGenerator源码的类路径参见{@link org.springframework.cache.interceptor.SimpleKeyGenerator}
 */

从SimpleKeyGenerator的源码中可以发现其生成规则如下(附SimpleKey源码):

  • 如果方法没有入参,则使用SimpleKey.EMPTY作为key(key = new SimpleKey())。

  • 如果只有一个入参,则使用该入参作为key(key = 入参的值)。

  • 如果有多个入参,则返回包含所有入参的一个SimpleKey(key = new SimpleKey(params))。

package org.springframework.cache.interceptor;

如需自定义键生成策略,可以通过实现org.springframework.cache.interceptor.KeyGenerator接口来定义自己实际需要的键生成器。示例如下,自定义了一个MyKeyGenerator类并且实现(implements)了KeyGenerator以实现自定义的键值生成器:

package com.example.cache.springcache;

同时在Spring配置文件中配置:

使用示例如下:

@Cacheable(cacheNames = 

执行的打印结果如下:

first query...

@CachePut

@CachePut注解属性与@Cacheable注解属性相比少了sync属性。其他用法基本相同:

属性名作用与描述
cacheNames/value指定缓存的名字,缓存使用CacheManager管理多个缓存Cache,这些Cache就是根据该属性进行区分。对缓存的真正增删改查操作在Cache中定义,每个缓存Cache都有自己唯一的名字。
key缓存数据时的key的值,默认是使用方法所有入参的值,可以使用SpEL表达式表示key的值。
keyGenerator缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。
cacheManager指定缓存管理器(例如ConcurrentHashMap、Redis等)。
cacheResolver和cacheManager作用一样,使用时二选一。
condition指定缓存的条件(对参数判断,满足什么条件时才缓存),可用SpEL表达式,例如:方法入参为对象user则表达式可以写为condition = "#user.age>18",表示当入参对象user的属性age大于18才进行缓存。
unless否定缓存的条件(对结果判断,满足什么条件时不缓存),即满足unless指定的条件时,对调用方法获取的结果不进行缓存,例如:unless = "result==null",表示如果结果为null时不缓存。

如果一个方法使用了@Cacheable注解,当重复(n>1)调用该方法时,由于缓存机制,并未再次执行方法体,其结果直接从缓存中找到并返回,即获取还的是第一次方法执行后放进缓存中的结果。

但实际业务并不总是如此,有些情况下要求方法一定会被调用,例如数据库数据的更新,系统日志的记录,确保缓存对象属性的实时性等等。

@CachePut注解就确保方法调用即执行,执行后更新缓存。

示例代码清单:

package com.example.cache.springcache;

测试代码清单:

package com.example.cache.springcache;

import com.example.cache.customize.entity.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: 博客「成猿手册」
 * @description: com.example.cache.springcache
 */
public class UserMain2 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService2 userService2 = (UserService2) context.getBean("userServiceBean2");
        //第一次查询,缓存中没有,从数据库查询
        System.out.println("first query...");
        User user1 = userService2.getUserByUserId("user001");
        System.out.println("result object: " + user1);

        user1.setAge(20);
        userService2.updateUser(user1);
        //调用即执行,然后更新缓存
        user1.setAge(21);
        userService2.updateUser(user1);

        System.out.println("second query...");
        User user2 = userService2.getUserByUserId("user001");
        System.out.println("result object: " + user2);
        System.out.println("result age: " + user2.getAge());
    }
}

测试打印结果如下:

first 

结果表明,执行了两次模拟调用数据库的方法。需要注意的是,在这个简单示例中,两次setAge()方法并不能够证明确实更新了缓存:把updateData()方法去掉也可以得到最终的用户年龄结果,因为set操作的仍然是getUserByName()之前获取的对象。

应该在实际操作中将getFromDBupdateData调整为更新数据库的具体方法,再通过加与不加@CachePut来对比最后的结果判断是否更新缓存。

@CacheEvict

@CacheEvict注解属性一览:

属性名作用与描述
cacheNames/value指定缓存的名字,缓存使用CacheManager管理多个缓存Cache,这些Cache就是根据该属性进行区分。对缓存的真正增删改查操作在Cache中定义,每个缓存Cache都有自己唯一的名字。
key缓存数据时的key的值,默认是使用方法所有入参的值,可以使用SpEL表达式表示key的值。
keyGenerator缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。
cacheManager指定缓存管理器(例如ConcurrentHashMap、Redis等)。
cacheResolver和cacheManager作用一样,使用时二选一。
condition指定删除缓存的条件(对参数判断,满足什么条件时才删除缓存),可用SpEL表达式,例如:入参为字符userId的方法删除缓存条件设定为当入参不是user001就删除缓存,则表达式可以写为condition = "!('user001').equals(#userId)"
allEntriesallEntries是布尔类型的,用来表示是否需要清除缓存中的所有元素。默认值为false,表示不需要。当指定allEntries为true时,Spring Cache将忽略指定的key,清除缓存中的所有内容。
beforeInvocation清除操作默认是在对应方法执行成功后触发的(beforeInvocation = false),即方法如果因为抛出异常而未能成功返回时则不会触发清除操作。使用beforeInvocation属性可以改变触发清除操作的时间。当指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

@CacheEvict注解是@Cachable注解的反向操作,它负责从给定的缓存中移除一个值。大多数缓存框架都提供了缓存数据的有效期,使用该注解可以显式地从缓存中删除失效的缓存数据。该注解通常用于更新或者删除用户的操作。下面的方法定义从数据库中删除-一个用户,而@CacheEvict 注解也完成了相同的工作,从users缓存中删除了被缓存的用户。

在上面的实例中添加删除方法:

"userCache")

测试代码清单:

package com.example.cache.springcache;

import com.example.cache.customize.entity.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author: 博客「成猿手册」
 * @description: com.example.cache.springcache
 */
public class UserMain3 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService2 userService2 = (UserService2) context.getBean("userServiceBean2");
        String userId = "user001";
        //第一次查询,缓存中没有,执行数据库查询
        System.out.println("first query...");
        User user1 = userService2.getUserByUserId(userId);
        System.out.println("result object: " + user1);

        //第二次查询从缓存中查询
        System.out.println("second query...");
        User user2 = userService2.getUserByUserId(userId);
        System.out.println("result object: " + user2);

        //先移除缓存再查询,缓存中没有,执行数据库查询
        userService2.delUserByUserId(userId);
        User user3 = userService2.getUserByUserId(userId);
        System.out.println("result object: " + user3);
    }
}

执行的打印结果如下:

first query...

通过打印结果验证了@CacheEvict移除缓存的效果。需要注意的是,在相同的方法上使用@Caheable@CacheEvict注解并使用它们指向相同的缓存没有任何意义,因为这相当于数据被缓存之后又被立即移除了,所以需要避免在同一方法上同时使用这两个注解。

@Caching

@Caching注解属性一览:

属性名作用与描述
cacheable取值为基于@Cacheable注解的数组,定义对方法返回结果进行缓存的多个缓存。
put取值为基于@CachePut注解的数组,定义执行方法后,对返回方的方法结果进行更新的多个缓存。
evict取值为基于@CacheEvict注解的数组。定义多个移除缓存。

总结来说,@Caching是一个组注解,可以为一个方法定义提供基于@Cacheable@CacheEvict或者@CachePut注解的数组。

示例定义了User(用户)、Member(会员)和Visitor(游客)3个实体类,它们彼此之间有一个简单的层次结构:User是一个抽象类,而Member和Visitor类扩展了该类。

User(用户抽象类)代码清单:

package com.example.cache.springcache.entity;

/**
 * @author: 博客「成猿手册」
 * @description: 用户抽象类
 */
public abstract class User {
    private String userId;
    private String userName;

    public User(String userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }
    //todo:此处省略get和set方法
}

Member(会员类)代码清单:

package com.example.cache.springcache.entity;

Visitor(游客类)代码清单:

package com.example.cache.springcache.entity;

UserService3类是一个Spring服务Bean,包含了getUser()方法。
同时声明了两个@Cacheable注解,并使其指向两个不同的缓存项: members和visitors。然后根据两个@Cacheable注解定义中的条件对方法的参数进行检查,并将对象存储在
members或visitors缓存中。

UserService3代码清单:

package com.example.cache.springcache;

UserService3类是-一个Spring服务Bean,包含了getUser()方法。同时声明了两个@Cacheable注解,并使其指向两个不同的缓存项: members 和visitors。
然后根据两个@Cacheable注解定义中的条件对方法的参数进行检查,并将对象存储在
members或visitors缓存中。

测试代码清单:

package com.example.cache.springcache;

执行的打印结果如下:

querying id from db...member001
member userName-->会员小张
member userName-->会员小张
querying id from db...visitor001
visitor userName-->访客小曹
visitor userName-->访客小曹

@CacheConfig

@CacheConfig注解属性一览:

属性名作用与描述
cacheNames/value指定类级别缓存的名字,缓存使用CacheManager管理多个缓存Cache,这些Cache就是根据该属性进行区分。对缓存的真正增删改查操作在Cache中定义,每个缓存Cache都有自己唯一的名字。
keyGenerator类级别缓存的生成策略(键生成器),和key二选一,作用是生成键值key,keyGenerator可自定义。
cacheManager指定类级别缓存管理器(例如ConcurrentHashMap、Redis等)。
cacheResolver和cacheManager作用一样,使用时二选一。

前面我们所介绍的注解都是基于方法的,如果在同一个类中需要缓存的方法注解属性都相似,则需要重复增加。Spring 4.0之后增加了@CacheConfig类级别的注解来解决这个问题。

一个简单的实例如下所示:

package com.example.cache.springcache;

可以看到,在@CacheConfig注解中定义了类级别的缓存users和自定义键生成器,
那么在findA0和findB(方法中不再需要重复指定,而是默认使用类级别的定义。

fe22cac8bb61f8774546f88815a59b75.gif

ed7b85cbfac454000f8fb0e356acb264.gif

  • 新款SpringBoot在线教育平台开源了

  • 50份优秀Java求职者简历

  • SpringCloud前后端分离实战项目视频教程分享

  • 2020年全网最全BAT笔试面试题打包分享

感谢点赞支持下哈 6736e8fbae4da8f88d5e0b844ed00cd7.gif

相关文章:

  • blob 图片_JavaScript之Blob对象类型的具体使用方法
  • tmemo 选择消除行_选择适合女生长期喝的茶? 这4款茶女性四季皆宜
  • .dat文件写入byte类型数组_用Python从Abaqus导出txt、dat数据
  • microbit和python哪个适合_掌控板和microbit哪个好?
  • vue子组件获取父组件数据_微信小程序自定义组件问题二:父(页面)子组件之间的通信...
  • python获取用户输入_python中使用input()函数获取用户输入值方式
  • python数据分析自学教程_【好程序员】Python数据分析全套视频教程
  • ea 通信图_深入浅出聊聊相干光通信(上)看看中长距如何实现400G传输
  • sublime text3 python找不到文件路径_Sublime text 3 集成python 3 环境配置
  • jsp可以使用iframe_使用 JavaScript Object URL,可以处理图像、音频和视频
  • python xlrd_Python xlrd库常用操作汇总
  • python如何调用文件_python中调用不同文件夹的py文件
  • python windows窗口开发_微软上线《在Windows上使用Python进行开发》教程
  • quartz 动态添加job_spring boot Quartz基于持久化存储的动态管理
  • 学python还是php_米凯seo: 到底是学Python、PHP还是Ruby?
  • ----------
  • 分享一款快速APP功能测试工具
  • CAP 一致性协议及应用解析
  • CentOS7 安装JDK
  • crontab执行失败的多种原因
  • javascript数组去重/查找/插入/删除
  • php ci框架整合银盛支付
  • rabbitmq延迟消息示例
  • Terraform入门 - 3. 变更基础设施
  • vue-cli3搭建项目
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 前嗅ForeSpider采集配置界面介绍
  • 巧用 TypeScript (一)
  • 适配mpvue平台的的微信小程序日历组件mpvue-calendar
  • 微信小程序设置上一页数据
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • #{}和${}的区别?
  • (1)(1.11) SiK Radio v2(一)
  • (libusb) usb口自动刷新
  • (pojstep1.1.2)2654(直叙式模拟)
  • (rabbitmq的高级特性)消息可靠性
  • (附源码)spring boot基于Java的电影院售票与管理系统毕业设计 011449
  • (附源码)基于SSM多源异构数据关联技术构建智能校园-计算机毕设 64366
  • (转)LINQ之路
  • .\OBJ\test1.axf: Error: L6230W: Ignoring --entry command. Cannot find argumen 'Reset_Handler'
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .bat批处理(一):@echo off
  • .Net Core 中间件验签
  • .NET core 自定义过滤器 Filter 实现webapi RestFul 统一接口数据返回格式
  • .NET Framework 4.6.2改进了WPF和安全性
  • .net MySql
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .net 反编译_.net反编译的相关问题
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • .net反编译工具
  • .NET运行机制
  • /run/containerd/containerd.sock connect: connection refused
  • :如何用SQL脚本保存存储过程返回的结果集
  • []error LNK2001: unresolved external symbol _m