Skip to content

Commit b3f9bdb

Browse files
authored
Support returning the name of the index an entity was persisted to.
Original Pull Request #2435 Closes #2112
1 parent d9bf76f commit b3f9bdb

18 files changed

+331
-111
lines changed

src/main/asciidoc/reference/elasticsearch-object-mapping.adoc

+38-28
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[[elasticsearch.mapping]]
22
= Elasticsearch Object Mapping
33

4-
Spring Data Elasticsearch Object Mapping is the process that maps a Java object - the domain entity - into the JSON
5-
representation that is stored in Elasticsearch and back. The class that is internally used for this mapping is the
4+
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.
5+
The class that is internally used for this mapping is the
66
`MappingElasticsearcvhConverter`.
77

88
[[elasticsearch.mapping.meta-model]]
@@ -52,21 +52,15 @@ The mapping metadata infrastructure is defined in a separate spring-data-commons
5252
[[elasticsearch.mapping.meta-model.annotations.read-write]]
5353
==== Controlling which properties are written to and read from Elasticsearch
5454

55-
This section details the annotations that define if the value of a property is written to or
56-
read from Elasticsearch.
55+
This section details the annotations that define if the value of a property is written to or read from Elasticsearch.
5756

58-
`@Transient`: A property annotated with this annotation will not be written to the mapping, it's value will not be
59-
sent to Elasticsearch and when documents are returned from Elasticsearch, this property will not be set in the
60-
resulting entity.
57+
`@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.
6158

62-
`@ReadOnlyProperty`: A property with this annotaiton will not have its value written to Elasticsearch, but when
63-
returning data, the proeprty will be filled with the value returned in the document from Elasticsearch. One use case
64-
for this are runtime fields defined in the index mapping.
65-
66-
`@WriteOnlyProperty`: A property with this annotaiton will have its value stored in Elasticsearch but will not be set
67-
with any value when reading document. This can be used for example for synthesized fields which should go into the
68-
Elasticsearch index but are not used elsewhere.
59+
`@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.
60+
One use case for this are runtime fields defined in the index mapping.
6961

62+
`@WriteOnlyProperty`: A property with this annotaiton will have its value stored in Elasticsearch but will not be set with any value when reading document.
63+
This can be used for example for synthesized fields which should go into the Elasticsearch index but are not used elsewhere.
7064

7165
[[elasticsearch.mapping.meta-model.annotations.date-formats]]
7266
==== Date format mapping
@@ -110,8 +104,7 @@ The following table shows the different attributes and the mapping created from
110104
NOTE: If you are using a custom date format, you need to use _uuuu_ for the year instead of _yyyy_.
111105
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].
112106

113-
Check the code of the `org.springframework.data.elasticsearch.annotations.DateFormat` enum for a complete list of
114-
predefined values and their patterns.
107+
Check the code of the `org.springframework.data.elasticsearch.annotations.DateFormat` enum for a complete list of predefined values and their patterns.
115108

116109
[[elasticsearch.mapping.meta-model.annotations.range]]
117110
==== Range types
@@ -172,12 +165,13 @@ A `FieldNamingStrategy` applies to all entities; it can be overwritten by settin
172165
[[elasticsearch.mapping.meta-model.annotations.non-field-backed-properties]]
173166
==== Non-field-backed properties
174167

175-
Normally the properties used in an entity are fields of the entity class. There might be cases, when a property value
176-
is calculated in the entity and should be stored in Elasticsearch. In this case, the getter method (`getProperty()`) can be
177-
annotated
178-
with the `@Field` annotation, in addition to that the method must be annotated with `@AccessType(AccessType.Type
179-
.PROPERTY)`. The third annotation that is needed in such a case is `@WriteOnlyProperty`, as such a value is only
180-
written to Elasticsearch. A full example:
168+
Normally the properties used in an entity are fields of the entity class.
169+
There might be cases, when a property value is calculated in the entity and should be stored in Elasticsearch.
170+
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
171+
.PROPERTY)`.
172+
The third annotation that is needed in such a case is `@WriteOnlyProperty`, as such a value is only written to Elasticsearch.
173+
A full example:
174+
181175
====
182176
[source,java]
183177
----
@@ -190,6 +184,19 @@ public String getProperty() {
190184
----
191185
====
192186

187+
[[elasticsearch.mapping.meta-model.annotations.misc]]
188+
==== Other property annotations
189+
190+
===== @IndexedIndexName
191+
192+
This annotation can be set on a String property of an entity.
193+
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.
194+
After an entity is persisted, for example with a call to `ElasticsearchOperations.save(T entity)`, the entity
195+
returned from that call will contain the name of the index that an entity was saved to in that property.
196+
This is useful when the index name is dynamically set by a bean, or when writing to a write alias.
197+
198+
Putting some value into such a property does not set the index into which an entity is stored!
199+
193200
[[elasticsearch.mapping.meta-model.rules]]
194201
=== Mapping Rules
195202

@@ -412,12 +419,15 @@ Looking at the `Configuration` from the <<elasticsearch.mapping.meta-model, prev
412419
[source,java]
413420
----
414421
@Configuration
415-
public class Config extends AbstractElasticsearchConfiguration {
422+
public class Config extends ElasticsearchConfiguration {
416423
417-
@Override
418-
public RestHighLevelClient elasticsearchClient() {
419-
return RestClients.create(ClientConfiguration.create("localhost:9200")).rest();
420-
}
424+
@NonNull
425+
@Override
426+
public ClientConfiguration clientConfiguration() {
427+
return ClientConfiguration.builder() //
428+
.connectedTo("localhost:9200") //
429+
.build();
430+
}
421431
422432
@Bean
423433
@Override
@@ -428,7 +438,7 @@ public class Config extends AbstractElasticsearchConfiguration {
428438
429439
@WritingConverter <2>
430440
static class AddressToMap implements Converter<Address, Map<String, Object>> {
431-
441+
432442
@Override
433443
public Map<String, Object> convert(Address source) {
434444
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.annotations;
17+
18+
import org.springframework.data.annotation.ReadOnlyProperty;
19+
import org.springframework.data.annotation.Transient;
20+
21+
import java.lang.annotation.Documented;
22+
import java.lang.annotation.ElementType;
23+
import java.lang.annotation.Retention;
24+
import java.lang.annotation.RetentionPolicy;
25+
import java.lang.annotation.Target;
26+
27+
/**
28+
* Annotation to mark a String property of an entity to be filled with the name of the index where the entity was
29+
* stored after it is indexed into Elasticsearch. This can be used when the name of the index is dynamically created
30+
* or when a document was indexed into a write alias.
31+
*
32+
* This can not be used to specify the index where an entity should be written to.
33+
*
34+
* @author Peter-Josef Meisch
35+
* @since 5.1
36+
*/
37+
@Retention(RetentionPolicy.RUNTIME)
38+
@Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE })
39+
@Documented
40+
@Field(type = FieldType.Auto) // prevents the property being written to the index mapping
41+
public @interface IndexedIndexName {
42+
}

src/main/java/org/springframework/data/elasticsearch/client/elc/ElasticsearchTemplate.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,8 @@ public String doIndex(IndexQuery query, IndexCoordinates indexCoordinates) {
217217
Object queryObject = query.getObject();
218218

219219
if (queryObject != null) {
220-
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.id(),
221-
indexResponse.seqNo(), indexResponse.primaryTerm(), indexResponse.version())));
220+
query.setObject(updateIndexedObject(queryObject, new IndexedObjectInformation(indexResponse.id(),
221+
indexResponse.index(), indexResponse.seqNo(), indexResponse.primaryTerm(), indexResponse.version())));
222222
}
223223

224224
return indexResponse.id();
@@ -629,7 +629,8 @@ protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkRespon
629629
}
630630

631631
return bulkResponse.items().stream()
632-
.map(item -> IndexedObjectInformation.of(item.id(), item.seqNo(), item.primaryTerm(), item.version()))
632+
.map(item -> new IndexedObjectInformation(item.id(), item.index(), item.seqNo(), item.primaryTerm(),
633+
item.version()))
633634
.collect(Collectors.toList());
634635

635636
}

src/main/java/org/springframework/data/elasticsearch/client/elc/ReactiveElasticsearchTemplate.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ protected <T> Mono<Tuple2<T, IndexResponseMetaData>> doIndex(T entity, IndexCoor
111111
return Mono.just(entity) //
112112
.zipWith(//
113113
Mono.from(execute((ClientCallback<Publisher<IndexResponse>>) client -> client.index(indexRequest))) //
114-
.map(indexResponse -> new IndexResponseMetaData(indexResponse.id(), //
114+
.map(indexResponse -> new IndexResponseMetaData(
115+
indexResponse.id(), //
116+
indexResponse.index(), //
115117
indexResponse.seqNo(), //
116118
indexResponse.primaryTerm(), //
117119
indexResponse.version() //
@@ -139,8 +141,12 @@ public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entitiesPubli
139141
.flatMap(indexAndResponse -> {
140142
T savedEntity = entities.entityAt(indexAndResponse.getT1());
141143
BulkResponseItem response = indexAndResponse.getT2();
142-
updateIndexedObject(savedEntity, IndexedObjectInformation.of(response.id(), response.seqNo(),
143-
response.primaryTerm(), response.version()));
144+
updateIndexedObject(savedEntity, new IndexedObjectInformation( //
145+
response.id(), //
146+
response.index(), //
147+
response.seqNo(), //
148+
response.primaryTerm(), //
149+
response.version()));
144150
return maybeCallbackAfterSave(savedEntity, index);
145151
});
146152
});

src/main/java/org/springframework/data/elasticsearch/client/erhlc/ElasticsearchRestTemplate.java

+13-4
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,12 @@ public String doIndex(IndexQuery query, IndexCoordinates index) {
194194
Object queryObject = query.getObject();
195195

196196
if (queryObject != null) {
197-
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(),
198-
indexResponse.getSeqNo(), indexResponse.getPrimaryTerm(), indexResponse.getVersion())));
197+
query.setObject(updateIndexedObject(queryObject, new IndexedObjectInformation( //
198+
indexResponse.getId(), //
199+
indexResponse.getIndex(), //
200+
indexResponse.getSeqNo(), //
201+
indexResponse.getPrimaryTerm(), //
202+
indexResponse.getVersion())));
199203
}
200204

201205
return indexResponse.getId();
@@ -369,10 +373,15 @@ protected List<IndexedObjectInformation> checkForBulkOperationFailure(BulkRespon
369373
return Stream.of(bulkResponse.getItems()).map(bulkItemResponse -> {
370374
DocWriteResponse response = bulkItemResponse.getResponse();
371375
if (response != null) {
372-
return IndexedObjectInformation.of(response.getId(), response.getSeqNo(), response.getPrimaryTerm(),
376+
return new IndexedObjectInformation( //
377+
response.getId(), //
378+
response.getIndex(), //
379+
response.getSeqNo(), //
380+
response.getPrimaryTerm(), //
373381
response.getVersion());
374382
} else {
375-
return IndexedObjectInformation.of(bulkItemResponse.getId(), null, null, null);
383+
return new IndexedObjectInformation(bulkItemResponse.getId(), bulkItemResponse.getIndex(), null, null,
384+
null);
376385
}
377386

378387
}).collect(Collectors.toList());

src/main/java/org/springframework/data/elasticsearch/client/erhlc/ReactiveElasticsearchTemplate.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,8 @@ public <T> Flux<T> saveAll(Mono<? extends Collection<? extends T>> entitiesPubli
155155
BulkItemResponse bulkItemResponse = indexAndResponse.getT2();
156156

157157
DocWriteResponse response = bulkItemResponse.getResponse();
158-
updateIndexedObject(savedEntity, IndexedObjectInformation.of(response.getId(), response.getSeqNo(),
159-
response.getPrimaryTerm(), response.getVersion()));
158+
updateIndexedObject(savedEntity, new IndexedObjectInformation(response.getId(), response.getIndex(),
159+
response.getSeqNo(), response.getPrimaryTerm(), response.getVersion()));
160160

161161
return maybeCallbackAfterSave(savedEntity, index);
162162
});
@@ -255,10 +255,11 @@ protected <T> Mono<Tuple2<T, IndexResponseMetaData>> doIndex(T entity, IndexCoor
255255
return Mono.just(entity).zipWith(doIndex(request) //
256256
.map(indexResponse -> new IndexResponseMetaData( //
257257
indexResponse.getId(), //
258+
indexResponse.getIndex(), //
258259
indexResponse.getSeqNo(), //
259260
indexResponse.getPrimaryTerm(), //
260261
indexResponse.getVersion() //
261-
))); //
262+
)));
262263
}
263264

264265
@Override

src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java

+14-7
Original file line numberDiff line numberDiff line change
@@ -408,23 +408,29 @@ protected <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedOb
408408
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
409409

410410
// Only deal with text because ES generated Ids are strings!
411-
if (indexedObjectInformation.getId() != null && idProperty != null && idProperty.isReadable()
411+
if (indexedObjectInformation.id() != null && idProperty != null && idProperty.isReadable()
412412
&& idProperty.getType().isAssignableFrom(String.class)) {
413-
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
413+
propertyAccessor.setProperty(idProperty, indexedObjectInformation.id());
414414
}
415415

416-
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
416+
if (indexedObjectInformation.seqNo() != null && indexedObjectInformation.primaryTerm() != null
417417
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
418418
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
419419
// noinspection ConstantConditions
420420
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
421-
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
421+
new SeqNoPrimaryTerm(indexedObjectInformation.seqNo(),
422+
indexedObjectInformation.primaryTerm()));
422423
}
423424

424-
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
425+
if (indexedObjectInformation.version() != null && persistentEntity.hasVersionProperty()) {
425426
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
426427
// noinspection ConstantConditions
427-
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
428+
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.version());
429+
}
430+
431+
var indexedIndexNameProperty = persistentEntity.getIndexedIndexNameProperty();
432+
if (indexedIndexNameProperty != null) {
433+
propertyAccessor.setProperty(indexedIndexNameProperty, indexedObjectInformation.index());
428434
}
429435

430436
// noinspection unchecked
@@ -791,8 +797,9 @@ public T doWith(@Nullable Document document) {
791797

792798
T entity = reader.read(type, documentAfterLoad);
793799

794-
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of( //
800+
IndexedObjectInformation indexedObjectInformation = new IndexedObjectInformation( //
795801
documentAfterLoad.hasId() ? documentAfterLoad.getId() : null, //
802+
documentAfterLoad.getIndex(), //
796803
documentAfterLoad.hasSeqNo() ? documentAfterLoad.getSeqNo() : null, //
797804
documentAfterLoad.hasPrimaryTerm() ? documentAfterLoad.getPrimaryTerm() : null, //
798805
documentAfterLoad.hasVersion() ? documentAfterLoad.getVersion() : null); //

0 commit comments

Comments
 (0)