mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-05-29 08:12:11 +00:00
Move dynamic mapping parameter configuration to @Document and @Field.
Original Pull Request #1872 Closes #1871
This commit is contained in:
parent
3b921b7454
commit
f74dd879df
@ -33,6 +33,7 @@ import org.springframework.data.annotation.Persistent;
|
||||
* @author Ivan Greene
|
||||
* @author Mark Paluch
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
*/
|
||||
@Persistent
|
||||
@Inherited
|
||||
@ -112,4 +113,12 @@ public @interface Document {
|
||||
* @since 4.3
|
||||
*/
|
||||
WriteTypeHint writeTypeHint() default WriteTypeHint.DEFAULT;
|
||||
|
||||
/**
|
||||
* Controls how Elasticsearch dynamically adds fields to the document.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
Dynamic dynamic() default Dynamic.INHERIT;
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.annotations;
|
||||
|
||||
/**
|
||||
* Values for the {@code dynamic} mapping parameter.
|
||||
*
|
||||
* @author Sascha Woo
|
||||
* @since 4.3
|
||||
*/
|
||||
public enum Dynamic {
|
||||
/**
|
||||
* New fields are added to the mapping.
|
||||
*/
|
||||
TRUE,
|
||||
/**
|
||||
* New fields are added to the mapping as
|
||||
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime.html">runtime fields</a>. These
|
||||
* fields are not indexed, and are loaded from {@code _source} at query time.
|
||||
*/
|
||||
RUNTIME,
|
||||
/**
|
||||
* New fields are ignored. These fields will not be indexed or searchable, but will still appear in the
|
||||
* {@code _source} field of returned hits. These fields will not be added to the mapping, and new fields must be added
|
||||
* explicitly.
|
||||
*/
|
||||
FALSE,
|
||||
/**
|
||||
* If new fields are detected, an exception is thrown and the document is rejected. New fields must be explicitly
|
||||
* added to the mapping.
|
||||
*/
|
||||
STRICT,
|
||||
/**
|
||||
* Inherit the dynamic setting from their parent object or from the mapping type.
|
||||
*/
|
||||
INHERIT
|
||||
}
|
@ -26,11 +26,14 @@ import java.lang.annotation.Target;
|
||||
* {@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html">elasticsearch doc</a>}
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @since 4.0
|
||||
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.FIELD })
|
||||
@Documented
|
||||
@Deprecated
|
||||
public @interface DynamicMapping {
|
||||
|
||||
DynamicMappingValue value() default DynamicMappingValue.True;
|
||||
|
@ -19,8 +19,11 @@ package org.springframework.data.elasticsearch.annotations;
|
||||
* values for the {@link DynamicMapping annotation}
|
||||
*
|
||||
* @author Peter-Josef Meisch
|
||||
* @author Sascha Woo
|
||||
* @since 4.0
|
||||
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public enum DynamicMappingValue {
|
||||
True, False, Strict
|
||||
}
|
||||
|
@ -195,4 +195,12 @@ public @interface Field {
|
||||
* @since 4.2
|
||||
*/
|
||||
int dims() default -1;
|
||||
|
||||
/**
|
||||
* Controls how Elasticsearch dynamically adds fields to the inner object within the document.<br>
|
||||
* To be used in combination with {@link FieldType#Object} or {@link FieldType#Nested}
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
Dynamic dynamic() default Dynamic.INHERIT;
|
||||
}
|
||||
|
@ -197,7 +197,9 @@ public class MappingBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
if (dynamicMapping != null) {
|
||||
if (entity != null && entity.dynamic() != Dynamic.INHERIT) {
|
||||
builder.field(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase());
|
||||
} else if (dynamicMapping != null) {
|
||||
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
|
||||
}
|
||||
|
||||
@ -440,8 +442,12 @@ public class MappingBuilder {
|
||||
|
||||
builder.startObject(property.getFieldName());
|
||||
|
||||
if (nestedOrObjectField && dynamicMapping != null) {
|
||||
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
|
||||
if (nestedOrObjectField) {
|
||||
if (annotation.dynamic() != Dynamic.INHERIT) {
|
||||
builder.field(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase());
|
||||
} else if (dynamicMapping != null) {
|
||||
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
addFieldMappingParameters(builder, annotation, nestedOrObjectField);
|
||||
@ -489,8 +495,12 @@ public class MappingBuilder {
|
||||
// main field
|
||||
builder.startObject(property.getFieldName());
|
||||
|
||||
if (nestedOrObjectField && dynamicMapping != null) {
|
||||
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
|
||||
if (nestedOrObjectField) {
|
||||
if (annotation.mainField().dynamic() != Dynamic.INHERIT) {
|
||||
builder.field(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase());
|
||||
} else if (dynamicMapping != null) {
|
||||
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
addFieldMappingParameters(builder, annotation.mainField(), nestedOrObjectField);
|
||||
|
@ -16,6 +16,7 @@
|
||||
package org.springframework.data.elasticsearch.core.mapping;
|
||||
|
||||
import org.elasticsearch.index.VersionType;
|
||||
import org.springframework.data.elasticsearch.annotations.Dynamic;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.core.index.Settings;
|
||||
import org.springframework.data.elasticsearch.core.join.JoinField;
|
||||
@ -160,4 +161,10 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
|
||||
* @since 4.3
|
||||
*/
|
||||
boolean writeTypeHints();
|
||||
|
||||
/**
|
||||
* @return the {@code dynamic} mapping parameter value.
|
||||
* @since 4.3
|
||||
*/
|
||||
Dynamic dynamic();
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Dynamic;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.annotations.Routing;
|
||||
@ -73,6 +74,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
private @Nullable ElasticsearchPersistentProperty joinFieldProperty;
|
||||
private @Nullable VersionType versionType;
|
||||
private boolean createIndexAndMapping;
|
||||
private final Dynamic dynamic;
|
||||
private final Map<String, ElasticsearchPersistentProperty> fieldNamePropertyCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Expression> routingExpressions = new ConcurrentHashMap<>();
|
||||
private @Nullable String routing;
|
||||
@ -102,8 +104,10 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
this.indexName = document.indexName();
|
||||
this.versionType = document.versionType();
|
||||
this.createIndexAndMapping = document.createIndex();
|
||||
this.dynamic = document.dynamic();
|
||||
} else {
|
||||
this.dynamic = Dynamic.INHERIT;
|
||||
}
|
||||
|
||||
Routing routingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Routing.class);
|
||||
|
||||
if (routingAnnotation != null) {
|
||||
@ -559,4 +563,9 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
|
||||
return fieldNamingStrategy;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dynamic dynamic() {
|
||||
return dynamic;
|
||||
}
|
||||
}
|
||||
|
@ -287,8 +287,18 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
|
||||
}
|
||||
|
||||
@Test // #1767
|
||||
@DisplayName("should write dynamic mapping entries")
|
||||
void shouldWriteDynamicMappingEntries() {
|
||||
@DisplayName("should write dynamic mapping annotations")
|
||||
void shouldWriteDynamicMappingAnnotations() {
|
||||
|
||||
IndexOperations indexOps = operations.indexOps(DynamicMappingAnnotationEntity.class);
|
||||
indexOps.create();
|
||||
indexOps.putMapping();
|
||||
|
||||
}
|
||||
|
||||
@Test // #1871
|
||||
@DisplayName("should write dynamic mapping")
|
||||
void shouldWriteDynamicMapping() {
|
||||
|
||||
IndexOperations indexOps = operations.indexOps(DynamicMappingEntity.class);
|
||||
indexOps.create();
|
||||
@ -1104,9 +1114,9 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Document(indexName = "dynamic-mapping")
|
||||
@Document(indexName = "dynamic-mapping-annotation")
|
||||
@DynamicMapping(DynamicMappingValue.False)
|
||||
static class DynamicMappingEntity {
|
||||
static class DynamicMappingAnnotationEntity {
|
||||
|
||||
@Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author;
|
||||
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
|
||||
@ -1124,6 +1134,31 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Document(indexName = "dynamic-mapping", dynamic = Dynamic.FALSE)
|
||||
static class DynamicMappingEntity {
|
||||
|
||||
@Nullable @Field(type = FieldType.Object) //
|
||||
private Map<String, Object> objectInherit;
|
||||
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.FALSE) //
|
||||
private Map<String, Object> objectFalse;
|
||||
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.TRUE) //
|
||||
private Map<String, Object> objectTrue;
|
||||
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.STRICT) //
|
||||
private Map<String, Object> objectStrict;
|
||||
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) //
|
||||
private Map<String, Object> objectRuntime;
|
||||
@Nullable @Field(type = FieldType.Nested) //
|
||||
private List<Map<String, Object>> nestedObjectInherit;
|
||||
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) //
|
||||
private List<Map<String, Object>> nestedObjectFalse;
|
||||
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) //
|
||||
private List<Map<String, Object>> nestedObjectTrue;
|
||||
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) //
|
||||
private List<Map<String, Object>> nestedObjectStrict;
|
||||
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) //
|
||||
private List<Map<String, Object>> nestedObjectRuntime;
|
||||
}
|
||||
|
||||
@Document(indexName = "dynamic-detection-mapping-true")
|
||||
@Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE,
|
||||
dynamicDateFormats = { "MM/dd/yyyy" })
|
||||
|
@ -419,7 +419,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
}
|
||||
|
||||
@Test // DATAES-148, #1767
|
||||
void shouldWriteDynamicMappingSettings() throws JSONException {
|
||||
void shouldWriteDynamicMappingFromAnnotation() throws JSONException {
|
||||
|
||||
String expected = "{\n" + //
|
||||
" \"dynamic\": \"false\",\n" + //
|
||||
@ -451,7 +451,65 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
" }\n" + //
|
||||
"}"; //
|
||||
|
||||
String mapping = getMappingBuilder().buildPropertyMapping(ConfigureDynamicMappingEntity.class);
|
||||
String mapping = getMappingBuilder().buildPropertyMapping(DynamicMappingAnnotationEntity.class);
|
||||
|
||||
assertEquals(expected, mapping, true);
|
||||
}
|
||||
|
||||
@Test // #1871
|
||||
void shouldWriteDynamicMapping() throws JSONException {
|
||||
|
||||
String expected = "{\n" //
|
||||
+ " \"dynamic\": \"false\",\n" //
|
||||
+ " \"properties\": {\n" //
|
||||
+ " \"_class\": {\n" //
|
||||
+ " \"type\": \"keyword\",\n" //
|
||||
+ " \"index\": false,\n" //
|
||||
+ " \"doc_values\": false\n" //
|
||||
+ " },\n" //
|
||||
+ " \"objectInherit\": {\n" //
|
||||
+ " \"type\": \"object\"\n" //
|
||||
+ " },\n" //
|
||||
+ " \"objectFalse\": {\n" //
|
||||
+ " \"dynamic\": \"false\",\n" //
|
||||
+ " \"type\": \"object\"\n" //
|
||||
+ " },\n" //
|
||||
+ " \"objectTrue\": {\n" //
|
||||
+ " \"dynamic\": \"true\",\n" //
|
||||
+ " \"type\": \"object\"\n" //
|
||||
+ " },\n" //
|
||||
+ " \"objectStrict\": {\n" //
|
||||
+ " \"dynamic\": \"strict\",\n" //
|
||||
+ " \"type\": \"object\"\n" //
|
||||
+ " },\n" //
|
||||
+ " \"objectRuntime\": {\n" //
|
||||
+ " \"dynamic\": \"runtime\",\n" //
|
||||
+ " \"type\": \"object\"\n" //
|
||||
+ " },\n" //
|
||||
+ " \"nestedObjectInherit\": {\n" //
|
||||
+ " \"type\": \"nested\"\n" //
|
||||
+ " },\n" //
|
||||
+ " \"nestedObjectFalse\": {\n" //
|
||||
+ " \"dynamic\": \"false\",\n" //
|
||||
+ " \"type\": \"nested\"\n" //
|
||||
+ " },\n" //
|
||||
+ " \"nestedObjectTrue\": {\n" //
|
||||
+ " \"dynamic\": \"true\",\n" //
|
||||
+ " \"type\": \"nested\"\n" //
|
||||
+ " },\n" //
|
||||
+ " \"nestedObjectStrict\": {\n" //
|
||||
+ " \"dynamic\": \"strict\",\n" //
|
||||
+ " \"type\": \"nested\"\n" //
|
||||
+ " },\n" //
|
||||
+ " \"nestedObjectRuntime\": {\n" //
|
||||
+ " \"dynamic\": \"runtime\",\n" //
|
||||
+ " \"type\": \"nested\"\n" //
|
||||
+ " }\n" //
|
||||
+ " }\n" //
|
||||
+ "}\n" //
|
||||
+ "";
|
||||
|
||||
String mapping = getMappingBuilder().buildPropertyMapping(DynamicMappingEntity.class);
|
||||
|
||||
assertEquals(expected, mapping, true);
|
||||
}
|
||||
@ -865,7 +923,8 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
" \"day_of_week\": {\n" + //
|
||||
" \"type\": \"keyword\",\n" + //
|
||||
" \"script\": {\n" + //
|
||||
" \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n" + //
|
||||
" \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n"
|
||||
+ //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
" },\n" + //
|
||||
@ -1441,7 +1500,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
|
||||
@Document(indexName = "test-index-configure-dynamic-mapping")
|
||||
@DynamicMapping(DynamicMappingValue.False)
|
||||
static class ConfigureDynamicMappingEntity {
|
||||
static class DynamicMappingAnnotationEntity {
|
||||
|
||||
@Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author;
|
||||
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
|
||||
@ -1459,6 +1518,32 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Document(indexName = "test-index-configure-dynamic-mapping", dynamic = Dynamic.FALSE)
|
||||
static class DynamicMappingEntity {
|
||||
|
||||
@Nullable @Field(type = FieldType.Object) //
|
||||
private Map<String, Object> objectInherit;
|
||||
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.FALSE) //
|
||||
private Map<String, Object> objectFalse;
|
||||
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.TRUE) //
|
||||
private Map<String, Object> objectTrue;
|
||||
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.STRICT) //
|
||||
private Map<String, Object> objectStrict;
|
||||
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) //
|
||||
private Map<String, Object> objectRuntime;
|
||||
@Nullable @Field(type = FieldType.Nested) //
|
||||
private List<Map<String, Object>> nestedObjectInherit;
|
||||
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) //
|
||||
private List<Map<String, Object>> nestedObjectFalse;
|
||||
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) //
|
||||
private List<Map<String, Object>> nestedObjectTrue;
|
||||
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) //
|
||||
private List<Map<String, Object>> nestedObjectStrict;
|
||||
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) //
|
||||
private List<Map<String, Object>> nestedObjectRuntime;
|
||||
|
||||
}
|
||||
|
||||
static class ValueObject {
|
||||
private final String value;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user