Support returning the name of the index an entity was persisted to.

Original Pull Request #2435
Closes #2112
This commit is contained in:
Peter-Josef Meisch 2023-01-25 20:17:26 +01:00 committed by GitHub
parent d9bf76fb31
commit b3f9bdb80f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 331 additions and 111 deletions

View File

@ -1,8 +1,8 @@
[[elasticsearch.mapping]]
= Elasticsearch Object Mapping
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON
representation that is stored in Elasticsearch and back. The class that is internally used for this mapping is the
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON representation that is stored in Elasticsearch and back.
The class that is internally used for this mapping is the
`MappingElasticsearcvhConverter`.
[[elasticsearch.mapping.meta-model]]
@ -52,21 +52,15 @@ The mapping metadata infrastructure is defined in a separate spring-data-commons
[[elasticsearch.mapping.meta-model.annotations.read-write]]
==== Controlling which properties are written to and read from Elasticsearch
This section details the annotations that define if the value of a property is written to or
read from Elasticsearch.
This section details the annotations that define if the value of a property is written to or read from Elasticsearch.
`@Transient`: A property annotated with this annotation will not be written to the mapping, it's value will not be
sent to Elasticsearch and when documents are returned from Elasticsearch, this property will not be set in the
resulting entity.
`@Transient`: A property annotated with this annotation will not be written to the mapping, it's value will not be sent to Elasticsearch and when documents are returned from Elasticsearch, this property will not be set in the resulting entity.
`@ReadOnlyProperty`: A property with this annotaiton will not have its value written to Elasticsearch, but when
returning data, the proeprty will be filled with the value returned in the document from Elasticsearch. One use case
for this are runtime fields defined in the index mapping.
`@WriteOnlyProperty`: A property with this annotaiton will have its value stored in Elasticsearch but will not be set
with any value when reading document. This can be used for example for synthesized fields which should go into the
Elasticsearch index but are not used elsewhere.
`@ReadOnlyProperty`: A property with this annotaiton will not have its value written to Elasticsearch, but when returning data, the proeprty will be filled with the value returned in the document from Elasticsearch.
One use case for this are runtime fields defined in the index mapping.
`@WriteOnlyProperty`: A property with this annotaiton will have its value stored in Elasticsearch but will not be set with any value when reading document.
This can be used for example for synthesized fields which should go into the Elasticsearch index but are not used elsewhere.
[[elasticsearch.mapping.meta-model.annotations.date-formats]]
==== Date format mapping
@ -110,8 +104,7 @@ The following table shows the different attributes and the mapping created from
NOTE: If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_.
This is due to a https://www.elastic.co/guide/en/elasticsearch/reference/current/migrate-to-java-time.html#java-time-migration-incompatible-date-formats[change in Elasticsearch 7].
Check the code of the `org.springframework.data.elasticsearch.annotations.DateFormat` enum for a complete list of
predefined values and their patterns.
Check the code of the `org.springframework.data.elasticsearch.annotations.DateFormat` enum for a complete list of predefined values and their patterns.
[[elasticsearch.mapping.meta-model.annotations.range]]
==== Range types
@ -172,12 +165,13 @@ A `FieldNamingStrategy` applies to all entities; it can be overwritten by settin
[[elasticsearch.mapping.meta-model.annotations.non-field-backed-properties]]
==== Non-field-backed properties
Normally the properties used in an entity are fields of the entity class. There might be cases, when a property value
is calculated in the entity and should be stored in Elasticsearch. In this case, the getter method (`getProperty()`) can be
annotated
with the `@Field` annotation, in addition to that the method must be annotated with `@AccessType(AccessType.Type
.PROPERTY)`. The third annotation that is needed in such a case is `@WriteOnlyProperty`, as such a value is only
written to Elasticsearch. A full example:
Normally the properties used in an entity are fields of the entity class.
There might be cases, when a property value is calculated in the entity and should be stored in Elasticsearch.
In this case, the getter method (`getProperty()`) can be annotated with the `@Field` annotation, in addition to that the method must be annotated with `@AccessType(AccessType.Type
.PROPERTY)`.
The third annotation that is needed in such a case is `@WriteOnlyProperty`, as such a value is only written to Elasticsearch.
A full example:
====
[source,java]
----
@ -190,6 +184,19 @@ public String getProperty() {
----
====
[[elasticsearch.mapping.meta-model.annotations.misc]]
==== Other property annotations
===== @IndexedIndexName
This annotation can be set on a String property of an entity.
This property will not be written to the mapping, it will not be stored in Elasticsearch and its value will not be read from an Elasticsearch document.
After an entity is persisted, for example with a call to `ElasticsearchOperations.save(T entity)`, the entity
returned from that call will contain the name of the index that an entity was saved to in that property.
This is useful when the index name is dynamically set by a bean, or when writing to a write alias.
Putting some value into such a property does not set the index into which an entity is stored!
[[elasticsearch.mapping.meta-model.rules]]
=== Mapping Rules
@ -412,12 +419,15 @@ Looking at the `Configuration` from the <<elasticsearch.mapping.meta-model, prev
[source,java]
----
@Configuration
public class Config extends AbstractElasticsearchConfiguration {
public class Config extends ElasticsearchConfiguration {
@Override
public RestHighLevelClient elasticsearchClient() {
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest();
}
@NonNull
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder() //
.connectedTo("localhost:9200") //
.build();
}
@Bean
@Override
@ -428,7 +438,7 @@ public class Config extends AbstractElasticsearchConfiguration {
@WritingConverter <2>
static class AddressToMap implements Converter<Address, Map<String, Object>> {
@Override
public Map<String, Object> convert(Address source) {

View File

@ -0,0 +1,42 @@
/*
* Copyright 2023 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;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Transient;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark a String property of an entity to be filled with the name of the index where the entity was
* stored after it is indexed into Elasticsearch. This can be used when the name of the index is dynamically created
* or when a document was indexed into a write alias.
*
* This can not be used to specify the index where an entity should be written to.
*
* @author Peter-Josef Meisch
* @since 5.1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Documented
@Field(type = FieldType.Auto) // prevents the property being written to the index mapping
public @interface IndexedIndexName {
}

View File

@ -217,8 +217,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Object queryObject = query.getObject();
if (queryObject != null) {
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.id(),
indexResponse.seqNo(), indexResponse.primaryTerm(), indexResponse.version())));
query.setObject(updateIndexedObject(queryObject, new IndexedObjectInformation(indexResponse.id(),
indexResponse.index(), indexResponse.seqNo(), indexResponse.primaryTerm(), indexResponse.version())));
}
return indexResponse.id();
@ -629,7 +629,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
}
return bulkResponse.items().stream()
.map(item -> IndexedObjectInformation.of(item.id(), item.seqNo(), item.primaryTerm(), item.version()))
.map(item -> new IndexedObjectInformation(item.id(), item.index(), item.seqNo(), item.primaryTerm(),
item.version()))
.collect(Collectors.toList());
}

View File

@ -111,7 +111,9 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return Mono.just(entity) //
.zipWith(//
Mono.from(execute((ClientCallback<Publisher<IndexResponse>>) client -> client.index(indexRequest))) //
.map(indexResponse -> new IndexResponseMetaData(indexResponse.id(), //
.map(indexResponse -> new IndexResponseMetaData(
indexResponse.id(), //
indexResponse.index(), //
indexResponse.seqNo(), //
indexResponse.primaryTerm(), //
indexResponse.version() //
@ -139,8 +141,12 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
.flatMap(indexAndResponse -> {
T savedEntity = entities.entityAt(indexAndResponse.getT1());
BulkResponseItem response = indexAndResponse.getT2();
updateIndexedObject(savedEntity, IndexedObjectInformation.of(response.id(), response.seqNo(),
response.primaryTerm(), response.version()));
updateIndexedObject(savedEntity, new IndexedObjectInformation( //
response.id(), //
response.index(), //
response.seqNo(), //
response.primaryTerm(), //
response.version()));
return maybeCallbackAfterSave(savedEntity, index);
});
});

View File

@ -194,8 +194,12 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
Object queryObject = query.getObject();
if (queryObject != null) {
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(),
indexResponse.getSeqNo(), indexResponse.getPrimaryTerm(), indexResponse.getVersion())));
query.setObject(updateIndexedObject(queryObject, new IndexedObjectInformation( //
indexResponse.getId(), //
indexResponse.getIndex(), //
indexResponse.getSeqNo(), //
indexResponse.getPrimaryTerm(), //
indexResponse.getVersion())));
}
return indexResponse.getId();
@ -369,10 +373,15 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
return Stream.of(bulkResponse.getItems()).map(bulkItemResponse -> {
DocWriteResponse response = bulkItemResponse.getResponse();
if (response != null) {
return IndexedObjectInformation.of(response.getId(), response.getSeqNo(), response.getPrimaryTerm(),
return new IndexedObjectInformation( //
response.getId(), //
response.getIndex(), //
response.getSeqNo(), //
response.getPrimaryTerm(), //
response.getVersion());
} else {
return IndexedObjectInformation.of(bulkItemResponse.getId(), null, null, null);
return new IndexedObjectInformation(bulkItemResponse.getId(), bulkItemResponse.getIndex(), null, null,
null);
}
}).collect(Collectors.toList());

View File

@ -155,8 +155,8 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
BulkItemResponse bulkItemResponse = indexAndResponse.getT2();
DocWriteResponse response = bulkItemResponse.getResponse();
updateIndexedObject(savedEntity, IndexedObjectInformation.of(response.getId(), response.getSeqNo(),
response.getPrimaryTerm(), response.getVersion()));
updateIndexedObject(savedEntity, new IndexedObjectInformation(response.getId(), response.getIndex(),
response.getSeqNo(), response.getPrimaryTerm(), response.getVersion()));
return maybeCallbackAfterSave(savedEntity, index);
});
@ -255,10 +255,11 @@ public class ReactiveElasticsearchTemplate extends AbstractReactiveElasticsearch
return Mono.just(entity).zipWith(doIndex(request) //
.map(indexResponse -> new IndexResponseMetaData( //
indexResponse.getId(), //
indexResponse.getIndex(), //
indexResponse.getSeqNo(), //
indexResponse.getPrimaryTerm(), //
indexResponse.getVersion() //
))); //
)));
}
@Override

View File

@ -408,23 +408,29 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isReadable()
if (indexedObjectInformation.id() != null && idProperty != null && idProperty.isReadable()
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
propertyAccessor.setProperty(idProperty, indexedObjectInformation.id());
}
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
if (indexedObjectInformation.seqNo() != null && indexedObjectInformation.primaryTerm() != null
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
new SeqNoPrimaryTerm(indexedObjectInformation.seqNo(),
indexedObjectInformation.primaryTerm()));
}
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
if (indexedObjectInformation.version() != null && persistentEntity.hasVersionProperty()) {
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.version());
}
var indexedIndexNameProperty = persistentEntity.getIndexedIndexNameProperty();
if (indexedIndexNameProperty != null) {
propertyAccessor.setProperty(indexedIndexNameProperty, indexedObjectInformation.index());
}
// noinspection unchecked
@ -791,8 +797,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
T entity = reader.read(type, documentAfterLoad);
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( //
IndexedObjectInformation indexedObjectInformation = new IndexedObjectInformation( //
documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, //
documentAfterLoad.getIndex(), //
documentAfterLoad.hasSeqNo() ? documentAfterLoad.getSeqNo() : null, //
documentAfterLoad.hasPrimaryTerm() ? documentAfterLoad.getPrimaryTerm() : null, //
documentAfterLoad.hasVersion() ? documentAfterLoad.getVersion() : null); //

View File

@ -261,23 +261,28 @@ abstract public class AbstractReactiveElasticsearchTemplate
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isReadable()
if (indexedObjectInformation.id() != null && idProperty != null && idProperty.isReadable()
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
propertyAccessor.setProperty(idProperty, indexedObjectInformation.id());
}
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
if (indexedObjectInformation.seqNo() != null && indexedObjectInformation.primaryTerm() != null
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
new SeqNoPrimaryTerm(indexedObjectInformation.seqNo(), indexedObjectInformation.primaryTerm()));
}
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
if (indexedObjectInformation.version() != null && persistentEntity.hasVersionProperty()) {
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.version());
}
var indexedIndexNameProperty = persistentEntity.getIndexedIndexNameProperty();
if (indexedIndexNameProperty != null) {
propertyAccessor.setProperty(indexedIndexNameProperty, indexedObjectInformation.index());
}
// noinspection unchecked
@ -286,7 +291,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
} else {
EntityOperations.AdaptableEntity<T> adaptableEntity = entityOperations.forEntity(entity,
converter.getConversionService(), routingResolver);
adaptableEntity.populateIdIfNecessary(indexedObjectInformation.getId());
adaptableEntity.populateIdIfNecessary(indexedObjectInformation.id());
}
return entity;
}
@ -317,8 +322,9 @@ abstract public class AbstractReactiveElasticsearchTemplate
.map(it -> {
T savedEntity = it.getT1();
IndexResponseMetaData indexResponseMetaData = it.getT2();
return updateIndexedObject(savedEntity, IndexedObjectInformation.of( //
return updateIndexedObject(savedEntity, new IndexedObjectInformation( //
indexResponseMetaData.id(), //
indexResponseMetaData.index(), //
indexResponseMetaData.seqNo(), //
indexResponseMetaData.primaryTerm(), //
indexResponseMetaData.version()));
@ -571,8 +577,9 @@ abstract public class AbstractReactiveElasticsearchTemplate
T entity = reader.read(type, documentAfterLoad);
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( //
IndexedObjectInformation indexedObjectInformation = new IndexedObjectInformation( //
documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, //
documentAfterLoad.getIndex(), //
documentAfterLoad.hasSeqNo() ? documentAfterLoad.getSeqNo() : null, //
documentAfterLoad.hasPrimaryTerm() ? documentAfterLoad.getPrimaryTerm() : null, //
documentAfterLoad.hasVersion() ? documentAfterLoad.getVersion() : null); //
@ -685,7 +692,7 @@ abstract public class AbstractReactiveElasticsearchTemplate
/**
* Value class to capture client independent information from a response to an index request.
*/
public record IndexResponseMetaData(String id, long seqNo, long primaryTerm, long version) {
public record IndexResponseMetaData(String id, String index, long seqNo, long primaryTerm, long version) {
}
// endregion

View File

@ -24,42 +24,12 @@ import org.springframework.lang.Nullable;
* @author Roman Puchkovskiy
* @since 4.1
*/
public class IndexedObjectInformation {
@Nullable private final String id;
@Nullable private final Long seqNo;
@Nullable private final Long primaryTerm;
@Nullable private final Long version;
private IndexedObjectInformation(@Nullable String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
@Nullable Long version) {
this.id = id;
this.seqNo = seqNo;
this.primaryTerm = primaryTerm;
this.version = version;
}
public static IndexedObjectInformation of(@Nullable String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
@Nullable Long version) {
return new IndexedObjectInformation(id, seqNo, primaryTerm, version);
}
@Nullable
public String getId() {
return id;
}
@Nullable
public Long getSeqNo() {
return seqNo;
}
@Nullable
public Long getPrimaryTerm() {
return primaryTerm;
}
@Nullable
public Long getVersion() {
return version;
}
public record IndexedObjectInformation( //
@Nullable String id, //
/** @since 5.1 */ //
@Nullable String index, //
@Nullable Long seqNo, //
@Nullable Long primaryTerm, //
@Nullable Long version //
) {
}

View File

@ -409,15 +409,15 @@ public class MappingElasticsearchConverter
PersistentPropertyAccessor<R> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
conversionService);
for (ElasticsearchPersistentProperty prop : entity) {
for (ElasticsearchPersistentProperty property : entity) {
if (entity.isCreatorArgument(prop) || !prop.isReadable()) {
if (entity.isCreatorArgument(property) || !property.isReadable() || property.isIndexedIndexNameProperty()) {
continue;
}
Object value = valueProvider.getPropertyValue(prop);
Object value = valueProvider.getPropertyValue(property);
if (value != null) {
accessor.setProperty(prop, value);
accessor.setProperty(property, value);
}
}
@ -939,7 +939,7 @@ public class MappingElasticsearchConverter
for (ElasticsearchPersistentProperty property : entity) {
if (!property.isWritable()) {
if (!property.isWritable() || property.isIndexedIndexNameProperty()) {
continue;
}

View File

@ -107,7 +107,7 @@ public interface Document extends StringObjectMap<Document> {
}
/**
* @return the index if this document was retrieved from an index
* @return the index if this document was retrieved from an index or was just stored.
* @since 4.1
*/
@Nullable

View File

@ -133,6 +133,14 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
}
}
/**
* @return the property annotated with {@link org.springframework.data.elasticsearch.annotations.IndexedIndexName} if
* it exists, otherwise null
* @since 5.1
*/
@Nullable
ElasticsearchPersistentProperty getIndexedIndexNameProperty();
/**
* returns the default settings for an index.
*

View File

@ -98,6 +98,13 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty<Elas
*/
boolean isCompletionProperty();
/**
* @return {@literal true} if this is a property annotated with
* {@link org.springframework.data.elasticsearch.annotations.IndexedIndexName}.
* @since 5.1
*/
boolean isIndexedIndexNameProperty();
/**
* calls {@link #getActualType()} but returns null when an exception is thrown
*

View File

@ -27,6 +27,7 @@ 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.IndexedIndexName;
import org.springframework.data.elasticsearch.annotations.Routing;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.core.index.Settings;
@ -71,6 +72,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
private final Lazy<SettingsParameter> settingsParameter;
private @Nullable ElasticsearchPersistentProperty seqNoPrimaryTermProperty;
private @Nullable ElasticsearchPersistentProperty joinFieldProperty;
private @Nullable ElasticsearchPersistentProperty indexedIndexNameProperty;
private @Nullable Document.VersionType versionType;
private boolean createIndexAndMapping;
private final Dynamic dynamic;
@ -218,6 +220,20 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
}
}
if (property.isIndexedIndexNameProperty()) {
if (!property.getActualType().isAssignableFrom(String.class)) {
throw new MappingException(String.format("@IndexedIndexName annotation must be put on String property"));
}
if (indexedIndexNameProperty != null) {
throw new MappingException(
String.format("@IndexedIndexName annotation can only be put on one property in an entity"));
}
this.indexedIndexNameProperty = property;
}
Class<?> actualType = property.getActualTypeOrNull();
if (actualType == JoinField.class) {
ElasticsearchPersistentProperty joinProperty = this.joinFieldProperty;
@ -280,6 +296,12 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
return joinFieldProperty;
}
@Nullable
@Override
public ElasticsearchPersistentProperty getIndexedIndexNameProperty() {
return indexedIndexNameProperty;
}
// region SpEL handling
/**
* resolves all the names in the IndexCoordinates object. If a name cannot be resolved, the original name is returned.

View File

@ -31,6 +31,7 @@ import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.annotations.GeoShapeField;
import org.springframework.data.elasticsearch.annotations.IndexedIndexName;
import org.springframework.data.elasticsearch.annotations.MultiField;
import org.springframework.data.elasticsearch.annotations.ValueConverter;
import org.springframework.data.elasticsearch.annotations.WriteOnlyProperty;
@ -358,4 +359,8 @@ public class SimpleElasticsearchPersistentProperty extends
return getActualType() == Completion.class;
}
@Override
public boolean isIndexedIndexNameProperty() {
return isAnnotationPresent(IndexedIndexName.class);
}
}

View File

@ -58,16 +58,9 @@ import org.springframework.data.annotation.Version;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.*;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.JoinTypeRelation;
import org.springframework.data.elasticsearch.annotations.JoinTypeRelations;
import org.springframework.data.elasticsearch.annotations.MultiField;
import org.springframework.data.elasticsearch.annotations.ScriptedField;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.annotations.WriteOnlyProperty;
import org.springframework.data.elasticsearch.client.erhlc.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.document.Explanation;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
@ -78,6 +71,7 @@ import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.join.JoinField;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
@ -137,6 +131,7 @@ public abstract class ElasticsearchIntegrationTests {
indexNameProvider.increment();
indexOperations = operations.indexOps(SampleEntity.class);
indexOperations.createWithMapping();
operations.indexOps(IndexedIndexNameEntity.class).createWithMapping();
}
@Test
@ -3573,6 +3568,18 @@ public abstract class ElasticsearchIntegrationTests {
operations.index(query, IndexCoordinates.of(indexNameProvider.indexName()));
}
@Test // #2112
@DisplayName("should set IndexedIndexName property")
void shouldSetIndexedIndexNameProperty() {
var entity = new IndexedIndexNameEntity();
entity.setId("42");
entity.setSomeText("someText");
var saved = operations.save(entity);
assertThat(saved.getIndexedIndexName()).isEqualTo(indexNameProvider.indexName() + "-indexedindexname");
}
@Test // #1945
@DisplayName("should error on sort with unmapped field and default settings")
void shouldErrorOnSortWithUnmappedFieldAndDefaultSettings() {
@ -4663,5 +4670,42 @@ public abstract class ElasticsearchIntegrationTests {
return result;
}
}
@Document(indexName = "#{@indexNameProvider.indexName()}-indexedindexname")
private static class IndexedIndexNameEntity {
@Nullable
@Id private String id;
@Nullable
@Field(type = Text) private String someText;
@Nullable
@IndexedIndexName private String indexedIndexName;
@Nullable
public String getId() {
return id;
}
public void setId(@Nullable String id) {
this.id = id;
}
@Nullable
public String getSomeText() {
return someText;
}
public void setSomeText(@Nullable String someText) {
this.someText = someText;
}
@Nullable
public String getIndexedIndexName() {
return indexedIndexName;
}
public void setIndexedIndexName(@Nullable String indexedIndexName) {
this.indexedIndexName = indexedIndexName;
}
}
// endregion
}

View File

@ -20,6 +20,7 @@ import static org.assertj.core.api.Assertions.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
import org.springframework.data.elasticsearch.annotations.IndexedIndexName;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@ -105,6 +106,7 @@ public abstract class ReactiveElasticsearchIntegrationTests {
indexNameProvider.increment();
operations.indexOps(SampleEntity.class).createWithMapping().block();
operations.indexOps(IndexedIndexNameEntity.class).createWithMapping().block();
}
@Test
@ -156,6 +158,19 @@ public abstract class ReactiveElasticsearchIntegrationTests {
.verifyComplete();
}
@Test // #2112
@DisplayName("should set IndexedIndexName property")
void shouldSetIndexedIndexNameProperty() {
var entity = new IndexedIndexNameEntity();
entity.setId("42");
entity.setSomeText("someText");
var saved = operations.save(entity).block();
assertThat(saved.getIndexedIndexName()).isEqualTo(indexNameProvider.indexName() + "-indexedindexname");
}
private Mono<Boolean> documentWithIdExistsInIndex(String id, String index) {
return operations.exists(id, IndexCoordinates.of(index));
}
@ -1528,5 +1543,41 @@ public abstract class ReactiveElasticsearchIntegrationTests {
this.part2 = part2;
}
}
// endregion
@Document(indexName = "#{@indexNameProvider.indexName()}-indexedindexname")
private static class IndexedIndexNameEntity {
@Nullable
@Id private String id;
@Nullable
@Field(type = Text) private String someText;
@Nullable
@IndexedIndexName
private String indexedIndexName;
@Nullable
public String getId() {
return id;
}
public void setId(@Nullable String id) {
this.id = id;
}
@Nullable
public String getSomeText() {
return someText;
}
public void setSomeText(@Nullable String someText) {
this.someText = someText;
}
@Nullable
public String getIndexedIndexName() {
return indexedIndexName;
}
public void setIndexedIndexName(@Nullable String indexedIndexName) {
this.indexedIndexName = indexedIndexName;
}
} // endregion
}

View File

@ -1043,6 +1043,28 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
assertEquals(expected, mapping, true);
}
@Test // #2112
@DisplayName("should not write mapping for property with IndexedIndexName anotation")
void shouldNotWriteMappingForPropertyWithIndexedIndexNameAnotation() throws JSONException {
var expected = """
{
"properties": {
"_class": {
"type": "keyword",
"index": false,
"doc_values": false
},
"someText": {
"type": "text"
}
}
}
""";
String mapping = getMappingBuilder().buildPropertyMapping(IndexedIndexNameEntity.class);
assertEquals(expected, mapping, true);
}
// region entities
@Document(indexName = "ignore-above-index")
@ -2193,5 +2215,13 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
@Nullable
@Field(name = "excluded-text", type = Text, excludeFromSource = true) private String excludedText;
}
private static class IndexedIndexNameEntity {
@Nullable
@Field(type = Text) private String someText;
@Nullable
@IndexedIndexName
private String storedIndexName;
}
// endregion
}