当前位置: 首页 > news >正文

spring cache(三)demo

目录

一、入门demo

1、pom

2、配置文件

3、config

4、controller

5、service

6、dao

 7、dto与常量

8、测试:

8.1、无参

8.2、单参

(1)缓存与删除缓存

(2)删除缓存加入异常

二、自定义删除key

1、pom

2、配置文件

3、spring cache重写

(1)RedisCache 

(2)RedisCacheManager 

(3)CacheConfig 

4、dto

5、service

6、dao

(1)repository

(2)mapper

(3)xml 

7、自定义key组合组件

8、controller

9、启动类

10、测试

(1)加缓存

(2)saveOrUpdate清除缓存

(3)updateBatch清除缓存

(4)deleteById清除缓存

(5)deleteByIds清除缓存

(6)验证原evict方法


一、入门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 {@Autowiredprivate 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 {@Autowiredprivate ParamRepository paramRepository;@Overridepublic List<ParamDTO> listParams() {return paramRepository.listParams();}@Overridepublic void addParam(ParamDTO paramDTO) {paramRepository.addParam(paramDTO);}@Overridepublic List<ParamDTO> listParamsByKey(String key) {return paramRepository.listParamsByKey(key);}@Overridepublic 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 {@Autowiredprivate 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的先后顺序。

二、自定义删除key

对上面的demo进行改造:

①  如果Repository中有多种组合key的缓存,CUD接口在@CacheEvict注解中维护key比较麻烦,所以我们可以把key组合提到自定义Component中维护。

② 在前面也提到了,Repository的CUD接口应该接收(集合)对象,可以根据实体(集合)在自定义Component中获取所有key的组合;

而spring cache的@CacheEvict只支持String,为了支持集合操作,需要重写evict方法。

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>cache-key-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><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><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency></dependencies>
</project>
2、配置文件
server.port=6666
server.servlet.context-path=/cacheKeyDemo
#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&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=wtyy
#mybatis
mybatis.mapper-locations=classpath*:mapper/*Mapper.xml
#打印SQL
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
#本例重写了cacheManage,无需配置spring.cache,这里配置也无效
3、spring cache重写
(1)RedisCache 
package com.pluscache.demo.config;import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheWriter;import java.util.List;@Slf4j
public class MyRedisCache extends RedisCache {protected MyRedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfiguration) {super(name, cacheWriter, cacheConfiguration);}@Overridepublic void evict(Object key) {log.info("key为:{}",key.toString());if(key instanceof List<?>) {List<String> keys = (List<String>) key;keys.forEach( item -> super.evict(item));}else{super.evict(key);}}}
(2)RedisCacheManager 
package com.pluscache.demo.config;import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;import java.util.Collections;
import java.util.HashMap;
import java.util.Map;public class MyRedisCacheManager extends RedisCacheManager {private final RedisCacheWriter cacheWriter;private final RedisCacheConfiguration defaultCacheConfig;public MyRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {super(cacheWriter, defaultCacheConfiguration);this.cacheWriter = cacheWriter;this.defaultCacheConfig = defaultCacheConfiguration;}public MyRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) {super(cacheWriter, defaultCacheConfiguration, initialCacheNames);this.cacheWriter = cacheWriter;this.defaultCacheConfig = defaultCacheConfiguration;}public MyRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) {super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames);this.cacheWriter = cacheWriter;this.defaultCacheConfig = defaultCacheConfiguration;}/*** 覆盖父类创建RedisCache,采用自定义的RedisCacheResolver*/@Overrideprotected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {return new MyRedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);}@Overridepublic Map<String, RedisCacheConfiguration> getCacheConfigurations() {Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(getCacheNames().size());getCacheNames().forEach(it -> {RedisCache cache = MyRedisCache.class.cast(lookupCache(it));configurationMap.put(it, cache != null ? cache.getCacheConfiguration() : null);});return Collections.unmodifiableMap(configurationMap);}
}
(3)CacheConfig 
package com.pluscache.demo.config;import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
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.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;import java.time.Duration;@Configuration
@EnableCaching
@EnableConfigurationProperties(RedisProperties.class)//开启属性绑定配置的功能
public class CacheConfig {private RedisCacheConfiguration defaultCacheConfig() {return RedisCacheConfiguration.defaultCacheConfig()//缓存key的前缀//.computePrefixWith(versionCacheKeyPrefix)// key过期时间,60分钟.entryTtl(Duration.ofMinutes(60));// .disableCachingNullValues()// Serialization & Deserialization when using annotation//.serializeKeysWith(STRING_PAIR)// .serializeValuesWith(FAST_JSON_PAIR);//.serializeValuesWith(PROTO_STUFF_PAIR);}@Beanpublic RedisConnectionFactory redisConnectionFactory() {// 这里可以根据实际情况创建并返回RedisConnectionFactory的实现类,例如LettuceConnectionFactory或JedisConnectionFactoryreturn new LettuceConnectionFactory();}@Beanpublic CacheManager cacheManager() {RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory());return new MyRedisCacheManager(cacheWriter,defaultCacheConfig());}
}
4、dto
package com.pluscache.demo.dto;import com.baomidou.mybatisplus.annotation.TableName;
import jakarta.validation.constraints.Max;
import lombok.Data;import java.io.Serializable;@Data
@TableName("t_user")
public class UserDTO implements Serializable {private Integer id;private String userName;private String userAccount;private Integer age;
}
5、service
package com.pluscache.demo.service.impl;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.pluscache.demo.dto.UserDTO;
import com.pluscache.demo.repository.UserRepository;
import com.pluscache.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;import java.util.List;@Service("userService")
public class UserServiceImpl  implements UserService {@Autowiredpublic UserRepository userRepository;@Overridepublic List<UserDTO> listUsers() {return userRepository.listUsers();}@Overridepublic List<UserDTO> listUsersByAccount(String userAccount) {return userRepository.listUsersByAccount(userAccount);}@Overridepublic UserDTO getById(Integer id) {return userRepository.getById(id);}@Overridepublic UserDTO getByIdAndAccount(Integer id, String userAccount) {return userRepository.getByIdAndAccount(id,userAccount);}@Overridepublic void saveOrUpdate(UserDTO userDTO) {userRepository.saveOrUpdate(userDTO);}@Overridepublic void updateBatch(List<UserDTO> userDTOs) {userRepository.updateBatch(userDTOs);}@Overridepublic void deleteById(Integer id) {UserDTO userDTO = userRepository.getById(id);if (userDTO != null){userRepository.deleteById(userDTO,id);}}@Overridepublic void deleteByIds(List<Integer> ids) {List<UserDTO> userDTOs = userRepository.listByIds(ids);if(CollectionUtils.isEmpty(userDTOs)){return;}userRepository.deleteByIds(userDTOs,ids);}
}
6、dao
(1)repository
package com.pluscache.demo.repository;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pluscache.demo.dto.UserDTO;
import com.pluscache.demo.mapper.UserMapper;
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 = "t_user")
public class UserRepository {@Autowiredprivate UserMapper userMapper;@Cacheable(key = "@userKeyConstructor.listAll()",unless = "@userKeyConstructor.checkCacheSize(#result)")public List<UserDTO> listUsers() {LambdaQueryWrapper<UserDTO> queryWrapper = new LambdaQueryWrapper<>();return userMapper.selectList(queryWrapper);}@Cacheable(key = "@userKeyConstructor.listByAccount(#userAccount)",unless = "@userKeyConstructor.checkCacheSize(#result)")public List<UserDTO> listUsersByAccount(String userAccount) {LambdaQueryWrapper<UserDTO> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(UserDTO::getUserAccount,userAccount);return userMapper.selectList(queryWrapper);}@Cacheable(key = "@userKeyConstructor.getById(#id)",unless = "@userKeyConstructor.checkCacheSize(#result)")public UserDTO getById(Integer id) {LambdaQueryWrapper<UserDTO> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(UserDTO::getId,id);return userMapper.selectOne(queryWrapper);}@Cacheable(key = "@userKeyConstructor.getByIdAccount(#id,#userAccount)",unless = "@userKeyConstructor.checkCacheSize(#result)")public UserDTO getByIdAndAccount(Integer id, String userAccount) {LambdaQueryWrapper<UserDTO> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(UserDTO::getUserAccount,userAccount);queryWrapper.eq(UserDTO::getId,id);return userMapper.selectOne(queryWrapper);}public List<UserDTO> listByIds(List<Integer> ids) {LambdaQueryWrapper<UserDTO> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.in(UserDTO::getId,ids);return userMapper.selectList(queryWrapper);}@CacheEvict(key = "@userKeyConstructor.getAllKeys(#userDTO)")public void saveOrUpdate(UserDTO userDTO) {if(userDTO.getId() != null){userMapper.updateById(userDTO);return;}userMapper.insert(userDTO);}@CacheEvict(key = "@userKeyConstructor.getAllKeys(#userDTOs)")public void updateBatch(List<UserDTO> userDTOs) {userMapper.updateBatch(userDTOs);}@CacheEvict(key = "@userKeyConstructor.getAllKeys(#userDTO)")public void deleteById(UserDTO userDTO, Integer id) {userMapper.deleteById(id);}@CacheEvict(key = "@userKeyConstructor.getAllKeys(#userDTOs)")public void deleteByIds(List<UserDTO> userDTOs, List<Integer> ids) {userMapper.deleteBatchIds(ids);}
}
(2)mapper
package com.pluscache.demo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.pluscache.demo.dto.UserDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;import java.util.List;//@Mapper
public interface UserMapper extends BaseMapper<UserDTO> {void updateBatch(@Param("list")List<UserDTO> userDTOs);
}
(3)xml 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pluscache.demo.mapper.UserMapper"><update id="updateBatch"><foreach collection="list" item="user" separator=";" index="index">update t_user<set>age = #{user.userName},user_account = #{user.userAccount}</set><where>id = #{user.id}</where></foreach></update></mapper>
7、自定义key组合组件
package com.pluscache.demo.constructor;import com.pluscache.demo.dto.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;import java.util.ArrayList;
import java.util.List;@Component("userKeyConstructor")
@Slf4j
public class UserKeyConstructor {private static final String LIST_ALL = "listAll";private static final String GET_BY_ID = "getById,%s";private static final String LIST_BY_ACCOUNT = "listByAccount,%s";private static final String GET_BY_ID_ACCOUNT = "getByIdAccount,%s,%s";public static List<String> getAllKeys(UserDTO userDTO){return List.of(LIST_ALL,getById(userDTO.getId()),listByAccount(userDTO.getUserAccount()),getByIdAccount(userDTO.getId(),userDTO.getUserAccount()));}public static List<String> getAllKeys(List<UserDTO> userDTOs){List<String> keys = new ArrayList<>();userDTOs.forEach(userDTO -> keys.addAll(getAllKeys(userDTO)));return keys;}public static String getById(Integer id) {return String.format(GET_BY_ID, id);}public static String listByAccount(String  userAccount) {return String.format(LIST_BY_ACCOUNT, userAccount);}public static String getByIdAccount(Integer id,String userAccount) {return String.format(GET_BY_ID_ACCOUNT,id, userAccount);}public static String listAll() {return LIST_ALL;}public boolean checkCacheSize(List<?> result) {if (CollectionUtils.isEmpty(result)) {return false;}if (result.size() > 50) {log.debug("数据过多,不宜缓存");return true;}return false;}
}
8、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.UserDTO;
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("/user")
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/listUsers")public List<UserDTO> listUsers() {return userService.listUsers();}@RequestMapping("/listByAccount")public List<UserDTO> listUsersByAccount(String userAccount) {return userService.listUsersByAccount(userAccount);}@RequestMapping("/getById")public UserDTO getById(Integer id) {return userService.getById(id);}@RequestMapping("/getByIdAndAccount")public UserDTO getByIdAndAccount(Integer id,String userAccount) {return userService.getByIdAndAccount(id,userAccount);}@RequestMapping("/saveOrUpdate")public void saveOrUpdate(UserDTO userDTO) {userService.saveOrUpdate(userDTO);}@RequestMapping("/updateBatch")public void updateBatch(@RequestBody List<UserDTO> userDTOs) {userService.updateBatch(userDTOs);}@RequestMapping("/deleteById")public void deleteById(Integer id) {userService.deleteById(id);}@RequestMapping("/deleteByIds")public void deleteByIds(@RequestBody List<Integer> ids) {userService.deleteByIds(ids);}
}
9、启动类
package com.pluscache.demo;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.pluscache.demo.mapper")
@SpringBootApplication
public class CacheApplication {public static void main(String[] args) {SpringApplication.run(CacheApplication.class, args);}
}
10、测试

先初始化两条数据

(1)加缓存

  ①   localhost:6666/cacheKeyDemo/user/listUsers

   第一次访问,控制台打印:

查看redis

再次访问命中缓存,控制台无SQL输出。下面的查询同。

② localhost:6666/cacheKeyDemo/user/listByAccount?userAccount=zs

第一次访问,查看redis,缓存生成成功

③ localhost:6666/cacheKeyDemo/user/getById?id=1

第一次访问,查看redis,缓存生成成功

④ localhost:6666/cacheKeyDemo/user/getByIdAndAccount?userAccount=zs&id=1

第一次访问,查看redis,缓存生成成功

以上操作,再执行一次id=2、userAccount=lisi,这时缓存如下:

(2)saveOrUpdate清除缓存

  在(1)的基础上,访问

localhost:6666/cacheKeyDemo/user/saveOrUpdate?userAccount=zs&id=1

查询redis,可以看到id=1、userAccount=zs的组合缓存成功删除

(3)updateBatch清除缓存

在(1)的基础上,

访问localhost:6666/cacheKeyDemo/user/updateBatch

查看redis,zs和lisi的缓存都删除了

(4)deleteById清除缓存

在(1)的基础上:

访问localhost:6666/cacheKeyDemo/user/deleteById?id=1

可以看到id=1对应的user数据,相关缓存都删除了:

(5)deleteByIds清除缓存

第4步把id=1的数据删除了,现在手动从数据库再创建一条

重新一次(1)

现在访问localhost:6666/cacheKeyDemo/user/deleteByIds

查看redis:id=1、2对应数据的相关缓存都删除了:

(6)验证原evict方法

如果使用原evict

    @Overridepublic void evict(Object key) {
//        if(key instanceof List<?>) {
//            List<String> keys = (List<String>) key;
//            keys.forEach( item -> super.evict(item));
//        }else{log.info("key为:{}",key.toString());super.evict(key);
//        }}

  访问localhost:6666/cacheKeyDemo/user/deleteById?id=1

  从控制台打印可以看到将整个list做为key了

缓存自然没有被清除

相关文章:

spring cache(三)demo

目录 一、入门demo 1、pom 2、配置文件 3、config 4、controller 5、service 6、dao 7、dto与常量 8、测试&#xff1a; 8.1、无参 8.2、单参 &#xff08;1&#xff09;缓存与删除缓存 &#xff08;2&#xff09;删除缓存加入异常 二、自定义删除key 1、pom 2、…...

Android 应用开发语言选择对比

Android开发语言有多种&#xff0c;但是每种语言的各有不同的适用场景&#xff0c;对比介绍如下&#xff1a; 一.首选&#xff1a;原生应用Java&#xff0c;Kotlin 1.截至目前&#xff0c;大约有70%的Android开发者仍然使用Java语言进行开发&#xff0c;而30%的开发者则选择…...

Git 小白入门到进阶—(基本概念和常用命令)

一.了解 Git 基本概念和常用命令的作用 (理论) 基本概念 1、工作区 包含.git文件夹的目录&#xff0c;主要用存放开发的代码2、仓库 分为本地仓库和远程仓库&#xff0c;本地仓库是自己电脑上的git仓库(.git文件夹);远程仓库是在远程服务器上的git仓库git文件夹无需我们进行操…...

大数据框架总结(全)

☔️ 大数据框架总结&#xff08;全&#xff09; 关注“大数据领航员”&#xff0c;在公众号号中回复关键字【大数据面试资料】&#xff0c;即可可获取2024最新大数据面试资料的pdf文件 一. Hadoop HDFS读流程和写流程 HDFS写数据流程 &#xff08;1&#xff09;客户端通过…...

44、Flink 的 Interval Join 详解

Interval Join Interval join 组合元素的条件为&#xff1a;两个流&#xff08;暂时称为 A 和 B&#xff09;中 key 相同且 B 中元素的 timestamp 处于 A 中元素 timestamp 的一定范围内&#xff0c;即 b.timestamp ∈ [a.timestamp lowerBound; a.timestamp upperBound] 或…...

H6246 60V降压3.3V稳压芯片 60V降压5V稳压芯片IC 60V降压12V稳压芯片

H6246降压稳压芯片是一款电源管理芯片&#xff0c;为高压输入、低压输出的应用设计。以下是对该产品的详细分析&#xff1a; 一、产品优势 宽电压输入范围&#xff1a;H6246支持8V至48V的宽电压输入范围&#xff0c;使其能够适应多种不同的电源环境&#xff0c;增强了产品的通用…...

【MySQL精通之路】查询优化器的使用(8)

MySQL通过影响查询计划评估方式的系统变量、可切换优化、优化器和索引提示以及优化器成本模型提供优化器控制。 服务器在column_statistics数据字典表中维护有关列值的直方图统计信息&#xff08;请参阅第10.9.6节“Optimizer统计信息”&#xff09;。与其他数据字典表一样&am…...

Docker in Docker(DinD)原理与实践

随着云计算和容器化技术的快速发展&#xff0c;Docker作为开源的应用容器引擎&#xff0c;已经成为企业部署和管理应用程序的首选工具。然而&#xff0c;在某些场景下&#xff0c;我们可能需要在Docker容器内部再运行一个Docker环境&#xff0c;即Docker in Docker&#xff08;…...

科技前沿:IDEA插件Translation v3.6 带来革命性更新,翻译和发音更智能!

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …...

【并发小知识】

计算机五大组成部分 控制器 运算器 存储器 输入设备 输出设备 计算机的核心真正干活的是CPU&#xff08;控制器运算器中央处理器&#xff09; 程序要想计算机运行&#xff0c;它的代码必须要先由硬盘读到内存&#xff0c;之后cpu取指再执行 操作系统发展史 穿孔卡片处理…...

python将多个音频文件与一张图片合成视频

代码中m4a可以换成mp3,图片和音频放同一目录&#xff0c;图片名image.jpg&#xff0c;多线程max_workers可以根据CPU核心数量修改。 import os import subprocess import sys import concurrent.futures import ffmpeg def get_media_duration(media_path): probe ffmp…...

JavaEE:Servlet创建和使用及生命周期介绍

目录 ▐ Servlet概述 ▐ Servlet的创建和使用 ▐ Servlet中方法介绍 ▐ Servlet的生命周期 ▐ Servlet概述 • Servlet是Server Applet的简称&#xff0c;意思是 用Java编写的服务器端的程序&#xff0c;Servlet被部署在服务器中&#xff0c;而服务器负责管理并调用Servle…...

【Python设计模式15】适配器模式

适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而无法一起工作的类能够一起工作。通过使用适配器模式&#xff0c;可以使得现有的类能够适应新的接口需…...

【Python设计模式05】装饰模式

装饰模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许向一个现有对象添加新的功能&#xff0c;同时又不改变其结构。装饰模式通过创建一个装饰类来包裹原始类&#xff0c;从而在不修改原始类代码的情况下扩展对象的功能。 装饰模式的结构…...

kafka 消费模式基础架构

kafka 消费模式 &基础架构 目录概述需求&#xff1a; 设计思路实现思路分析1.kafka 消费模式基础架构基础架构2&#xff1a; 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,…...

nginx安装部署问题

记一次nginx启动报错问题处理 问题1 内网部署nginx&#xff0c;开始执行make&#xff0c;执行不了&#xff0c;后面装了依赖的环境 yum install gcc-c 和 yum install -y pcre pcre-devel 问题2&#xff0c;启动nginx报错 解决nginx: [emerg] unknown directive “stream“ in…...

揭开Java序列化的神秘面纱(上)Serializable使用详解

Java序列化(Serialization)作为一项核心技术&#xff0c;在Java应用程序的多个领域都有着广泛的应用。无论是通过网络进行对象传输&#xff0c;还是实现对象的持久化存储&#xff0c;序列化都扮演着关键的角色。然而&#xff0c;这个看似简单的概念蕴含着丰富的原理和用法细节&…...

深度学习——自己的训练集——图像分类(CNN)

图像分类 1.导入必要的库2.指定图像和标签文件夹路径3.获取文件夹内的所有图像文件名4.获取classes.txt文件中的所有标签5.初始化一个字典来存储图片名和对应的标签6.遍历每个图片名的.txt文件7.随机选择一张图片进行展示8.构建图像的完整路径9.加载图像10.检查图像是否为空 随…...

goimghdr,一个有趣的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个有趣的 Python 库 - goimghdr。 Github地址&#xff1a;https://github.com/corona10/goimghdr 在图像处理和分析过程中&#xff0c;识别图像文件的类型是一个常见的需求。Python自带的imghdr…...

每小时电量的计算sql

计算思路&#xff0c;把每小时的电表最大记录取出来&#xff0c;然后用当前小时的最大值减去上个小时的最大值即可。 使用了MYSQL8窗口函数进行计算。 SELECT b.*,b.epimp - b.lastEmimp ecValue FROM ( SELECT a.deviceId,a.ctime,a.epimp, lag(epimp) over (ORDER BY a.dev…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

【JavaEE】-- HTTP

1. HTTP是什么&#xff1f; HTTP&#xff08;全称为"超文本传输协议"&#xff09;是一种应用非常广泛的应用层协议&#xff0c;HTTP是基于TCP协议的一种应用层协议。 应用层协议&#xff1a;是计算机网络协议栈中最高层的协议&#xff0c;它定义了运行在不同主机上…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar&#xff1a;依赖注入与仓储模式实践 在 C# 的应用开发中&#xff0c;数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护&#xff0c;许多开发者会选择成熟的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;SqlSugar 就是其中备受…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制

目录 节点的功能承载层&#xff08;GATT/Adv&#xff09;局限性&#xff1a; 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能&#xff0c;如 Configuration …...

热烈祝贺埃文科技正式加入可信数据空间发展联盟

2025年4月29日&#xff0c;在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上&#xff0c;可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞&#xff0c;强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...

怎么开发一个网络协议模块(C语言框架)之(六) ——通用对象池总结(核心)

+---------------------------+ | operEntryTbl[] | ← 操作对象池 (对象数组) +---------------------------+ | 0 | 1 | 2 | ... | N-1 | +---------------------------+↓ 初始化时全部加入 +------------------------+ +-------------------------+ | …...