DATAES-920 - Add parameter to @Field annotation to store null values.

Original PR: #516
This commit is contained in:
Peter-Josef Meisch 2020-09-07 22:24:17 +02:00 committed by GitHub
parent ef1cbc35f6
commit d03510528b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 68 additions and 9 deletions

View File

@ -154,4 +154,11 @@ public @interface Field {
* @since 4.0 * @since 4.0
*/ */
int maxShingleSize() default -1; int maxShingleSize() default -1;
/**
* if true, the field will be stored in Elasticsearch even if it has a null value
*
* @since 4.1
*/
boolean storeNullValue() default false;
} }

View File

@ -497,6 +497,11 @@ public class MappingElasticsearchConverter
Object value = accessor.getProperty(property); Object value = accessor.getProperty(property);
if (value == null) { if (value == null) {
if (property.storeNullValue()) {
sink.set(property, null);
}
continue; continue;
} }
@ -797,7 +802,8 @@ public class MappingElasticsearchConverter
}); });
} }
org.springframework.data.elasticsearch.annotations.Field fieldAnnotation = property.findAnnotation(org.springframework.data.elasticsearch.annotations.Field.class); org.springframework.data.elasticsearch.annotations.Field fieldAnnotation = property
.findAnnotation(org.springframework.data.elasticsearch.annotations.Field.class);
if (fieldAnnotation != null) { if (fieldAnnotation != null) {
field.setFieldType(fieldAnnotation.type()); field.setFieldType(fieldAnnotation.type());
@ -864,14 +870,17 @@ public class MappingElasticsearchConverter
return result; return result;
} }
public void set(ElasticsearchPersistentProperty property, Object value) { public void set(ElasticsearchPersistentProperty property, @Nullable Object value) {
if (property.isIdProperty()) { if (value != null) {
((Document) target).setId(value.toString());
}
if (property.isVersionProperty()) { if (property.isIdProperty()) {
((Document) target).setVersion((Long) value); ((Document) target).setId(value.toString());
}
if (property.isVersionProperty()) {
((Document) target).setVersion((Long) value);
}
} }
target.put(property.getFieldName(), value); target.put(property.getFieldName(), value);

View File

@ -96,6 +96,12 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty<Elas
*/ */
boolean isReadable(); boolean isReadable();
/**
* @return {@literal true} if null values should be stored in Elasticsearch
* @since 4.1
*/
boolean storeNullValue();
enum PropertyToFieldNameConverter implements Converter<ElasticsearchPersistentProperty, String> { enum PropertyToFieldNameConverter implements Converter<ElasticsearchPersistentProperty, String> {
INSTANCE; INSTANCE;

View File

@ -64,6 +64,7 @@ public class SimpleElasticsearchPersistentProperty extends
private final boolean isSeqNoPrimaryTerm; private final boolean isSeqNoPrimaryTerm;
private final @Nullable String annotatedFieldName; private final @Nullable String annotatedFieldName;
@Nullable private ElasticsearchPersistentPropertyConverter propertyConverter; @Nullable private ElasticsearchPersistentPropertyConverter propertyConverter;
private boolean storeNullValue;
public SimpleElasticsearchPersistentProperty(Property property, public SimpleElasticsearchPersistentProperty(Property property,
PersistentEntity<?, ElasticsearchPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) { PersistentEntity<?, ElasticsearchPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
@ -85,6 +86,8 @@ public class SimpleElasticsearchPersistentProperty extends
this.isParent = isAnnotationPresent(Parent.class); this.isParent = isAnnotationPresent(Parent.class);
this.isSeqNoPrimaryTerm = SeqNoPrimaryTerm.class.isAssignableFrom(getRawType()); this.isSeqNoPrimaryTerm = SeqNoPrimaryTerm.class.isAssignableFrom(getRawType());
boolean isField = isAnnotationPresent(Field.class);
if (isVersionProperty() && !getType().equals(Long.class)) { if (isVersionProperty() && !getType().equals(Long.class)) {
throw new MappingException(String.format("Version property %s must be of type Long!", property.getName())); throw new MappingException(String.format("Version property %s must be of type Long!", property.getName()));
} }
@ -98,11 +101,13 @@ public class SimpleElasticsearchPersistentProperty extends
throw new MappingException(String.format("Parent property %s must be of type String!", property.getName())); throw new MappingException(String.format("Parent property %s must be of type String!", property.getName()));
} }
if (isAnnotationPresent(Field.class) && isAnnotationPresent(MultiField.class)) { if (isField && isAnnotationPresent(MultiField.class)) {
throw new MappingException("@Field annotation must not be used on a @MultiField property."); throw new MappingException("@Field annotation must not be used on a @MultiField property.");
} }
initDateConverter(); initDateConverter();
storeNullValue = isField && getRequiredAnnotation(Field.class).storeNullValue();
} }
@Override @Override
@ -126,6 +131,11 @@ public class SimpleElasticsearchPersistentProperty extends
return !isTransient() && !isSeqNoPrimaryTermProperty(); return !isTransient() && !isSeqNoPrimaryTermProperty();
} }
@Override
public boolean storeNullValue() {
return storeNullValue;
}
/** /**
* Initializes an {@link ElasticsearchPersistentPropertyConverter} if this property is annotated as a Field with type * Initializes an {@link ElasticsearchPersistentPropertyConverter} if this property is annotated as a Field with type
* {@link FieldType#Date}, has a {@link DateFormat} set and if the type of the property is one of the Java8 temporal * {@link FieldType#Date}, has a {@link DateFormat} set and if the type of the property is one of the Java8 temporal

View File

@ -39,6 +39,7 @@ import java.util.Map;
import org.json.JSONException; import org.json.JSONException;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
@ -616,7 +617,7 @@ public class MappingElasticsearchConverterUnitTests {
assertEquals(expected, json, false); assertEquals(expected, json, false);
} }
@Test @Test // DATAES-716
void shouldReadLocalDate() { void shouldReadLocalDate() {
Document document = Document.create(); Document document = Document.create();
document.put("id", "4711"); document.put("id", "4711");
@ -813,6 +814,25 @@ public class MappingElasticsearchConverterUnitTests {
assertEquals(expected, document.toJson(), false); assertEquals(expected, document.toJson(), false);
} }
@Test // DATAES-920
@DisplayName("should write null value if configured")
void shouldWriteNullValueIfConfigured() throws JSONException {
EntityWithNullField entity = new EntityWithNullField();
entity.setId("42");
String expected = "{\n" + //
" \"id\": \"42\",\n" + //
" \"saved\": null\n" + //
"}\n"; //
Document document = Document.create();
mappingElasticsearchConverter.write(entity, document);
assertEquals(expected, document.toJson(), false);
}
private String pointTemplate(String name, Point point) { private String pointTemplate(String name, Point point) {
return String.format(Locale.ENGLISH, "\"%s\":{\"lat\":%.1f,\"lon\":%.1f}", name, point.getX(), point.getY()); return String.format(Locale.ENGLISH, "\"%s\":{\"lat\":%.1f,\"lon\":%.1f}", name, point.getX(), point.getY());
} }
@ -1046,4 +1066,11 @@ public class MappingElasticsearchConverterUnitTests {
@Id private String id; @Id private String id;
private Object content; private Object content;
} }
@Data
static class EntityWithNullField {
@Id private String id;
@Field(type = FieldType.Text) private String notSaved;
@Field(type = FieldType.Text, storeNullValue = true) private String saved;
}
} }