diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index 18c1924f9..7fbe06192 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -8,4 +8,4 @@ In order to run the tests locally with `./mvnw test` you need to have docker run == Class names of the test classes -Tset classes that do depend on the client have either `ERHLC` (when using the deprecated Elasticsearch `RestHighLevelClient`) or `ELC` (the new `ElasticsearchClient`) in their name. +Test classes that do depend on the client have either `ERHLC` (when using the deprecated Elasticsearch `RestHighLevelClient`) or `ELC` (the new `ElasticsearchClient`) in their name. diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java index 919c221de..315c149ea 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -49,6 +49,7 @@ import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.elasticsearch.core.query.UpdateQuery; +import org.springframework.data.elasticsearch.core.query.UpdateResponse; import org.springframework.data.elasticsearch.core.routing.DefaultRoutingResolver; import org.springframework.data.elasticsearch.core.routing.RoutingResolver; import org.springframework.data.elasticsearch.support.VersionInfo; @@ -75,6 +76,7 @@ * @author Subhobrata Dey * @author Steven Pearce * @author Anton Naydenov + * @author Haibo Liu */ public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware { @@ -305,7 +307,7 @@ public String delete(Object entity) { @Override public String delete(Object entity, IndexCoordinates index) { String entityId = getEntityId(entity); - Assert.notNull(entityId, "entity must have an if that is notnull"); + Assert.notNull(entityId, "entity must have an id that is notnull"); return this.delete(entityId, index); } @@ -460,6 +462,27 @@ public IndexCoordinates getIndexCoordinatesFor(Class clazz) { return getRequiredPersistentEntity(clazz).getIndexCoordinates(); } + @Override + public UpdateResponse update(T entity) { + return update(buildUpdateQueryByEntity(entity), getIndexCoordinatesFor(entity.getClass())); + } + + protected UpdateQuery buildUpdateQueryByEntity(T entity) { + Assert.notNull(entity, "entity must not be null"); + + String id = getEntityId(entity); + Assert.notNull(entity, "entity must have an id that is notnull"); + + UpdateQuery.Builder updateQueryBuilder = UpdateQuery.builder(id) + .withDocument(elasticsearchConverter.mapObject(entity)); + + String routing = getEntityRouting(entity); + if (Objects.nonNull(routing)) { + updateQueryBuilder.withRouting(routing); + } + return updateQueryBuilder.build(); + } + protected T updateIndexedObject(T entity, IndexedObjectInformation indexedObjectInformation) { ElasticsearchPersistentEntity persistentEntity = elasticsearchConverter.getMappingContext() diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java index 5a7eb9ebb..8f3afb928 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DocumentOperations.java @@ -37,6 +37,7 @@ * @author Peter-Josef Meisch * @author Farid Faoudi * @author Sijia Liu + * @author Haibo Liu * @since 4.0 */ public interface DocumentOperations { @@ -293,6 +294,15 @@ default void bulkUpdate(List queries, IndexCoordinates index) { */ ByQueryResponse delete(Query query, Class clazz, IndexCoordinates index); + /** + * Partially update a document by the given entity. + * + * @param entity the entity to update partially + * @return the update response + * @param the entity type + */ + UpdateResponse update(T entity); + /** * Partial update of the document. * diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java index 0a0c31863..949ad383f 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchIntegrationTests.java @@ -112,6 +112,7 @@ * @author Farid Faoudi * @author Peer Mueller * @author Sijia Liu + * @author Haibo Liu */ @SpringIntegrationTest public abstract class ElasticsearchIntegrationTests { @@ -186,6 +187,20 @@ protected abstract Query getMatchAllQueryWithIncludesAndInlineExpressionScript(@ protected abstract Query getQueryWithRescorer(); + @Test // #2304 + public void shouldThrowDataAccessExceptionIfDocumentDoesNotExistWhileDoingPartialUpdateByEntity() { + + // given + String documentId = nextIdAsString(); + String messageBeforeUpdate = "some test message"; + + SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message(messageBeforeUpdate) + .version(System.currentTimeMillis()).build(); + + assertThatThrownBy(() -> operations.update(sampleEntity)) + .isInstanceOf(DataAccessException.class); + } + @Test public void shouldThrowDataAccessExceptionIfDocumentDoesNotExistWhileDoingPartialUpdate() { @@ -1505,6 +1520,32 @@ public void shouldDeleteIndexForGivenEntity() { assertThat(indexOperations.exists()).isFalse(); } + @Test // #2304 + public void shouldDoPartialUpdateBySuppliedEntityForExistingDocument() { + + // given + String documentId = nextIdAsString(); + String messageBeforeUpdate = "some test message"; + String messageAfterUpdate = "test message"; + String originalTypeInfo = "some type"; + + SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message(messageBeforeUpdate).type(originalTypeInfo) + .version(System.currentTimeMillis()).build(); + operations.save(sampleEntity); + + // modify the entity + sampleEntity.setMessage(messageAfterUpdate); + sampleEntity.setType(null); + + // when + operations.update(sampleEntity); + + // then + SampleEntity indexedEntity = operations.get(documentId, SampleEntity.class); + assertThat(indexedEntity.getType()).isEqualTo(originalTypeInfo); + assertThat(indexedEntity.getMessage()).isEqualTo(messageAfterUpdate); + } + @Test public void shouldDoPartialUpdateForExistingDocument() {