Spring Cache 详细介绍——补充

Spring Cache 是 Spring Framework 提供的一个强大的缓存抽象层,它允许开发者通过简单的注解方式将缓存功能集成到应用程序中,而无需关心底层缓存实现的细节。下面我将从多个方面详细介绍 Spring Cache。

一、核心概念

Spring Cache 基于以下几个核心概念构建:

  1. 缓存抽象接口:Spring 定义了一套缓存抽象接口,如 Cache 和 CacheManager,使应用能够与各种缓存实现进行交互。

  2. 声明式缓存:通过注解(如 @Cacheable@CachePut@CacheEvict 等)来声明缓存行为,无需编写复杂的缓存逻辑代码。

  3. 缓存管理器CacheManager 接口负责创建、配置和管理缓存实例。

  4. 缓存解析器CacheResolver 接口用于确定在特定操作中应该使用哪些缓存。

  5. 键生成器KeyGenerator 接口用于生成缓存键,默认实现使用方法参数。

二、常用注解

Spring Cache 提供了几个核心注解:

  1. @Cacheable:标记方法的返回值应该被缓存。如果缓存中已存在相同的键,则直接返回缓存值,否则执行方法并缓存结果。

  2. @CachePut:强制执行方法并将结果放入缓存,不考虑缓存中是否已存在相同的键。

  3. @CacheEvict:标记方法用于从缓存中移除数据。

  4. @Caching:组合多个缓存注解。

  5. @CacheConfig:在类级别设置缓存配置。

三、缓存管理器实现

Spring Cache 支持多种缓存管理器实现:

  • SimpleCacheManager:使用简单的内存 Map 实现,主要用于测试。
  • ConcurrentMapCacheManager:使用 ConcurrentHashMap 作为缓存存储。
  • EhCacheCacheManager:与 EhCache 集成。
  • RedisCacheManager:与 Redis 集成。
  • CaffeineCacheManager:与 Caffeine 集成。
  • JCacheCacheManager:与 JSR-107 (JCache) 兼容的缓存提供程序集成。

四、基本配置示例

下面是一个使用 Spring Boot 和 Redis 配置 Spring Cache 的示例:

controller:


@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public ResponseEntity getUser(@PathVariable Long id) {
        Optional user = userService.getUserById(id);
        return user.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
    }
    
    @PostMapping
    public ResponseEntity saveUser(@RequestBody User user) {
        return ResponseEntity.ok(userService.saveUser(user));
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
    
    @DeleteMapping("/cache")
    public ResponseEntity clearCache() {
        userService.clearAllUserCache();
        return ResponseEntity.noContent().build();
    }
}

service

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    // 缓存注解示例
    @Cacheable(value = "users", key = "#id", unless = "#result == null")
    public Optional getUserById(Long id) {
        return userRepository.findById(id);
    }
    
    @CachePut(value = "users", key = "#user.id")
    public User saveUser(User user) {
        return userRepository.save(user);
    }
    
    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
    
    // 清空所有用户缓存
    @CacheEvict(value = "users", allEntries = true)
    public void clearAllUserCache() {
        // 方法体为空,仅用于触发缓存清除
    }
}

Repository:

@Repository
public class UserRepository {
    private final Map userMap = new HashMap<>();
    
    // 初始化一些测试数据
    public UserRepository() {
        userMap.put(1L, new User(1L, "张三", 25));
        userMap.put(2L, new User(2L, "李四", 30));
        userMap.put(3L, new User(3L, "王五", 35));
    }
    
    public Optional findById(Long id) {
        System.out.println("从数据库查询用户: " + id);
        return Optional.ofNullable(userMap.get(id));
    }
    
    public User save(User user) {
        userMap.put(user.getId(), user);
        System.out.println("保存用户到数据库: " + user);
        return user;
    }
    
    public void deleteById(Long id) {
        userMap.remove(id);
        System.out.println("从数据库删除用户: " + id);
    }
}

model

public class User implements Serializable {
    private Long id;
    private String name;
    private Integer age;
    
    // 构造函数、getter和setter方法
    public User() {}
    
    public User(Long id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    
    // getter和setter方法
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
    
    @Override
    public String toString() {
        return "User{id=" + id + ", name='" + name + "', age=" + age + '}';
    }
}

DemoApplication.java

@SpringBootApplication
@EnableCaching // 启用缓存功能
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

application.yml

spring:
  redis:
    host: localhost
    port: 6379
  cache:
    type: redis

UserServiceTest.java

package com.example.demo;

import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class UserServiceTest {
    
    @Autowired
    private UserService userService;
    
    @Test
    void testGetUserById_CacheHit() {
        // 第一次调用 - 应该从数据库获取
        Optional user1 = userService.getUserById(1L);
        assertTrue(user1.isPresent());
        assertEquals("张三", user1.get().getName());
        
        // 第二次调用 - 应该从缓存获取
        Optional user2 = userService.getUserById(1L);
        assertTrue(user2.isPresent());
        assertEquals("张三", user2.get().getName());
        
        // 验证两次获取的是同一个对象(缓存命中)
        assertSame(user1.get(), user2.get());
    }
    
    @Test
    void testSaveUser() {
        // 创建新用户
        User newUser = new User(4L, "赵六", 40);
        
        // 保存用户,应该触发缓存更新
        User savedUser = userService.saveUser(newUser);
        assertEquals("赵六", savedUser.getName());
        
        // 获取用户,应该从缓存中获取
        Optional retrievedUser = userService.getUserById(4L);
        assertTrue(retrievedUser.isPresent());
        assertEquals("赵六", retrievedUser.get().getName());
    }
    
    @Test
    void testDeleteUser() {
        // 确保用户存在
        Optional user = userService.getUserById(1L);
        assertTrue(user.isPresent());
        
        // 删除用户,应该触发缓存清除
        userService.deleteUser(1L);
        
        // 再次获取用户,应该从数据库获取且不存在
        Optional deletedUser = userService.getUserById(1L);
        assertFalse(deletedUser.isPresent());
    }
}

五、注解详解

1.@Cacheable

这个注解标记方法的返回值应该被缓存。常用属性:

  • value/cacheNames:指定缓存名称。
  • key:指定缓存键,可以使用 SpEL 表达式。
  • condition:指定缓存条件,使用 SpEL 表达式。
  • unless:指定不缓存的条件,使用 SpEL 表达式。
  • sync:是否同步缓存,防止缓存击穿。

2.@CachePut

这个注解标记方法的返回值应该被更新到缓存中,无论缓存中是否已存在该键。常用属性与@Cacheable相同。

3.@CacheEvict

这个注解标记方法用于从缓存中移除数据。常用属性:

  • value/cacheNames:指定缓存名称。
  • key:指定要移除的缓存键。
  • allEntries:是否移除所有缓存项。
  • beforeInvocation:是否在方法执行前移除缓存。

4.@Caching

这个注解用于组合多个缓存注解。

5.@CacheConfig

这个注解用于类级别,设置缓存的默认配置。

六、高级特性

1.自定义键生成器

可以通过实现KeyGenerator接口来自定义缓存键的生成策略:

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component("myKeyGenerator")
public class MyKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        // 自定义键生成逻辑
        return target.getClass().getSimpleName() + "_" + method.getName() + "_" + String.join("_", params);
    }
}

2.自定义缓存解析器

可以通过实现CacheResolver接口来自定义缓存解析策略。

3.条件缓存

使用conditionunless属性可以实现条件缓存:

@Cacheable(value = "users", key = "#id", condition = "#id > 0", unless = "#result == null")
public User getUserById(Long id) {
    return userRepository.findById(id).orElse(null);
}

4.缓存同步

使用sync属性可以防止缓存击穿:

@Cacheable(value = "users", key = "#id", sync = true)
public User getUserById(Long id) {
    // 当缓存不存在时,只有一个线程会执行此方法
    return userRepository.findById(id).orElse(null);
}

你可能感兴趣的:(Redis,spring,mybatis,java)