Spring Cache

数据缓存Cache

为什么要使用缓存技术?

  内存的速度是远大于硬盘的,项目在运行时的瓶颈往往在数据库,当一些数据是重复且频繁使用时,需要一次一次的请求数据库或远程服务,所以如果能把这部分数据缓存起来,就可以提升一部分性能。


Spring Boot缓存支持

  Spring通过抽象接口CacheManager和Cache来统一不同的缓存技术。

CacheManager|描述
|:—:|:—|
SimpleCacheManager|使用简单的Collection来存储缓存,主要用来测试
ConcurrentCacheManager|使用ConcurrentMap来存储缓存
NoOpCacheManager|仅测试,并没有实际存储缓存
EhCacheCacheManager|使用EhCache作为缓存技术
GuavaCacheManager|使用Google Guava的Guava Cache作为缓存技术
HazelcastCacheManager|使用Hazelcast作为缓存技术
JCacheCacheManager|使用JCache标准的实现作为缓存技术,如Apache Commons JCS
RedisCacheManager|使用Redis作为缓存技术

  Spring需要根据不同的缓存技术配置不同的CacheManager等。

  Spring提供了四个注解来声明缓存规则。

注解|描述
|:—:|:—|
@Cacheable|在方法执行前先查看缓存中是否有数据,若有则返回缓存数据,没有则调用方法并将返回值放入缓存
@CachePut|无论如何都会将方法返回值放入缓存
@CacheEvict|将一条或多条数据从缓存中删除
@Caching|可以通过此注解组合多个策略在一个方法上

  开启声明式缓存支持,只要在配置类使用@EnableCaching注解即可

  Spring Boot则自动化了配置过程,如下所示,Spring Boot自动生成了如下文件。

  Spring Boot环境下使用缓存技术只需要导入要使用的缓存技术的依赖包,然后在配置类使用@EnableCaching开启缓存支持即可,通过一个实战可以简单学习一下如何在Spring Boot中使用某个缓存技术。


测试代码

  pom.xml

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>

  application.properties

1
2
3
4
5
6
#缓存配置
spring.cache.type=ehcache
#程序启动时创建缓存名称
spring.cache.cache-names=testCache
#ehcache配置文件地址
spring.cache.ehcache.config=classpath:/config/ehcache.xml

  ehcache.xml

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<cache name="users" maxElementsInMemory="200" />
</ehcache>

  启动类TestprojectApplication

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

  实体类UserInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class UserInfo {
@Id
private String userId;
private String userName;
private String password;

public String toString(){
return "[userId : " + userId + " userName : " + userName + " password : " + password + "]";
}
}

  服务层UserServiceImpl,实现@CachePut,@CacheEvict,@Cacheable的对应方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class UserServiceImpl implements UserService{
private Logger logger = Logger.getLogger(UserServiceImpl.class);
@Autowired
UserRepository userRepository;

@Override
@CachePut(value = "users",key = "#userInfo.userId") //更新数据到缓存
public UserInfo save(UserInfo userInfo) {
logger.info("为"+ userInfo.toString() + "做了缓存");
return userRepository.saveAndFlush(userInfo);
}

@Override
@CacheEvict(value = "users") //从缓存删除数据
public void remove(String userId) {
logger.info("删除"+ userId + "缓存数据");
userRepository.deleteById(userId);
}

@Override
@Cacheable(value = "users",key = "#userInfo.userId") //取缓存数据
public UserInfo findOne(UserInfo userInfo) {
UserInfo u = userRepository.findByUserId(userInfo.getUserId());
logger.info("为"+ u.toString() + "做了缓存");
return u;
}
}

  控制层CacheController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
public class CacheController {
@Autowired
UserService userService;

@RequestMapping("/put")
public UserInfo put(@RequestBody UserInfo userInfo){
return userService.save(userInfo);
}

@RequestMapping("/able")
public UserInfo cacheable(UserInfo userInfo){
return userService.findOne(userInfo);
}

@RequestMapping("/evit")
public String evit(String userId){
userService.remove(userId);
return "ok";
}
}

执行结果

  测试@Cacheable,查询某条数据,观察输出过程和结果。

http://localhost:8080/able?userId=0001

1
2
3
4
5
6
7
8
Hibernate: select userinfo0_.userId as userId1_1_, userinfo0_.password as password2_1_, userinfo0_.userName as userName3_1_ from UserInfo userinfo0_ where userinfo0_.userId=?
10:41:30,207 INFO UserServiceImpl:43 - 为[userId : 0001 userName : 阿大 password : 123456]做了缓存

{
"userId": "0001",
"userName": "阿大",
"password": "123456"
}

  再次访问并观察

http://localhost:8080/able?userId=0001

  控制台没有Hibernate数据库查询,和为…做了缓存输出,表示直接从缓存中读取数据。

  测试@CachePut,新增某条数据,观察输出过程和结果。

http://localhost:8080/put{"userId" : “0003”,”userName” : “阿三”,”password” : “123456”}

1
2
3
4
5
6
7
8
9
10:44:59,720  INFO UserServiceImpl:28 - 为[userId : 0003 userName : 阿三 password : 123456]做了缓存
Hibernate: select userinfo0_.userId as userId1_1_0_, userinfo0_.password as password2_1_0_, userinfo0_.userName as userName3_1_0_ from UserInfo userinfo0_ where userinfo0_.userId=?
Hibernate: insert into UserInfo (password, userName, userId) values (?, ?, ?)

{
"userId": "36efc2fb-eb0a-4e69-a179-4ad82f4df1bc",
"userName": "阿三",
"password": "123456"
}

  继续查询此条新增数据,发现直接从缓存中读取并返回。

http://localhost:8080/able?userId=0003

  测试@CacheEvict,先查询某条数据,确认其被缓存,然后测试删除。

1
2
3
10:54:07,214  INFO UserServiceImpl:35 - 删除0003缓存数据
Hibernate: select userinfo0_.userId as userId1_1_0_, userinfo0_.password as password2_1_0_, userinfo0_.userName as userName3_1_0_ from UserInfo userinfo0_ where userinfo0_.userId=?
Hibernate: delete from UserInfo where userId=?

  再次访问发现无法查询此数据,表示缓存和数据库都已删除。


参考博客和文章书籍等:

《Spring Boot 实战》

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容