欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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代码演示)

spring boot 缓存详解(一) JSR107和spring缓存抽象
spring boot 缓存详解(一) JSR107和spring缓存抽象
spring boot 缓存详解(一) JSR107和spring缓存抽象

需要注意,如果不配置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;
    }
}