【SpringBoot 2学习笔记】《十二》SpringBoot2缓存之旅SpringCache

前面介绍了数据库的使用,但是数据库并不能完全高性能地解决所有任务,这个时候缓存就出现了。缓存是进行数据交换的缓冲区,一般将访问量比较大的数据从数据库中查询出来放入缓存中,当下次需要数据的时候,直接从缓存中获取。通常缓存会放入内存或硬盘中,方便开发者使用。

12.1 使用Spring Cache

Spring Cache是Spring3.1版本开始引入的新技术。其核心思想是:当我们调动一个缓存方法时,会把该方法参数和返回结果作为一个键值对存放到缓存中,等下次利用同样参数来调用该方法时将不再执行,而是直接从缓存中取得结果并返回,从而实现缓存的功能。Spring Chache的使用方法也是基于注解或者基于XML配置的方式。我们下面的内容主要基于注解方式进行说明。

我们接下里重点关于如下三个注解:@Cacheable@CachePut@CacheEvict

12.1.1 @Cacheable &@CachePut &@CacheEvict

@Cacheable注解用于标记缓存,可以标记在方法或者类上,当对方法进行标记时,表示该方法支持缓存;当标记在类上时,表示该类的所有方法都支持缓存。对于支持Spring Cache的程序中,Spring在每次调用时,会对标记了@Cacheable的方法,先根据Key去查询当前的缓存中是否有返回结果,如果存在则直接将该结果返回,不在进行数据库检索。如果缓存内没有相应的数据,则继续执行方法,并把返回的结果存入缓存中并同时返回给客户端。

  • value:缓存的名称。在使用@Cacheable注解时,该属性是必须指定的,表明当前缓存的返回值用在哪个缓存上。

  • key:缓存的 key,可以为空。当我们没有指定key时,Spring会为我们使用默认策略生成一个key。通常我们使用缓存方法的参数作为key,一般为:“#参数名”。如果参数为对象,也可以使用对象的属性作为key。

  • condition:主要用于指定当前缓存的触发条件。很多情况下可能并不需要使用所有缓序的方法进行缓存,所以Spring Cache为我们提供了这种属性来排除些特定的情况。 以属性指定key为user.id为例,比如我们只需要id为偶数才进行缓存,进行配置condition属性配置如下:

    @Cacheable(value="user", key="#user.id", condition="user.id%2 == 0")
    

@CachePut注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。主要在添加方法使用较多

@CacheEvict注解标志的方法,会清空指定的缓存。主要用于更新或者删除方法

12.1.2 配置和代码

POM.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

项目启动类上加入**@EnableCaching**注解,表明启动缓存。

package com.gavinbj.confmng;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

/**
 *  @SpringBootApplication 标注这是SpringBoot项目的主程序类
 */
@MapperScan("com.gavinbj.confmng.persistence.mapper")
@SpringBootApplication
@EnableCaching
public class ConfManageApplication {

	public static void main(String[] args) {
		SpringApplication.run(ConfManageApplication.class, args);
	}

}

用户接口Controller:

package com.gavinbj.confmng.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.gavinbj.common.bean.BaseResult;
import com.gavinbj.common.bean.EnumCodeMsg;
import com.gavinbj.common.bean.ResponseUtils;
import com.gavinbj.common.exception.SysException;
import com.gavinbj.confmng.persistence.entity.UserInfo;
import com.gavinbj.confmng.service.UserInfoService;

@RestController
@RequestMapping("/api")
public class UserController {

	@Autowired
	UserInfoService userInfoService;

	/**
	 * 检索用户信息
	 * 
	 */
	@GetMapping("/users/{userId}")
	public UserInfo getUserById(@PathVariable("userId") String userId) {

		UserInfo userInfo = userInfoService.getUserByPK(userId);
		if(userInfo == null) {
			// 没有检索到对应的用户信息
			throw new SysException(EnumCodeMsg.SERARCH_NO_RESULT, "用户信息");
		}

		return userInfo;
	}
	
	/**
	 * 保存用户注册信息
	 * 
	 */
	@PostMapping("/users")
	public BaseResult<UserInfo> saveUserInfo(@RequestBody UserInfo user){
		
		this.userInfoService.saveUserInfo(user);
		
		return ResponseUtils.makeOKRsp(user);
	}
	
	/**
	 * 删除用户信息
	 */
	@DeleteMapping("/users/{userId}")
	public BaseResult<String> delUserInfo(@PathVariable("userId") String userId){
		
		this.userInfoService.delUserInfo(userId);
		return ResponseUtils.makeOKRsp("OK");
	}

}

接口对应的业务层(UserInfoServiceImpl.java)

package com.gavinbj.confmng.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.gavinbj.confmng.persistence.entity.UserInfo;
import com.gavinbj.confmng.persistence.entity.UserInfoExample;
import com.gavinbj.confmng.persistence.mapper.UserInfoMapper;
import com.gavinbj.confmng.service.UserInfoService;

/**
 * 用户信息
 * 
 * @author gavinbj
 *
 */
@Service
public class UserInfoServiceImpl implements UserInfoService {

	@Autowired 
	private UserInfoMapper userInfoMapper;
	
	/**
	 * 根据用户ID进行主键检索
	 */
	@Override
	@Cacheable(value ="user", key="#userId")
	public UserInfo getUserByPK(String userId) {
		return this.userInfoMapper.selectByPrimaryKey(userId);
	}
	
	/**
	 * 保存用户信息
	 */
	@Override
	@CachePut(value="user", key="#user.userId" )
	public UserInfo saveUserInfo(UserInfo user) {
		UserInfo userExist = this.userInfoMapper.selectByPrimaryKey(user.getUserId());
		if(userExist == null) {
			// 该用户ID不存在可以插入用户
			this.userInfoMapper.insertSelective(user);
		}
		return user;
	}
	
	
	/**
	 * 删除用户信息
	 * 
	 */
	@Override
	@CacheEvict(value="user", key="#userId")
	public void delUserInfo(String userId) {
		this.userInfoMapper.deleteByPrimaryKey(userId);
	}

}

DB操作利用MyBatis自动生成的代码。

12.1.3 验证缓存

1、验证:@CachePut注解将信息保存到缓存中

验证工具:Postman (UserInfoServiceImpl.saveUserInfo)

POST
http://localhost:9003/gavin/api/users
JSON Body:
{
	"userId" : "lijing",
	"userName" : "李静",
	"introduce" : "美女主播",
	"mobilephone" : "13948474647",
	"email": "[email protected]"
}

执行结果:

{
    "status": 0,
    "code": 1003,
    "msg": "处理成功!",
    "data": {
        "userId": "lijing",
        "userName": "李静",
        "introduce": "美女主播",
        "mobilephone": "13948474647",
        "email": "[email protected]",
        "birthday": null,
        "gender": null
    }
}

控制台输出:

2020-02-13 18:34:48.133 DEBUG 2692 --- [nio-9003-exec-4] c.g.c.p.m.U.selectByPrimaryKey           : ==>  Preparing: select user_id, user_name, introduce, mobilephone, email, birthday, gender from user_info where user_id = ? 
2020-02-13 18:34:48.134 DEBUG 2692 --- [nio-9003-exec-4] c.g.c.p.m.U.selectByPrimaryKey           : ==> Parameters: lijing(String)
2020-02-13 18:34:48.182 DEBUG 2692 --- [nio-9003-exec-4] c.g.c.p.m.U.selectByPrimaryKey           : <==      Total: 0
2020-02-13 18:34:48.207 DEBUG 2692 --- [nio-9003-exec-4] c.g.c.p.m.U.insertSelective              : ==>  Preparing: insert into user_info ( user_id, user_name, introduce, mobilephone, email ) values ( ?, ?, ?, ?, ? ) 
2020-02-13 18:34:48.244 DEBUG 2692 --- [nio-9003-exec-4] c.g.c.p.m.U.insertSelective              : ==> Parameters: lijing(String), 李静(String), 美女主播(String), 13948474647(String), [email protected](String)
2020-02-13 18:34:48.342 DEBUG 2692 --- [nio-9003-exec-4] c.g.c.p.m.U.insertSelective              : <==    Updates: 1

此时在数据库中已经保存了该数据,接下来我们用检索数据的方式,验证数据是直接从缓存取得相应数据。因为我们开启了SQL执行日志的打印,所以,如果检索时,有没有打印输出检索SQL使我们检验是否走缓存的依据。

GET
http://localhost:9003/gavin/api/users/lijing

执行结果:

{
    "status": 0,
    "code": 1003,
    "msg": "处理成功!",
    "data": {
        "userId": "lijing",
        "userName": "李静",
        "introduce": "美女主播",
        "mobilephone": "13948474647",
        "email": "[email protected]",
        "birthday": "",
        "gender": ""
    }
}

此时看控制台,没有输出新的检索SQL,可以验证标记了@CachePut的注解的方法,将相应的结果已经保存到了Spring的缓存中。

2、验证@CacheEvict注解清除缓存

基于第一步中,我们在向数据库保存用户信息时,同时将该数据保存到了Spring Cache中。现在,我们调用标记了@CacheEvict注解的删除方法,看看是否同时将缓存的用户信息删除掉。验证步骤如下:

  • 调用用户删除方法

  • 调用用户信息查询

如果缓存删除后,在调用用户信息查询,应该去数据库检索,控制台能够输出相应的检索语句。

DELETE
http://localhost:9003/gavin/api/users/lijing

执行结果

{
    "status": 0,
    "code": 1003,
    "msg": "处理成功!",
    "data": "OK"
}

控制台信息:

2020-02-13 18:44:56.581 DEBUG 2692 --- [nio-9003-exec-8] c.g.c.p.m.U.deleteByPrimaryKey           : ==>  Preparing: delete from user_info where user_id = ? 
2020-02-13 18:44:56.583 DEBUG 2692 --- [nio-9003-exec-8] c.g.c.p.m.U.deleteByPrimaryKey           : ==> Parameters: lijing(String)
2020-02-13 18:44:56.703 DEBUG 2692 --- [nio-9003-exec-8] c.g.c.p.m.U.deleteByPrimaryKey           : <==    Updates: 1

从上面可以看出,已经从数据库删除了数据并打印了删除语句。

接下来执行检索用的接口

GET
http://localhost:9003/gavin/api/users/lijing

执行结果

{
    "status": 1,
    "code": 5003,
    "msg": "用户信息检索结果为空",
    "data": ""
}

我们看控制台输出:

2020-02-13 18:47:13.719 DEBUG 2692 --- [nio-9003-exec-3] c.g.c.p.m.U.selectByPrimaryKey           : ==>  Preparing: select user_id, user_name, introduce, mobilephone, email, birthday, gender from user_info where user_id = ? 
2020-02-13 18:47:13.720 DEBUG 2692 --- [nio-9003-exec-3] c.g.c.p.m.U.selectByPrimaryKey           : ==> Parameters: lijing(String)
2020-02-13 18:47:13.768 DEBUG 2692 --- [nio-9003-exec-3] c.g.c.p.m.U.selectByPrimaryKey           : <==      Total: 0

可见执行检索时,没有从缓存中区的数据,接下里直接检索数据库,数据库检索结果也没有。所以返回用户信息为空。可见注解@CacheEvict也已经生效。

3、验证@Cacheable注解同时可以存储缓存和查询缓存

通过如上两步,现在缓存中没有缓存的用户信息,我们此时执行两次检索方法,检索一个数据库中存在的用户。第一次,因为缓存中没有该用户,所以会输出检索用SQL。第二次在执行该用户的检索,此时应该直接使用缓存中的检索结果,不去数据库重新检索,控制台中不输出SQL。

GET
http://localhost:9003/gavin/api/users/zhangsanfeng

连续执行两次结果:

{
    "status": 0,
    "code": 1003,
    "msg": "处理成功!",
    "data": {
        "userId": "zhangsanfeng",
        "userName": "张三丰",
        "introduce": "一代宗师",
        "mobilephone": "13948474647",
        "email": "[email protected]",
        "birthday": "",
        "gender": ""
    }
}

控制台输出:

2020-02-13 18:54:35.465 DEBUG 2692 --- [nio-9003-exec-6] c.g.c.p.m.U.selectByPrimaryKey           : ==>  Preparing: select user_id, user_name, introduce, mobilephone, email, birthday, gender from user_info where user_id = ? 
2020-02-13 18:54:35.465 DEBUG 2692 --- [nio-9003-exec-6] c.g.c.p.m.U.selectByPrimaryKey           : ==> Parameters: zhangsanfeng(String)
2020-02-13 18:54:35.515 DEBUG 2692 --- [nio-9003-exec-6] c.g.c.p.m.U.selectByPrimaryKey           : <==      Total: 1

从上可以看出第一次检索zhangsanfeng时,直接检索了数据库。第二次检索使用了缓存。如上功能好用。

12.1.4 注意事项

其中使用Value没有问题,使用key时,注意使用参数里面对应的属性的值作为Key。单独传递一个变量和一个对象时,上面的Key是不一样的。我开始都按照统一的使用:“#对象.userId”,导致存储和检索总是对不上。

发布了37 篇原创文章 · 获赞 24 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/gavinbj/article/details/104310547