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

为何ProjectionFactory将null转为空Optional?升级Spring Boot后如何恢复旧行为

问题根因

Spring Data Commons 从2.4.0版本开始(对应Spring Boot 2.4.x及更高版本),修改了SpelAwareProxyProjectionFactory的默认处理逻辑:当投影接口的方法返回类型为Optional时,会自动将底层查询返回的null值包装为Optional.empty(),旧版本不会做这个自动包装,这就是升级后两种场景返回结果一致的根本原因,官方没有提供直接关闭这个行为的配置项。

解决方案

方案1:自定义投影工厂(推荐,侵入性最小)

通过自定义ProjectionFactory重写Optional类型的转换逻辑,仅当原始返回值非空时才做Optional包装,空值直接保留null:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import java.util.Optional;

@Configuration
public class CustomProjectionConfig {

    @Bean
    public SpelAwareProxyProjectionFactory projectionFactory() {
        return new SpelAwareProxyProjectionFactory() {
            @Override
            protected Object convertResult(Object result, Class<?> targetType) {
                // 目标类型为Optional且原始结果为null时,直接返回null不做包装
                if (targetType == Optional.class && result == null) {
                    return null;
                }
                // 其他情况走原有默认逻辑
                return super.convertResult(result, targetType);
            }
        };
    }
}

如果上述配置没有生效,说明你的项目使用了自定义的Repository工厂,需要将自定义投影工厂注入到RepositoryFactory中:

import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import jakarta.persistence.EntityManager;

public class CustomJpaRepositoryFactoryBean<T, ID> extends JpaRepositoryFactoryBean<T, ID> {

    private final SpelAwareProxyProjectionFactory projectionFactory;

    public CustomJpaRepositoryFactoryBean(Class<T> repositoryInterface, SpelAwareProxyProjectionFactory projectionFactory) {
        super(repositoryInterface);
        this.projectionFactory = projectionFactory;
    }

    @Override
    protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
        JpaRepositoryFactory factory = new JpaRepositoryFactory(entityManager);
        factory.setProjectionFactory(projectionFactory);
        return factory;
    }
}

然后在启动类上指定使用自定义的Repository工厂Bean:

@SpringBootApplication
@EnableJpaRepositories(repositoryFactoryBeanClass = CustomJpaRepositoryFactoryBean.class)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

方案2:替换Optional为自定义包装类

如果不想修改Spring的核心组件逻辑,可以自定义一个和Optional功能类似的包装类,Spring不会对自定义类型做自动空包装,用法和原Optional一致,仅需要修改投影接口的返回类型即可:

// 自定义包装类
public class NullableWrapper<T> {
    private final T value;

    private NullableWrapper(T value) {
        this.value = value;
    }

    public static <T> NullableWrapper<T> of(T value) {
        return new NullableWrapper<>(value);
    }

    public static <T> NullableWrapper<T> empty() {
        return new NullableWrapper<>(null);
    }

    public T get() {
        return value;
    }

    public boolean isPresent() {
        return value != null;
    }
}

// 投影接口修改为
public interface MyProjection {
    @JsonInclude(JsonInclude.Include.NON_NULL)
    NullableWrapper<String> getMyAttribute();
}
注意事项

自定义SpelAwareProxyProjectionFactory依赖Spring Data的内部非公开API,后续升级Spring Boot版本时需要验证该方法的兼容性。

内容的提问来源于stack exchange,提问作者Steffen Bobek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 17:36:03