1. 前言
一个系统在于数据库交互的过程中,内存的速度远远快于硬盘速度,当我们重复地获取相同数据时,我们一次又一次地请求数据库或远程服务,者无疑时性能上地浪费(这会导致大量时间被浪费在数据库查询或者远程方法调用上致使程序性能恶化),于是有了“缓存”。
本文将介绍在spring boot项目开发中怎样使用spring提供的Spring Cache 与最近很火的 Redis 数据库来实现数据的缓存。
2. SpringCache简介
Spring Cache是Spring框架提供的对缓存使用的抽象类,支持多种缓存,比如Redis、EHCache等,集成很方便。同时提供了多种注解来简化缓存的使用,可对方法进行缓存。
2.1 关于SpringCache 注解的简单介绍
@Cacheable:标记在一个方法上,也可以标记在一个类上。主要是缓存标注对象的返回结果,标注在方法上缓存该方法的返回值,标注在类上,缓存该类所有的方法返回值。
参数: value缓存名、 key缓存键值、 condition满足缓存条件、unless否决缓存条件
@CacheEvict:从缓存中移除相应数据。
@CachePut:支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@Caching:多个Cache注解使用,比如新增用户时,删除用户属性等需要删除或者更新多个缓存时,集合以上三个注解。
2.2 SpEL上下文数据
Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:
其他关于 Cache 详细配置或注解,请参考文章基于Redis的Spring cache 缓存介绍或spring官方文档
3. Redis简介
Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:
Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
Redis支持数据的备份,即master-slave模式的数据备份。
Redis 的安装和使用请自行Google 。
4. 实践–SpringCache和Redis集成
4.1 步骤
我们要把一个查询函数加入缓存功能,大致需要三步。
一、在函数执行前,我们需要先检查缓存中是否存在数据,如果存在则返回缓存数据。
二、如果不存在,就需要在数据库的数据查询出来。
三、最后把数据存放在缓存中,当下次调用此函数时,就可以直接使用缓存数据,减轻了数据库压力。
本实例没有存入MySQL数据库,主要是为了方便实践,实际使用中大家可以把service层中的方法改为数据库操作代码即可。
4.2 具体操作
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
注: 其实我们从官方文档可以看到spring-boot-starter-data-redis 已经包含了jedis客户端,我们在使用jedis连接池的时候不必再添加jedis依赖。
配置SpringCache,Redis连接等信息
SpringCache配置–RedisConfig.java
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @EnableCaching public class RedisConfig { /** * 申明缓存管理器,会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut) * 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值 * @return */ @Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { return RedisCacheManager.create(redisConnectionFactory); } @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { // 创建一个模板类 RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); // 将刚才的redis连接工厂设置到模板类中 template.setConnectionFactory(factory); // 设置key的序列化器 template.setKeySerializer(new StringRedisSerializer()); // 设置value的序列化器 //使用Jackson 2,将对象序列化为JSON Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //json转对象类,不设置默认的会将json转成hashmap ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setValueSerializer(jackson2JsonRedisSerializer); return template; } }
redis配置–application.yml
server:
port: 8080
spring:
# redis相关配置
redis:
database: 0
host: localhost
port: 6379
password: 123456
jedis:
pool:
# 连接池最大连接数(使用负值表示没有限制)
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 0
# 连接超时时间(毫秒)默认是2000ms
timeout: 2000ms
cache:
redis:
## Entry expiration in milliseconds. By default the entries never expire.
time-to-live: 1d
#写入redis时是否使用键前缀。
use-key-prefix: true
编写实体类
import lombok.Data; import java.io.Serializable; @Data //lombok依赖,可省略get set方法 public class User implements Serializable { private int userId; private String userName; private String userPassword; public User(int userId, String userName, String userPassword) { this.userId = userId; this.userName = userName; this.userPassword = userPassword; } }
service简单操作
import com.ml.demo.entity.User; import org.springframework.stereotype.Service; @Service public class UserDao { public User getUser(int userId) { System.out.println("执行此方法,说明没有缓存,如果没有走到这里,就说明缓存成功了"); User user = new User(userId, "没有缓存_"+userId, "password_"+userId); return user; } public User getUser2(int userId) { System.out.println("执行此方法,说明没有缓存,如果没有走到这里,就说明缓存成功了"); User user = new User(userId, "name_nocache"+userId, "nocache"); return user; } }
控制层
在方法上添加相应的方法即可操作缓存了,SpringCache 对象可以对redis自行操作,减少了很多工作啊,还是那个开箱即用的Spring
import com.ml.demo.dao.UserDao; import com.ml.demo.entity.User; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController public class testController { @Resource private UserDao userDao; /** * 查询出一条数据并且添加到缓存 * * @param userId * @return */ @RequestMapping("/getUser") @Cacheable("userCache") public User getPrud(@RequestParam(required = true) String userId) { System.out.println("如果没有缓存,就会调用下面方法,如果有缓存,则直接输出,不会输出此段话"); return userDao.getUser(Integer.parseInt(userId)); } /** * 删除一个缓存 * * @param userId * @return */ @RequestMapping(value = "/deleteUser") @CacheEvict("userCache") public String deleteUser(@RequestParam(required = true) String userId) { return "删除成功"; } /** * 添加一条保存的数据到缓存,缓存的key是当前user的id * * @param user * @return */ @RequestMapping("/saveUser") @CachePut(value = "userCache", key = "#result.userId +''") public User saveUser(User user) { return user; } /** * 返回结果userPassword中含有nocache字符串就不缓存 * * @param userId * @return */ @RequestMapping("/getUser2") @CachePut(value = "userCache", unless = "#result.userPassword.contains('nocache')") public User getUser2(@RequestParam(required = true) String userId) { System.out.println("如果走到这里说明,说明缓存没有生效!"); User user = new User(Integer.parseInt(userId), "name_nocache" + userId, "nocache"); return user; } @RequestMapping("/getUser3") @Cacheable(value = "userCache", key = "#root.targetClass.getName() + #root.methodName + #userId") public User getUser3(@RequestParam(required = true) String userId) { System.out.println("如果第二次没有走到这里说明缓存被添加了"); return userDao.getUser(Integer.parseInt(userId)); } }
接下来最重要的工作:跑起来
运行结果
存入数据:
从缓存读取数据:
删除缓存:
再读取:
此时没有缓存,调用方法,并存入缓存
此为cache中的条件:含有nocache字符时不存入缓存。自己去探索就好。
转自:https://blog.csdn.net/chachapaofan/article/details/88829265