Improve handling of immutable classes.

Original Pull Request #1801
Closes #1800
This commit is contained in:
Peter-Josef Meisch 2021-05-03 21:45:22 +02:00 committed by GitHub
parent 502ce0b6aa
commit 159687e241
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 255 additions and 82 deletions

View File

@ -188,7 +188,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
IndexQuery query = getIndexQuery(entityAfterBeforeConvert);
doIndex(query, index);
T entityAfterAfterSave = maybeCallbackAfterSave(entityAfterBeforeConvert, index);
T entityAfterAfterSave = (T) maybeCallbackAfterSave(query.getObject(), index);
return entityAfterAfterSave;
}
@ -215,13 +215,18 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
List<IndexQuery> indexQueries = Streamable.of(entities).stream().map(this::getIndexQuery)
.collect(Collectors.toList());
if (!indexQueries.isEmpty()) {
List<IndexedObjectInformation> indexedObjectInformations = bulkIndex(indexQueries, index);
Iterator<IndexedObjectInformation> iterator = indexedObjectInformations.iterator();
entities.forEach(entity -> updateIndexedObject(entity, iterator.next()));
if (indexQueries.isEmpty()) {
return Collections.emptyList();
}
return indexQueries.stream().map(IndexQuery::getObject).map(entity -> (T) entity).collect(Collectors.toList());
List<IndexedObjectInformation> indexedObjectInformations = bulkIndex(indexQueries, index);
Iterator<IndexedObjectInformation> iterator = indexedObjectInformations.iterator();
// noinspection unchecked
return indexQueries.stream() //
.map(IndexQuery::getObject) //
.map(entity -> (T) updateIndexedObject(entity, iterator.next())) //
.collect(Collectors.toList()); //
}
@Override
@ -419,7 +424,9 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
Assert.notNull(query.getId(), "No document id defined for MoreLikeThisQuery");
MoreLikeThisQueryBuilder moreLikeThisQueryBuilder = requestFactory.moreLikeThisQueryBuilder(query, index);
return search(new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(), clazz, index);
return search(
new NativeSearchQueryBuilder().withQuery(moreLikeThisQueryBuilder).withPageable(query.getPageable()).build(),
clazz, index);
}
@Override
@ -611,7 +618,7 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}).collect(Collectors.toList());
}
protected void updateIndexedObject(Object entity, IndexedObjectInformation indexedObjectInformation) {
protected <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
ElasticsearchPersistentEntity<?> persistentEntity = elasticsearchConverter.getMappingContext()
.getPersistentEntity(entity.getClass());
@ -621,22 +628,30 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
if (indexedObjectInformation.getId() != null && idProperty != null
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
}
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
}
// noinspection unchecked
T updatedEntity = (T) propertyAccessor.getBean();
return updatedEntity;
}
return entity;
}
ElasticsearchPersistentEntity<?> getRequiredPersistentEntity(Class<?> clazz) {
@ -807,13 +822,16 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
protected void updateIndexedObjectsWithQueries(List<?> queries,
List<IndexedObjectInformation> indexedObjectInformations) {
for (int i = 0; i < queries.size(); i++) {
Object query = queries.get(i);
if (query instanceof IndexQuery) {
IndexQuery indexQuery = (IndexQuery) query;
Object queryObject = indexQuery.getObject();
if (queryObject != null) {
updateIndexedObject(queryObject, indexedObjectInformations.get(i));
indexQuery.setObject(updateIndexedObject(queryObject, indexedObjectInformations.get(i)));
}
}
}
@ -848,6 +866,10 @@ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOper
}
T entity = reader.read(type, document);
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of(
document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(),
document.getVersion());
entity = updateIndexedObject(entity, indexedObjectInformation);
return maybeCallbackAfterConvert(entity, document, index);
}
}

View File

@ -157,9 +157,10 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
IndexResponse indexResponse = execute(client -> client.index(request, RequestOptions.DEFAULT));
Object queryObject = query.getObject();
if (queryObject != null) {
updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(), indexResponse.getSeqNo(),
indexResponse.getPrimaryTerm(), indexResponse.getVersion()));
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(indexResponse.getId(),
indexResponse.getSeqNo(), indexResponse.getPrimaryTerm(), indexResponse.getVersion())));
}
return indexResponse.getId();
@ -168,6 +169,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate {
@Override
@Nullable
public <T> T get(String id, Class<T> clazz, IndexCoordinates index) {
GetRequest request = requestFactory.getRequest(id, routingResolver.getRouting(), index);
GetResponse response = execute(client -> client.get(request, RequestOptions.DEFAULT));

View File

@ -177,8 +177,8 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate {
Object queryObject = query.getObject();
if (queryObject != null) {
updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(),
response.getPrimaryTerm(), response.getVersion()));
query.setObject(updateIndexedObject(queryObject, IndexedObjectInformation.of(documentId, response.getSeqNo(),
response.getPrimaryTerm(), response.getVersion())));
}
return documentId;

View File

@ -477,7 +477,6 @@ class EntityOperations {
*/
private static class AdaptibleMappedEntity<T> extends MappedEntity<T> implements AdaptibleEntity<T> {
private final T bean;
private final ElasticsearchPersistentEntity<?> entity;
private final ConvertingPropertyAccessor<T> propertyAccessor;
private final IdentifierAccessor identifierAccessor;
@ -490,7 +489,6 @@ class EntityOperations {
super(entity, identifierAccessor, propertyAccessor);
this.bean = bean;
this.entity = entity;
this.propertyAccessor = propertyAccessor;
this.identifierAccessor = identifierAccessor;
@ -510,6 +508,11 @@ class EntityOperations {
new ConvertingPropertyAccessor<>(propertyAccessor, conversionService), conversionService, routingResolver);
}
@Override
public T getBean() {
return propertyAccessor.getBean();
}
@Nullable
@Override
public T populateIdIfNecessary(@Nullable Object id) {
@ -584,7 +587,7 @@ class EntityOperations {
@Override
public String getRouting() {
String routing = routingResolver.getRouting(bean);
String routing = routingResolver.getRouting(propertyAccessor.getBean());
if (routing != null) {
return routing;

View File

@ -25,12 +25,12 @@ import org.springframework.lang.Nullable;
* @since 4.1
*/
public class IndexedObjectInformation {
private final String id;
@Nullable private final String id;
@Nullable private final Long seqNo;
@Nullable private final Long primaryTerm;
@Nullable private final Long version;
private IndexedObjectInformation(String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
private IndexedObjectInformation(@Nullable String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
@Nullable Long version) {
this.id = id;
this.seqNo = seqNo;
@ -38,11 +38,12 @@ public class IndexedObjectInformation {
this.version = version;
}
public static IndexedObjectInformation of(String id, @Nullable Long seqNo, @Nullable Long primaryTerm,
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;
}

View File

@ -275,27 +275,42 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
private <T> T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) {
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(entity, converter.getConversionService(),
routingResolver);
adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId());
ElasticsearchPersistentEntity<?> persistentEntity = getPersistentEntityFor(entity.getClass());
ElasticsearchPersistentEntity<?> persistentEntity = converter.getMappingContext()
.getPersistentEntity(entity.getClass());
if (persistentEntity != null) {
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with text because ES generated Ids are strings!
if (indexedObjectInformation.getId() != null && idProperty != null
&& idProperty.getType().isAssignableFrom(String.class)) {
propertyAccessor.setProperty(idProperty, indexedObjectInformation.getId());
}
if (indexedObjectInformation.getSeqNo() != null && indexedObjectInformation.getPrimaryTerm() != null
&& persistentEntity.hasSeqNoPrimaryTermProperty()) {
ElasticsearchPersistentProperty seqNoPrimaryTermProperty = persistentEntity.getSeqNoPrimaryTermProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(seqNoPrimaryTermProperty,
new SeqNoPrimaryTerm(indexedObjectInformation.getSeqNo(), indexedObjectInformation.getPrimaryTerm()));
}
if (indexedObjectInformation.getVersion() != null && persistentEntity.hasVersionProperty()) {
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// noinspection ConstantConditions
propertyAccessor.setProperty(versionProperty, indexedObjectInformation.getVersion());
}
}
// noinspection unchecked
T updatedEntity = (T) propertyAccessor.getBean();
return updatedEntity;
} else {
AdaptibleEntity<T> adaptibleEntity = operations.forEntity(entity, converter.getConversionService(),
routingResolver);
adaptibleEntity.populateIdIfNecessary(indexedObjectInformation.getId());
}
return entity;
}
@ -457,7 +472,7 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
DocumentCallback<T> callback = new ReadDocumentCallback<>(converter, entityType, index);
return doGet(id, index).flatMap(it -> callback.toEntity(DocumentAdapters.from(it)));
return doGet(id, index).flatMap(response -> callback.toEntity(DocumentAdapters.from(response)));
}
private Mono<GetResult> doGet(String id, IndexCoordinates index) {
@ -1097,6 +1112,10 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera
}
T entity = reader.read(type, document);
IndexedObjectInformation indexedObjectInformation = IndexedObjectInformation.of(
document.hasId() ? document.getId() : null, document.getSeqNo(), document.getPrimaryTerm(),
document.getVersion());
entity = updateIndexedObject(entity, indexedObjectInformation);
return maybeCallAfterConvert(entity, document, index);
}
}

View File

@ -15,6 +15,7 @@
*/
package org.springframework.data.elasticsearch.core.join;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.lang.Nullable;
/**
@ -35,6 +36,7 @@ public class JoinField<ID> {
this(name, null);
}
@PersistenceConstructor
public JoinField(String name, @Nullable ID parent) {
this.name = name;
this.parent = parent;

View File

@ -35,7 +35,6 @@ import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mapping.model.FieldNamingStrategy;
import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory;
import org.springframework.data.spel.ExpressionDependencies;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
@ -238,19 +237,6 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
getType());
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.BasicPersistentEntity#setPersistentPropertyAccessorFactory(org.springframework.data.mapping.model.PersistentPropertyAccessorFactory)
*/
@SuppressWarnings("SpellCheckingInspection")
@Override
public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory factory) {
// Do nothing to avoid the usage of ClassGeneratingPropertyAccessorFactory for now
// DATACMNS-1322 switches to proper immutability behavior which Spring Data Elasticsearch
// cannot yet implement
}
@Nullable
@Override
public ElasticsearchPersistentProperty getPersistentPropertyWithFieldName(String fieldName) {

View File

@ -279,11 +279,6 @@ public class SimpleElasticsearchPersistentProperty extends
throw new UnsupportedOperationException();
}
@Override
public boolean isImmutable() {
return false;
}
@Override
public boolean isSeqNoPrimaryTermProperty() {
return isSeqNoPrimaryTerm;

View File

@ -60,7 +60,6 @@ import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -137,7 +136,8 @@ public abstract class ElasticsearchTemplateTests {
@BeforeEach
public void before() {
indexOperations = operations.indexOps(SampleEntity.class);
deleteIndices();
operations.indexOps(IndexCoordinates.of("*")).delete();
indexOperations.create();
indexOperations.putMapping(SampleEntity.class);
@ -155,29 +155,6 @@ public abstract class ElasticsearchTemplateTests {
indexOpsJoinEntity.putMapping(SampleJoinEntity.class);
}
@AfterEach
public void after() {
deleteIndices();
}
private void deleteIndices() {
indexOperations.delete();
operations.indexOps(SampleEntityUUIDKeyed.class).delete();
operations.indexOps(UseServerConfigurationEntity.class).delete();
operations.indexOps(SampleMappingEntity.class).delete();
operations.indexOps(Book.class).delete();
operations.indexOps(IndexCoordinates.of(INDEX_1_NAME)).delete();
operations.indexOps(IndexCoordinates.of(INDEX_2_NAME)).delete();
operations.indexOps(IndexCoordinates.of(INDEX_3_NAME)).delete();
operations.indexOps(SearchHitsEntity.class).delete();
operations.indexOps(HighlightEntity.class).delete();
operations.indexOps(OptimisticEntity.class).delete();
operations.indexOps(OptimisticAndVersionedEntity.class).delete();
operations.indexOps(IndexCoordinates.of(INDEX_NAME_JOIN_SAMPLE_ENTITY)).delete();
}
@Test // DATAES-106
public void shouldReturnCountForGivenCriteriaQuery() {
@ -1126,7 +1103,8 @@ public abstract class ElasticsearchTemplateTests {
Collection<String> ids = IntStream.rangeClosed(1, 10).mapToObj(i -> nextIdAsString()).collect(Collectors.toList());
ids.add(referenceId);
ids.stream()
.map(id -> getIndexQuery(SampleEntity.builder().id(id).message(sampleMessage).version(System.currentTimeMillis()).build()))
.map(id -> getIndexQuery(
SampleEntity.builder().id(id).message(sampleMessage).version(System.currentTimeMillis()).build()))
.forEach(indexQuery -> operations.index(indexQuery, index));
indexOperations.refresh();
@ -1141,7 +1119,8 @@ public abstract class ElasticsearchTemplateTests {
assertThat(searchHits.getTotalHits()).isEqualTo(10);
assertThat(searchHits.getSearchHits()).hasSize(5);
Collection<String> returnedIds = searchHits.getSearchHits().stream().map(SearchHit::getId).collect(Collectors.toList());
Collection<String> returnedIds = searchHits.getSearchHits().stream().map(SearchHit::getId)
.collect(Collectors.toList());
moreLikeThisQuery.setPageable(PageRequest.of(1, 5));
@ -3588,6 +3567,24 @@ public abstract class ElasticsearchTemplateTests {
assertThat(explanation).isNotNull();
}
@Test // #1800
@DisplayName("should work with immutable classes")
void shouldWorkWithImmutableClasses() {
ImmutableEntity entity = new ImmutableEntity(null, "some text", null);
ImmutableEntity saved = operations.save(entity);
assertThat(saved).isNotNull();
assertThat(saved.getId()).isNotEmpty();
SeqNoPrimaryTerm seqNoPrimaryTerm = saved.getSeqNoPrimaryTerm();
assertThat(seqNoPrimaryTerm).isNotNull();
ImmutableEntity retrieved = operations.get(saved.getId(), ImmutableEntity.class);
assertThat(retrieved).isEqualTo(saved);
}
// region entities
@Document(indexName = INDEX_NAME_SAMPLE_ENTITY)
@Setting(shards = 1, replicas = 0, refreshInterval = "-1")
@ -4366,5 +4363,61 @@ public abstract class ElasticsearchTemplateTests {
this.text = text;
}
}
//endregion
@Document(indexName = "immutable-class")
private static final class ImmutableEntity {
@Id private final String id;
@Field(type = FieldType.Text) private final String text;
@Nullable private final SeqNoPrimaryTerm seqNoPrimaryTerm;
public ImmutableEntity(@Nullable String id, String text, @Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) {
this.id = id;
this.text = text;
this.seqNoPrimaryTerm = seqNoPrimaryTerm;
}
public String getId() {
return id;
}
public String getText() {
return text;
}
@Nullable
public SeqNoPrimaryTerm getSeqNoPrimaryTerm() {
return seqNoPrimaryTerm;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ImmutableEntity that = (ImmutableEntity) o;
if (!id.equals(that.id))
return false;
if (!text.equals(that.text))
return false;
return seqNoPrimaryTerm != null ? seqNoPrimaryTerm.equals(that.seqNoPrimaryTerm) : that.seqNoPrimaryTerm == null;
}
@Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + text.hashCode();
result = 31 * result + (seqNoPrimaryTerm != null ? seqNoPrimaryTerm.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ImmutableEntity{" + "id='" + id + '\'' + ", text='" + text + '\'' + ", seqNoPrimaryTerm="
+ seqNoPrimaryTerm + '}';
}
}
// endregion
}

View File

@ -36,6 +36,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@ -64,6 +65,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.UncategorizedElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.Mapping;
import org.springframework.data.elasticsearch.annotations.Setting;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
@ -1149,6 +1151,26 @@ public class ReactiveElasticsearchTemplateIntegrationTests {
}).verifyComplete();
}
@Test // #1800
@DisplayName("should work with immutable classes")
void shouldWorkWithImmutableClasses() {
ImmutableEntity entity = new ImmutableEntity(null, "some text", null);
AtomicReference<ImmutableEntity> savedEntity = new AtomicReference<>();
template.save(entity).as(StepVerifier::create).consumeNextWith(saved -> {
assertThat(saved).isNotNull();
savedEntity.set(saved);
assertThat(saved.getId()).isNotEmpty();
SeqNoPrimaryTerm seqNoPrimaryTerm = saved.getSeqNoPrimaryTerm();
assertThat(seqNoPrimaryTerm).isNotNull();
}).verifyComplete();
template.get(savedEntity.get().getId(), ImmutableEntity.class).as(StepVerifier::create)
.consumeNextWith(retrieved -> {
assertThat(retrieved).isEqualTo(savedEntity.get());
}).verifyComplete();
}
// endregion
// region Helper functions
@ -1243,8 +1265,10 @@ public class ReactiveElasticsearchTemplateIntegrationTests {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Message message1 = (Message) o;
@ -1301,14 +1325,19 @@ public class ReactiveElasticsearchTemplateIntegrationTests {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
SampleEntity that = (SampleEntity) o;
if (rate != that.rate) return false;
if (id != null ? !id.equals(that.id) : that.id != null) return false;
if (message != null ? !message.equals(that.message) : that.message != null) return false;
if (rate != that.rate)
return false;
if (id != null ? !id.equals(that.id) : that.id != null)
return false;
if (message != null ? !message.equals(that.message) : that.message != null)
return false;
return version != null ? version.equals(that.version) : that.version == null;
}
@ -1440,5 +1469,60 @@ public class ReactiveElasticsearchTemplateIntegrationTests {
}
}
@Document(indexName = "immutable-class")
private static final class ImmutableEntity {
@Id private final String id;
@Field(type = FieldType.Text) private final String text;
@Nullable private final SeqNoPrimaryTerm seqNoPrimaryTerm;
public ImmutableEntity(@Nullable String id, String text, @Nullable SeqNoPrimaryTerm seqNoPrimaryTerm) {
this.id = id;
this.text = text;
this.seqNoPrimaryTerm = seqNoPrimaryTerm;
}
public String getId() {
return id;
}
public String getText() {
return text;
}
@Nullable
public SeqNoPrimaryTerm getSeqNoPrimaryTerm() {
return seqNoPrimaryTerm;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ImmutableEntity that = (ImmutableEntity) o;
if (!id.equals(that.id))
return false;
if (!text.equals(that.text))
return false;
return seqNoPrimaryTerm != null ? seqNoPrimaryTerm.equals(that.seqNoPrimaryTerm) : that.seqNoPrimaryTerm == null;
}
@Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + text.hashCode();
result = 31 * result + (seqNoPrimaryTerm != null ? seqNoPrimaryTerm.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "ImmutableEntity{" + "id='" + id + '\'' + ", text='" + text + '\'' + ", seqNoPrimaryTerm="
+ seqNoPrimaryTerm + '}';
}
}
// endregion
}

View File

@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.IndexOperations;
@ -95,11 +96,16 @@ public class ImmutableElasticsearchRepositoryTests {
static class ImmutableEntity {
private final String id, name;
public ImmutableEntity(String name) {
this.id = null;
@PersistenceConstructor
public ImmutableEntity(String id, String name) {
this.id = id;
this.name = name;
}
public ImmutableEntity(String name) {
this(null, name);
}
public String getId() {
return id;
}