Spring Data Elasticsearch升级后Set属性报MappingInstantiationException如何解决
问题根因
你遇到的报错是Spring Data Elasticsearch 4.x(对应Spring Boot 2.5.x版本)的实体映射模块反序列化限制导致的:当实体类中集合类型属性声明为接口(比如Set、List)时,默认的实体实例化器无法直接实例化接口类型,就会抛出找不到构造函数的异常。
最优解决方案(无需修改任何实体代码,不影响JPA逻辑)
全局自定义Spring Data Elasticsearch的实体实例化策略,指定集合接口的默认实现类,配置代码如下:
首先新增自定义实例化器实现:
import org.springframework.data.mapping.InstanceCreatorMetadata; import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.model.EntityInstantiator; import org.springframework.data.mapping.model.ParameterValueProvider; import org.springframework.util.ReflectionUtils; import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class CustomCollectionInstantiator implements EntityInstantiator { private final Map<Class<?>, Class<?>> COLLECTION_INTERFACE_MAPPINGS = new ConcurrentHashMap<>(); public CustomCollectionInstantiator() { // 配置集合接口对应的默认实现类 COLLECTION_INTERFACE_MAPPINGS.put(Set.class, HashSet.class); COLLECTION_INTERFACE_MAPPINGS.put(List.class, ArrayList.class); COLLECTION_INTERFACE_MAPPINGS.put(Map.class, HashMap.class); COLLECTION_INTERFACE_MAPPINGS.put(Collection.class, ArrayList.class); // 可按需添加其他集合接口映射 } @Override public <T> T createInstance(PersistentEntity<?, T> entity, ParameterValueProvider provider) { // 先处理带参构造函数场景 InstanceCreatorMetadata<? extends Parameter<?, ?>> creator = entity.getInstanceCreatorMetadata(); if (creator != null && !creator.getParameters().isEmpty()) { Object[] params = creator.getParameters().stream() .map(provider::getParameterValue) .toArray(); Class<?>[] paramTypes = creator.getParameters().stream() .map(Parameter::getType) .toArray(Class[]::new); try { return entity.getType().getConstructor(paramTypes).newInstance(params); } catch (Exception e) { throw new RuntimeException(e); } } // 无参构造函数实例化 try { T instance = entity.getType().getDeclaredConstructor().newInstance(); // 初始化未赋值的集合类型字段 entity.doWithProperties(property -> { if (property.getType().isInterface() && COLLECTION_INTERFACE_MAPPINGS.containsKey(property.getType())) { ReflectionUtils.makeAccessible(property.getField()); try { // 字段已有初始值则跳过赋值 if (ReflectionUtils.getField(property.getField(), instance) == null) { Object collectionImpl = COLLECTION_INTERFACE_MAPPINGS.get(property.getType()).getDeclaredConstructor().newInstance(); ReflectionUtils.setField(property.getField(), instance, collectionImpl); } } catch (Exception e) { throw new RuntimeException(e); } } }); return instance; } catch (Exception e) { throw new RuntimeException(e); } } }
然后将这个实例化器注册到Spring Data Elasticsearch的配置中:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.EntityInstantiators; @Configuration public class ElasticsearchConfig extends ElasticsearchConfigurationSupport { @Bean @Override public MappingElasticsearchConverter elasticsearchEntityMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, ?> mappingContext) { MappingElasticsearchConverter converter = super.elasticsearchEntityMapper(mappingContext); // 注册自定义实例化器 converter.setInstantiators(new EntityInstantiators((entity, provider) -> new CustomCollectionInstantiator().createInstance(entity, provider))); return converter; } }
该配置为Spring Data Elasticsearch专属,和JPA配置完全隔离,不会影响JPA Metamodel的正常运行。
可选轻量方案(仅需新增实体注解,无需修改字段声明)
如果不想调整全局配置,可以给所有实体类增加全参数+无参构造函数,配合Lombok注解实现即可:
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.data.elasticsearch.annotations.Document; import java.util.Set; @Data @NoArgsConstructor @AllArgsConstructor @Document(indexName = "customer") public class Customer { private String id; private String name; private Set<Address> addresses; }
Spring Data Elasticsearch检测到全参数构造函数时,会直接用查询到的参数值注入构造函数,不需要单独实例化集合接口,也不会和JPA逻辑冲突。
内容的提问来源于stack exchange,提问作者Ma Va
相关产品推荐
相关产品推荐

