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

Spring Boot默认缓存管理

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 {@Autowiredprivate BookRepository bookRepository;@Cacheablepublic 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:rootpassword:rootjpa: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"; // 替换为你要请求的URLint 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() + "毫秒");}

 

相关文章:

  • tzdata 安装失败的一种处理
  • Excel处理控件Aspose.Cells教程:使用 Python 在 Excel 中进行数据验
  • Kotlin学习基础知识大全(上)
  • 大模型面经 | 春招、秋招算法面试常考八股文附答案(六)
  • Milvus(6):Collection 管理分区、管理别名
  • 运维打铁:Centos 7 使用yum安装 mysql5.7
  • Rust 学习笔记:编程语言的相关概念
  • HTML、XHTML 和 XML区别
  • 关于Safari浏览器在ios<16.3版本不支持正则表达式零宽断言的解决办法
  • HTML给图片居中
  • 【OSG学习笔记】Day 7: 材质与光照——让模型“活”起来
  • 数据库-数据类型、约束 和 DQL语言
  • 内网数据库怎么通过外网访问?本地SQL无公网IP如何让公网连接实现通用方法
  • 【ES实战】Elasticsearch中模糊匹配类的查询
  • 【mysql】windows mysql命令
  • Redis 基础和高级用法入门
  • 【TensorFlow深度学习框架】从数学原理到工业级应用
  • 国产紫光同创FPGA视频采集转SDI编码输出,基于HSSTHP高速接口,提供2套工程源码和技术支持
  • w~视觉~合集3
  • CV和NLP领域常见模型列表
  • 外交部:美国是国际军控与防扩散体系的最大破坏者
  • 政治局会议:创新推出债券市场的“科技板”,加快实施“人工智能+”行动
  • 《不眠之夜》上演8年推出特别版,多业态联动形成戏剧经济带
  • 《哪吒之魔童降世》电影版权方诉《仙侠神域》游戏运营方侵权案开庭
  • 广西北海市人大常委会副主任李安洪已兼任合浦县委书记
  • 中方警告韩国公司不要向美军工企业出口含中国稀土矿物产品?外交部回应