Bug:SpringBoot整合SpringCache与Redis踩过的坑
1 场景回现
今天在做项目的时候需要使用SpringCache与Redis作为缓存
但是在,配置完成后,发现并没有达到目的
1.1 无法连接到远程的Redis
org.springframework.data.redis.RedisConnectionFailureException: Unable to
connect to Redis;
1.2 无法实现缓存
在排查完无法连接到Redis之后,以为项目能够正常运行,结果发现前端无法访问到数据
后台直接报错
Bean named**is expected to be of type**but
was actually of type ‘com.sun.proxy.$Proxy**
2 SpringBoot整合SpringCache与Redis步骤
整合步骤:
①导入依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
②application.properties配置文件
spring.redis.host=192.168.145.13
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
③Redis配置类【自定义key规则、缓存规则、转换问题(序列化)】
RedisConfig:
/**
* @author 夏末
* @description TODO
* @date 2022/10/1 10:11
*/
@Configuration
@EnableCaching
public class RedisConfig {
/**
* 自定义key规则
* @return
*/
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
/**
* 设置RedisTemplate规则
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//序列号key value
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 设置CacheManager缓存规则
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer 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);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
④在service的对应实现类中添加相应注解
/**
* 根据id查询子数据列表
* @param id
* @return
*/
@Override
@Cacheable(value = "dict",keyGenerator = "keyGenerator")
public List<Dict> findChildData(Long id) {
QueryWrapper<Dict> wrapper = new QueryWrapper<>();
wrapper.eq("parent_id", id);
List<Dict> dicts = dictMapper.selectList(wrapper);
for(Dict dict : dicts){
Long dictId = dict.getId();
dict.setHasChildren(hasChild(dictId));
}
return dicts;
}
/**
* 导入数据
* allEntries = true: 方法调用后清空所有缓存
*/
@Override
@CacheEvict(value = "dict", allEntries=true)
public void importDictData(MultipartFile file) {
try{
EasyExcel.read(file.getInputStream(), DictEeVo.class, new DictListener(dictMapper))
.sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}
⑤在启动类上开启扫描(扫描RedisConfig配置类)
@SpringBootApplication
@EnableSwagger2
@ComponentScan(basePackages = {"com.zi"})
public class ServiceCmnApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceCmnApplication.class);
}
}
3 原因排查
3.1 无法连接到远程Redis
①配置文件端口配错
②远程Redis的保护模式打开了
"修改redis.conf文件",将protected-mode配置设置为no
③bind绑定问题
"redis.conf文件",bind设置为了只允许本机访问。
将该行注释,或者将项目运行的服务器地址配置上去
④设置了访问密码
"redis.conf文件",在配置文件中将密码去掉或者在SpringBoot的application.yml中添加正确密码
3.2 无法实现缓存
①检查对应注解的value值是否正确
如:
Redis配置类的键生成器:
对应注解上的value值是否为类名首字母小写
②是否在配置类上开启注解,是否将配置类作为Bean交给Spring管理
@Configuration
@EnableCaching
public class RedisConfig {
...
}
③如果是多个模块的话,是否在启动类上添加了注解扫描
@SpringBootApplication
@EnableSwagger2
@ComponentScan(basePackages = {"com.zi"})
public class ServiceCmnApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceCmnApplication.class);
}
}
④如果是代码本身报错,查看是否是Idea自动导包导入错了
4 解决问题
无法访问到远程Redis,是因为我开启了保护模式,将其改为no即可
无法实现缓存目的是因为我的项目是多个模块,但是在启动上没有添加注解扫描,同时将注解上面的value值对应错了,修改为keyGenerator即可