I'm using spring data elasticsearch to search elasticsearch(7.12.1) documents:
- spring-boot-starter-data-elasticsearch: 2.7.0, which uses
- spring-data-elasticsearch: 4.4.0
and find spring data elasticsearch can deserialize array field of basic type correctly but not custom object.
Here comes the issue--
A simple Person index with two fields may be array, one is basic string hobbies and the other is custom object children(there is no need to use nested object for me, plain object is enough):
@Data
@Builder
@Document(indexName = "person", writeTypeHint = WriteTypeHint.FALSE, createIndex = true)
public class Person {
@Id
@ReadOnlyProperty
private String id;
@Field(type = FieldType.Keyword)
private Set<String> hobbies;
@Field(type = FieldType.Object)
private Set<Child> children;
@Data
public static class Child {
@Field(type = FieldType.Keyword)
private String name;
}
}
it creates mappings automatically as expected:
{
"person" : {
"mappings" : {
"properties" : {
"children" : {
"properties" : {
"name" : {
"type" : "keyword"
}
}
},
"hobbies" : {
"type" : "keyword"
}
}
}
}
}
put 4 docs manually now:
PUT person/_doc/one-hobby
{
"hobbies": "one"
}
PUT person/_doc/two-hobbies
{
"hobbies": ["one", "two"]
}
PUT person/_doc/one-child
{
"children": {"name": "name1"}
}
PUT person/_doc/two-children
{
"children": [{"name": "name1"}, {"name": "name2"}]
}
Then query them one by one by their ids using ElasticsearchRepository#findById(id), the two docs with array values deserialize ok:
Person(id=two-children, hobbies=null, children=[Person.Child(name=name1), Person.Child(name=name2)])
Person(id=two-hobbies, hobbies=[one, two], children=null)
This is understandable because convert arrays to Set in java seems natural.
the one with only a basic field deserializes is ok too:
Person(id=one-hobby, hobbies=[one], children=null)
Although it convers a single string to Set, I've seen some of the deserializing source code, spring-data-elasticsearch uses string to collection converter to convert the single value hobbies into arraied-one, thus hobbies field in java as a Set behaves well.
but the one with only a custom object field(id=one-child) can't be deserialized:
Exception in thread "main" org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate java.util.Set using constructor NO_CONSTRUCTOR with arguments
at org.springframework.data.mapping.model.ReflectionEntityInstantiator.instantiateClass(ReflectionEntityInstantiator.java:116)
at org.springframework.data.mapping.model.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:53)
at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:102)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter$Reader.readEntity(MappingElasticsearchConverter.java:320)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter$Reader.read(MappingElasticsearchConverter.java:258)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter$Reader.readValue(MappingElasticsearchConverter.java:458)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter$Reader.readValue(MappingElasticsearchConverter.java:442)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter$Reader$ElasticsearchPropertyValueProvider.getPropertyValue(MappingElasticsearchConverter.java:621)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter$Reader$ElasticsearchPropertyValueProvider.getPropertyValue(MappingElasticsearchConverter.java:601)
at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:74)
at org.springframework.data.mapping.model.SpELExpressionParameterValueProvider.getParameterValue(SpELExpressionParameterValueProvider.java:53)
at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.extractInvocationArguments(ClassGeneratingEntityInstantiator.java:313)
at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:285)
at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:102)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter$Reader.readEntity(MappingElasticsearchConverter.java:320)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter$Reader.read(MappingElasticsearchConverter.java:258)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter$Reader.read(MappingElasticsearchConverter.java:217)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.read(MappingElasticsearchConverter.java:161)
at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.read(MappingElasticsearchConverter.java:83)
at org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate$ReadDocumentCallback.doWith(AbstractElasticsearchTemplate.java:745)
at org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate.get(ElasticsearchRestTemplate.java:207)
at org.springframework.data.elasticsearch.repository.support.SimpleElasticsearchRepository.lambda$findById$0(SimpleElasticsearchRepository.java:102)
at org.springframework.data.elasticsearch.repository.support.SimpleElasticsearchRepository.execute(SimpleElasticsearchRepository.java:355)
at org.springframework.data.elasticsearch.repository.support.SimpleElasticsearchRepository.findById(SimpleElasticsearchRepository.java:102)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
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.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 com.sun.proxy.$Proxy62.findById(Unknown Source)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at com.sun.proxy.$Proxy62.findById(Unknown Source)
at io.puppylpg.PersonOps.get(PersonOps.java:23)
at io.puppylpg.App.main(App.java:43)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [java.util.Set]: Specified class is an interface
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:143)
at org.springframework.data.mapping.model.ReflectionEntityInstantiator.instantiateClass(ReflectionEntityInstantiator.java:112)
... 54 more
Seems that spring-data-elasticsearch won't try to convert custom object into Set because in MappingElasticsearchConverter if value is a map(a custom object), it just tries to read it directly, thus doesn't go the last line getPotentiallyConvertedSimpleRead(thus doesn't use some kine of custom object to collection converter like before):
private <T> T readValue(Object value, TypeInformation<?> type) {
Class<?> rawType = type.getType();
if (conversions.hasCustomReadTarget(value.getClass(), rawType)) {
return (T) conversionService.convert(value, rawType);
} else if (value instanceof List) {
return (T) readCollectionOrArray(type, (List<Object>) value);
} else if (value.getClass().isArray()) {
return (T) readCollectionOrArray(type, Arrays.asList((Object[]) value));
} else if (value instanceof Map) {
return (T) read(type, (Map<String, Object>) value);
} else {
return (T) getPotentiallyConvertedSimpleRead(value, rawType);
}
}
Is this supported in spring data elasticsearch? And any ideas to fix this problem or work around it?
Any suggestion would be appreciated.
List? If so, do you need a set instead of a list?MappingInstantiationException: Failed to instantiate java.util.List using constructor NO_CONSTRUCTOR with arguments