一、入门demo
1、pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>plus-cache-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--尽量不要同时导入mybatis 和 mybatis_plus,避免版本差异-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--cache-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-data-redis</artifactId>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.springframework.boot</groupId>-->
<!--            <artifactId>spring-boot-starter-cache</artifactId>-->
<!--        </dependency>-->
        <!--spring-boot-starter-data-redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--spring cache-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
    </dependencies>
</project>2、配置文件
server.port=1111
server.servlet.context-path=/plusDemo
#mysql
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3308/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=wtyy
#mybatis
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml
#打印日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#redis
spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
spring.data.redis.password=
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-wait=-1
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0
spring.data.redis.lettuce.pool.enabled=true
spring.data.redis.lettuce.pool.time-between-eviction-runs=30s
#cache
#类型指定redis
spring.cache.type=redis
#一个小时,以毫秒为单位
spring.cache.redis.time-to-live=3600000
#给缓存的建都起一个前缀。  如果指定了前缀就用我们指定的,如果没有就默认使用缓存的名字作为前缀,一般不指定
#spring.cache.redis.key-prefix=CACHE_
#指定是否使用前缀
spring.cache.redis.use-key-prefix=true
#是否缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true
3、config
package com.pluscache.demo.config;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableCaching
@EnableConfigurationProperties(RedisProperties.class)//开启属性绑定配置的功能
public class MyCacheConfig {
}4、controller
package com.pluscache.demo.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.pluscache.demo.dto.ParamDTO;
import com.pluscache.demo.dto.UserDTO;
import com.pluscache.demo.service.ParamService;
import com.pluscache.demo.service.UserService;
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.RestController;
import java.util.List;
@RestController
@RequestMapping("/param")
public class ParamController {
    @Autowired
    private ParamService paramService;
    @RequestMapping("/listParams")
    public List<ParamDTO> listParams() {
        return paramService.listParams();
    }
    @RequestMapping("/addParam")
    public void addParam(ParamDTO paramDTO) {
         paramService.addParam(paramDTO);
    }
    @RequestMapping("/listParamsByKey")
    public List<ParamDTO> listParamsByKey(String key) {
        return paramService.listParamsByKey(key);
    }
    @RequestMapping("/deleteById")
    public void deleteById(String key) {
        paramService.deleteByKey(key);
    }
}
5、service
package com.pluscache.demo.service.impl;
import com.pluscache.demo.dto.ParamDTO;
import com.pluscache.demo.repository.ParamRepository;
import com.pluscache.demo.service.ParamService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service("paramService")
public class ParamServiceImpl implements ParamService {
    @Autowired
    private ParamRepository paramRepository;
    @Override
    public List<ParamDTO> listParams() {
        return paramRepository.listParams();
    }
    @Override
    public void addParam(ParamDTO paramDTO) {
         paramRepository.addParam(paramDTO);
    }
    @Override
    public List<ParamDTO> listParamsByKey(String key) {
        return paramRepository.listParamsByKey(key);
    }
    @Override
    public void deleteByKey(String key) {
        paramRepository.deleteByKey(key);
    }
}
6、dao
package com.pluscache.demo.repository;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.pluscache.demo.constant.DbConstant;
import com.pluscache.demo.dto.ParamDTO;
import com.pluscache.demo.dto.UserDTO;
import com.pluscache.demo.mapper.ParamMapper;
import com.pluscache.demo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@CacheConfig(cacheNames = {DbConstant.TABLE_PARAM_NAME_KEY_FORMAT})
@Slf4j
public class ParamRepository {
    @Autowired
    private ParamMapper paramMapper;
    //1、无参
    @Cacheable(key = "#root.methodName")
    public List<ParamDTO> listParams() {
        log.info("listParams无参start");
        LambdaQueryWrapper<ParamDTO> queryWrapper = new LambdaQueryWrapper<>();
        return paramMapper.selectList(queryWrapper);
    }
    @CacheEvict(key = "#root.methodName")
    public void addParam(ParamDTO paramDTO) {
        paramMapper.insert(paramDTO);
    }
    //2、单参
    @Cacheable(key = "{#p0}")
    public List<ParamDTO> listParamsByKey(String key) {
        log.info("listParamsByKey有参start");
        LambdaQueryWrapper<ParamDTO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ParamDTO::getParamKey,key);
        return paramMapper.selectList(queryWrapper);
    }
    @CacheEvict(key = "{#p0}",beforeInvocation = true)
    public void deleteByKey(String key) {
        LambdaQueryWrapper<ParamDTO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ParamDTO::getParamKey,key);
        paramMapper.delete(queryWrapper);
        //int a = 1/0;
    }
}
package com.pluscache.demo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.pluscache.demo.dto.ParamDTO;
import com.pluscache.demo.dto.UserDTO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ParamMapper extends BaseMapper<ParamDTO> {
    void batchDeleteByKey(@Param("keys") List<String> keys);
}
7、dto与常量
package com.pluscache.demo.dto;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("t_param")
public class ParamDTO implements Serializable {
    private Integer id;
    private String paramKey;
    private String paramValue;
}
package com.pluscache.demo.constant;
public class DbConstant {
    public static final String TABLE_PARAM_NAME_KEY_FORMAT = "cache:t_param:";
}
8、测试:
8.1、无参
① 第一次访问localhost:1111/plusDemo/param/listParams控制台输出:
2024-04-25T17:14:33.330+08:00  INFO 36136 --- [nio-1111-exec-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@644769222 wrapping com.mysql.cj.jdbc.ConnectionImpl@5ee5b9f5] will not be managed by Spring
==>  Preparing: SELECT id,param_key,param_value FROM t_param
==> Parameters: 
<==    Columns: id, param_key, param_value
<==        Row: 1, a, a_1
<==        Row: 2, a, a_2
<==      Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@14369ce6]
查看redis:

② 再次访问,返回数据和第一次一样,但是控制台无输出,断点可以看到在serveimImpl直接返回了,并没有进入ParamRepository。
8.2、单参
(1)缓存与删除缓存
访问localhost:1111/plusDemo/param/listParamsByKey?key=a
第一次走db查询,后面不走db查询。查看redis:
key为t_param:::a:

value为:

访问localhost:1111/plusDemo/param/deleteById?key=a 刷新redis可以看到缓存已经被删除了

这时再访问listParamsByKey发现又走db层了
(2)删除缓存加入异常
 @CacheEvict(key = "{#p0}",beforeInvocation = true)
    public void deleteByKey(String key) {
        LambdaQueryWrapper<ParamDTO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ParamDTO::getParamKey,key);
        paramMapper.delete(queryWrapper);
        int a = 1/0;
    }访问localhost:1111/plusDemo/param/listParamsByKey?key=a后再访问localhost:1111/plusDemo/param/deleteById?key=a可以看到缓存已经清除了,重新走db查询
如果没有写beforeInvocation = true,仍然走缓存查询,验证beforeInvocation决定了缓存和db的先后顺序。
二、自定义KeyConstructor
对上面的demo进行改造:
① 在前面也提到了,Repository的CUD接口应该接收(集合)对象;
② 如果Repository中有多种组合key的缓存,CUD接口在@CacheEvict注解中维护key比较麻烦,所以我们可以把key组合提到自定义KeyConstructor中。



















