我们在做后端服务的时候,要非常注重性能,比如我们一个项目,大家回频繁地打开某一个页面的时候,回频繁地去后台请求数据库访问数据,重复地查询一样的数据,这样一想,在数据库里那部分数据没有被更新的情况下还重复地去查询,是不是觉得有点浪费资源了呢?是的,那么我们就要想办法把那些不必要每次都从数据库里拿的数据(更新频率低)做缓存,不要每次访问页面都去查询一次。
我们使用 Redis 来实现缓存
1.引入需要的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
关于Redis的配置这里就不重复啰嗦了
2.往Redis里做的缓存的单位并不是单纯的一个对象,或者一个字符串这么简单,而是缓存了某个接口的全部返回内容
例如我有一个获取商品列表的接口,那么我的 Redis 缓存的就是这个接口返回的数据,所以我们是对接口进行操作的
但是我们要先把返回的内容进行序列化,必须是可以序列化的对象才能被缓存到 Redis 里
/**
* http请求返回的最外层对象
* @author: 林之谦
* @date: 2018/7/27
* @description:
*/
@Data
//@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResultVO<T> implements Serializable {
private static final long serialVersionUID = 1451589387303987463L;
/** 错误码 */
private Integer code;
/** 提示信息 */
private String msg;
/** 返回的具体内容 */
private T data;
}
所以我这里举例的返回对象是 ResultVO 这个类,那么我们要让它实现 Serializable接口,并且给一个 UID
这里生成UID 推荐一个插件叫 GenerateSeriaVersionUID,设置了快捷键之后可以直接生成 UID
3.在接口上加 cache 的注解,他就会自动地把这个接口的返回结果缓存到 Redis 里,下次再访问这个接口的时候它就会先去 Redis 里查一下有没有要的数据,有的话就不再进入这个接口,而是直接从 Redis 里获取那些数据,提高了效率,也节约了资源
如果一个类里要在 Redis 里使用相同的文件名的话,可以给类加一个注解,如下
@CacheConfig(cacheNames = "product")
public class BuyerProductController {
...
}
那么给接口的注解里就不用加 cacheNames 这一项了,只需要 key,如下
@Cacheable(key = "123")
public ResultVO list(){
...
}
如果不给类加那个注解的话那么就要填写完整
@Cacheable(cacheNames = "product", key = "123")
public ResultVO list(){
...
}
此外我们还可以加一个过虑,就是当这个接口返回正确时才缓存,不正确时不缓存,看一下我们上面的 ResultVO 类,有一个属性叫错误码,返回正确时为 0,那么我们可以使用 unless 这个注解项,如下
@Cacheable(cacheNames = "product", key = "123",unless = "#result.getCode() != 0")
public ResultVO list(){
...
}
里面的 #result 表示返回的对象,那么这个注解的意思时 它会进行缓存除非状态码不为 0 ,所以只有状态码为 0 时才会缓存
所以这个类的完整代码如下
/**
* @author: 林之谦
* @date: 2018/7/27
* @description:
*/
@RestController
@RequestMapping("/buyer/product")
@CacheConfig(cacheNames = "product")
public class BuyerProductController {
@Autowired
private ProductInfoService productService;
@Autowired
private ProductCategoryService categoryService;
@GetMapping("/list")
@Cacheable( key = "123",unless = "#result.getCode() != 0")
public ResultVO list(){
System.out.println("【进入数据库查询商品信息】");
// 1.查询所有的上架商品
List<ProductInfo> productInfoList = productService.findUpAll();
// 2.查询类目(一次性查询)
//传统方法
// for (ProductInfo productInfo : productInfoList){
// categoryTypeList.add(productInfo.getCategoryType());
// }
//精简的做法 (java8,lambda)
List<Integer> categoryTypeList = productInfoList.stream()
.map(e->e.getCategoryType())
.collect(Collectors.toList());
List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypeList);
// 3.数据拼装
List<ProductVO> productVOList = new ArrayList<>();
for (ProductCategory productCategory : productCategoryList){
ProductVO productVO = new ProductVO();
productVO.setCategoryType(productCategory.getCategoryType());
productVO.setCategoryName(productCategory.getCategoryName());
List<ProductInfoVO> productInfoVOList = new ArrayList<>();
for(ProductInfo productInfo : productInfoList){
if(productInfo.getCategoryType().equals(productCategory.getCategoryType())){
ProductInfoVO productInfoVO = new ProductInfoVO();
BeanUtils.copyProperties(productInfo,productInfoVO);
productInfoVOList.add(productInfoVO);
}
}
productVO.setProductInfoVOList(productInfoVOList);
productVOList.add(productVO);
}
return ResultVOUtil.success(productVOList);
}
}
4.那么如果我们数据更新了怎么办?没关系,我们再更新数据的接口上做操作,让它更新数据的时候刷新一下缓存
我们有两个注解
@CacheEvict 这个注解会在方法执行后去清除注解里指定 key 的缓存
@CachePut 这个注解跟 Cacheable一样会把返回的内容做缓存,但是不一样的是,它不会在方法执行前去判断是否执行方法,而是永远都执行方法,然后更新掉
以上两种方法都是可以的,一般都把他们用在发生数据更新的地方
注意 key 要对应好,我们上面的注解给的 key 是 123,那么我们要清除缓存时这个 key 要一样
@CacheEvict(key = "123")
public ModelAndView save(@Valid ProductForm form,
BindingResult bindingResult,
Map<String,Object> map){
if(bindingResult.hasErrors()){
map.put("msg",bindingResult.getFieldError().getDefaultMessage());
map.put("url","/sell/seller/product/index");
return new ModelAndView("common/error",map);
}
ProductInfo productInfo = new ProductInfo();
try {
// 如果productId 为空是新增
if(!StringUtils.isEmpty(form.getProductId())){
productInfo = productInfoService.findOne(form.getProductId());
}else{
form.setProductId(KeyUtil.genUniqueKey());
}
BeanUtils.copyProperties(form,productInfo);
productInfoService.save(productInfo);
}catch (SellException e){
map.put("msg",e.getMessage());
map.put("url","/sell/seller/product/index");
return new ModelAndView("common/error",map);
}
map.put("url","/sell/seller/product/list");
return new ModelAndView("common/success",map);
}
再举个 CachePut的例子吧
@Override
@Cacheable(key = "111")
public ProductInfo findOne(String productId) {
System.out.println("【进入查询数据库商品信息】");
return repository.findOne(productId);
}
@Override
@CachePut(key = "111")
public ProductInfo save(ProductInfo productInfo) {
return repository.save(productInfo);
}
以上就是使用 Redis 缓存的基本操作,但是注意不要滥用缓存