Spring Boot默认缓存管理

news2025/4/26 15:43:41

Spring框架支持透明地向应用程序添加缓存,以及对缓存进行管理,其管理缓存的核心是将缓存应用于操作数据的方法,从而减少操作数据的执行次数,同时不会对程序本身造成任何干扰。Spring Boot继承了Spring框架的缓存管理功能,下面将对Spring Boot内置的缓存方案进行讲解。

Spring的缓存机制将提供的缓存作用于Java 方法上,基于缓存中的可用信息,可以减少方法的执行次数。每次目标方法调用时,抽象使用缓存行为来检查执行方法,即检查执行方法是否给定了缓存的执行参数,如果是,则返回缓存结果,不执行具体方法;如果否,则执行方法,并将结果缓存后,返回给用户。

Spring的默认的缓存方案通过org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术。

Cache接口:缓存的组件定义规范,包含缓存的各种操作集合。Spring中为Cache接口提供了各种缓存的实现:RedisCache,EhCache,ConcurrentMapCache等

CacheManager接口:缓存管理器,基于缓存名称对缓存进行管理,并制定了管理Cache的规则。 

在项目中添加某个缓存管理组件(如Redis)后,Spring Boot项目会选择并启用对应的缓存管理器。如果项目中同时添加了多个缓存组件,且没有定义类型为CacheManager的Bean组件或者名为cacheResolver的缓存解析器,Spring Boot将尝试按以下列表的顺序查找有效的缓存组件进行缓存管理。 (1)Generic (2)JCache (EhCache 3、Hazelcast、Infinispan等) (3)EhCache 2.x (4)Hazelcast (5)Infinispan (6)Couchbase (7)Redis (8)Caffeine (9)Simple

声明式缓存注解

要想使用Spring提供的默认缓存,需要对缓存进行声明,也就是标志缓存的方法及缓存策略。对于缓存声明,Spring提供了一系列的注解,使用这些注解可以实现Spring 默认的基于注解的缓存管理。

1.@EnableCaching注解

@EnableCaching是Spring框架提供的用于开启基于注解的缓存支持的注解,当配置类上使用@EnableCaching注解,会默认提供CacheManager的实现,并通过AOP将缓存行为添加到应用程序。执行操作时,会检查是否已经存在注解对应的缓存。如果找到了,就会自动创建一个代理拦截方法调用,使用缓存的Bean执行处理。

2.@Cacheable注解

@Cacheable注解用于标注可缓存的方法,通常标注的方法为数据查询方法。标注@Cacheable注解的方法在执行时,会先查询缓存,如果查询到的缓存为空,则执行该方法,并将方法的执行结果添加到缓存;如果查询到缓存数据,则不执行该方法,而是直接使用缓存数据。

@Cacheable注解提供了多个属性,用于对缓存进行相关配置。

属性名

说明

value/cacheNames

指定缓存的名称,必备属性,这两个属性二选一使用

key

指定缓存数据的key,默认使用方法参数值,可以使用SpEL表达式

keyGenerator

指定缓存数据的key的生成器,与key属性二选一使用

cacheManager

指定缓存管理器

cacheResolver

指定缓存解析器,与cacheManager属性二选一使用

condition

指定在符合某条件下进行数据缓存

unless

指定在符合某条件下不进行数据缓存

sync

指定是否使用异步缓存,默认为false

(1)value/cacheNames属性

value和cacheNames属性作用相同,用于指定缓存的名称,方法的返回结果会存放在指定名称的缓存中。这两个属于必备选项,且要二选一使用。如果@Cacheable注解只配置value或者cacheNames属性,那么属性名可以省略。

@Cacheable("book")
public Book findById(Integer id){
	return bookDao.findById(id).get();
}

@Cacheable注解中可以指定多个缓存的名称,以便使用多个缓存。

@Cacheable({"book","hotBook"})
public Book findById(Integer id){
	return bookDao.findById(id).get();
}

(2)key属性

缓存的本质是键值对存储,key用于指定唯一的标识,value用于指定缓存的数据,所以每次调用缓存方法都会转换为访问缓存的键。缓存的键通过key属性进行指定,进行数据缓存时,如果没有指定key属性,Spring Boot默认配置类SimpleKeyGenerator中的generateKey(Object... params)方法会根据方法参数生成key值。对于没有参数的方法,其key是默认创建的空参SimpleKey[]对象;对于只有一个参数的方法,其key默认是参数值;对于有多个参数的方法,其key是包含所有参数的SimpleKey对象。 

如果方法有多个参数,但是部分参数对缓存没有任何用处,通常会选择手动指定key属性的值,key属性的值可以通过SpEL表达式选择所需要的参数。

@Cacheable(cacheNames="book", key="#id")
public Book findBookById(Integer id, boolean includeUsed){
	return bookDao.findById(id).get();
}

Cache缓存支持的SpEL表达式及说明

参数名

位置

描述

示例

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

执行上下文

当前被调用的方法参数,可以用#参数名或者#a0、#p0的形式(0代表参数索引,从0开始)

#comment_id、#a0、#p0

result

执行上下文

当前方法执行后的返回结果

#result

(3)keyGenerator属性

keyGenerator属性与key属性本质作用相同,都是用于指定缓存数据的key,只不过keyGenerator属性指定的不是具体的key值,而是key值的生成器规则,由其中指定的生成器生成具体的key。使用时,keyGenerator属性与key属性要二者选一。关于自定义key值生成器的定义,可以参考Spring Boot默认配置类SimpleKeyGenerator的定义方式,这里不再做具体说明。

(4)cacheManager/cacheResolver属性

cacheManager和cacheResolver属性分别用于指定缓存管理器和缓存解析器,这两个属性也是二选一使用,默认情况不需要配置,对于需要使用多个缓存管理器(如Redis、Ehcache等)的应用,可以为每个操作设置一个缓存管理器或缓存解析器。 

(5)condition属性

condition属性用于对数据进行有条件的选择性存储,只有当指定条件为true时才会对查询结果进行缓存,可以使用SpEL表达式指定属性值。 

@Cacheable(cacheNames="book", condition="#id > 1")
public Book findBook(Integer id){
	return bookDao.findById(id).get();
}

(6)unless属性

unless属性的作用与condition属性相反,当指定的条件为true时,方法的返回值不会被缓存,也可以使用SpEL表达式指定。 

@Cacheable(cacheNames="book", unless = "#result==null")
public Book findBook(Integer id){
	return bookDao.findById(id).get();
}

(7)sync属性

在多线程程序中,某些操作可能会同时引用相同的参数,导致相同的对象被计算好几次,从而达不到缓存的目的。对于这种情况,可以使用sync属性,sync属性表示数据缓存过程中是否使用同步模式,默认值为false,通常不会使用该属性。 

3.@CachePut注解

@CachePut注解的作用是更新缓存数据,当需要更新缓存且不影响方法执行时,可以使用@CachePut注解,通常用在数据更新方法上。@CachePut注解的执行顺序是,先进行方法调用,然后将方法结果更新到缓存中。

@CachePut注解也提供了多个属性,这些属性与@Cacheable注解的属性完全相同。通常不建议在同一个方法同时使用@CachePut和@Cacheable注解,这两个注解关注不同的行为,@CachePut注解会强制执行方法并进行缓存更新,使用@Cacheable 注解时,如果请求能够在缓存中获取到对应的数据,就不会执行当前被@Cacheable 注解标注的方法。 

4.@CacheEvict注解

@CacheEvict注解的作用删除缓存中的数据,通常标注在数据删除方法上。@CacheEvict注解的默认执行顺序是先进行方法调用,然后将缓存清除。 @CacheEvict注解也提供了多个属性,这些属性与@Cacheable注解的属性基本相同,除此之外,还额外提供了两个特殊属性allEntries和beforeInvocation,下面对这两个属性分别进行讲解。

(1)allEntries属性 allEntries属性表示是否清除指定缓存空间中的所有缓存数据,默认值为false,即默认只删除指定key对应的缓存数据。

@CacheEvict(cacheNames = "book",allEntries = true)
public void delById(Integer id){
	 bookDao.deleteById(id);
}

(2)beforeInvocation属性 beforeInvocation属性表示是否在方法执行之前进行缓存清除,默认值为false,即默认在执行方法后再进行缓存清除。

@CacheEvict(cacheNames = "book",beforeInvocation = true)
public void delById(Integer id){
	 bookDao.deleteById(id);
}

5.@Caching注解

如果不同缓存之间的条件或者键表达式不同,就需要指定相同类型的多个注解,例如需要同时指定多个@CacheEvict或@CachePut,这个时候可以使用@Caching注解。@Caching注解用于针对复杂规则的数据缓存管理,@Caching注解中允许使用多个嵌套的 @Cacheable 、@CachePut 或 @CacheEvict。在@Caching注解内部包含有Cacheable、put和evict三个属性,分别对应于@Cacheable、@CachePut和@CacheEvict三个注解。

@Caching(evict = { @CacheEvict("primary"), 
@CacheEvict(cacheNames="secondary", key="#date")})
public void delById(Integer id, Date date){
	bookDao.deleteById(id);
}
@Caching(cacheable={@Cacheable(cacheNames ="comment",key = "#id")},

put = {@CachePut(cacheNames = "comment",key = "#result.author")})

public Comment getComment(int comment_id){

return commentRepository.findById(comment_id).get();

}

 6.@CacheConfig注解

@CacheConfig注解使用在类上,主要用于统筹管理类中所有使用@Cacheable、@CachePut和@CacheEvict注解标注方法中的公共属性。 

@CacheConfig(cacheNames = "book")
@Service
public class BookService {
    @Autowired
    private BookRepository bookRepository;
    @Cacheable
    public Book findById(Integer id){
        return  bookRepository.findById(id).get();
    }
}

声明式缓存注解的应用

1.创建项目

2.配置依赖

<!-- springboot默认应该有,不配应该没关系-->
<dependency>
    <groupId>
        org.springframework.boot
    </groupId>
    <artifactId>
        spring-boot-starter-cache
    </artifactId>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-web
</artifactId>
</dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-data-jpa
</artifactId>
</dependency>
<dependency>
<groupId>
mysql
</groupId>
<artifactId>
mysql-connector-java
</artifactId>
</dependency>

3.设置配置信息

spring:
    datasource:
        url:"jdbc:mysql://localhost:3306/springbootdata?characterEncoding=utf-8&serverTimezone=Asia/Shanghai"
        username:root
        password:root
    jpa:
        show-sql:true

4.创建实体类

@Entity
@Table(name="book")
public  class  Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private  Integer id; //图书编号
@Column(name="name")
private  String name; //图书名称
private  String author; //图书作者
private  String press; //图书出版社
private  String status; //图书状态

public  Integer getId() {
return  id;
}

public  void  setId(Integer id) {
this .id = id;
}

public  String getName() {
return  name;
}

public  void  setName(String name) {
this .name = name;
}

public  String getPress() {
return  press;
}

public  void  setPress(String press) {
this .press = press;
}

public  String getAuthor() {
return  author;
}

public  void  setAuthor(String author) {
this .author = author;
}

public  String getStatus() {
return  status;
}

public  void  setStatus(String status) {
this .status = status;
}

public  Book() {
}

public  Book(Integer id, String name, String author, String press, String status) {
this .id = id;
this .name = name;
this .author = author;
this .press = press;
this .status = status;
}

@Override
public  String toString() {
return  "Book{" +
"id=" + id +
", name='" + name + ''' +
", author='" + author + ''' +
", press='" + press + ''' +
", status='" + status + ''' +
'
}';
}
}

5.创建Repository接口

@Repository
public  interface  BookRepository extends  JpaRepository<Book,Integer> {
}

 6.创建Service接口和实现类

public  interface  BookService {
public  Book findById(Integer id);
public  Book updateById(Integer id,String name);
public  void  delById(Integer id);
}
@Service
@CacheConfig(cacheNames = "book")
@Transactional
public  class  BookServiceImpl implements  BookService{
@Autowired
private  BookRepository bookRepository;

@Cacheable(key = "#id")
public  Book findById(Integer id){
//根据id查找图书信息
return  bookRepository.findById(id).get();
}
@CachePut(key = "#id")
public  Book updateById(Integer id,String name){
Book book=this .findById(id);
book.setName(name);
//更新图书信息
return  bookRepository.save(book);
}
@CacheEvict(key = "#id")
public  void  delById(Integer id){
//根据id删除图书信息
bookRepository.deleteById(id);
}
}

 7.创建控制器类

@RestController
@RequestMapping("book")
public  class  BookController {
@Autowired
private  BookService bookService;

@RequestMapping("/findById/{id}")
public  Book findById(@PathVariable Integer id){
//根据id查询图书信息
return  bookService.findById(id);
}
@RequestMapping("/editById/{id}/{name}")
public  Book editById(@PathVariable Integer id,@PathVariable String name){
//根据id修改图书的名称
return  bookService.updateById(id,name);
}
@RequestMapping("/delById/{id}")
public  void  delById(@PathVariable Integer id){
//根据id删除图书信息
bookService.delById(id);
}
}

8.在启动类上开启缓存

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

9.测试缓存效果

启动项目,在浏览器中访问http://localhost:8080/book/findById/3,查询图书信息,控制台输出信息。

查询图书信息后,浏览器中查询到图书信息。

再次在浏览器中访问http://localhost:8080/book/findById/3,查询id为3的图书信息,控制台输出信息。

在浏览器中访问http://localhost:8080/book/editById/3/西游释厄传,将id为3的图书名称更新为“西游释厄传”,此时控制台输出信息。

更新图书信息后,浏览器中查询图书信息。

浏览器中访问http://localhost:8080/book/delById/3,删除id为3的图书信息,控制台输出信息。

在浏览器中再次访问http://localhost:8080/book/findById/3,查询id为3的图书信息,控制台输出信息。

动手试一试 Spring Boot默认缓存管理 

测试发生一万次请求需要的时间(jdk11以上)

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class HttpRequestBenchmark {

    public static void main(String[] args) throws Exception {
        String url = "http://localhost:8081/get/1"; // 替换为你要请求的URL
        int numRequests = 10000; // 请求次数

        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(new URI(url))
                .build();

        long startTime = System.nanoTime();

        // 使用CompletableFuture来异步发送请求,并等待所有请求完成
        CompletableFuture<Void>[] futures = IntStream.range(0, numRequests)
                .mapToObj(i -> CompletableFuture.runAsync(() -> {
                    try {
                        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
                        // 在这里可以处理响应,例如检查状态码等
                        System.out.println("xiangying------------------");
                        // 这里只是简单发送请求并忽略响应内容
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }))
                .toArray(CompletableFuture[]::new);

        // 等待所有请求完成
        CompletableFuture.allOf(futures).join();

        long endTime = System.nanoTime();

        Duration duration = Duration.ofNanos(endTime - startTime);
        System.out.println("发送" + numRequests + "次HTTP请求需要的时间:" + duration.toMillis() + "毫秒");
    }

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2343361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

XYNU2024信安杯-REVERSE(复现)

前言 记录记录 1.Can_you_find_me? 签到题&#xff0c;秒了 2.ea_re 快速定位 int __cdecl main_0(int argc, const char **argv, const char **envp) {int v4; // [esp0h] [ebp-1A0h]const char **v5; // [esp4h] [ebp-19Ch]const char **v6; // [esp8h] [ebp-198h]char v7;…

MySQL的MVCC【学习笔记】

MVCC 事务的隔离级别分为四种&#xff0c;其中Read Committed和Repeatable Read隔离级别&#xff0c;部分实现就是通过MVCC&#xff08;Multi-Version Concurrency Control&#xff0c;多版本并发控制&#xff09; 版本链 版本链是通过undo日志实现的&#xff0c; 事务每次修改…

达梦数据库压力测试报错超出全局hash join空间,适当增加HJ_BUF_GLOBAL_SIZE解决

1.名词解释&#xff1a;达梦数据库中的HJ_BUF_GLOBAL_SIZE是所有哈希连接操作可用的最大哈希缓冲区大小&#xff0c;单位为兆字节&#xff08;MB&#xff09; 2.达梦压测报错&#xff1a; 3.找到达梦数据库安装文件 4.压力测试脚本 import http.client import multiprocessi…

Oracle--SQL性能优化与提升策略

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 一、导致性能问题的内在原因 系统性能问题的底层原因主要有三个方面&#xff1a; CPU占用率过高导致资源争用和等待内存使用率过高导致内存不足并需…

六个能够白嫖学习资料的网站

一、咖喱君的资源库 地址&#xff1a;https://flowus.cn/galijun/share/de0f6d2f-df17-4075-86ed-ebead0394a77 这是一个学习资料/学习网站分享平台&#xff0c;包含了英语、法语、德语、韩语、日语、泰语等几十种外国语言的学习资料及平台&#xff0c;这个网站的优势就是外语…

IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤

以下是在 IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤&#xff1a; 步骤 1&#xff1a;创建 Maven Web 项目 新建项目 File -> New -> Project → 选择 Maven → 勾选 Create from archetype → 选择 maven-archetype-webapp。输入 GroupId&#xff08;如 com.examp…

手机打电话时电脑坐席同时收听对方说话并插入IVR预录声音片段

手机打电话时电脑坐席同时收听对方说话并插入IVR预录声音片段 --本地AI电话机器人 前言 书接上一篇&#xff0c;《手机打电话通话时如何向对方播放录制的IVR引导词声音》中介绍了【蓝牙电话SDK示例App】可以实现手机app在电话通话过程中插播预先录制的开场白等语音片段的功能。…

SpringCloud——负载均衡

一.负载均衡 1.问题提出 上一篇文章写了服务注册和服务发现的相关内容。这里再提出一个新问题&#xff0c;如果我给一个服务开了多个端口&#xff0c;这几个端口都可以访问服务。 例如&#xff0c;在上一篇文章的基础上&#xff0c;我又新开了9091和9092端口&#xff0c;现在…

string的基本使用

string的模拟实现 string的基本用法string的遍历&#xff08;三种方式&#xff09;&#xff1a;关于auto&#xff08;自动推导&#xff09;:范围for: 迭代器普通迭代器(可读可改&#xff09;const迭代器&#xff08;可读不可改&#xff09; string细小知识点string的常见接口引…

深入解析Mlivus Cloud核心架构:rootcoord组件的最佳实践与调优指南

作为大禹智库的向量数据库高级研究员,同时也是《向量数据库指南》的作者,我在过去30年的向量数据库和AI应用实战中见证了这项技术的演进与革新。今天,我将以专业视角为您深入剖析Mlivus Cloud的核心组件之一——rootcoord,这个组件在系统架构中扮演着至关重要的角色。如果您…

Python常用的第三方模块之【pymysql库】操作数据库

pymysql是在Python3.x版本中用于连接MySQL服务器的一个实现库&#xff0c;Python2中则是使用musqldb。 PyMySQL 是一个纯 Python 实现的 MySQL 客户端库&#xff0c;它允许我们直接在 Python 中执行 SQL 语句并与 MySQL 数据库进行交互。下面我们将详细介绍如何使用 PyMySQL 进…

【Python数据分析】Pandas模块之pd.concat 函数

💭 写在前面:合并多个数据框,收集各种数据,并将其合并为一个数据框进行分析。本章我们介绍 Pandas 库中数据框合并的函数 —— concat。 0x00 引入:数据框的合并操作 合并多个数据框:收集各种数据,并将其合并为一个数据框进行分析。 下面介绍一些常用的 Pandas 库中数…

C# 综合示例 库存管理系统7 主界面(FormMain)

版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的 图99A-22 主界面窗口设计 主界面是多文档界面容器,需要将窗体属性IsMdiContainer设置为True。关于多文档界面编程请参看教程第7.12节《多文档界面》。 主界面并不提…

系统思考:看清问题背后的结构

组织的挑战&#xff0c;往往不是因为不努力&#xff0c;而是“看不清” 结束了为期两天系统思考课程的第一天&#xff0c;被学员的全情投入深深打动。我们用系统结构图&#xff0c;一步步揭示那些表面看起来“习以为常”的问题&#xff1a; 什么原因跨部门协作总是磕磕绊绊&am…

Langchain_Agent+数据库

本处使用Agent数据库&#xff0c;可以直接执行SQL语句。可以多次循环查询问题 前文通过chain去联系数据库并进行操作&#xff1b; 通过链的不断内嵌组合&#xff0c;生成SQL在执行SQL再返回。 初始化 import os from operator import itemgetterimport bs4 from langchain.ch…

QT6 源(45):分隔条 QSplitter 允许程序的用户修改布局,程序员使用 IDE时,就是分隔条的用户,以及其 QSplitter 源代码

&#xff08;1&#xff09; &#xff08;2&#xff09;本类的继承关系如下&#xff0c;所以说分隔条属于容器&#xff1a; &#xff08;3&#xff09;本类的属性&#xff1a; &#xff08;4&#xff09; 这是一份 QSplitter 的举例代码&#xff0c;注意其构造函数时候的传参&am…

Huffman(哈夫曼)解/压缩算法实现

一、文件压缩 哈夫曼压缩算法需要对输入的文件&#xff0c;逐字节扫描&#xff0c;统计出不同字节出现的数量&#xff08;频率&#xff09;&#xff0c;根据的得到的频率生成一组叶子节点&#xff0c;这些节点存储着<字节信息>和<频率>,通常需要按频率排序后存储在…

迭代器模式:统一数据遍历方式的设计模式

迭代器模式&#xff1a;统一数据遍历方式的设计模式 一、模式核心&#xff1a;将数据遍历逻辑与数据结构解耦 在软件开发中&#xff0c;不同的数据结构&#xff08;如数组、链表、集合&#xff09;有不同的遍历方式。如果客户端直接依赖这些数据结构的内部实现来遍历元素&…

LeetCode每日一题4.23

题目 问题分析 计算每个数字的数位和&#xff1a;对于从 1 到 n 的每个整数&#xff0c;计算其十进制表示下的数位和。 分组&#xff1a;将数位和相等的数字放到同一个组中。 统计每个组的数字数目&#xff1a;统计每个组中有多少个数字。 找到并列最多的组&#xff1a;返回数…

RunnerGo API性能测试实战与高并发调优

API 性能测试通过模拟不同负载场景&#xff0c;量化评估 API 的响应时间、吞吐量、稳定性、可扩展性等性能指标&#xff0c;关注其在正常、高峰甚至极限负载下的表现。这有助于确保 API 稳定高效地运行&#xff0c;为调用者提供优质服务。 接下来&#xff0c;我们借助 RunnerG…