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、测试: 8.1、无参 8.2、单参 (1)缓存与删除缓存 (2)删除缓存加入异常 二、自定义删除key 1、pom 2、…...
Android 应用开发语言选择对比
Android开发语言有多种,但是每种语言的各有不同的适用场景,对比介绍如下: 一.首选:原生应用Java,Kotlin 1.截至目前,大约有70%的Android开发者仍然使用Java语言进行开发,而30%的开发者则选择…...

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

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

44、Flink 的 Interval Join 详解
Interval Join Interval join 组合元素的条件为:两个流(暂时称为 A 和 B)中 key 相同且 B 中元素的 timestamp 处于 A 中元素 timestamp 的一定范围内,即 b.timestamp ∈ [a.timestamp lowerBound; a.timestamp upperBound] 或…...

H6246 60V降压3.3V稳压芯片 60V降压5V稳压芯片IC 60V降压12V稳压芯片
H6246降压稳压芯片是一款电源管理芯片,为高压输入、低压输出的应用设计。以下是对该产品的详细分析: 一、产品优势 宽电压输入范围:H6246支持8V至48V的宽电压输入范围,使其能够适应多种不同的电源环境,增强了产品的通用…...
【MySQL精通之路】查询优化器的使用(8)
MySQL通过影响查询计划评估方式的系统变量、可切换优化、优化器和索引提示以及优化器成本模型提供优化器控制。 服务器在column_statistics数据字典表中维护有关列值的直方图统计信息(请参阅第10.9.6节“Optimizer统计信息”)。与其他数据字典表一样&am…...
Docker in Docker(DinD)原理与实践
随着云计算和容器化技术的快速发展,Docker作为开源的应用容器引擎,已经成为企业部署和管理应用程序的首选工具。然而,在某些场景下,我们可能需要在Docker容器内部再运行一个Docker环境,即Docker in Docker(…...

科技前沿:IDEA插件Translation v3.6 带来革命性更新,翻译和发音更智能!
博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通鸿蒙》 …...
【并发小知识】
计算机五大组成部分 控制器 运算器 存储器 输入设备 输出设备 计算机的核心真正干活的是CPU(控制器运算器中央处理器) 程序要想计算机运行,它的代码必须要先由硬盘读到内存,之后cpu取指再执行 操作系统发展史 穿孔卡片处理…...
python将多个音频文件与一张图片合成视频
代码中m4a可以换成mp3,图片和音频放同一目录,图片名image.jpg,多线程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的简称,意思是 用Java编写的服务器端的程序,Servlet被部署在服务器中,而服务器负责管理并调用Servle…...
【Python设计模式15】适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而无法一起工作的类能够一起工作。通过使用适配器模式,可以使得现有的类能够适应新的接口需…...
【Python设计模式05】装饰模式
装饰模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有对象添加新的功能,同时又不改变其结构。装饰模式通过创建一个装饰类来包裹原始类,从而在不修改原始类代码的情况下扩展对象的功能。 装饰模式的结构…...
kafka 消费模式基础架构
kafka 消费模式 &基础架构 目录概述需求: 设计思路实现思路分析1.kafka 消费模式基础架构基础架构2: 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy,skip hardness,…...

nginx安装部署问题
记一次nginx启动报错问题处理 问题1 内网部署nginx,开始执行make,执行不了,后面装了依赖的环境 yum install gcc-c 和 yum install -y pcre pcre-devel 问题2,启动nginx报错 解决nginx: [emerg] unknown directive “stream“ in…...
揭开Java序列化的神秘面纱(上)Serializable使用详解
Java序列化(Serialization)作为一项核心技术,在Java应用程序的多个领域都有着广泛的应用。无论是通过网络进行对象传输,还是实现对象的持久化存储,序列化都扮演着关键的角色。然而,这个看似简单的概念蕴含着丰富的原理和用法细节&…...

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

goimghdr,一个有趣的 Python 库!
更多Python学习内容:ipengtao.com 大家好,今天为大家分享一个有趣的 Python 库 - goimghdr。 Github地址:https://github.com/corona10/goimghdr 在图像处理和分析过程中,识别图像文件的类型是一个常见的需求。Python自带的imghdr…...
每小时电量的计算sql
计算思路,把每小时的电表最大记录取出来,然后用当前小时的最大值减去上个小时的最大值即可。 使用了MYSQL8窗口函数进行计算。 SELECT b.*,b.epimp - b.lastEmimp ecValue FROM ( SELECT a.deviceId,a.ctime,a.epimp, lag(epimp) over (ORDER BY a.dev…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...

企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...