Spring项目中Redis作为MyBatis二级缓存的类型转换异常问题
问题:Redis作为MyBatis二级缓存时的类型转换异常
场景复现
在Spring项目中将Redis用作MyBatis二级缓存,执行特定Mapper方法时出现类型转换错误,其他SQL执行正常。
Mapper代码
@Select("select count(*) from passages where area = #{area}") public int searchOilInfo(@Param("area") StringBuilder area);
执行代码
int res = oilInfoDAO.searchOilInfo(new StringBuilder("xxx"));
控制台报错
class org.apache.ibatis.cache.CacheKey cannot be cast to class java.lang.String (org.apache.ibatis.cache.CacheKey is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')
自定义Redis缓存实现类
public class RedisMybatisCache implements Cache { private final String id; // private static StringRedisTemplate template; private static RedisTemplate<Object, Object> template; public RedisMybatisCache(String id) { this.id = id; } public static void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) { RedisMybatisCache.template = redisTemplate; } @Override public String getId() { return this.id; } @Override public void putObject(Object k, Object v) { template.opsForValue().set(k, v, 60, TimeUnit.SECONDS); } @Override public Object getObject(Object o) { return template.opsForValue().get(o); } @Override public Object removeObject(Object o) { return template.delete(o); } @Override public void clear() { template.execute((RedisCallback<Void>) collection -> { collection.serverCommands().flushDb(); return null; }); } @Override public int getSize() { return Objects.requireNonNull(template.execute(RedisServerCommands::dbSize)).intValue(); } }
问题原因
异常触发于getObject()方法,核心原因是RedisTemplate序列化配置不匹配:
- MyBatis生成的
CacheKey作为缓存键存入Redis时,默认JDK序列化会将其转为二进制格式,取出反序列化后仍是CacheKey对象,但MyBatis在处理该参数场景时错误地尝试将其转为String。 - 之前将参数从String改为StringBuilder是错误操作:String本身可序列化,而StringBuilder未实现Serializable接口,不仅不符合MyBatis二级缓存的序列化要求,还会导致缓存键的哈希值不稳定(可变对象状态变化会破坏缓存一致性)。
解决方案
1. 修正参数类型(改回String)
String作为不可变类,既符合Serializable要求,也能保证缓存键的稳定性:
// Mapper修改 @Select("select count(*) from passages where area = #{area}") public int searchOilInfo(@Param("area") String area); // 调用代码修改 int res = oilInfoDAO.searchOilInfo("xxx");
2. 配置RedisTemplate的序列化规则
替换默认的JDK序列化,改用字符串序列化键、JSON序列化值,避免类型转换问题:
@Configuration public class RedisConfig { @Bean public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); // 键序列化:StringRedisSerializer StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); // 值序列化:Jackson2JsonRedisSerializer Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jacksonSerializer.setObjectMapper(objectMapper); template.setValueSerializer(jacksonSerializer); template.setHashValueSerializer(jacksonSerializer); template.afterPropertiesSet(); return template; } }
3. 确认缓存对象的序列化合规性
确保所有存入二级缓存的对象(包括结果集、参数相关对象)都实现Serializable接口,本次场景中返回值int自动装箱为Integer(已实现Serializable),改回String参数后完全符合要求。
验证
修改后重新执行代码,缓存键以字符串形式存入Redis,取出时不会出现类型转换错误,同时避免了可变参数带来的缓存一致性问题。
内容的提问来源于stack exchange,提问作者Da_PenGu
相关产品推荐
相关产品推荐

