这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战
Spring3.1中提供了一个Cache Abstraction来屏蔽各种缓存组件的差异,统一不同缓存组件的使用方式,类似于Spring transaction (事务)使用注解完成事务管理一样,Spring Cache通过注解完成缓存的管理(新增、更新、删除、淘汰等操作)。
Spring Cache将缓存应用于Java方法,即在方法上加上Spring Cache的注解后,每次调用该方法时,Spring Cache都会检查是否已经使用此参数调用了该方法。如果已调用,则直接返回缓存的结果,而不去调用实际的方法。如果尚未调用,则会调用该方法缓存结果并返回给用户。
相同参数调用方法时,只会在第一次调用时执行该方法,其他时候从缓存中返回结果
Spring Cache是一种缓存抽象(不是缓存实现),需要使用实际存储来存储缓存数据。即Spring Cache只提供了缓存逻辑,而没有提供存储数据的能力。Spring提供了该抽象的一些实现:Redis、基于JDK java.util.concurrent.ConcurrentMap的缓存、Ehcache、Gemfire、Caffeine和符合JSR-107的缓存。
缓存抽象对于多线程和多进程环境没有特殊处理,因为这些特性由缓存实现处理。
使用缓存抽象,需要注意两个方面:
Caching declaratio:确定需要缓存的方法及其策略。
Cache configuration:存储数据并从中读取数据的后备缓存。
注解
Spring Cache提供了一下注解对缓存进行管理:
@Cacheable:将方法返回结果存入缓存@CacheEvict:删除缓存中的数据@CachePut:更新缓存中的数据@Caching:组合多个缓存操作@CacheConfig:在类级别共享缓存的相关设置
@Cacheable
@Cacheable的作用是将方法的返回结果存储在缓存中,以便在后续调用(使用相同的参数)时,返回缓存中的值。可以写在类和方法上,一般写在查询方法上
value: 缓存key的前缀名cacheNames:同valuekey:缓存key的后缀。基于SpEL表达式, 默认为""keyGenerator: 缓存key的后缀,与key属性互斥。要使用的自定义org.springframework.cache.interceptor.KeyGenerator的 bean name。cacheManager:缓存管理器,实现了缓存逻辑和缓存能力的bean name,例如RedisCacheManager。cacheResolver:缓存解析器,与cacheManager互斥condition:缓存数据的条件,即满足该条件的数据才会呗缓存,基于SpEL表达式unless:不缓存数据的条件,与condition作用相反,基于SpEL表达式sync:是否使用异步,默认false。为true时会加锁阻塞
@CacheEvict
@CacheEvict的作用是删除缓存中缓存的相应数据。可以写在类和方法上,一般写在查询方法上
allEntries:是否删除缓存中的所有条目,默认false即仅删除关联键下的值beforeInvocation:是否在调用方法前删除缓存,默认false。为true时无论方法结果如何(是否抛出异常)都会导致删除该缓存。为false ,即先删数据库后删缓存 其余属性同@Cacheable
@CachePut
@CacheEvict的作用是更新缓存中缓存的相应数据。可以写在类和方法上,一般写在更新方法上
属性同@Cacheable
@Caching
@Caching是一个多功能的注解,既可以只完成@Cacheable、@CacheEvict和@CachePut注解的功能,也可以同时完成@Cacheable、@CacheEvict和@CachePut注解的功能
cacheable:一个@Cacheable的数组,可以同时指定多个@Cacheable缓存put:一个@CachePut的数组,可以同时指定多个@CachePut缓存evict:一个@CacheEvict的数组,可以同时指定多个@CacheEvict缓存
@CacheConfig
@CacheConfig提供了一种在类级别共享通用缓存相关设置的机制。此注解为该类中定义的任何缓存操作提供一组默认设置。被@CacheConfig注解的类其中的@Cacheable、@CacheEvict和@CachePut注解共用@CacheConfig中的配置
属性同@Cacheable
Spring Cache的SpEL
Spring Cache支持SpEL的如下:
| 表达式 | 位置 | 描述 | 示例 |
|---|---|---|---|
methodName | Root 属性 | 方法名 | #root.methodName |
method | Root 属性 | 方法 | #root.method.name |
target | Root 属性 | 方法的所属类对象 | #root.target |
targetClass | Root 属性 | 方法的所属类Class | #root.targetClass |
args | Root 属性 | 方法的参数。类型为数组 | #root.args[0]:表示第一个参数 |
caches | Root 属性 | 该方法调用的其他方法 | #root.caches[0].name |
| 参数名 | 调用上下文 | 调用时方法时,方法的实参 | #id , #name等 |
result | 调用上下文 | 调用时方法时,方法的返回结果 | #result |
示例
- 新建项目 新建项目勾选如下选项:
- 编写yml
server:
port: 7909
spring:
data:
jpa:
repositories:
enabled: true
redis:
repositories:
enabled: true
jpa:
show-sql: true
database: mysql
hibernate:
ddl-auto: none
generate-ddl: false
datasource:
url: jdbc:mysql://**********:****/user?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
username: ****
password: ****
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: *********
port: ****
password: *******
cache:
type: redis # 使用redis存储缓存数据
redis:
time-to-live: -1 # 过期时间,-1:永不过期
use-key-prefix: true # 开启缓存key的前缀
key-prefix: spring_cache_ # 缓存key的前缀
cache-null-values: false # 缓存null
- 开启缓存
在启动类上加上
@EnableCaching注解开启缓存功能。
- 创建实体类
创建User实体类,并加上@
Data、@Entity和@Table注解
@Data
@Entity
@Table(name = "user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private Date birthday;
private String sex;
}
- 创建实体类的
Repository创建接口,继承JpaRepository接口。JpaRepository接口提供了基本的增删改查方法
public interface UserRepository extends JpaRepository<User, Integer> {
}
- 创建service 创建UserService 定义增删改查方法并实现, 在对应的增删改查方法上放上相应的缓存注解
public interface UserService {
/**
* value: 缓存key的前缀
* #id:参数id
*
**/
@Cacheable(value = "user", key = "#id")
User getUserById(Integer id);
/**
* #result: 方法的返回结果
*
**/
@CachePut(value = "user", key = "#result.id")
User create(User user);
/**
* #a0: 方法的第一个参数, 等同于#arg[0]
*
**/
@CachePut(value = "user", key = "#a0.id")
User update(User user);
@CacheEvict(value = "user", key = "#id")
void del(Integer id);
@Caching(
cacheable = {
@Cacheable(value= "user::name", key = "#name")
},
put = {
@CachePut(value = "user::name", key = "#result.id")
}
)
User getUserByName(String name);
}
@Slf4j
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserRepository userRepository;
@Override
public User getUserById(Integer id) {
log.info("执行getUserById方法,id: {}", id);
return userRepository.findById(id).orElse(null);
}
@Override
public User create(User user) {
return userRepository.save(user);
}
@Override
public User update(User user) {
return userRepository.save(user);
}
@Override
public void del(Integer id) {
userRepository.deleteById(id);
}
@Override
public User getUserByName(String name) {
log.info("执行getUserByName方法,name: {}", name);
return userRepository.findByName(name);
}
}
缓存注解也可以加载方式的实现上
- 编写controller
@RestController
public class UserController {
@Resource
private UserService userService;
@GetMapping("users/{id}")
public User getUserById(@PathVariable("id") Integer id) {
return userService.getUserById(id);
}
@PostMapping("users")
public User create(@RequestBody User user) {
return userService.create(user);
}
@PutMapping("users")
public User update(@RequestBody User user) {
return userService.update(user);
}
@DeleteMapping("users/{id}")
public void del(@PathVariable("id") Integer id) {
userService.del(id);
}
@GetMapping("users")
public User getUserByName(@RequestParam("name") String name) {
return userService.getUserByName(name);
}
}
测试
-
创建User 调用创建User的接口
查看redis,可以看到缓存了一个id为10的User
-
修改User 调用修改User的接口,修改User信息
- 调用获取User的接口
查看控制台,可以发现并没有打印方法中的日志
删除Redis中的数据后再次调用获取User的接口,可以看到控制台打印了该方法的日志,证明调用了该方法
-
调用通过用户名查询的接口
查看redis,可以看到新增了两条缓存数据
-
调用删除User的方法 调用后查看redis,发现后缀为10的缓存数据已经被删除
此时在调用获取User的接口,控制台依然打印调用该方法的日志,但此时缓存的是空值