* 本文作者:唐亚峰
* 本文链接:http://blog.battcn.com/2018/05/13/springboot/v2-cache-redis/
<http://blog.battcn.com/2018/05/13/springboot/v2-cache-redis/>
* 版权声明:本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0
<https://creativecommons.org/licenses/by-nc-sa/3.0/>许可协议。转载请注明出处!
SpringBoot是为了简化Spring应用的创建、运行、调试、部署等一系列问题而诞生的产物,
自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖就可以轻易的搭建出一个 WEB 工程

Spring 3.1引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如EHCache或者
Redis),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种annotation,即能够达到缓存方法的返回对象的效果。

特点

具备相当的好的灵活性,不仅能够使用SpEL(Spring Expression Language)来定义缓存的 key 和各种
condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 EHCache、Redis、Guava 的集成。

* 基于annotation即可使得现有代码支持缓存
* 开箱即用Out-Of-The-Box,不用安装和部署额外第三方组件即可使用缓存
* 支持Spring Express Language,能使用对象的任何属性或者方法来定义缓存的key和condition
* 支持AspectJ,并通过其实现任何方法的缓存支持
* 支持自定义key和自定义缓存管理者,具有相当的灵活性和扩展性
使用前后

下面针对Spring Cache使用前后给出了伪代码部分,具体中也许比这要更加复杂,但是Spring Cache都可以很好的应对

使用前

我们需要硬编码,如果切换Cache Client还需要修改代码,耦合度高,不易于维护
public String get(String key) { String value = userMapper.selectById(key); if (
value != null) { cache.put(key,value); } return value; }
使用后

基于Spring Cache注解,缓存由开发者自己配置,但不用参与到具体编码
@Cacheable(value = "user", key = "#key") public String get(String key) { return
userMapper.selectById(key); }
添加依赖

在pom.xml中添加spring-boot-starter-data-redis的依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>
spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>
org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency
> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>
spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
属性配置

在application.properties文件中配置如下内容,由于Spring Boot2.x的改动,连接池相关配置需要通过
spring.redis.lettuce.pool或者spring.redis.jedis.pool进行配置了。使用了Spring Cache后,能指定
spring.cache.type就手动指定一下,虽然它会自动去适配已有Cache的依赖,但先后顺序会对Redis使用有影响(JCache ->
EhCache -> Redis -> Guava)
spring.redis.host=localhost spring.redis.port=6379 # 一般来说是不用配置的,Spring Cache
会根据依赖的包自行装配 spring.cache.type=redis # 连接超时时间(毫秒) spring.redis.timeout=10000 #
Redis默认情况下有16个分片,这里配置具体使用的分片 spring.redis.database=0 # 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 spring
.redis.lettuce.pool.max-wait=-1 # 连接池中的最大空闲连接 默认 8 spring.redis.lettuce.pool.max
-idle=8 # 连接池中的最小空闲连接 默认 0 spring.redis.lettuce.pool.min-idle=0
具体编码

实体类

创建一个User类,目的是为了模拟对象存储
package com.winterchen.domain; import java.io.Serializable; /** * Created by
Donghua.Chen on 2018/6/19. */ public class User implements Serializable {
private static final long serialVersionUID = 8655851615465363473L; private Long
id;private String username; private String password; public User() { } public
User(Long id, String username, String password) { this.id = id; this.username =
username;this.password = password; } // TODO 省略get set }
定义接口
package com.winterchen.service; import com.winterchen.domain.User; /** *
Created by Donghua.Chen on 2018/6/19. */ public interface UserService { /** *
删除 * * @param user 用户对象 * @return 操作结果 */ User saveOrUpdate(User user); /** *
添加 * * @param id key值 * @return 返回结果 */ User get(Long id); /** * 删除 * * @param
id key值 */ void delete(Long id); }
实现类

为了方便演示数据库操作,直接定义了一个Map<Long, User> DATABASES,这里的核心就是@Cacheable、@CachePut、
@CacheEvict三个注解
package com.winterchen.service.impl; import com.winterchen.domain.User; import
com.winterchen.service.UserService;import org.slf4j.Logger; import
org.slf4j.LoggerFactory;import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut; import
org.springframework.cache.annotation.Cacheable;import
org.springframework.stereotype.Service;import java.util.HashMap; import
java.util.Map;/** * Created by Donghua.Chen on 2018/6/19. */ @Service public
class UserServiceImpl implements UserService{ private static final Map<Long,
User> DATABASES =new HashMap<>(); static { DATABASES.put(1L, new User(1L, "u1",
"p1")); DATABASES.put(2L, new User(2L, "u2", "p2")); DATABASES.put(3L, new User(
3L, "u3", "p3")); } private static final Logger log =
LoggerFactory.getLogger(UserServiceImpl.class);@Cacheable(value = "user", key =
"#id") @Override public User get(Long id) { // TODO 我们就假设它是从数据库读取出来的 log.info(
"进入 get 方法"); return DATABASES.get(id); } @CachePut(value = "user", key =
"#user.id") @Override public User saveOrUpdate(User user) {
DATABASES.put(user.getId(), user); log.info("进入 saveOrUpdate 方法"); return user;
}@CacheEvict(value = "user", key = "#id") @Override public void delete(Long id)
{ DATABASES.remove(id); log.info("进入 delete 方法"); } }
主函数

@EnableCaching必须要加,否则spring-data-cache相关注解不会生效…
package com.winterchen; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import
org.springframework.cache.annotation.EnableCaching;@SpringBootApplication
@EnableCaching public class SpringBootCacheRedisApplication { public static void
main(String[] args) {
SpringApplication.run(SpringBootCacheRedisApplication.class, args); } }
测试

完成准备事项后,编写一个junit测试类来检验代码的正确性,有很多人质疑过Redis线程安全性,故下面也提供了响应的测试案例,如有疑问欢迎指正
package com.winterchen; import com.winterchen.domain.User; import com
.winterchen.service.UserService; import org.junit.Test; import org.junit.runner
.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org
.springframework.beans.factory.annotation.Autowired; import org.springframework
.boot.test.context.SpringBootTest; import org.springframework.test.context.junit
4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class
SpringBootCacheRedisApplicationTests { private static final Logger log =
LoggerFactory.getLogger(SpringBootCacheRedisApplicationTests.class); @Autowired
private UserService userService; @Test public void get() { final User user =
userService.saveOrUpdate(new User(5L, "u5", "p5")); log.info("[saveOrUpdate] -
[{}]", user); final User user1 = userService.get(5L); log.info("[get] - [{}]",
user1); userService.delete(5L); } }
启动测试类,结果和我们期望的一致,可以看到增删改查中,查询是没有日志输出的,因为它直接从缓存中获取的数据
,而添加、修改、删除都是会进入方法内执行具体的业务代码,然后通过切面去删除掉Redis中的缓存数据。其中 # 号代表这是一个SpEL 表达式
,此表达式可以遍历方法的参数对象,具体语法可以参考 Spring 的相关文档手册。
2018-06-19 11:21:08.819 INFO 21659 --- [ main] c.w.service.impl.UserServiceImpl
: 进入 saveOrUpdate 方法2018-06-19 11:21:09.234 INFO 21659 --- [ main] io.lettuce
.core.EpollProvider : Starting without optional epoll library 2018-06-19 11:21:
09.236 INFO 21659 --- [ main] io.lettuce.core.KqueueProvider : Starting without
optional kqueue library2018-06-19 11:21:09.484 INFO 21659 --- [ main] c.w
.SpringBootCacheRedisApplicationTests : [saveOrUpdate] - [com.winterchen.domain
.User@57a48985] 2018-06-19 11:21:09.499 INFO 21659 --- [ main] c.w
.SpringBootCacheRedisApplicationTests : [get] - [com.winterchen.domain.User@52
d6cd34]2018-06-19 11:21:09.504 INFO 21659 --- [ main] c.w.service.impl
.UserServiceImpl : 进入 delete 方法
其它类型

下列的就是Redis其它类型所对应的操作方式

* opsForValue:对应 String(字符串)
* opsForZSet:对应 ZSet(有序集合)
* opsForHash:对应 Hash(哈希)
* opsForList:对应 List(列表)
* opsForSet:对应 Set(集合)
* opsForGeo:对应 GEO(地理位置)
根据条件操作缓存

根据条件操作缓存内容并不影响数据库操作,条件表达式返回一个布尔值,true/false,当条件为true,则进行缓存操作,否则直接调用方法执行的返回结果。

* 长度:@CachePut(value = "user", key = "#user.id",condition =
"#user.username.length() < 10")只缓存用户名长度少于10的数据
* 大小:@Cacheable(value = "user", key = "#id",condition = "#id < 10")
只缓存ID小于10的数据
* 组合:@Cacheable(value="user",key="#user.username.concat(##user.password)")
* 提前操作:@CacheEvict(value="user",allEntries=true,beforeInvocation=true)加上
beforeInvocation=true后,不管内部是否报错,缓存都将被清除,默认情况为false
注解介绍

@Cacheable(根据方法的请求参数对其结果进行缓存)

* key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合(如:
@Cacheable(value="user",key="#userName"))
* value:缓存的名称,在 Spring 配置文件中定义,必须指定至少一个(如:@Cacheable(value="user")或者
@Cacheable(value={"user1","use2"}))
* condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:
@Cacheable(value = "user", key = "#id",condition = "#id < 10"))
@CachePut(根据方法的请求参数对其结果进行缓存,和@Cacheable不同的是,它每次都会触发真实方法的调用)

* key:同上
* value:同上
* condition:同上
@CachEvict(根据条件对缓存进行清空)

* key:同上
* value:同上
* condition:同上
* allEntries:是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存(如:
@CacheEvict(value = "user", key = "#id", allEntries = true))
* beforeInvocation:是否在方法执行前就清空,缺省为 false,如果指定为
true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存(如:@CacheEvict(value = "user",
key = "#id", beforeInvocation = true))
总结

spring-cache文档:
https://docs.spring.io/spring/docs/5.0.5.RELEASE/spring-framework-reference/integration.html#cache-introduction

<https://docs.spring.io/spring/docs/5.0.5.RELEASE/spring-framework-reference/integration.html#cache-introduction>
spring-data-redis文档:
https://docs.spring.io/spring-data/redis/docs/2.0.1.RELEASE/reference/html/#new-in-2.0.0

<https://docs.spring.io/spring-data/redis/docs/2.0.1.RELEASE/reference/html/#new-in-2.0.0>
Redis文档:https://redis.io/documentation <https://redis.io/documentation>
Redis中文文档:http://www.redis.cn/commands.html <http://www.redis.cn/commands.html>

目前很多大佬都写过关于SpringBoot的教程了,如有雷同,请多多包涵,本教程基于最新的
spring-boot-starter-parent:2.0.1.RELEASE编写,包括新版本的特性都会一起介绍…

说点什么

springboot技术交流群:681513531

个人博客:https://blog.winterchen.com <https://blog.winterchen.com>

全文代码:https://github.com/WinterChenS/springboot-learning-experience
<https://github.com/WinterChenS/springboot-learning-experience>

* 本文作者:唐亚峰
* 本文链接:http://blog.battcn.com/2018/05/13/springboot/v2-cache-redis/
<http://blog.battcn.com/2018/05/13/springboot/v2-cache-redis/>
* 版权声明:本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0
<https://creativecommons.org/licenses/by-nc-sa/3.0/>许可协议。转载请注明出处!

友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信