文章转载声明:转载请附带原文链接
以下是我本机的环境:
如果您在实操过程中,使用的是IDEA2020 和springboot2.3.x 版本及以上,可能会出现以下和我一样的问题:
SpringBoot启动报错: -Property 'configuration' and 'configLocation' can not specified with together
这个问题我头一次碰到,因为我之前一直使用的是 yaml 配置文件中配置 mybatis,如图:
而实操时候我使用SSM的方式,引入mybatis-comfig.xml 全局配置:
所以运行springboot项目时候就报错了!
解决方式:
检查一下application.yaml文件,如果确实是configuration和config-location同时出现在了配置文件中,将configuration配置的内容放入到config-location指向的配置文件中,再次重启项目,文件解决!
建议:
这其实是IDEA自动识别的问题,并不是错误,解决方法:
参考文章 @EnableAutoConfiguration注解的作用
同理,如果出现下图问题:
解决方式:
包括其他类似问题(idea 识别报红,可以使用组合功能键ALT+ENTER ),选中如图所示:
取消选中对应复选框即可:
OK,下面我们进入正题!
问题:什么是mybatis的一/二级缓存?
详情请参考文章:浅谈 MyBatis 三级缓存
CREATE TABLE `score_flow` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`score` bigint(19) unsigned NOT NULL COMMENT '用户积分流水',
`user_id` int(11) unsigned NOT NULL COMMENT '用户主键id',
`user_name` varchar(30) NOT NULL DEFAULT '' COMMENT '用户姓名',
PRIMARY KEY (`id`),
KEY `idx_userid` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4;
CREATE TABLE `sys_user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_name` varchar(11) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '用户名',
`image` varchar(11) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '用户头像',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
CREATE TABLE `user_score` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` int(11) unsigned NOT NULL COMMENT '用户ID',
`user_score` bigint(19) unsigned NOT NULL COMMENT '用户积分',
`name` varchar(30) NOT NULL DEFAULT '' COMMENT '用户姓名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
# 端口
server:
port: 8080
# 项目访问名称
servlet:
context-path: /demo
#=====================================数据库相关配置=====================================
spring:
#=====================================Redis=========================================
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 8.XXXXXX.136
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password: cspXXXXXX29
jedis:
pool:
# 连接池最大连接数(使用负值表示没有限制)
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 0
# 连接超时时间(毫秒)
timeout: 8000
#=====================================Mysql=========================================
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource # 德鲁伊
minIdle: 5
maxActive: 100
initialSize: 10
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 50
removeAbandoned: true
filters: stat # ,wall,log4j # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据
druidLoginName: wjf # 登录druid的账号
druidPassword: wjf # 登录druid的密码
cachePrepStmts: true # 开启二级缓存
# 开启控制台打印sql日志
mybatis:
# 配置mapper文件扫描
mapper-locations: com.haust.redisdemo.mapper/*.xml
# 配置实体类扫描
type-aliases-package: com.haust.redisdemo.domain
# 指定全局mybatis 配置文件位置
config-location: classpath:/mybatis-config.xml
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="NULL"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
settings>
configuration>
@SpringBootApplication
@EnableAutoConfiguration
@MapperScan("com.haust.redisdemo.mapper")
public class XdRedisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(XdRedisDemoApplication.class, args);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
/**
* 用户实体类
*/
public class User implements Serializable {
// 必须实现序列化接口!
/**
* 序列号版本号
*/
private static final long serialVersionUID = -4415438719697624729L;
/**
* 用户id
*/
private String id;
/**
* 用户名
*/
private String userName;
}
/**
* @Auther: csp1999
* @Date: 2020/11/17/10:36
* @Description: UserMapper
*/
@Repository
public interface UserMapper {
void insert(User user);
void update(User user);
void delete(@Param("id") String id);
User find(@Param("id") String id);
List<User> query(@Param("userName") String userName);
void deleteAll();
}
<mapper namespace="com.haust.redisdemo.mapper.UserMapper">
<select id="query" resultType="com.haust.redisdemo.domain.User">
select id ,user_name
from sys_user
where 1=1
<if test="userName != null">
and user_name like CONCAT('%',#{userName},'%')
if>
select>
<insert id="insert" parameterType="com.haust.redisdemo.domain.User">
insert sys_user(id,user_name) values(#{id},#{userName})
insert>
<update id="update" parameterType="com.haust.redisdemo.domain.User">
update sys_user set user_name = #{userName} where id=#{id}
update>
<delete id="delete" parameterType="string">
delete from sys_user where id= #{id}
delete>
<select id="find" resultType="com.haust.redisdemo.domain.User" parameterType="string">
select id,user_name from sys_user where id=#{id}
select>
<delete id="deleteAll">
delete from sys_user
delete>
mapper>
这个工具类就是为了操作redis时候相对比较方便而已,其实就是封装了一下RedisTemplete,可以选择不用封装的工具类,直接使用RedisTemplete
/**
* @Auther: csp1999
* @Date: 2020/11/17/10:08
* @Description: redis操作工具类
*/
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
private static double size = Math.pow(2, 32);
/**
* 写入缓存
*
* @param key
* @param offset 位 8Bit=1Byte
* @return
*/
public boolean setBit(String key, long offset, boolean isShow) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.setBit(key, offset, isShow);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入缓存
*
* @param key
* @param offset
* @return
*/
public boolean getBit(String key, long offset) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.getBit(key, offset);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入缓存
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 写入缓存设置时效时间
*
* @param key
* @param value
* @return
*/
public boolean set(final String key, Object value, Long expireTime) {
boolean result = false;
try {
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 批量删除对应的value
*
* @param keys
*/
public void remove(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 删除对应的value
*
* @param key
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判断缓存中是否有对应的value
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 读取缓存
*
* @param key
* @return
*/
public Object get(final String key) {
Object result = null;
ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
}
/**
* 哈希 添加
*
* @param key
* @param hashKey
* @param value
*/
public void hmSet(String key, Object hashKey, Object value) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
hash.put(key, hashKey, value);
}
/**
* 哈希获取数据
*
* @param key
* @param hashKey
* @return
*/
public Object hmGet(String key, Object hashKey) {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
return hash.get(key, hashKey);
}
/**
* 列表添加
*
* @param k
* @param v
*/
public void lPush(String k, Object v) {
ListOperations<String, Object> list = redisTemplate.opsForList();
list.rightPush(k, v);
}
/**
* 列表获取
*
* @param k
* @param l
* @param l1
* @return
*/
public List<Object> lRange(String k, long l, long l1) {
ListOperations<String, Object> list = redisTemplate.opsForList();
return list.range(k, l, l1);
}
/**
* 集合添加
*
* @param key
* @param value
*/
public void add(String key, Object value) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
set.add(key, value);
}
/**
* 集合获取
*
* @param key
* @return
*/
public Set<Object> setMembers(String key) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
return set.members(key);
}
/**
* 有序集合添加
*
* @param key
* @param value
* @param scoure
*/
public void zAdd(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.add(key, value, scoure);
}
/**
* 有序集合获取
*
* @param key
* @param scoure
* @param scoure1
* @return
*/
public Set<Object> rangeByScore(String key, double scoure, double scoure1) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
redisTemplate.opsForValue();
return zset.rangeByScore(key, scoure, scoure1);
}
//第一次加载的时候将数据加载到redis中
public void saveDataToRedis(String name) {
double index = Math.abs(name.hashCode() % size);
long indexLong = new Double(index).longValue();
boolean availableUsers = setBit("availableUsers", indexLong, true);
}
//第一次加载的时候将数据加载到redis中
public boolean getDataToRedis(String name) {
double index = Math.abs(name.hashCode() % size);
long indexLong = new Double(index).longValue();
return getBit("availableUsers", indexLong);
}
/**
* 有序集合获取排名
*
* @param key 集合名称
* @param value 值
*/
public Long zRank(String key, Object value) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.rank(key, value);
}
/**
* 有序集合获取排名
*
* @param key
*/
public Set<ZSetOperations.TypedTuple<Object>> zRankWithScore(String key, long start, long end) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
Set<ZSetOperations.TypedTuple<Object>> ret = zset.rangeWithScores(key, start, end);
return ret;
}
/**
* 有序集合添加
*
* @param key
* @param value
*/
public Double zSetScore(String key, Object value) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.score(key, value);
}
/**
* 有序集合添加分数
*
* @param key
* @param value
* @param scoure
*/
public void incrementScore(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.incrementScore(key, value, scoure);
}
/**
* 有序集合获取排名
*
* @param key
*/
public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithScore(String key, long start, long end) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeByScoreWithScores(key, start, end);
return ret;
}
/**
* 有序集合获取排名
*
* @param key
*/
public Set<ZSetOperations.TypedTuple<Object>> reverseZRankWithRank(String key, long start, long end) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
Set<ZSetOperations.TypedTuple<Object>> ret = zset.reverseRangeWithScores(key, start, end);
return ret;
}
}
在RedisConfig中将RedisTemplate 注入IOC容器:
/**
* @Auther: csp1999
* @Date: 2020/11/14/18:44
* @Description: Redis 相关配置类
*/
@Configuration
//@EnableCaching // 开启缓存
public class RedisConfig {
/**
* 将 redisTemplate 注入IOC
*
* @param factory
* @return
*/
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
// RedisTemplate 放入 RedisConnectionFactory 工厂
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
/**
* @Auther: csp1999
* @Date: 2020/11/17/11:38
* @Description:
*/
@RestController
public class UserController {
/**
* 缓存盐值:key
*/
private static final String key = "userCache_";
@Resource
private UserMapper userMapper;
@Resource
private RedisUtil redisUtil;
/**
* 根据id获取用户信息方式一:
* 先从redis缓存查,如果有则取出,如果没有再从数据库查(查到后保存到缓存)
* 注意:set值和get值的时候序列化方式必须保持一致
*
* @param id
* @return
*/
@RequestMapping("/getUserCache")
public User getUseCache(String id) {
// step1: 先从redis里面取值
User user = (User) redisUtil.get(key + id);
// step2: 如果拿不到则从DB取值
if (user == null) {
User userDB = userMapper.find(id);
System.out.println("fresh value from DB id:" + id);
// step3: DB非空情况刷新redis值
if (userDB != null) {
redisUtil.set(key + id, userDB);
return userDB;
}
}
return user;
}
}
假设数据库中已经存在用户信息记录:
我们来访问下该接口(第一次访问):
查看控制台打印:
如图,可以看出,第一次根据id查询user信息时候,redis缓存中不存在该user信息,所以直接去数据库中查询!
接下来我们清空控制台打印信息,并刷新一次 http://localhost:8080/demo/getExpire?id=1 链接,模拟第二次访问:
效果如图:
从图中得出,未打印sql日志,因此本次访问并未从数据库中获取user信息,而是直接从redis缓存中获取的user信息!
加缓存的好处:学过redis和mysql的应该都知道,MySQL读取的是磁盘中的数据,而redis读取的是内存中的数据(速度快),在大数据量高访问量的情况下,项目后端热点接口不用每次调用都去数据库中查询相关记录,如果缓存中存在相关数据则先从缓存中取,这样会提高了效率!
在springboot中提供了简化redis缓存操作的注解:
1、springboot cache的使用:可以结合redis、ehcache等缓存
2、springboot cache的整合步骤:
1)引入pom.xml依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
2)RedisConfig开启缓存注解: @EnableCaching
@Configuration
@EnableCaching // 开启缓存
public class RedisConfig {
3)在方法上面加入SpEL spring的el表达式
UserService.java
/**
* User表的增删改查
*/
@Service
// 本类内方法指定使用缓存时,默认的名称就是userInfoCache
@CacheConfig(cacheNames = "userInfoCache")
// 开启事务
@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = Exception.class)
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* 因为必须要有返回值,才能保存到数据库中。
* 如果保存的对象的某些字段是需要数据库生成的,
* 那保存对象进数据库的时候,就没必要放到缓存了
*
* 数据库中增加某条数据时,缓存中也增加
*
* @param user
* @return
*/
// #p0表示第一个参数作为redis中的key
// 如果是#p1表示第二个参数作为redis中的key... 这里只有1个参数user
// #p0.id 就表示获取user的id作为redis中的key
@CachePut(key = "#p0.id")
// 必须要有返回值,否则没数据放到缓存中
public User insertUser(User user) {
userMapper.insert(user);
// user对象中可能只有只几个有效字段,其他字段值靠数据库生成,比如id
return userMapper.find(user.getId());
}
/**
* 当需要更新缓存而不干扰方法执行时可以使用@CachePut注解。
* 也就是说,数据库中对应内容更新时,需要同步更新缓存可使用该注解
*
* @param user
* @return
*/
@CachePut(key = "#p0.id")
public User updateUser(User user) {
userMapper.update(user);
// 可能只是更新某几个字段而已,所以查次数据库把数据全部拿出来全部
return userMapper.find(user.getId());
}
/**
* 使用 @Cacheable注解 会先查询缓存,如果缓存中存在,则不执行查询数据库的方法
*
* 使用 springboot cache 默认缓存配置
*
* @param id
* @return
*/
@Nullable// 如果可以传入NULL值,则标记为@Nullable,如果不可以,则标注为@Nonnull
@Cacheable(key = "#p0")
public User findById(String id) {
System.err.println("根据id=" + id + "获取用户对象,从数据库中获取");
Assert.notNull(id, "id不用为空");
return userMapper.find(id);
}
/**
* 删除缓存名称为userInfoCache,key等于指定的id对应的缓存
* 数据库中删除某条数据时,缓存中也删除
*
* @param id
*/
@CacheEvict(key = "#p0")
public void deleteById(String id) {
userMapper.delete(id);
}
/**
* 清空缓存名称为userInfoCache(看类名上的注解)下的所有缓存
* 如果数据失败了,缓存时不会清除的
*/
@CacheEvict(allEntries = true)
public void deleteAll() {
userMapper.deleteAll();
}
}
在UserController使用UserService来操作redis缓存:
/**
* 根据id获取用户信息方式二:
*
* userService中加入了springboot cache缓存相关注解
* @param id
* @return
*/
@RequestMapping("/getByCache")
public User getByCache(String id) {
User user = userService.findById(id);
return user;
}
可以看出方式二,简化了方式一的代码!
提问:springboot cache 存在什么问题:
第一:生成key过于简单,例如:userCache::3,容易造成冲突
第二:无法设置过期时间,默认过期时间为永久不过期(如果数据过多且不过期,会造成内存泄漏)
第三:配置序列化方式,默认的是序列化JDKSerialazable
解决方式:
springboot cache 自定义项:
1)自定义KeyGenerator :解决springboot cache默认生成的key过于简单,容易冲突userCache::3问题;
2)自定义cacheManager,设置缓存过期时间:解决springboot cache 默认无法设置过期时间,默认过期时间为永久不过期;
3)自定义序列化方式为,Jackson或者Gson(我们这里使用jackson即可):不适用springboot cache默认序列化方式JDKSerialazable,为什么要更换默认序列号方式呢?因为boot默认的序列化方式可能不支持 日期时间、空值这些变量的序列化,会导致一些错乱乱码问题;
步骤:
1. 在RedisConfig中添加配置:
/**
* 自定义KeyGenerator:解决springboot cache默认生成的key过于简单,容易造成重复和冲突的问题
*
* @return
*/
@Bean
public KeyGenerator simpleKeyGenerator() {
return (o, method, objects) -> {
// o:类 method:方法 objects:方法参数
/**
* 我们可以使用如下方式(保证唯一性),来自定义KeyGenerator:
* 类名 + 方法名 + 参数
* eg: UserInfoList::UserService.findByIdTtl[1]
*
* 扩展:JVM定位是否是同一个方法的方式 和 这种方式类似
*/
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(o.getClass().getSimpleName());
stringBuilder.append(".");
stringBuilder.append(method.getName());
stringBuilder.append("[");
for (Object obj : objects) {
stringBuilder.append(obj.toString());
}
stringBuilder.append("]");
return stringBuilder.toString();
};
}
/**
* 设置缓存的过期时间
*
* @param redisConnectionFactory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
// 如果未配置指定的 key 就会使用这个默认策略,过期时间600s
this.getRedisCacheConfigurationWithTtl(600),
// 如果配置了指定的 key 就会使用指定 key 策略
this.getRedisCacheConfigurationMap()
);
}
// 指定相应 key 过期时间策略的Map: key:键值 value:缓存过期时间
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
// key为UserInfoList时: 过期时间100s
redisCacheConfigurationMap.put("UserInfoList", this.getRedisCacheConfigurationWithTtl(100));
// key为UserInfoListAnother时: 过期时间18000s == 5h
redisCacheConfigurationMap.put("UserInfoListAnother", this.getRedisCacheConfigurationWithTtl(18000));
return redisCacheConfigurationMap;
}
// 指定jackson序列化方式
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer)
).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
在UserService.java中添加方法:
/**
* 使用 @Cacheable注解 会先查询缓存,如果缓存中存在,则不执行方法
*
* 自定义了 springboot cache 缓存相关的配置
*
* @param id
* @return
*/
@Nullable
@Cacheable(value = "UserInfoList", keyGenerator = "simpleKeyGenerator")
public User findByIdTtl(String id) {
// 日志打印
System.err.println("根据id=" + id + "获取用户对象,从数据库中获取");
Assert.notNull(id, "id不用为空");
return userMapper.find(id);
}
在controller中使用:
/**
* 根据id获取用户信息方式三:
* 自定义了 springboot cache 缓存相关的配置
* 有过期时间策略
* 自定义了key: UserInfoList::UserService.findByIdTtl[1]
* 自定义序列化方式为jackson
* @param id
* @return
*/
@RequestMapping(value = "/getExpire", method = RequestMethod.GET)
public User findByIdTtl(String id) {
User user = new User();
try {
user = userService.findByIdTtl(id);
} catch (Exception e) {
System.err.println(e.getMessage());
}
return user;
}
测试访问该接口:
当显示出数据后,说明后端已经从数据库/缓存中读取到了数据,下面我们来看一下redis缓存中对应的key的声明周期:
当redis缓存中key过期后:
我们再次访问该接口查看效果:
由图可得出,这时候redis缓存没有要查询的用户数,这时候是从数据库中查询的!
1、什么是缓存雪崩?你有什么解决方案来防止缓存雪崩?
2、你有什么解决方案来防止缓存雪崩?
如果文章对您有帮助,点个赞或者点个关注支持下谢谢~