介于数据永久存储介质与数据应用之间
的数据临时存储
介质注意最后这条,缓存的不一定就是从持久层数据库来的数据,也可以是程序运行的临时数据,理解别太狭义,如手机验证码,对应于下图的Cache1:
传统的一个查询接口,每次都查需要去和数据库交互,数据库压力大且容易产生性能瓶颈:
在Service层引入一个Map类型的非局部
变量,来模拟缓存:(这种使用一个Map来充当临时缓冲池的思想需要学习)
private HashMap<Integer,Book> cache = new HashMap<>();
@Override
public Book getBookById(Integer id){
Book book = cache.get(id);
if(book == null){
book = bookDao.selectById(id);
cache.put(id,book);
}
return book;
}
此时调用之前的接口,除了第一次需要查数据库,后面直到服务重启,变量被回收,都不用再去查数据库。同理,写个手机验证码的demo代码:
看下效果:
首先导入缓存技术对应的starter:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
配置配或者直接启动类上加@EnableCaching启动缓存
:
@SpringBootApplication
@EnableCaching //!!!!
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
此时,上面用HashMap模拟缓存的Service码就可改为:
@Cacheable(value="cacheSpace",key="#id")
public Book getById(Integer id) {
return bookDao.selectById(id);
}
即先从cacheSpace这块缓存空间查看,有则返回,没有再查持久层。@Cacheable注解,即以属性里的key值为键,以方法的返回值为value,既存又取,有则取,无则查后存。 以上是背后使用的缓存技术是SpringBoot默认的Simple。SpringBoot提供的缓存技术除了提供默认的缓存方案外,还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理:
引入@Cacheable注解后,重写并完善下这个验证码的案例。先写个工具类来生成验证码,这个工具类写的不优雅,重点备份下补0串的这种思想吧:
public class CodeUtil {
private static final String[] patch = {"000000","00000","0000","000","00","0",""};
public static String generatorCode(String tel){
int hash = tel.hashCode();
int encryption = 20230927; //加密常量码
long result = hash ^ encryption; //第一次加密,此时,我加密码写死,同一个电话号码的验证码会一直不变
long nowTime = System.currentTimeMillis();
result = result ^ nowTime; //引入时间,二次加密
long code = Math.abs(result % 1000000); //取后六位
String codeStr = code + ""; //也可能不够6位,比如000147,则上面的long code就是147
return patch[codeStr.length()] + codeStr; //加一个补0数组,根据字符串长度来取对应的补0串,最多补5个0,最少不补,这里为了适配数组下标从0开始,给array[0]给个值
}
}
注意根据字符串长度来取数组中对应的补0串时,长度为6,则取array[6],但数组下标从0开始,会越界:
此时有两种思路处理,一种是上面的,给数组加个下标为0的值,此时str.length()就和数组下标对应上了,也可以直接让str.length()-1:
return patch[codeStr.length() -1 ] + codeStr;
Service层,生成验证码,并存缓存,不能用@Cacheable,它既存又取,这样验证码在缓存失效前都一样,可改为@CachePut只存不取
@Service
@Slf4j
public class MsgServiceImpl implements MsgService {
@Resource
@Lazy //解决下循环以来的问题
MsgService msgService;
/**
* 返回验证码
* @param tel
* @return
*/
@Override
//@Cacheable(value = "telCode",key = "#tel")
@CachePut(value = "telCode",key = "#tel")
public String getCheckCode(String tel) {
String checkCode = CodeUtil.generatorCode(tel);
return checkCode;
}
/**
* 校验验证码
* @param tel
* @param checkCode
* @return
*/
@Override
public boolean verify(String tel, String checkCode) {
return checkCode.equals(msgService.getCode(tel));
}
@Override
@Cacheable(value = "telCode",key = "#tel")
public String getCode(String tel){
return null;
}
}
关于校验方法里的取验证码,之前写需求用redis,直接redisTemplate.get了,但这里底层缓存技术是Simple,可单独写个方法,上面加@Caceable注解,然后校验方法里调用这个获取缓存值的方法即可实现(此时会有@Cacheable注解失效问题,注意出去绕一圈拿代理对象来调用)。还是利用了@Cacheable注解的特点,先查有无这个key,有就把value当作这个方法的返回值,没有再执行代码方法体。
@Cacheable(value = "telCode",key = "#tel")
public String getCode(String tel){
return null;
}
Controller层随便写就行:
@RestController
@RequestMapping("/msg")
public class MsgController {
@Resource
MsgService msgService;
@GetMapping("{tel}")
public String getCode(@PathVariable String tel){
return msgService.getCheckCode(tel);
}
@PostMapping
public Boolean verifyCode(@RequestParam String tel,String code){
return msgService.verify(tel,code);
}
}
重点关注: