mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-29 15:22:11 +00:00
Support returning the name of the index an entity was persisted to.
Original Pull Request #2435 Closes #2112
This commit is contained in:
parent
d9bf76fb31
commit
b3f9bdb80f
@ -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,11 +419,14 @@ Looking at the `Configuration` from the <<elasticsearch.mapping.meta-model, prev
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class Config extends AbstractElasticsearchConfiguration {
|
||||
public class Config extends ElasticsearchConfiguration {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RestHighLevelClient elasticsearchClient() {
|
||||
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest();
|
||||
public ClientConfiguration clientConfiguration() {
|
||||
return ClientConfiguration.builder() //
|
||||
.connectedTo("localhost:9200") //
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
@ -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 {
|
||||
}
|
@ -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());
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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); //
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 //
|
||||
) {
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user