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
相关产品推荐
相关产品推荐

