Springboot3配置缓存

chenxin
36
2024-10-14

缓存的作用

在项目实际上线后,如果有大量的用户同时请求,那么对数据库的频繁读写就可能会造成系统卡顿。
SpringBoot为我们提供多种缓存机制,如果请求的参数是一致的,那么就从缓存中读取数据,而不必对数据库进行重复的操作。

相关注解

SpringBoot的缓存允许我们使用多种缓存机制,但都涉及以下这些注解。
在我们配置缓存的时候,这些注解是通用的,使用不同缓存类型,只需要在全局配置文件中修改缓存提供者即可。

@EnableCaching

@EnableCaching 是 Spring 框架中启用缓存支持的注解,它用于开启基于注解的缓存机制。添加此注解后,Spring将扫描项目中的缓存注解(如 @Cacheable, @CachePut, @CacheEvict 等),并自动配置相应的缓存功能。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching  // 启用缓存
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

@Cacheable

  • 作用:当调用带有@Cacheable注解的方法时,Spring会先检查缓存中是否有结果。如果有,直接返回缓存中的结果;如果没有,则执行方法并将结果存入缓存。
  • value:缓存的名称,通常是缓存区域的标识。
  • key:缓存条目的唯一标识,可以使用SpEL表达式自定义。
  • condition:指定一个SpEL表达式,仅当满足该条件时,缓存才会应用。
    • 例如:condition = "#id > 10"表示只有id大于10时才缓存数据。
  • unless:也是一个SpEL表达式,返回true时不会缓存结果
    • 例如:unless = "#result == null"表示返回结果为空时不缓存。
@Cacheable(value = "books", key = "#id")
public Book getBookById(Long id) {
    // 执行查询逻辑
    return bookRepository.findById(id);
}

@CachePut

  • 作用:每次调用方法时,方法都会执行,返回值也会被更新到缓存中。适用于需要更新缓存的场景。
  • 这个注解适合在更新操作中使用,例如修改数据库中的记录并同步更新缓存。
  • 主要属性
    • value:与@Cacheable中的作用一致,指定缓存名称。
    • key:指定缓存条目的唯一标识符,类似于@Cacheable中的key。
    • condition:类似于@Cacheable,用来指定缓存更新的条件。
    • unless:类似于@Cacheable,当unless的条件为true时不更新缓存。
@CachePut(value = "books", key = "#book.id")
public Book updateBook(Book book) {
    return bookRepository.save(book);
}

@CacheEvict

  • 作用:用于移除缓存中的某个条目或整个缓存。
  • 主要属性
    • value:缓存的名称。
    • key:指定要清除的缓存条目,可以使用SpEL表达式。
    • allEntries:如果设置为true,则清空缓存区域中的所有条目。默认为false。
    • beforeInvocation:如果设置为true,则在方法执行之前清除缓存;如果为false(默认值),则在方法执行之后清除缓存。用于防止方法异常导致缓存数据不一致的情况。
@CacheEvict(value = "books", key = "#id")
public void deleteBook(Long id) {
    bookRepository.deleteById(id);
}

@Caching

  • 作用:允许在一个方法上同时使用多个缓存注解。适用于复杂缓存场景。
  • 主要属性
    • cacheable、put、evict:分别对应多个@Cacheable、@CachePut和@CacheEvict注解,适用于在一个方法上应用多个缓存操作。
    • 例如,可以同时缓存数据并清除另一个缓存区域的数据。
@Caching(
    put = { @CachePut(value = "books", key = "#book.id") },
    evict = { @CacheEvict(value = "authors", key = "#book.authorId") }
)
public Book saveBook(Book book) {
    return bookRepository.save(book);
}

@CacheConfig

  • 作用:类级别的注解,用于指定缓存的公共配置,减少重复配置。
  • cacheNames/value:指定类中所有方法共享的缓存区域名称。
  • 在@CacheConfig中,cacheNames属性可以用value代替,二者等价
//缓存区域为books,对应上方的注解的value
@CacheConfig(cacheNames = "books")
public class BookService {
    @Cacheable
    public Book getBookById(Long id) {
        return bookRepository.findById(id);
    }
}

缓存提供者

SpringBoot提供了多种缓存提供者,如Redis,EhCache,使用对应的缓存提供者即使用相应的缓存类型。
下面是常用的缓存提供者。

Ehcache

特点

  • 适用场景:
    • 本地缓存,适合单个应用节点,不涉及分布式环境。
    • 读取频率高且数据变动少的场景,能够显著提升性能。
  • 特点:
    • 缓存在应用程序本地内存中,读取速度非常快,适用于减少对远程存储系统的访问。
    • 支持持久化,将缓存数据存储到磁盘,确保数据在重启后能够恢复。
    • 配置简单,可通过 XML 或 Java 配置进行灵活设置。
  • 优点:
    • 低延迟,访问速度极快。
    • 部署简单,不需要额外的服务器或服务。
  • 缺点:
    • 缓存数据仅限于当前应用节点,无法在分布式系统中共享。
    • 如果节点宕机,缓存数据将丢失(除非使用持久化)。

    引入依赖

<!-- Spring Boot Cache Starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!-- Ehcache -->
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>3.10.6</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.1</version>
        </dependency>

配置文件

EhCache的配置文件需要放在项目的resource文件夹中并通过classpath引用

<config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
        xmlns='http://www.ehcache.org/v3'
        xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd">
      <!-- 配置默认缓存设置 -->
    <cache alias="defaultCache">
        <expiry>
            <ttl unit="seconds">600</ttl> <!-- 设置默认过期时间为600秒 -->
        </expiry>
        <heap unit="entries">500</heap> <!-- 缓存的最大条目数 -->
    </cache>
       <!-- 自定义缓存区域,EhCache无法直接在xml中直接配置磁盘路径-->
    <cache alias="book">
        <key-type>java.lang.Long</key-type> <!-- 缓存键类型 -->
        <value-type>java.lang.Object</value-type> <!-- 缓存值类型 -->
        <expiry>
            <ttl unit="seconds">3600</ttl> <!-- 设置过期时间为3600秒 -->
        </expiry>
        <heap unit="entries">1000</heap> <!-- 使用内存堆存储的最大缓存条目数 -->
        <disk persistent="true" directory="java.io.tmpdir"/> <!-- 将缓存数据持久化到磁盘 -->
    </cache>
</config>

使用EhCache作为缓存提供者

全局配置文件

spring:
  datasource:
    url: "jdbc:mysql://localhost:3306/javaee?characterEncoding=utf-8&serverTimezone=Asia/Shanghai"
    username: root
    password: 123456
  jpa:
    show-sql: true
  data:
    redis:
      host: localhost
      port: 6379
      password: 123456
# 使用EnCache作为缓存提供者,并指定EnCache的配置文件
  cache:
    jcache:
      config: classpath:ehcache.xml

使用注解

package com.example.demochapter05.service.impl;

import com.example.demochapter05.dao.BookRepository;
import com.example.demochapter05.entity.Book;
import com.example.demochapter05.service.BookService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
// 指定缓存空间为book
@CacheConfig(cacheNames = "book")
@Transactional
public  class  BookServiceImpl implements BookService {
    @Autowired
    private BookRepository bookRepository;
// 缓存条目的key,若key存在于book的缓存空间中,则返回缓存结果,否则正常执行语句并把key的条目放到缓存空间中。
    @Cacheable(key = "#id")
    public Book findById(Integer id){
//根据id查找图书信息
        return  bookRepository.findById(id).get();
    }
// 根据key更新缓存空间条目
    @CachePut(key = "#id")
    public  Book updateById(Integer id,String name){
        Book book=this .findById(id);
        book.setName(name);
//更新图书信息
        return  bookRepository.save(book);
    }
// 根据key删除缓存空间条目
    @CacheEvict(key = "#id")
    public  void  delById(Integer id){
//根据id删除图书信息
        bookRepository.deleteById(id);
    }
}

单元测试

package com.example.demochapter05;
import com.example.demochapter05.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Demochapter06ApplicationTests {
    @Autowired
    private BookService bookService;
    @Test
    public void testCache(){
        bookService.findById(1);
        bookService.findById(1);
    }
}

在控制台可以看到只执行了一次sql,缓存生效。

Redis

  • 适用场景
    • 分布式缓存,适合多节点共享数据的分布式环境。
    • 需要缓存复杂数据结构(如列表、集合、哈希)或需要高并发访问的场景。
  • 特点
    • 作为远程缓存提供服务,所有节点都可以访问相同的缓存数据。
    • 支持丰富的数据结构,适合多种缓存需求,如列表、集合、哈希等。
    • 提供持久化功能,可以将数据写入磁盘,保证在服务器重启后数据仍然可用。
  • 优点
    • 支持分布式部署,适合多实例、多节点系统。
    • 支持数据持久化和高可用,通过主从复制与哨兵模式实现数据的高可用性。
    • 丰富的数据类型支持,能够满足复杂的缓存需求。
  • 缺点
    • 相比本地缓存,访问需要网络请求,存在一定的网络延迟。
    • 部署复杂,需额外维护 Redis 服务,并配置集群以保证高可用性。

使用Redis作为缓存提供者,既可以是本机的Redis,也可以是分布式Redis集群。

引入依赖

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

全局配置文件

使用Redis作为缓存提供者不需要在编写其它配置文件,下面是基于本机的Redis单机模式,只需要在全局配置文件中配置Redis相关配置即可

spring:
  datasource:
    url: "jdbc:mysql://localhost:3306/javaee?characterEncoding=utf-8&serverTimezone=Asia/Shanghai"
    username: root
    password: 123456
  jpa:
    show-sql: true
  data:
    redis:
      host: localhost # Redis 服务器的主机地址
      port: 6379 # Redis 服务器的端口
      password: 123456 # Redis 服务器的密码(如果设置了密码)
# 使用Redis作为缓存提供者
  cache:
    type: redis
    redis:
      use-key-prefix: true #是否使用全局的 key 前缀
      key-prefix: book_   # 自定义 Redis key 的前缀
      cache-null-values: false # 是否缓存 null 值,如果为 false,则不缓存返回 null 的结果
      time-to-live: 400s # 缓存数据的生存时间,单位为秒

使用注解

package com.example.demochapter05.service.impl;

import com.example.demochapter05.dao.BookRepository;
import com.example.demochapter05.entity.Book;
import com.example.demochapter05.service.BookService;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
// 指定缓存空间为book
@CacheConfig(cacheNames = "book")
@Transactional
public  class  BookServiceImpl implements BookService {
    @Autowired
    private BookRepository bookRepository;
// 缓存条目的key,若key存在于book的缓存空间中,则返回缓存结果,否则正常执行语句并把key的条目放到缓存空间中。
    @Cacheable(key = "#id")
    public Book findById(Integer id){
//根据id查找图书信息
        return  bookRepository.findById(id).get();
    }
// 根据key更新缓存空间条目
    @CachePut(key = "#id")
    public  Book updateById(Integer id,String name){
        Book book=this .findById(id);
        book.setName(name);
//更新图书信息
        return  bookRepository.save(book);
    }
// 根据key删除缓存空间条目
    @CacheEvict(key = "#id")
    public  void  delById(Integer id){
//根据id删除图书信息
        bookRepository.deleteById(id);
    }
}

测试

package com.example.demochapter05;

import com.example.demochapter05.service.BookService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class Demochapter06ApplicationTests {
    @Autowired
    private BookService bookService;
    @Test
    public void testCache(){
        bookService.findById(1);
        bookService.findById(1);
    }
}

控制台中仍然只打印了一次Sql,缓存机制生效

动物装饰