Spring Boot 缓存技术

Spring 定义 CacheManager 和 Cache 接口用来统一不同的缓存技术。例如 JCache、 EhCache、 Hazelcast、 Guava、 Redis 等。在使用 Spring 集成 Cache 的时候,我们需要注册实现的 CacheManager 的 Bean。Spring Boot 默认使用的是 SimpleCacheConfiguration,即使用 ConcurrentMapCacheManager 来实现的缓存。

CacheManager:缓存管理器,管理各种缓存组件

CacheManager 描述
SimpleCacheManager 使用检点 Collection 来存储缓存,主要用于测试
ConcurrentMapCacheManager 使用 ConcurrentMap 存储缓存
NoOpCacheManager 仅用于测试,不会实际存储缓存
EhCacheCacheManager 使用 EhCache 作为缓存技术
GuavaCacheManager 使用 Guava 作为缓存技术
HazelcastCacheManager 使用 Hazelcast 作为缓存技术
JCacheCacheManager 支持 JCache(JSR-107)标准的实现作为缓存技术
RedisCacheManager 使用 Redis 作为缓存技术

Cache 注解详解

  • @CacheConfig:主要用于配置该类中会用到的一些共用的缓存配置。
    在这里 @CacheConfig(cacheNames = “users”):配置了该数据访问对象中返回的内容将存储于名为 users 的缓存对象中,我们也可以不使用该注解,直接通过 @Cacheable 自己配置缓存集的名字来定义。

  • @Cacheable:主要针对方法配置,能够根据方法的请求参数对其结果进行缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。该注解主要有下面几个参数:

    • value、cacheNames:两个等同的参数(cacheNames 为Spring 4 新增,作为 value 的别名),用于指定缓存存储的集合名。由于 Spring 4 中新增了 @CacheConfig,因此在 Spring 3 中原本必须有的 value 属性,也成为非必需项了。

    • key:缓存对象存储在 Map 集合中的 key 值,非必需,缺省按照函数的所有参数组合作为 key 值,若自己配置需使用 SpEL表 达式,比如:@Cacheable(key = “#p0”):使用函数第一个参数作为缓存的 key 值,更多关于 SpEL 表达式的详细内容可参考官方文档。

    • condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = “#p0”, condition = “#p0.length() < 3”),表示只有当第一个参数的长度小于3的时候才会被缓存,若做此配置上面的AAA用户就不会被缓存,读者可自行实验尝试。

    • unless:另外一个缓存条件参数,非必需,需使用 SpEL 表达式。它不同于 condition 参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对 result 进行判断。

    • keyGenerator:用于指定 key 生成器,非必需。若需要指定一个自定义的 key 生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator 接口,并使用该参数来指定。需要注意的是,该参数与 key 是互斥的。

    • cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用。

    • cacheResolver:用于指定使用那个缓存解析器,非必需。需通过
      org.springframework.cache.interceptor.CacheResolver 接口来实现自己的缓存解析器,并用该参数指定。

  • @CachePut:配置于方法上,能够根据参数定义条件来进行缓存,它与 @Cacheable 不同的是,它不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入缓存中,所以主要用于数据新增和修改操作上。它的参数与 @Cacheable 类似,具体功能可参考上面对 @Cacheable 参数的解析。

  • @CacheEvict:配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。除了同 @Cacheable 一样的参数之外,它还有下面两个参数:

    • allEntries:非必需,默认为 false。当为 true 时,会移除所有数据。

    • beforeInvocation:非必需,默认为 false,会在调用方法之后移除数据;当为 true 时,会在调用方法之前移除数据。

搭建 Spring Boot 默认缓存

开启缓存支持

在启动类上添加 @EnableCaching 开启缓存支持,进行自动扫描。

1
@SpringBootApplication
2
@EnableCaching  // 开启缓存功能
3
public class CacheApplication {
4
5
	public static void main(String[] args) {
6
		SpringApplication.run(CacheApplication.class, args);
7
	}
8
}

添加 spring-boot-starter-cache 依赖

在 pom.xml 中添加 spring-boot-starter-cache 依赖。

1
<dependency>
2
    <groupId>org.springframework.boot</groupId>
3
    <artifactId>spring-boot-starter-cache</artifactId>
4
</dependency>

准备数据

模拟数据库数据

1
/**
2
 * 数据工厂,模拟数据库的数据
3
 *
4
 * @author star
5
 **/
6
public class DataFactory {
7
8
    private DataFactory() {
9
    }
10
11
    private static List<UserDto> userDtoList;
12
13
    static {
14
        // 初始化集合
15
        userDtoList = new ArrayList<>();
16
17
        UserDto user = null;
18
        for (int i = 0; i < 10; i++) {
19
            user = new UserDto();
20
            user.setName("star" + i);
21
            user.setAge("2" + i);
22
            userDtoList.add(user);
23
        }
24
    }
25
26
    public static List<UserDto> getUserDaoList() {
27
        return userDtoList;
28
    }
29
}

编写缓存业务代码

  • 编写 DAO 层
1
/**
2
 * UserRepository
3
 *
4
 * @author star
5
 **/
6
@Repository
7
public class UserRepository {
8
9
    /**
10
     * 获取用户信息(此处是模拟的数据)
11
     */
12
    public UserDto getUser(String username) {
13
        UserDto user = getUserFromList(username);
14
        return user;
15
    }
16
17
    /**
18
     * 删除用户信息
19
     */
20
    public List<UserDto> deleteUser(String username) {
21
22
        List<UserDto> userDaoList = DataFactory.getUserDaoList();
23
        userDaoList.remove(getUserFromList(username));
24
25
        return userDaoList;
26
    }
27
28
    /**
29
     * 新增数据
30
     */
31
    public List<UserDto> save(String username) {
32
        // 添加到集合
33
        List<UserDto> userDaoList = DataFactory.getUserDaoList();
34
        for (UserDto userDto : userDaoList) {
35
            // 不能重复添加相同数据
36
            if (Objects.equals(userDto.getName(), username)) {
37
                return userDaoList;
38
            }
39
        }
40
        UserDto user = new UserDto();
41
        user.setName(username);
42
        user.setAge("50");
43
        userDaoList.add(user);
44
45
        return userDaoList;
46
    }
47
48
    /**
49
     * 从模拟的数据集合中筛选 username 的数据
50
     */
51
    private UserDto getUserFromList(String username) {
52
53
        List<UserDto> userDaoList = DataFactory.getUserDaoList();
54
        for (UserDto user : userDaoList) {
55
            if (Objects.equals(user.getName(), username)) {
56
                return user;
57
            }
58
        }
59
        return null;
60
    }
61
}
  • 编写 Service 层
1
/**
2
 * UserService
3
 *
4
 * @author star
5
 **/
6
@Service
7
@CacheConfig(cacheNames = "users")// 指定缓存名称,在本类中是全局的
8
public class UserService {
9
10
    @Autowired
11
    private UserRepository userRepository;
12
13
    /**
14
     * 缓存 key 是 username 的数据到缓存 users 中,
15
     * 如果没有指定 key,则方法参数作为 key 保存到缓存中
16
     */
17
    @Cacheable(key = "#username")
18
    public UserDto getUser(String username) {
19
        System.out.println("从数据库中获取数据,而不是读取缓存");
20
        return userRepository.getUser(username);
21
    }
22
23
24
    /**
25
     * 新增或更新缓存中的数据
26
     */
27
    @CachePut(key = "#username")
28
    public List<UserDto> save(String username) {
29
        return userRepository.save(username);
30
    }
31
32
    /**
33
     * 从缓存 users 中删除 key 是 username 的数据
34
     */
35
    @CacheEvict(key = "#username")
36
    public List<UserDto> deleteUser(String username) {
37
        System.out.println("从数据库中删除数据,以及缓存中的数据");
38
        return userRepository.deleteUser(username);
39
    }
40
}
  • 编写 Controller 层
1
/**
2
 * CacheResource
3
 *
4
 * @author star
5
 **/
6
@RestController
7
@RequestMapping("/api")
8
public class CacheResource {
9
10
    @Autowired
11
    private UserService userService;
12
13
    @GetMapping("/users/{username}")
14
    public ResponseEntity<UserDto> getUser(@PathVariable String username) {
15
        // 获取数据
16
        UserDto user = userService.getUser(username);
17
        return ResponseEntity.ok(user);
18
    }
19
20
    @PutMapping("/users/{username}")
21
    public ResponseEntity<List<UserDto>> save(@PathVariable String username) {
22
        List<UserDto> userDtoList = userService.save(username);
23
24
        return ResponseEntity.ok(userDtoList);
25
    }
26
27
    @DeleteMapping("/users/{username}")
28
    public ResponseEntity<List<UserDto>> delete(@PathVariable String username) {
29
        List<UserDto> userDtoList = userService.deleteUser(username);
30
31
        return ResponseEntity.ok(userDtoList);
32
    }
33
}

演示

通过多次向接口 http://localhost:8080/api/users/star1 GET 数据来观察效果:

get

可以看到缓存的启用和效果如下所示:

result