You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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序列化配置不匹配:

  1. MyBatis生成的CacheKey作为缓存键存入Redis时,默认JDK序列化会将其转为二进制格式,取出反序列化后仍是CacheKey对象,但MyBatis在处理该参数场景时错误地尝试将其转为String。
  2. 之前将参数从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 13:05:19