Spring Cache
1.简介
核心思想是当在调用一个缓存方法时,会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下一次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回,从而实现缓存的功能。
1.@Cacheable
用于标记缓存,也就是对使用@Cacheable注解的位置进行缓存。@Cacheable可以在方法或者类上进行标记,当对方法进行标记时,表示此方法支持缓存;当对此类进行标记时,表明当前类中的所有方法都支持缓存。在支持Spring Cache的环境下,对于使用@Cacheable标记的方法,Spring在每次调用方法前都会根据key查询当前Cache中是否存在相同的key的缓存元素,如果存在,就不再执行该方法,而是直接从缓存中获取结果进行返回,否则执行该方法并将返回结果存入指定的缓存中。他有3个属性:
- value:在使用@Cacheable注解的时候,value属性是必须要指定的,这个属性用于指定Cache的名称,也就是说,表明当前缓存的返回值用于哪个缓存上。
- key:用于指定缓存对应的key。key属性不是必须指定的,如果没有指定key,Spring就会为我们使用默认策略生成对应的key。默认策略规定:当前缓存方法没有参数,那么当前key为0;当前缓存方法有一个参数,那么以key为参数值;当前缓存方法有多个参数,那么key为所有参数的hashcode值。当然,也可以用Spring提供的EL表达式来指定当前缓存方法的key。通常来说,我们可以使用当前缓存方法的参数指定key,一般为“#参数名”。如果参数为对象,就可以使用对象的属性指定key。
@Cacheable(value="users",key="#users.id") public User findUser(User user){ return new User(); }
- condition:主要用于指定当前缓存的触发条件。
@Cacheable(value="users",key="#users.id",condition="#user.id%2==0") public User findUser(User user){ return new User(); }
2.@CachePut
@CahePut只是用于将标记该注解的方法返回值放入缓存中,无论缓存中是否包含当前缓存,只是以键值的形式将执行结果放入缓存中。在使用方面,@CachePut注解和@Cacheable注解一致。
3.@CacheEvict
@CacheEvict注解用于清除缓存数据,与@Cacheable类似,不过@CacheEvict用于方法是清除当前方法的缓存,用于类时清除当前类所有方法的缓存。@CacheEvict除了提供与@Cacheable一致的3个属性外,还提供了一个常用的属性allEntries,这个属性的默认值为false,如果指定属性值为true,就会清除当前value值所有的缓存。
2.配置Spring Cache依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
3.测试
在启动类上加入@EnableCaching注解,表示启动缓存
package com.springboot.controller;
//imports
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/saveUser")
@CachePut(value = "user", key = "#id")
public User saveUser(Long id, String userName, String userPassword){
User user = new User(id,userName, userPassword);
userRepository.save(user);
return user;
}
@GetMapping("/queryUser")
@Cacheable(value = "user", key = "#id")
public Optional<User> queryUser(Long id){
return userRepository.findById(id);
}
@GetMapping("/deleteUser")
@CacheEvict(value = "user", key = "#id")
public String deleteUser(Long id){
userRepository.deleteById(id);
return "success";
}
@GetMapping("/deleteCache")
@CacheEvict(value = "user", allEntries = true)
public void deleteCache() {
}
}
- saveUser方法,其中使用到@CachePut,将返回值存放在缓存中
- queryUser方法,使用到@Cacheable,这个注解在执行前查看是否已经存在缓存,如果存在,这直接返回;如果不存在,就将返回值存入缓存后再返回
- deleteUser方法,使用到@CacheEvict,用于删除对应的缓存
- deleteCache方法,用于删除所有value为user的缓存
Redis
1.简介
Redis是一个高性能的缓存存储系统,并且以key-value的形式存储数据。目前Value支持5种数据类型,其中包括string(字符串)、list(链表)、set(集合)、zset(有序集合)、hash(哈希类型)。Redis还支持数据持久化。
2.配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
对于使用Redis,常用操作无非就是set方法和get方法。使用RedisTemplate进行存放数据和取出数据。
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));修改Redis里面查看key编码问题
package com.springboot.service;
//imports
@Service
public class RedisService {
@Autowired
private RedisTemplate redisTemplate;
public void set(String key, Object value) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
vo.set(key, value);
}
public void set(String key, Object value, Long time, TimeUnit t) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
vo.set(key, value, time, t);
}
public Object get(String key) {
ValueOperations<String, Object> vo = redisTemplate.opsForValue();
return vo.get(key);
}
}
3.测试
创建一个UserController进行测试,需要对实体类进行序列化,
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Object.class));里面key的编码问题
package com.springboot.controller;
//imports
@RestController
public class UserController {
@Autowired
private RedisService redisService;
@Autowired
private UserRepository userRepository;
@GetMapping(value = "saveUser")
public String saveUser(Long id, String userName, String userPassword) {
User user = new User(id, userName, userPassword);
redisService.set(id.toString(), user);
return "success";
}
@GetMapping(value = "getUserById")
public Object getUserById(Long id) {
return redisService.get(id.toString());
}
@GetMapping("/saveUser2")
public User saveUser2(Long id, String userName, String userPassword) {
User user = new User(id, userName, userPassword);
userRepository.save(user);
return user;
}
@GetMapping(value = "getUser")
public Object getUser(Long id) {
Object object = redisService.get(id.toString());
if (object == null) {
object = (userRepository.findById(id)).get();
if (object != null) {
redisService.set(id.toString(), object, 100L, TimeUnit.SECONDS);
}
}
return object;
}
}
Memcached
1.简介
Memcached是一个自由开源的、高性能的、分布式的内存对象缓存系统。是一种基于内存的key-value存储,用来存储小块的任意数据(字符串、对象)。这些数据可以是数据库调用、API调用或者页面渲染的结果。一般的使用目的是通过缓存数据库查询结果,减少数据库访问次数,以提高动态web应用的速度和可扩展性。
2.配置Memcached依赖
<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.2</version>
</dependency>
memcache.ip=localhost
memcache.port=11211
配置MemcachedConfig,并且封装一些Memcached常用的方法
package com.springboot.config;
//imports
@Component
public class MemcachedConfig implements CommandLineRunner {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${memcache.ip}")
private String memcacheIp;
@Value("${memcache.port}")
private Integer memcachePort;
private MemcachedClient client = null;
@Override
public void run(String... args) throws Exception {
try {
client = new MemcachedClient(new InetSocketAddress(memcacheIp,memcachePort));
} catch (IOException e) {
logger.error("Connection to server failed",e);
}
logger.info("Connection to server success");
}
public MemcachedClient getClient() {
return client;
}
public Boolean set(String key,int time,String value) {
Boolean b = false;
try{
b=(this.getClient().set(key, time, value)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public Boolean add(String key,int time,String value){
Boolean b = false;
try{
b=(this.getClient().add(key, time, value)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public Object replace(String key,int time,String value){
Boolean b = false;
try{
b=(this.getClient().replace(key, time, value)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public Object append(String key,int time,String value){
Boolean b = false;
try{
b=(this.getClient().append(key, value)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public Object prepend(String key,int time,String value){
Boolean b = false;
try{
b=(this.getClient().prepend(key, value)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public Object cas(String key,int time,String value){
return this.getClient().cas(key, time, value);
}
public Object get(String key){
return this.getClient().get(key);
}
public Boolean delete(String key){
Boolean b = false;
try{
b=(this.getClient().delete(key)).get();
}catch (Exception e){
logger.error(e.getMessage());
}
return b;
}
public long incr(String key,Integer value){
return this.getClient().incr(key,value);
}
public long decr(String key,Integer value){
return this.getClient().decr(key,value);
}
}
- set方法:当前方法用于将value存储在指定的key中,并可以设置对应的过期时间。如果当前key存在值,就更新value;如果不存在或已经过期,就存储对应的数据值。
- add方法:当前方法用于将value存储在指定的key中,如果add的可以已经存在,就不会更新数据(过期的key会更新),之前的值将仍然保持相同,并且将获得响应NOT_STORED。
- replace方法:的其方法用于替换已经存在的key的value。如果key不存在替换将会失败,并且你将获得响应NOT_STORED
- append方法:当前方法用于向已存在key的value后面追加数据。
- prepend方法:当前方法用于向已存在key的value前面追加数据。
- cas方法:当前方法用于执行一个“检查并设置”的操作,他仅在当前客户端最后一次取值后,该key对应的值没有被其他客户端修改的情况下才能够将值写入。检查是通过cas_token参数进行的,这个参数是Memcached指定给已经存在的元素的唯一的64位值。
- get方法:当前方法用于获取存储在key中的value,如果key不存在或者已经过期,就返回为空。
- gets方法:当前方法用于获取带有CAS令牌存储的value,如果key不存在,就返回空。
- delete方法:当前方法用于删除已存在的key。
- incr方法:当前方法用于对已存在的key的数字值进行自增操作。如果key不存在就返回NOT_FOUND;如果键的值不为数字,就返回CLIENT_ERROR;其他错误返回ERROR。
- decr方法:当前方法用于对已存在的key的数字值进行自减操作。如果key不存在就返回NOT_FOUND;如果键的值不为数字,就返回CLIENT_ERROR;其他错误返回ERROR。
package com.springboot.controller;
//imports
@RestController
public class UserController {
@Resource
private MemcachedConfig memcachedConfig;
@GetMapping(value = "saveUser")
public Boolean saveUser(Long id, String userName, String userPassword){
User user = new User(id, userName, userPassword);
return memcachedConfig.set(id.toString(), 1000,user.toString());
}
@GetMapping(value = "getUserById")
public Object getUserById(Long id) {
return memcachedConfig.get(id.toString());
}
@GetMapping(value = "deleteCacheById")
public Boolean deleteCacheById(Long id) {
return memcachedConfig.delete(id.toString());
}
}
3.使用Memcached缓存
使用Memcached作为数据库缓存的流程其实和使用Redis缓存一致。首先查询Memcached是否含有数据,如果数据不存在或已经过期,就先从数据库查询,再插入Memcached数据已提供下次使用。
@Autowired
private UserRepository userRepository;
@GetMapping("/saveUser2")
public User saveUser2(Long id, String userName, String userPassword) {
User user = new User(id, userName, userPassword);
userRepository.save(user);
return user;
}
@GetMapping(value = "getUserById2")
public Object getUserById2(Long id) {
Object object = memcachedConfig.get(id.toString());
if (object == null) {
object = (userRepository.findById(id)).get();
if (object != null) {
memcachedConfig.set(id.toString(), 1000, object.toString());
}
}
return object;
}
4.Redis和Memcached的区别
- 数据类型支持不同——Memcached只支持简单的key-value存储,而Redis除了支持key-value外,还支持list、set、zset、string、hashcode结构
- 数据一致性——Memcached内部提供了cas命令,可以保证在高并发下访问数据的一致性问题,而Redis没有提供类似cas命令,但是Redis提供了事务的功能,可以用其保证事务的原子性。
- value值大小——Redis的value值最大可为1GB,而Memcached只有1MB。
- 存储方式——Memcached将数据全部存储在内存中,当发生断电或内存分配不足时会造成数据丢失。Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载使用。
- 网络IO模型——Redis使用单线程的IO复用模型,Memcached是多线程,非阻塞IO复用的网络模型。
- 持久化支持——Redis提供了RDB和AOF的持久化支持,Memcached不支持持久化
- 应用场景——Memcached多应用于缓存数据集、临时数据、Session等、,而Redis除了可以用作缓存数据库外,还可以用作消息队列、数据堆栈等。