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

Spring Boot中自定义@Cacheable:仅缓存返回对象指定字段

实现自定义缓存特定字段的几种思路

当然可以实现!Spring Cache的扩展性很强,不管是自定义@Cacheable相关逻辑,还是用其他替代方案,都能做到只缓存Person对象的personId和personName字段。下面我会从简单到复杂给你梳理几种可行的方式:

一、最直接:在缓存方法里返回精简对象

不需要自定义注解,直接在被@Cacheable标记的方法里,把查询到的完整Person对象转换成只保留目标字段的实例再返回——这样Spring Cache就只会缓存这个精简版的对象。

示例代码:

// 从数据库/其他数据源获取完整Person的方法(不缓存)
private Person getFullPersonFromDB(String personID) {
    // 这里写实际查询逻辑,返回包含所有字段的Person
}

// 带缓存的对外方法,只返回精简版Person
@Cacheable(value = "personCache", key = "#personID")
public Person getPersonById(String personID) {
    Person fullPerson = getFullPersonFromDB(personID);
    
    // 构建只包含需要字段的Person实例
    Person cachedPerson = new Person();
    cachedPerson.setPersonId(fullPerson.getPersonId());
    cachedPerson.setPersonName(fullPerson.getPersonName());
    
    // 其他字段不赋值,缓存时自然不会存储这些内容
    return cachedPerson;
}

这种方式的好处是简单直接,不需要额外配置,适合只针对单个方法的场景。

二、自定义注解+AOP切面(更灵活)

如果需要给多个方法复用这个“缓存特定字段”的逻辑,可以自定义一个扩展@Cacheable的注解,再结合AOP切面来统一处理对象的转换。

步骤1:自定义注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
// 继承@Cacheable的所有属性,保留原生缓存能力
@Cacheable
public @interface CacheablePersonFields {
    String value() default "";
    String key() default "";
    // 可以根据需求添加@Cacheable的其他属性,比如condition、unless等
}

步骤2:编写AOP切面处理值转换

切面会拦截被@CacheablePersonFields标记的方法,在方法返回后把完整Person转成精简版,缓存时只存精简对象;读取缓存时再把精简对象转成Person返回(如果需要)。

@Aspect
@Component
public class PersonCacheAspect {

    // 拦截自定义注解的方法
    @Around("@annotation(cacheablePersonFields)")
    public Object handlePersonCache(ProceedingJoinPoint joinPoint, CacheablePersonFields cacheablePersonFields) throws Throwable {
        // 先执行原方法,获取完整Person对象
        Object result = joinPoint.proceed();
        
        if (result instanceof Person) {
            // 转换为只包含目标字段的对象(这里也可以用专门的DTO)
            Person fullPerson = (Person) result;
            Person cachedPerson = new Person();
            cachedPerson.setPersonId(fullPerson.getPersonId());
            cachedPerson.setPersonName(fullPerson.getPersonName());
            return cachedPerson;
        }
        
        return result;
    }
}

步骤3:使用自定义注解

@CacheablePersonFields(value = "personCache", key = "#personID")
public Person getPersonById(String personID) {
    // 直接返回完整Person,切面会自动处理转换
    return getFullPersonFromDB(personID);
}

三、替代方案:用DTO专门缓存

创建一个只包含需要字段的DTO类,缓存这个DTO而不是原Person实体,这样逻辑更清晰,也避免原实体类字段变化影响缓存结构。

步骤1:定义缓存用DTO

// 只包含需要缓存的字段
public class PersonCacheDTO {
    private String personId;
    private String personName;

    // 从Person构造DTO
    public PersonCacheDTO(Person person) {
        this.personId = person.getPersonId();
        this.personName = person.getPersonName();
    }

    // 从DTO转回Person的方法(如果需要)
    public Person toPerson() {
        Person person = new Person();
        person.setPersonId(this.personId);
        person.setPersonName(this.personName);
        return person;
    }

    // getter/setter 或者用Lombok简化
}

步骤2:缓存DTO

@Cacheable(value = "personCache", key = "#personID")
public PersonCacheDTO getPersonCacheDTO(String personID) {
    Person fullPerson = getFullPersonFromDB(personID);
    return new PersonCacheDTO(fullPerson);
}

// 对外提供的方法,把DTO转成Person返回
public Person getPersonById(String personID) {
    PersonCacheDTO cachedDTO = getPersonCacheDTO(personID);
    return cachedDTO.toPerson();
}

四、替代方案:自定义缓存序列化器(适合Redis等缓存中间件)

如果你的项目用Redis作为缓存,可以自定义序列化器,在序列化Person对象时只序列化指定字段,从缓存读取时也只填充这些字段。

步骤1:自定义Redis序列化器

public class PersonRedisSerializer implements RedisSerializer<Person> {
    private final ObjectMapper objectMapper = new ObjectMapper();

    static {
        // 使用MixIn指定只序列化/反序列化的字段
        objectMapper.addMixIn(Person.class, PersonCacheMixIn.class);
    }

    @Override
    public byte[] serialize(Person person) throws SerializationException {
        try {
            return objectMapper.writeValueAsBytes(person);
        } catch (JsonProcessingException e) {
            throw new SerializationException("序列化Person失败", e);
        }
    }

    @Override
    public Person deserialize(byte[] bytes) throws SerializationException {
        try {
            return objectMapper.readValue(bytes, Person.class);
        } catch (IOException e) {
            throw new SerializationException("反序列化Person失败", e);
        }
    }

    // 定义MixIn,只标记需要序列化的字段
    abstract static class PersonCacheMixIn {
        @JsonProperty
        abstract String getPersonId();
        
        @JsonProperty
        abstract String getPersonName();
        
        // 其他字段不标注@JsonProperty,不会被序列化
    }
}

步骤2:配置RedisCacheManager使用自定义序列化器

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
            // 设置值的序列化器为我们自定义的PersonRedisSerializer
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new PersonRedisSerializer()));
    
    return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(cacheConfig)
            .build();
}

这样配置后,即使你用原生的@Cacheable注解,缓存的Person对象也只会包含personId和personName两个字段,完全不需要修改业务方法。


内容的提问来源于stack exchange,提问作者Mehraj Malik

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 17:12:55