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

Spring Boot+MongoDB:不可修改final实体findAll/findById异常解决求助

问题:Spring Data MongoDB查询含final字段的外部领域类抛出异常

场景说明

基于Spring Boot + MongoDB开发服务,使用外部库中不可修改的领域类(如Discount),类中实例变量均为final,代码如下:

@Value
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@SuperBuilder(toBuilder = true)
@JsonDeserialize(builder = Discount.DiscountBuilderImpl.class)
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class Discount extends AbstractEntityBase {

    @NonNull
    UUID id;

    @NonNull
    String no;

    @JsonProperty("iNo")
    Integer iNo;

    @NonNull
    LocalizedTexts designation;

    LocalizedTexts printText;
    
    boolean isAutomatic;

    LocalDate validFromDate;

    LocalDate validToDate;

    LocalTime validFromTime;

    LocalTime validToTime;

    String checkScriptParameters;

    ReferenceScript checkScript;

    @NonNull
    ReferenceScript calculationScript;

    public static DiscountBuilder<?, ?> builder(UUID id,
                                            String no,
                                            LocalizedTexts designation,
                                            ReferenceScript calculationScript,
                                            boolean isAutomatic) {
        return new DiscountBuilderImpl()
            .id(id)
            .no(no)
            .designation(designation)
            .calculationScript(calculationScript)
            .isAutomatic(isAutomatic);
    }

     /**
     * Overwritten getters for optional properties
     */
    ...

    @JsonPOJOBuilder(withPrefix = "")
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static final class DiscountBuilderImpl
        extends DiscountBuilder<Discount, DiscountBuilderImpl> {

    }
}

服务执行save/saveAll操作正常,但调用findAll()/findById()时抛出异常。

异常栈信息

java.lang.IllegalStateException: Cannot set property id because no setter, no wither and it's not part of the persistence constructor private net.mspos.possible.svc_pos_controller_data.entity.Discount()!

at org.springframework.data.mapping.model.InstantiationAwarePropertyAccessor.setProperty(InstantiationAwarePropertyAccessor.java:118)
at org.springframework.data.mapping.model.ConvertingPropertyAccessor.setProperty(ConvertingPropertyAccessor.java:64)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readAndPopulateIdentifier(MappingMongoConverter.java:450)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.populateProperties(MappingMongoConverter.java:418)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:394)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readDocument(MappingMongoConverter.java:356)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:292)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:288)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:107)
at org.springframework.data.mongodb.core.MongoTemplate$ReadDocumentCallback.doWith(MongoTemplate.java:3207)
at org.springframework.data.mongodb.core.MongoTemplate.executeFindOneInternal(MongoTemplate.java:2822)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2529)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:2499)
at org.springframework.data.mongodb.core.MongoTemplate.findById(MongoTemplate.java:888)
at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.findById(SimpleMongoRepository.java:132)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:529)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:639)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:163)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at jdk.proxy2/jdk.proxy2.$Proxy93.findById(Unknown Source)
at net.mspos.possible.svc_pos_controller_data.repository.TestDiscountRepository.find_by_id_success(TestDiscountRepository.java:209)

数据库文档结构

MongoDB中存储的文档使用_id作为主键(对应实体的id字段,类型为字符串格式的UUID),其他字段与实体属性一一对应,包含no、iNo、designation等字段。

解决方案

方案1:自定义读取转换器(ReadingConverter)

利用实体提供的Builder构建实例,绕过Spring Data默认的反射设值逻辑。

1.1 实现读取转换器

import org.bson.Document;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.UUID;

@ReadingConverter
public class DiscountReadingConverter implements Converter<Document, Discount> {

    @Override
    public Discount convert(Document source) {
        // 从Mongo文档中提取字段,调用实体的Builder构建实例
        return Discount.builder(
                UUID.fromString(source.getString("_id")),
                source.getString("no"),
                source.get("designation", LocalizedTexts.class),
                source.get("calculationScript", ReferenceScript.class),
                source.getBoolean("isAutomatic")
        )
                .iNo(source.getInteger("iNo"))
                .printText(source.get("printText", LocalizedTexts.class))
                .validFromDate(source.get("validFromDate", LocalDate.class))
                .validToDate(source.get("validToDate", LocalDate.class))
                .validFromTime(source.get("validFromTime", LocalTime.class))
                .validToTime(source.get("validToTime", LocalTime.class))
                .checkScriptParameters(source.getString("checkScriptParameters"))
                .checkScript(source.get("checkScript", ReferenceScript.class))
                .build();
    }
}

1.2 注册转换器到Spring容器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import java.util.List;

@Configuration
public class MongoConfig {

    @Bean
    public MongoCustomConversions mongoCustomConversions() {
        return new MongoCustomConversions(List.of(new DiscountReadingConverter()));
    }
}

方案2:配置Spring Data优先使用Builder实例化对象

通过修改MappingMongoConverter的实例化策略,让Spring Data自动使用实体的Builder创建对象,无需手动编写转换器。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mapping.model.BuilderInstantiationStrategy;

@Configuration
public class MongoConfig {

    @Bean
    public MappingMongoConverter mappingMongoConverter(MongoMappingContext context) {
        MappingMongoConverter converter = new MappingMongoConverter(null, context);
        // 设置实例化策略为优先使用Builder
        converter.setInstantiationStrategy(new BuilderInstantiationStrategy());
        return converter;
    }
}

注意:BuilderInstantiationStrategy需要Spring Data Commons 2.4+版本支持,确保你的依赖版本符合要求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 19:20:35