SpringBoot整合SpringCache

SpringCache

为什么要学习SpringCache,他解决了什么问题?

SpringCache是Spring3.1版本发布的,他是对使用缓存进行封装和抽象,通过在方法上使用annotation注解就能拿到缓存结果,正式因为用了annotation,所以它解决了业务代码和缓存代码的耦合度问题,即再不入侵业务代码的基础上让现有代码即刻支持缓存,让开发者无感知redis的存在
(对于redis缓存,springcache只支持string.其他的Hash,List,Set,ZSet都不支持)

开始

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

<!--springboot的redis支持-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
#服务配置
server:
  #端口
  port: 8081
  servlet:
    #项目路径
    context-path: /platform
  #服务编码
  tomcat:
    uri-encoding: UTF-8
#spring相关配置
spring:
  #应用配置
  application:
    #应用名称
    name: springboot-day01-basic-project
  #数据源配置
  datasource:
    #选择druid数据源
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/spring-boot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
      username: root
      password: root
  redis:
    host: 127.0.0.1 #Redis服务器地址
    port: 6379#Redis服务器连接端口
    timeout: 2000 #请求redis服务的超时时间,这里注意设置成0时取默认时间2000
    jedis: #阻塞的
      pool:
        #连接池最大连接数(使用负值表示没有限制)
        #建议为业务期望QPS/一个连接的QPS,例如50000/1000=50
        #一次命令时间(borrow|return resource+Jedis执行命令+网络延迟)的平均耗时约为1ms,一个连接的QPS大约是1000
        max-active: 50
        #连接池中的最大空闲连接
        #建议和最大连接数一致,这样做的好处是连接数从不减少,从而避免了连接池伸缩产生的性能开销。
        max-idle: 50
        #连接池中的最小空闲连接
        #建议为0,在无请求的状况下从不创建链接
        min-idle: 0
        #连接池最大阻塞等待时间 毫秒(-1表示没有限制)
        #建议不要为-1,连接池占满后无法获取连接时将在该时间内阻塞等待,超时后将抛出异常。
        max-wait: 2000
#mubatis配置
mybatis-plus:
  # MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: com.springboot.pojo
  # 该配置请和 typeAliasesPackage 一起使用,如果配置了该属性,则仅仅会扫描路径下以该类作为父类的域对象 。
  type-aliases-super-type: com.springboot.basic.BasicPojo
  configuration:
    # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
    # 驼峰下划线转换
    map-underscore-to-camel-case: true
    use-generated-keys: true
    default-statement-timeout: 60
    default-fetch-size: 100
  global-config:
    db-config:
      #主键类型(雪花ID)
      id-type: assign_id
    #机器 ID 部分(影响雪花ID)
    worker-id: 1
    #数据标识 ID 部分(影响雪花ID)
    datacenter-id: 1
logging:
  config: classpath:logback.xml

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.itheima.springboot.serializer.KryoRedisSerializer;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;

import java.time.Duration;

/**
 * @ClassName RedisCacheConfig.java
 * @Description redis配置
 */
@Configuration
@EnableCaching
public class RedisCacheConfig {


    /**
     * 申明缓存管理器,会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut)
     * 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值

     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        // KryoRedisSerializer 替换默认序列化
//        KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(Object.class);

        // 配置序列化(解决乱码的问题)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(60))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer))
                .disableCachingNullValues();

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(config)
                .build();
    }
}

- @Cacheable:触发缓存写入。
- @CacheEvict:触发缓存清除。
- @CachePut:更新缓存(不会影响到方法的运行)。
- @Caching:重新组合要应用于方法的多个缓存操作
- @CacheConfig:设置类级别上共享的一些常见缓存设置

@Cacheable注解

​ 如果缓存中没有:查询数据库,存储缓存,返回结果,如果缓存中有:直接返回结果

​ 作用:可以用来进行缓存的写入,将结果存储在缓存中,以便于在后续调用的时候可以直接返回缓存中的值,而不必再执行实际的方法。 最简单的使用方式,注解名称=缓存名称,使用例子如下:
SpringBoot整合SpringCache

@CacheEvict注解

@CacheEvict:删除缓存的注解,这对删除旧的数据和无用的数据是非常有用的。这里还多了一个参数(allEntries),设置allEntries=true时,可以对整个条目进行批量删除
SpringBoot整合SpringCache

@CachePut注解

@CachePut:当需要更新缓存而不干扰方法的运行时 ,可以使用该注解。也就是说,始终执行该方法,并将结果放入缓存
SpringBoot整合SpringCache

@Caching注释

在使用缓存的时候,有可能会同时进行更新和删除,会出现同时使用多个注解的情况.而@Caching可以实现

//添加user缓存的同时,移除userPage的缓存
@Caching(put =@CachePut(value = "user",key ="#userVo.id"),
		evict = @CacheEvict(value = "userPage",allEntries = true))
@CacheConfig注解

缓存提供了许多的注解选项,但是有一些公用的操作,我们可以使用@CacheConfig在类上进行全局设置。 以下是个简单的例子

@CacheConfig("books") 
public class BookRepositoryImpl implements BookRepository {

    @Cacheable
    public Book findBook(ISBN isbn) {...}
} 


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.springboot.basic.ResponseWrap;
import com.itheima.springboot.enums.StatusEnum;
import com.itheima.springboot.exception.ProjectException;
import com.itheima.springboot.pojo.User;
import com.itheima.springboot.service.IUserService;
import com.itheima.springboot.utils.BeanConv;
import com.itheima.springboot.utils.EmptyUtil;
import com.itheima.springboot.utils.ExceptionsUtil;
import com.itheima.springboot.vo.UserVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
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.cache.annotation.Caching;
import org.springframework.web.bind.annotation.*;

import java.util.Date;

/**
 * @Description:前端控制器
 */
@RestController
@RequestMapping("/springboot/user")
@Api(tags = "用户操作")
@Log4j2
@CrossOrigin
public class UserController {

    @Autowired
    IUserService userService;

    /***
     * @description 注册用户
     * @param userVo 注册信息
     * @return: java.lang.Boolean
     */
    @PostMapping
    @ApiOperation(value = "用户注册",notes = "用户注册")
    @ApiImplicitParam(name = "userVo",value = "注册信息",required = true,dataType = "UserVo")
    @Caching(put =@CachePut(value = "user",key ="#result.data.id"),evict = @CacheEvict(value = "userPage",allEntries = true))
    public ResponseWrap<UserVo> saveUser(@RequestBody UserVo userVo) throws ProjectException {
        try {
            User user = BeanConv.toBean(userVo, User.class);
            userService.save(user);
            BeanConv.toBean(user, userVo);
            return ResponseWrap.<UserVo>builder()
                    .code(StatusEnum.SUCCEED.getCode())
                    .msg(StatusEnum.SUCCEED.getMsg())
                    .operationTime(new Date())
                    .data(userVo)
                    .build();
        }catch (Exception e){
            log.error("用户注册:{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(StatusEnum.REGISTER_USER_FAIL.getCode(),
                    StatusEnum.REGISTER_USER_FAIL.getMsg());
        }
    }

    /***
     * @description 删除用户
     * @param userId 用户Id
     * @return: java.lang.Boolean
     */
    @DeleteMapping("{userId}")
    @ApiOperation(value = "删除用户",notes = "删除用户")
    @ApiImplicitParam(name = "userId",value = "删除用户",required = true,dataType = "Long")
    @Caching(evict = {@CacheEvict(value = "user",key ="#userId"),@CacheEvict(value = "userPage",allEntries = true)})
    public ResponseWrap<Boolean> deleteUser(@PathVariable("userId") Long userId) throws ProjectException {
        try {
            Boolean flag = userService.removeById(userId);
            return ResponseWrap.<Boolean>builder()
                    .code(StatusEnum.SUCCEED.getCode())
                    .msg(StatusEnum.SUCCEED.getMsg())
                    .operationTime(new Date())
                    .data(flag)
                    .build();
        }catch (Exception e){
            log.error("删除用户:{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(StatusEnum.DELETE_USER_FAIL.getCode(),
                    StatusEnum.DELETE_USER_FAIL.getMsg());
        }
    }

    /***
     * @description 编辑用户
     * @param userVo 注册信息
     * @return: java.lang.Boolean
     */
    @PutMapping
    @ApiOperation(value = "编辑用户",notes = "编辑用户")
    @ApiImplicitParam(name = "userVo",value = "编辑用户",required = true,dataType = "UserVo")
    @Caching(put =@CachePut(value = "user",key ="#userVo.id"),evict = @CacheEvict(value = "userPage",allEntries = true))
    public ResponseWrap<Boolean> updateUser(@RequestBody UserVo userVo) throws ProjectException {
        try {
            Boolean flag = userService.saveOrUpdate(BeanConv.toBean(userVo, User.class));
            return ResponseWrap.<Boolean>builder()
                    .code(StatusEnum.SUCCEED.getCode())
                    .msg(StatusEnum.SUCCEED.getMsg())
                    .operationTime(new Date())
                    .data(flag)
                    .build();
        }catch (Exception e){
            log.error("查询用户:{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(StatusEnum.UPDATE_USER_FAIL.getCode(),
                    StatusEnum.UPDATE_USER_FAIL.getMsg());
        }
    }

    /***
     * @description 查询用户
     * @param userId 用户Id
     * @return: java.lang.Boolean
     */
    @GetMapping("/{userId}")
    @ApiOperation(value = "查询用户",notes = "查询用户")
    @ApiImplicitParam(name = "userId",value = "查询用户",required = true,dataType = "Long")
    @Cacheable(value = "user",key ="#userId")
    public ResponseWrap<UserVo> findUserById(@PathVariable("userId") Long userId) throws ProjectException {
        try {
            User user = userService.getById(userId);
            return ResponseWrap.<UserVo>builder()
                    .code(StatusEnum.SUCCEED.getCode())
                    .msg(StatusEnum.SUCCEED.getMsg())
                    .operationTime(new Date())
                    .data(BeanConv.toBean(user,UserVo.class))
                    .build();
        }catch (Exception e){
            log.error("查询用户:{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(StatusEnum.FIND_USER_FAIL.getCode(),
                    StatusEnum.FIND_USER_FAIL.getMsg());
        }
    }

    /***
     * @description 分页查询用户
     * @param current 当前页面
     * @param size 每页条数
     * @return: java.lang.Boolean
     */
    @PostMapping("{current}/{size}")
    @ApiOperation(value = "分页查询用户",notes = "查询用户")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "current",value = "当前页码",required = true,dataType = "Long"),
            @ApiImplicitParam(name = "size",value = "每页条数",required = true,dataType = "Long"),
            @ApiImplicitParam(name = "userVo",value = "用户条件",required = true,dataType = "UserVo")
    })
    @Cacheable(value = "userPage",key ="'current_'+#current+'_userVo_'+#userVo.hashCode()")
    public ResponseWrap<Page<UserVo>> findUserPage(@PathVariable("current") Long current,
                                                   @PathVariable("size") Long size,
                                                   @RequestBody UserVo userVo) throws ProjectException {
        try {
            //构建分页
            Page<User> page = new Page<>(current,size);
            //查询条件
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            if (!EmptyUtil.isNullOrEmpty(userVo.getLoginName())) {
                queryWrapper.likeRight(StringUtils.camelToUnderline(User.Fields.loginName),userVo.getLoginName());
            }
            if (!EmptyUtil.isNullOrEmpty(userVo.getMobil())) {
                queryWrapper.likeRight(StringUtils.camelToUnderline(User.Fields.mobil),userVo.getMobil());
            }
            if (!EmptyUtil.isNullOrEmpty(userVo.getEnableFlag())) {
                queryWrapper.eq(StringUtils.camelToUnderline(User.Fields.enableFlag),userVo.getEnableFlag());
            }
            //执行查询
            Page<User> userPageResult = userService.page(page, queryWrapper);
            Page<UserVo> userVoPageResult = new Page<>();
            BeanConv.toBean(userPageResult,userVoPageResult);
            userVoPageResult.setRecords(BeanConv.toBeanList(userPageResult.getRecords(),UserVo.class));
            return ResponseWrap.<Page<UserVo>>builder()
                    .code(StatusEnum.SUCCEED.getCode())
                    .msg(StatusEnum.SUCCEED.getMsg())
                    .operationTime(new Date())
                    .data(userVoPageResult)
                    .build();
        }catch (Exception e){
            log.error("用户注册:{}", ExceptionsUtil.getStackTraceAsString(e));
            throw new ProjectException(StatusEnum.FIND_USER_FAIL.getCode(),
                    StatusEnum.FIND_USER_FAIL.getMsg());
        }
    }

}

上一篇:为什么人家的开源项目文档如此炫酷?原来用的是这款神器!


下一篇:SpringCache与redis集成,优雅的缓存解决方案