spring boot 缓存详解(一) JSR107和spring缓存抽象
程序员文章站
2022-06-19 14:14:01
...
前言
最近想用redis在项目中用缓存,就是在按照网上的教程配置了下,然而卵用没有,许多内容看不懂,还有许多误导性的东西,无奈,从源头开始看,梳理一些基础知识,需要注意,在用各种缓存之前,一定要将这些东西看完。
话不多说,为什么用缓存这些就不说了,先来了解下 JSR107规范,这是基础。
JSR107 缓存规范
Java Specification Requests 简称 JSR,是java提供的一个接口规范,类似于JDBC规范,没有具体的实现,具体的实现就是reids等这些缓存。
Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry和Expiry。
- CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
- CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
- Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
- Entry是一个存储在Cache中的key-value对。
- Expiry每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
可以用maven引入
<!-- JSR 107 缓存 规范 -->
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
</dependency>
了解的以上,我们移步到spring框架,spring之所以强大,就是因为其有一个很好的生态,而对缓存的支持就是其中的一个方面。
spring 缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发;
-
1 Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
-
2 Cache接口下Spring提供了各种xxxCache的实现,如RedisCache,EhCacheCache , ConcurrentMapCache等;每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果返回给用户。下次直接从缓存中获取。
缓存注解(下面会有具体Springboot代码演示)
需要注意,如果不配置cacheManager,Spring boot默认使用的是SimpleCacheConfiguration,即使用ConcurrentMapCacheManager来实现缓存。还可以通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),Spring Boot根据下面的顺序去侦测缓存提供者:
• Generic
• JCache (JSR-107)
• EhCache 2.x
• Hazelcast
• Infinispan
• Redis
• Guava
• Simple
除了按顺序侦测外,我们也可以通过配置属性spring.cache.type来强制指定。
代码部分
1. 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2. @EnableCaching开启缓存
@SpringBootApplication
@MapperScan("com.example.dao")
@EnableSwagger2
@EnableCaching // 启动类开启缓存支持
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
3. javaBean实体类
package com.example.model;
import java.io.Serializable;
public class UserModel implements Serializable {
private Integer id;
private String userName;
private String email;
private Integer gender; // 1 男 0 女
public UserModel() {
}
public UserModel(Integer id, String userName, String email, Integer gender) {
this.id = id;
this.userName = userName;
this.email = email;
this.gender = gender;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
@Override
public String toString(){
return "UserModel = [id="+this.id+",username="+this.userName+",email="+this.email+",gender="+this.gender+"]";
}
}
4 . dao层
package com.example.dao;
import com.example.model.UserModel;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
public interface UserMapper {
@Select("select * from user_info where id=#{id}")
UserModel getUserById(Integer id);
@Select("select * from user_info where userName=#{userName}")
UserModel getUserByName(String userName);
@Insert("insert into user_info values (id_seq.nextval,#{userName},#{email},#{gender})")
void insertUser(UserModel userModel);
@Update("update user_info set userName=#{userName},email=#{email},gender=#{gender} where id=#{id}")
void updateUserById(UserModel userModel);
@Delete("delete from user_info where id=#{id}")
void deleteUserById(Integer id);
@Select("select id_seq.currval from dual")
int getCurrentIdSeq();
}
5. DTO 数据中间层(前后端分离的,不知道的建议看更基础的)
package com.example.dto;
import io.swagger.annotations.ApiModelProperty;
public class UserDto {
@ApiModelProperty(value = "用户名",required = true)
private String userName;
@ApiModelProperty(value = "电子邮件",required = true)
private String email;
@ApiModelProperty(value = "性别",required = true)
private Integer gender;
@ApiModelProperty(value = "id",required = false)
private Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
}
6. service 层(主要的,注解在这个地方使用)
package com.example.service;
import com.example.dao.UserMapper;
import com.example.model.UserModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Service;
@Service
@CacheConfig(cacheNames = "userCache")
public class UserMapperService {
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Autowired
UserMapper userMapper;
@Cacheable(cacheNames = {"userCache"},key = "#id")
public UserModel getUserById(Integer id) {
return userMapper.getUserById(id);
}
@SuppressWarnings("SpringCacheableComponentsInspection")
@Cacheable(cacheNames = {"userCache1"},keyGenerator = "nameKeyGenerator")
public UserModel getUserById2(Integer id) {
return userMapper.getUserById(id);
}
@CachePut(key = "#userModel.id",condition = "#userModel.id != null")
public UserModel insertUser(UserModel userModel) {
userMapper.insertUser(userModel);
int id = userMapper.getCurrentIdSeq();
userModel.setId(id);
return userModel;
}
public void updateUserById(UserModel userModel) {
userMapper.updateUserById(userModel);
}
@CacheEvict(value = "userCache1",key = "#id")
public void deleteUserById(Integer id) {
userMapper.deleteUserById(id);
}
}
7. 自定义key生成,加上这个
package com.example.config;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
@Configuration
public class MyKeyGenerator {
@Bean("nameKeyGenerator")
public KeyGenerator nameKeyGenerator(){
return new KeyGenerator(){
@Override
public Object generate(Object o, Method method, Object... objects) {
System.out.println(o.toString()); //Object o, 调用的类的对象 UserMapperService
System.out.println("method.getName() is "+method.getName()); // 方法。包括整个方法的内容
System.out.println("method.getReturnType() is "+method.getReturnType());
System.out.println("objects is "+objects); // 方法参数
List<Object> list = Arrays.asList(objects);
Integer id = (Integer)list.get(0);
return id;
}
};
}
}
有个疑问:当没有缓存时,会调用两次,不知道怎么回事,可能和加载机制有关,没有仔细研究。
8. controle层
package com.example.controller;
import com.example.common.JsonUtil;
import com.example.common.ResponseInfo;
import com.example.common.ResponseUtil;
import com.example.dto.UserDto;
import com.example.model.UserModel;
import com.example.service.UserMapperService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Api(value = "UserController" ,description = "用户控制器,测试缓存")
@RequestMapping("/user")
public class UserController {
private static final Logger log = LoggerFactory.getLogger(StudentController.class);
@Autowired
private UserMapperService userMapperService;
@ApiOperation(value = "Cacheable根据Id查询用户信息,默认的key生产策略",notes = "id:1")
@RequestMapping(value = "/getUserById",method = {RequestMethod.POST})
public ResponseInfo<UserModel> getUserById(Integer id){
log.info("===getUserById request id = "+ id);
ResponseInfo<UserModel> responseInfo = new ResponseInfo<>();
try{
UserModel userModel = userMapperService.getUserById(id);
responseInfo.setRtnCode(ResponseUtil.SUCCESS_CODE);
responseInfo.setRtnMsg(ResponseUtil.SUCCESS_MSG);
responseInfo.setData(userModel);
}catch(Exception e){
e.printStackTrace();
responseInfo.setRtnCode(ResponseUtil.FAIL_CODE);
responseInfo.setRtnMsg(ResponseUtil.FAIL_MSG);
log.info("getUserById 接口异常,错误原因:"+e);
}
return responseInfo;
}
@ApiOperation(value = "Cacheable根据Id查询用户信息,自定义的key生产策略",notes = "userName:张三")
@RequestMapping(value = "/getUserById2",method = {RequestMethod.POST})
public ResponseInfo<UserModel> getUserById2(Integer id){
log.info("=====================>getUserById2 request id = "+ id);
ResponseInfo<UserModel> responseInfo = new ResponseInfo<>();
try{
UserModel userModel = userMapperService.getUserById2(id);
responseInfo.setRtnCode(ResponseUtil.SUCCESS_CODE);
responseInfo.setRtnMsg(ResponseUtil.SUCCESS_MSG);
responseInfo.setData(userModel);
}catch(Exception e){
e.printStackTrace();
responseInfo.setRtnCode(ResponseUtil.FAIL_CODE);
responseInfo.setRtnMsg(ResponseUtil.FAIL_MSG);
log.info("getUserByName 接口异常,错误原因:"+e);
}
return responseInfo;
}
@ApiOperation(value = "根据id删除用户信息",notes = "id:1")
@RequestMapping(value = "/deleteUserById",method = {RequestMethod.POST})
public ResponseInfo<UserModel> deleteUserById(Integer id){
log.info("=================>deleteUserById request id = "+ id);
ResponseInfo<UserModel> responseInfo = new ResponseInfo<>();
try{
userMapperService.deleteUserById(id);
responseInfo.setRtnCode(ResponseUtil.SUCCESS_CODE);
responseInfo.setRtnMsg(ResponseUtil.SUCCESS_MSG);
}catch(Exception e){
e.printStackTrace();
responseInfo.setRtnCode(ResponseUtil.FAIL_CODE);
responseInfo.setRtnMsg(ResponseUtil.FAIL_MSG);
log.info("deleteUserById 接口异常,错误原因:"+e);
}
return responseInfo;
}
@ApiOperation(value = "CachePut 新增用户信息用户信息",notes = "userName:张三")
@RequestMapping(value = "/inserrUser",method = {RequestMethod.POST})
public ResponseInfo<UserModel> inserrUser(@RequestBody UserDto userDto){
log.info("================>inserrUser request params is = "+ JsonUtil.toJsonString(userDto));
ResponseInfo<UserModel> responseInfo = new ResponseInfo<>();
try{
UserModel userModel = new UserModel();
userModel.setUserName(userDto.getUserName());
userModel.setEmail(userDto.getEmail());
userModel.setGender(userDto.getGender());
userMapperService.insertUser(userModel);
responseInfo.setRtnCode("000");
responseInfo.setRtnMsg("添加成功");
}catch(Exception e){
e.printStackTrace();
responseInfo.setRtnCode("200");
responseInfo.setRtnMsg("添加失败");
log.info("inserrUser 接口异常,错误原因:"+e);
}
return responseInfo;
}
@ApiOperation(value = "根据id修改用户信息",notes = "userName:张三")
@RequestMapping(value = "/updateUserById",method = {RequestMethod.POST})
public ResponseInfo<UserModel> updateUserById(@RequestBody UserDto userDto){
log.info("====================>updateUserById request params is = "+ JsonUtil.toJsonString(userDto));
ResponseInfo<UserModel> responseInfo = new ResponseInfo<>();
try{
UserModel userModel = new UserModel();
userModel.setUserName(userDto.getUserName());
userModel.setEmail(userDto.getEmail());
userModel.setGender(userDto.getGender());
userModel.setId(userDto.getId());
userMapperService.updateUserById(userModel);
responseInfo.setRtnCode(ResponseUtil.SUCCESS_CODE);
responseInfo.setRtnMsg(ResponseUtil.SUCCESS_MSG);
responseInfo.setData(userModel);
}catch(Exception e){
e.printStackTrace();
responseInfo.setRtnCode(ResponseUtil.FAIL_CODE);
responseInfo.setRtnMsg(ResponseUtil.FAIL_MSG);
log.info("getUserByName 接口异常,错误原因:"+e);
}
return responseInfo;
}
}