mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-22 03:52:10 +00:00
Allow to customize the mapped type name for @InnerField and @Field annotations.
Original Pull request: #2950 Closes #2942
This commit is contained in:
parent
eba8eec6c3
commit
06de217ceb
@ -38,6 +38,7 @@ import org.springframework.core.annotation.AliasFor;
|
|||||||
* @author Morgan Lutz
|
* @author Morgan Lutz
|
||||||
* @author Sascha Woo
|
* @author Sascha Woo
|
||||||
* @author Haibo Liu
|
* @author Haibo Liu
|
||||||
|
* @author Andriy Redko
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD })
|
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD })
|
||||||
@ -240,4 +241,11 @@ public @interface Field {
|
|||||||
* @since 5.1
|
* @since 5.1
|
||||||
*/
|
*/
|
||||||
boolean storeEmptyValue() default true;
|
boolean storeEmptyValue() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* overrides the field type in the mapping which otherwise will be taken from corresponding {@link FieldType}
|
||||||
|
*
|
||||||
|
* @since 5.4
|
||||||
|
*/
|
||||||
|
String mappedTypeName() default "";
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import java.lang.annotation.Target;
|
|||||||
* @author Brian Kimmig
|
* @author Brian Kimmig
|
||||||
* @author Morgan Lutz
|
* @author Morgan Lutz
|
||||||
* @author Haibo Liu
|
* @author Haibo Liu
|
||||||
|
* @author Andriy Redko
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.ANNOTATION_TYPE)
|
@Target(ElementType.ANNOTATION_TYPE)
|
||||||
@ -171,4 +172,11 @@ public @interface InnerField {
|
|||||||
* @since 5.4
|
* @since 5.4
|
||||||
*/
|
*/
|
||||||
KnnIndexOptions[] knnIndexOptions() default {};
|
KnnIndexOptions[] knnIndexOptions() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* overrides the field type in the mapping which otherwise will be taken from corresponding {@link FieldType}
|
||||||
|
*
|
||||||
|
* @since 5.4
|
||||||
|
*/
|
||||||
|
String mappedTypeName() default "";
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,7 @@ import com.fasterxml.jackson.databind.util.RawValue;
|
|||||||
* @author Peter-Josef Meisch
|
* @author Peter-Josef Meisch
|
||||||
* @author Xiao Yu
|
* @author Xiao Yu
|
||||||
* @author Subhobrata Dey
|
* @author Subhobrata Dey
|
||||||
|
* @author Andriy Redko
|
||||||
*/
|
*/
|
||||||
public class MappingBuilder {
|
public class MappingBuilder {
|
||||||
|
|
||||||
@ -175,7 +176,8 @@ public class MappingBuilder {
|
|||||||
.findAnnotation(org.springframework.data.elasticsearch.annotations.Document.class);
|
.findAnnotation(org.springframework.data.elasticsearch.annotations.Document.class);
|
||||||
var dynamicMapping = docAnnotation != null ? docAnnotation.dynamic() : null;
|
var dynamicMapping = docAnnotation != null ? docAnnotation.dynamic() : null;
|
||||||
|
|
||||||
mapEntity(objectNode, entity, true, "", false, FieldType.Auto, null, dynamicMapping, runtimeFields);
|
final FieldType fieldType = FieldType.Auto;
|
||||||
|
mapEntity(objectNode, entity, true, "", false, fieldType, fieldType.getMappedName(), null, dynamicMapping, runtimeFields);
|
||||||
|
|
||||||
if (!excludeFromSource.isEmpty()) {
|
if (!excludeFromSource.isEmpty()) {
|
||||||
ObjectNode sourceNode = objectNode.putObject(SOURCE);
|
ObjectNode sourceNode = objectNode.putObject(SOURCE);
|
||||||
@ -210,7 +212,7 @@ public class MappingBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentEntity<?> entity,
|
private void mapEntity(ObjectNode objectNode, @Nullable ElasticsearchPersistentEntity<?> entity,
|
||||||
boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType,
|
boolean isRootObject, String nestedObjectFieldName, boolean nestedOrObjectField, FieldType fieldType, String fieldTypeMappedName,
|
||||||
@Nullable Field parentFieldAnnotation, @Nullable Dynamic dynamicMapping, @Nullable Document runtimeFields)
|
@Nullable Field parentFieldAnnotation, @Nullable Dynamic dynamicMapping, @Nullable Document runtimeFields)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
@ -244,7 +246,7 @@ public class MappingBuilder {
|
|||||||
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
|
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
|
||||||
if (writeNestedProperties) {
|
if (writeNestedProperties) {
|
||||||
|
|
||||||
String type = nestedOrObjectField ? fieldType.getMappedName() : FieldType.Object.getMappedName();
|
String type = nestedOrObjectField ? fieldTypeMappedName : FieldType.Object.getMappedName();
|
||||||
|
|
||||||
ObjectNode nestedObjectNode = objectMapper.createObjectNode();
|
ObjectNode nestedObjectNode = objectMapper.createObjectNode();
|
||||||
nestedObjectNode.put(FIELD_PARAM_TYPE, type);
|
nestedObjectNode.put(FIELD_PARAM_TYPE, type);
|
||||||
@ -370,7 +372,7 @@ public class MappingBuilder {
|
|||||||
nestedPropertyPrefix = nestedPropertyPath;
|
nestedPropertyPrefix = nestedPropertyPath;
|
||||||
|
|
||||||
mapEntity(propertiesNode, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(),
|
mapEntity(propertiesNode, persistentEntity, false, property.getFieldName(), true, fieldAnnotation.type(),
|
||||||
fieldAnnotation, dynamicMapping, null);
|
getMappedTypeName(fieldAnnotation), fieldAnnotation, dynamicMapping, null);
|
||||||
|
|
||||||
nestedPropertyPrefix = currentNestedPropertyPrefix;
|
nestedPropertyPrefix = currentNestedPropertyPrefix;
|
||||||
return;
|
return;
|
||||||
@ -473,7 +475,7 @@ public class MappingBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
propertiesNode.set(property.getFieldName(), objectMapper.createObjectNode() //
|
propertiesNode.set(property.getFieldName(), objectMapper.createObjectNode() //
|
||||||
.put(FIELD_PARAM_TYPE, field.type().getMappedName()) //
|
.put(FIELD_PARAM_TYPE, getMappedTypeName(field)) //
|
||||||
.put(MAPPING_ENABLED, false) //
|
.put(MAPPING_ENABLED, false) //
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -482,6 +484,15 @@ public class MappingBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the mapping type name to be used for the {@link Field}
|
||||||
|
* @param field field to return the mapping type name for
|
||||||
|
* @return the mapping type name
|
||||||
|
*/
|
||||||
|
private String getMappedTypeName(Field field) {
|
||||||
|
return StringUtils.hasText(field.mappedTypeName()) ? field.mappedTypeName() : field.type().getMappedName();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add mapping for @Field annotation
|
* Add mapping for @Field annotation
|
||||||
*
|
*
|
||||||
|
@ -116,6 +116,7 @@ public final class MappingParameters {
|
|||||||
private final boolean store;
|
private final boolean store;
|
||||||
private final TermVector termVector;
|
private final TermVector termVector;
|
||||||
private final FieldType type;
|
private final FieldType type;
|
||||||
|
private final String mappedTypeName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* extracts the mapping parameters from the relevant annotations.
|
* extracts the mapping parameters from the relevant annotations.
|
||||||
@ -141,6 +142,7 @@ public final class MappingParameters {
|
|||||||
store = field.store();
|
store = field.store();
|
||||||
fielddata = field.fielddata();
|
fielddata = field.fielddata();
|
||||||
type = field.type();
|
type = field.type();
|
||||||
|
mappedTypeName = StringUtils.hasText(field.mappedTypeName()) ? field.mappedTypeName() : type.getMappedName();
|
||||||
dateFormats = field.format();
|
dateFormats = field.format();
|
||||||
dateFormatPatterns = field.pattern();
|
dateFormatPatterns = field.pattern();
|
||||||
analyzer = field.analyzer();
|
analyzer = field.analyzer();
|
||||||
@ -187,6 +189,7 @@ public final class MappingParameters {
|
|||||||
store = field.store();
|
store = field.store();
|
||||||
fielddata = field.fielddata();
|
fielddata = field.fielddata();
|
||||||
type = field.type();
|
type = field.type();
|
||||||
|
mappedTypeName = StringUtils.hasText(field.mappedTypeName()) ? field.mappedTypeName() : type.getMappedName();
|
||||||
dateFormats = field.format();
|
dateFormats = field.format();
|
||||||
dateFormatPatterns = field.pattern();
|
dateFormatPatterns = field.pattern();
|
||||||
analyzer = field.analyzer();
|
analyzer = field.analyzer();
|
||||||
@ -245,7 +248,7 @@ public final class MappingParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type != FieldType.Auto) {
|
if (type != FieldType.Auto) {
|
||||||
objectNode.put(FIELD_PARAM_TYPE, type.getMappedName());
|
objectNode.put(FIELD_PARAM_TYPE, mappedTypeName);
|
||||||
|
|
||||||
if (type == FieldType.Date || type == FieldType.Date_Nanos || type == FieldType.Date_Range) {
|
if (type == FieldType.Date || type == FieldType.Date_Nanos || type == FieldType.Date_Range) {
|
||||||
List<String> formats = new ArrayList<>();
|
List<String> formats = new ArrayList<>();
|
||||||
|
@ -59,6 +59,7 @@ import org.springframework.lang.Nullable;
|
|||||||
* @author Brian Kimmig
|
* @author Brian Kimmig
|
||||||
* @author Morgan Lutz
|
* @author Morgan Lutz
|
||||||
* @author Haibo Liu
|
* @author Haibo Liu
|
||||||
|
* @author Andriy Redko
|
||||||
*/
|
*/
|
||||||
@SpringIntegrationTest
|
@SpringIntegrationTest
|
||||||
public abstract class MappingBuilderIntegrationTests extends MappingContextBaseTests {
|
public abstract class MappingBuilderIntegrationTests extends MappingContextBaseTests {
|
||||||
@ -77,6 +78,12 @@ public abstract class MappingBuilderIntegrationTests extends MappingContextBaseT
|
|||||||
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
|
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSupportAllTypes() {
|
||||||
|
IndexOperations indexOperations = operations.indexOps(EntityWithAllTypes.class);
|
||||||
|
indexOperations.createWithMapping();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldNotFailOnCircularReference() {
|
public void shouldNotFailOnCircularReference() {
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@ import org.springframework.lang.Nullable;
|
|||||||
* @author Brian Kimmig
|
* @author Brian Kimmig
|
||||||
* @author Morgan Lutz
|
* @author Morgan Lutz
|
||||||
* @author Haibo Liu
|
* @author Haibo Liu
|
||||||
|
* @author Andriy Redko
|
||||||
*/
|
*/
|
||||||
public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
||||||
|
|
||||||
@ -1242,6 +1243,59 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
|||||||
|
|
||||||
assertEquals(expected, mapping, true);
|
assertEquals(expected, mapping, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // #2942
|
||||||
|
@DisplayName("should use custom mapped name")
|
||||||
|
void shouldUseCustomMappedName() throws JSONException {
|
||||||
|
|
||||||
|
var expected = """
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"_class": {
|
||||||
|
"type": "keyword",
|
||||||
|
"index": false,
|
||||||
|
"doc_values": false
|
||||||
|
},
|
||||||
|
"someText": {
|
||||||
|
"type": "match_only_text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
String mapping = getMappingBuilder().buildPropertyMapping(FieldMappedNameEntity.class);
|
||||||
|
|
||||||
|
assertEquals(expected, mapping, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // #2942
|
||||||
|
@DisplayName("should use custom mapped name for multifield")
|
||||||
|
void shouldUseCustomMappedNameMultiField() throws JSONException {
|
||||||
|
|
||||||
|
var expected = """
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"_class": {
|
||||||
|
"type": "keyword",
|
||||||
|
"index": false,
|
||||||
|
"doc_values": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "match_only_text",
|
||||||
|
"fields": {
|
||||||
|
"lower_case": {
|
||||||
|
"type": "constant_keyword",
|
||||||
|
"normalizer": "lower_case_normalizer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
String mapping = getMappingBuilder().buildPropertyMapping(MultiFieldMappedNameEntity.class);
|
||||||
|
|
||||||
|
assertEquals(expected, mapping, true);
|
||||||
|
}
|
||||||
|
|
||||||
// region entities
|
// region entities
|
||||||
|
|
||||||
@Document(indexName = "ignore-above-index")
|
@Document(indexName = "ignore-above-index")
|
||||||
@ -2503,5 +2557,18 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
|||||||
@Nullable
|
@Nullable
|
||||||
@Field(type = Text) private String otherText;
|
@Field(type = Text) private String otherText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static class FieldMappedNameEntity {
|
||||||
|
@Nullable
|
||||||
|
@Field(type = Text, mappedTypeName = "match_only_text") private String someText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static class MultiFieldMappedNameEntity {
|
||||||
|
@Nullable
|
||||||
|
@MultiField(mainField = @Field(type = FieldType.Text, mappedTypeName = "match_only_text"), otherFields = { @InnerField(suffix = "lower_case",
|
||||||
|
type = FieldType.Keyword, normalizer = "lower_case_normalizer", mappedTypeName = "constant_keyword") }) private String description;
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user