SpringBoot - 缓存的使用详解2(使用Redis单机缓存)
在上文中我介绍了 Spring Boot 使用 EhCache 2.x 来作为缓存的实现(点击查看),本文接着介绍使用单机版的 Redis 作为缓存的实现。
和 Ehcache 一样,如果在 classpath 下存在 Redis 并且 Redis 已经配置好了,此时默认就会使用 RedisCacheManager 作为缓存提供者。下面演示具体的使用步骤。
(2)接着创建一个 BookDao 并添加相关的缓存注解:
(3)创建一个测试 Controller 对 BookDao 中的方法进行测试:
(4)访问这个接口,可以看到控制台打印日志如下:

和 Ehcache 一样,如果在 classpath 下存在 Redis 并且 Redis 已经配置好了,此时默认就会使用 RedisCacheManager 作为缓存提供者。下面演示具体的使用步骤。
二、使用 Redis 单机缓存
1,添加依赖
首先编辑项目的 pom.xml 文件,添加 spring-boot-starter-cache 依赖以及 Redis 依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
2,进行缓存配置
Redis 单机缓存只需要开发者在 application.properties 中添加 Redis 配置及缓存配置即可,代码如下:
(1)第 2 行配置的是缓存有效期,即 Redis 中 key 的过期时间。
(2)其它则是 Redis 的基本配置,更详细的介绍可以参考我之前写的这篇文章:
(2)其它则是 Redis 的基本配置,更详细的介绍可以参考我之前写的这篇文章:
# 缓存配置
spring.cache.redis.time-to-live=1800s
# 基本连接信息配置
spring.redis.database=0
spring.redis.host=192.168.60.133
spring.redis.port=6379
spring.redis.password=123
# 连接池信息配置
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
3,开启缓存
在项目的入口类上添加 @EnableCaching 注解开启缓存,代码如下:
@SpringBootApplication @EnableCaching public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args); } }
4,开始测试
(1)首先我们创建一个 Book 实体类:@Setter @Getter @NoArgsConstructor public class Book implements Serializable { private Integer id; private String name; private String author; }
(2)接着创建一个 BookDao 并添加相关的缓存注解:
(1)在 BookDao 上添加 @CacheConfig 注解指定使用的缓存的名字,这个配置可选。若不使用 @CacheConfig 注解,则直接在 @Cacheable 注解中指明缓存名字。
(2)在方法上添加 @Cacheable 注解表示对该方法进行缓存:
(2)在方法上添加 @Cacheable 注解表示对该方法进行缓存:
- 默认情况下,缓存的 key 是方法的参数,缓存的 value 是方法的返回值。
- 当开发者在其他类中调用该方法时,首先会根据调用参数查看缓存中是否有相关数据,若有则直接使用缓存数据,该方法不会执行。
- 否则执行该方法,执行成功后将返回值缓存起来。
- 但若是在当前类中调用该方法,则缓存不会生效。
- @Cacheable(condition="#id%2-0") 表示当 id 对 2 取模为 0 时才进行缓存,否则不缓存。
- 第 15 行表示缓存的 key 为参数 book 对象中 id 的值。
- 第 22 行表示缓存的 key 为参数 id。
- #root.methodName:当前方法名
- #root.method.name:当前方法对象
- #root.caches[0].name:当前方法使用的缓存
- #root.target:当前被调用的对象
- #root.targetClass:当前被调用的对象的 class
- #root.args[0]:当前方法参数数组
- 与 @Cacheable 注解不同,添加了 @Cacheput 注解的方法每次在执行时都不去检查缓存中是否有数据,而是直接执行方法,然后将方法的执行结果缓存起来。
- 如果该 key 对应的数据已经被缓存起来了,就会覆盖之前的数据,这样可以避免再次加载数据时获取到脏数据。
- 同时,@Cacheput 具有和 @Cacheable 类似的属性,这里不再赘述。
- allEntries 表示是否将所有的缓存数据都移除,默认为 false。
- beforelnvocation 表示是否在方法执行之前移除缓存中的数据,默认为 false,即在方法执行之后移除缓存中的数据。
@Repository @CacheConfig(cacheNames = "book_cache") public class BookDao { @Cacheable public Book getBookById(Integer id) { System.out.println("getBookById"); Book book = new Book(); book.setId(id); book.setName("三国演义"); book.setAuthor("罗贯中"); return book; } @CachePut(key = "#book.id") public Book updateBookById(Book book) { System.out.println("updateBookById"); book.setName("三国演义2"); return book; } @CacheEvict(key = "#id") public void deleteBookById(Integer id) { System.out.println("deleteBookById"); } }
(3)创建一个测试 Controller 对 BookDao 中的方法进行测试:
@RestController public class HelloController { @Autowired BookDao bookDao; @GetMapping("/test") public void test() { // 连续获取两次数据 System.out.println("--- 连续获取两次数据 ---"); bookDao.getBookById(1); bookDao.getBookById(1); // 删除后再次获取数据 System.out.println("--- 删除后再次获取数据 ---"); bookDao.deleteBookById(1); Book b3 = bookDao.getBookById(1); System.out.println("b3:"+b3); // 更新后再次获取数据 System.out.println("--- 更新后再次获取数据 ---"); Book b = new Book(); b.setName("平凡的世界"); b.setAuthor("路遥"); b.setId(1); bookDao.updateBookById(b); Book b4 = bookDao.getBookById(1); System.out.println("b4:"+b4); } }
(4)访问这个接口,可以看到控制台打印日志如下:
- 一开始执行了两个査询,但是查询方法只打印了一次,因为第二次使用了缓存。
- 接下来执行了删除方法,删除方法执行完之后再次执行查询,查询方法又被执行了,因为在删除方法中缓存已经被删除了。
- 再接下来执行更新方法,更新方法中不仅更新数据,也更新了缓存,所以在最后的査询方法中,查询方法日志没打印,说明该方法没执行,而是使用了缓存中的数据,而缓存中的数据已经被更新了。

(2)查看 Redis 服务器的缓存结果,可以发现 Redis 中的 key 都有一个前缀,默认前缀就是“缓存名::”

附:自定义缓存 key 的生成器 KeyGenerator
根据前面介绍可知,默认情况下缓存的 key 是方法的参数。如果不想使用默认的 key,可以使用自定义 key(使用参数定义 key 的方式、或者使用 root 对象来生成 key)
如果这些 key 不能满足开发需求,我们也可以自定义缓存 key 的生成器 KeyGenerator
如果这些 key 不能满足开发需求,我们也可以自定义缓存 key 的生成器 KeyGenerator
1,创建自定义 KeyGenerator
自定义的 MyKeyGenerator 实现 KeyGenerator 接口,然后实现该接口中的 generate 方法。 generate 方法的三个参数分别是当前对象、当前请求的方法以及方法的参数,开发者可根据这些信息纽成一个新的 key 返回,返回值就是缓存的 key。
@Component public class MyKeyGenerator implements KeyGenerator { @Override public Object generate(Object target, Method method, Object... params) { return Arrays.toString(params); } }
2,使用自定义的 KeyGenerator
使用时我们只需要在 @Cacheable 注解中引用 MyKeyGenermor 实例即可。@Repository @CacheConfig(cacheNames = "book_cache") public class BookDao { @Autowired MyKeyGenerator myKeyGenerator; @Cacheable(keyGenerator = "myKeyGenerator") public Book getBookById(Integer id) { System.out.println("getBookById"); Book book = new Book(); book.setId(id); book.setName("三国演义"); book.setAuthor("罗贯中"); return book; } }