mirror of
				https://github.com/spring-projects/spring-data-elasticsearch.git
				synced 2025-10-30 22:28:47 +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