Optimistic Lock implementation based on _etag field (#396)
* Optimistic Lock first implementation * Ignore warnings on "_etag" field name not matching Java conventions * Fix JUnit test that was checking on wrong illegal arguments * Update precondition not met message to match new version * Precondition is not met to is not met
This commit is contained in:
Родитель
a1f7dd3116
Коммит
4f43dcf266
|
@ -0,0 +1,3 @@
|
|||
exclude_paths:
|
||||
- 'src/test/java/com/microsoft/azure/spring/data/cosmosdb/domain/Person.java'
|
||||
- 'src/test/java/com/microsoft/azure/spring/data/cosmosdb/repository/support/DocumentDbEntityInformationUnitTest.java'
|
|
@ -15,7 +15,7 @@ mvnw clean install
|
|||
```
|
||||
|
||||
## Test
|
||||
There're 3 profiles: `dev`, `integration-test-azure` and `integration-test-emulator`. Default profile is `dev`. Profile `integration-test-azure` will trigger integration test execution against Azure Cosmos DB. Profile `integration-test-emulator` will trigger integration test execution against [Azure Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator), you need to follow the link to setup emualator before test execution.
|
||||
There're 3 profiles: `dev`, `integration-test-azure` and `integration-test-emulator`. Default profile is `dev`. Profile `integration-test-azure` will trigger integration test execution against Azure Cosmos DB. Profile `integration-test-emulator` will trigger integration test execution against [Azure Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator), you need to follow the link to setup emulator before test execution.
|
||||
|
||||
- Run unit tests
|
||||
```bash
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE in the project root for
|
||||
* license information.
|
||||
*/
|
||||
package com.microsoft.azure.spring.data.cosmosdb.common;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Memoize function computation results
|
||||
* @author Domenico Sibilio
|
||||
*
|
||||
*/
|
||||
public class Memoizer<I, O> {
|
||||
|
||||
private final Map<I, O> cache = new ConcurrentHashMap<>();
|
||||
|
||||
private Memoizer() {}
|
||||
|
||||
public static <I, O> Function<I, O> memoize(Function<I, O> function) {
|
||||
return new Memoizer<I, O>().internalMemoize(function);
|
||||
}
|
||||
|
||||
private Function<I, O> internalMemoize(Function<I, O> function) {
|
||||
return input -> cache.computeIfAbsent(input, function);
|
||||
}
|
||||
|
||||
}
|
|
@ -11,6 +11,7 @@ import com.microsoft.azure.documentdb.PartitionKey;
|
|||
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.repository.support.DocumentDbEntityInformation;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
|
@ -20,7 +21,7 @@ public interface DocumentDbOperations {
|
|||
|
||||
String getCollectionName(Class<?> entityClass);
|
||||
|
||||
DocumentCollection createCollectionIfNotExists(DocumentDbEntityInformation information);
|
||||
DocumentCollection createCollectionIfNotExists(DocumentDbEntityInformation<?, ?> information);
|
||||
|
||||
<T> List<T> findAll(Class<T> entityClass);
|
||||
|
||||
|
@ -38,7 +39,7 @@ public interface DocumentDbOperations {
|
|||
|
||||
<T> void upsert(String collectionName, T object, PartitionKey partitionKey);
|
||||
|
||||
<T> void deleteById(String collectionName, Object id, PartitionKey partitionKey);
|
||||
void deleteById(String collectionName, Object id, PartitionKey partitionKey);
|
||||
|
||||
void deleteAll(String collectionName, Class<?> domainClass);
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
package com.microsoft.azure.spring.data.cosmosdb.core;
|
||||
|
||||
import com.azure.data.cosmos.AccessCondition;
|
||||
import com.azure.data.cosmos.AccessConditionType;
|
||||
import com.azure.data.cosmos.CosmosClient;
|
||||
import com.azure.data.cosmos.CosmosContainerResponse;
|
||||
import com.azure.data.cosmos.CosmosItemProperties;
|
||||
|
@ -17,6 +19,7 @@ import com.azure.data.cosmos.SqlQuerySpec;
|
|||
import com.microsoft.azure.documentdb.DocumentCollection;
|
||||
import com.microsoft.azure.documentdb.PartitionKey;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.common.Memoizer;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.generator.CountQueryGenerator;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.generator.FindQuerySpecGenerator;
|
||||
|
@ -27,6 +30,9 @@ import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery;
|
|||
import com.microsoft.azure.spring.data.cosmosdb.exception.DocumentDBAccessException;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.repository.support.DocumentDbEntityInformation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
@ -36,15 +42,19 @@ import org.springframework.data.domain.Pageable;
|
|||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Domenico Sibilio
|
||||
*
|
||||
*/
|
||||
@Slf4j
|
||||
public class DocumentDbTemplate implements DocumentDbOperations, ApplicationContextAware {
|
||||
|
||||
|
@ -54,10 +64,12 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
private final String databaseName;
|
||||
|
||||
private final CosmosClient cosmosClient;
|
||||
private Function<Class<?>, DocumentDbEntityInformation<?, ?>> entityInfoCreator =
|
||||
Memoizer.memoize(this::getDocumentDbEntityInformation);
|
||||
|
||||
public DocumentDbTemplate(CosmosDbFactory cosmosDbFactory,
|
||||
MappingDocumentDbConverter mappingDocumentDbConverter,
|
||||
String dbName) {
|
||||
MappingDocumentDbConverter mappingDocumentDbConverter,
|
||||
String dbName) {
|
||||
Assert.notNull(cosmosDbFactory, "CosmosDbFactory must not be null!");
|
||||
Assert.notNull(mappingDocumentDbConverter, "MappingDocumentDbConverter must not be null!");
|
||||
|
||||
|
@ -92,10 +104,10 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
final Class<T> domainClass = (Class<T>) objectToSave.getClass();
|
||||
|
||||
final CosmosItemResponse response = cosmosClient.getDatabase(this.databaseName)
|
||||
.getContainer(collectionName)
|
||||
.createItem(originalItem, options)
|
||||
.onErrorResume(Mono::error)
|
||||
.block();
|
||||
.getContainer(collectionName)
|
||||
.createItem(originalItem, options)
|
||||
.onErrorResume(Mono::error)
|
||||
.block();
|
||||
|
||||
if (response == null) {
|
||||
throw new DocumentDBAccessException("Failed to insert item");
|
||||
|
@ -125,16 +137,16 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
final FeedOptions options = new FeedOptions();
|
||||
options.enableCrossPartitionQuery(true);
|
||||
return cosmosClient
|
||||
.getDatabase(databaseName)
|
||||
.getContainer(collectionName)
|
||||
.queryItems(query, options)
|
||||
.flatMap(cosmosItemFeedResponse -> Mono.justOrEmpty(cosmosItemFeedResponse
|
||||
.results()
|
||||
.stream()
|
||||
.map(cosmosItem -> mappingDocumentDbConverter.read(domainClass, cosmosItem))
|
||||
.findFirst()))
|
||||
.onErrorResume(Mono::error)
|
||||
.blockFirst();
|
||||
.getDatabase(databaseName)
|
||||
.getContainer(collectionName)
|
||||
.queryItems(query, options)
|
||||
.flatMap(cosmosItemFeedResponse -> Mono.justOrEmpty(cosmosItemFeedResponse
|
||||
.results()
|
||||
.stream()
|
||||
.map(cosmosItem -> mappingDocumentDbConverter.read(domainClass, cosmosItem))
|
||||
.findFirst()))
|
||||
.onErrorResume(Mono::error)
|
||||
.blockFirst();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new DocumentDBAccessException("findById exception", e);
|
||||
|
@ -158,12 +170,13 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
|
||||
final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
|
||||
options.partitionKey(toCosmosPartitionKey(partitionKey));
|
||||
applyVersioning(object.getClass(), originalItem, options);
|
||||
|
||||
final CosmosItemResponse cosmosItemResponse = cosmosClient.getDatabase(this.databaseName)
|
||||
.getContainer(collectionName)
|
||||
.upsertItem(originalItem, options)
|
||||
.onErrorResume(Mono::error)
|
||||
.block();
|
||||
.getContainer(collectionName)
|
||||
.upsertItem(originalItem, options)
|
||||
.onErrorResume(Mono::error)
|
||||
.block();
|
||||
|
||||
if (cosmosItemResponse == null) {
|
||||
throw new DocumentDBAccessException("Failed to upsert item");
|
||||
|
@ -187,8 +200,8 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
|
||||
final List<CosmosItemProperties> documents = findDocuments(query, domainClass, collectionName);
|
||||
return documents.stream()
|
||||
.map(d -> getConverter().read(domainClass, d))
|
||||
.collect(Collectors.toList());
|
||||
.map(d -> getConverter().read(domainClass, d))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void deleteAll(@NonNull String collectionName, @NonNull Class<?> domainClass) {
|
||||
|
@ -206,26 +219,26 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
cosmosClient.getDatabase(this.databaseName).getContainer(collectionName).delete().block();
|
||||
} catch (Exception e) {
|
||||
throw new DocumentDBAccessException("failed to delete collection: " + collectionName,
|
||||
e);
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getCollectionName(Class<?> domainClass) {
|
||||
Assert.notNull(domainClass, "domainClass should not be null");
|
||||
|
||||
return new DocumentDbEntityInformation<>(domainClass).getCollectionName();
|
||||
return entityInfoCreator.apply(domainClass).getCollectionName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentCollection createCollectionIfNotExists(@NonNull DocumentDbEntityInformation information) {
|
||||
public DocumentCollection createCollectionIfNotExists(@NonNull DocumentDbEntityInformation<?, ?> information) {
|
||||
final CosmosContainerResponse response = cosmosClient
|
||||
.createDatabaseIfNotExists(this.databaseName)
|
||||
.flatMap(cosmosDatabaseResponse -> cosmosDatabaseResponse
|
||||
.database()
|
||||
.createContainerIfNotExists(information.getCollectionName(),
|
||||
"/" + information.getPartitionKeyFieldName())
|
||||
.map(cosmosContainerResponse -> cosmosContainerResponse))
|
||||
.block();
|
||||
.createDatabaseIfNotExists(this.databaseName)
|
||||
.flatMap(cosmosDatabaseResponse -> cosmosDatabaseResponse
|
||||
.database()
|
||||
.createContainerIfNotExists(information.getCollectionName(),
|
||||
"/" + information.getPartitionKeyFieldName())
|
||||
.map(cosmosContainerResponse -> cosmosContainerResponse))
|
||||
.block();
|
||||
if (response == null) {
|
||||
throw new DocumentDBAccessException("Failed to create collection");
|
||||
}
|
||||
|
@ -246,12 +259,12 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
final CosmosItemRequestOptions options = new CosmosItemRequestOptions();
|
||||
options.partitionKey(pk);
|
||||
cosmosClient.getDatabase(this.databaseName)
|
||||
.getContainer(collectionName)
|
||||
.getItem(id.toString(), partitionKey)
|
||||
.delete(options)
|
||||
.onErrorResume(Mono::error)
|
||||
.then()
|
||||
.block();
|
||||
.getContainer(collectionName)
|
||||
.getItem(id.toString(), partitionKey)
|
||||
.delete(options)
|
||||
.onErrorResume(Mono::error)
|
||||
.then()
|
||||
.block();
|
||||
} catch (Exception e) {
|
||||
throw new DocumentDBAccessException("deleteById exception", e);
|
||||
}
|
||||
|
@ -275,9 +288,9 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
|
||||
try {
|
||||
return findDocuments(query, domainClass, collectionName)
|
||||
.stream()
|
||||
.stream()
|
||||
.map(cosmosItemProperties -> toDomainObject(domainClass, cosmosItemProperties))
|
||||
.collect(Collectors.toList());
|
||||
.collect(Collectors.toList());
|
||||
} catch (Exception e) {
|
||||
throw new DocumentDBAccessException("Failed to execute find operation from " + collectionName, e);
|
||||
}
|
||||
|
@ -300,7 +313,7 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
*/
|
||||
@Override
|
||||
public <T> List<T> delete(@NonNull DocumentQuery query, @NonNull Class<T> domainClass,
|
||||
@NonNull String collectionName) {
|
||||
@NonNull String collectionName) {
|
||||
Assert.notNull(query, "DocumentQuery should not be null.");
|
||||
Assert.notNull(domainClass, "domainClass should not be null.");
|
||||
Assert.hasText(collectionName, "collection should not be null, empty or only whitespaces");
|
||||
|
@ -308,11 +321,11 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
final List<CosmosItemProperties> results = findDocuments(query, domainClass, collectionName);
|
||||
final List<String> partitionKeyName = getPartitionKeyNames(domainClass);
|
||||
|
||||
results.forEach(d -> deleteDocument(d, partitionKeyName, collectionName));
|
||||
results.forEach(d -> deleteDocument(d, partitionKeyName, collectionName, domainClass));
|
||||
|
||||
return results.stream()
|
||||
.map(d -> getConverter().read(domainClass, d))
|
||||
.collect(Collectors.toList());
|
||||
.map(d -> getConverter().read(domainClass, d))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -341,11 +354,11 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
|
||||
final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generateCosmos(query);
|
||||
final FeedResponse<CosmosItemProperties> feedResponse =
|
||||
cosmosClient.getDatabase(this.databaseName)
|
||||
.getContainer(collectionName)
|
||||
.queryItems(sqlQuerySpec, feedOptions)
|
||||
.next()
|
||||
.block();
|
||||
cosmosClient.getDatabase(this.databaseName)
|
||||
.getContainer(collectionName)
|
||||
.queryItems(sqlQuerySpec, feedOptions)
|
||||
.next()
|
||||
.block();
|
||||
|
||||
if (feedResponse == null) {
|
||||
throw new DocumentDBAccessException("Failed to query documents");
|
||||
|
@ -366,9 +379,9 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
}
|
||||
|
||||
final DocumentDbPageRequest pageRequest = DocumentDbPageRequest.of(pageable.getPageNumber(),
|
||||
pageable.getPageSize(),
|
||||
feedResponse.continuationToken(),
|
||||
query.getSort());
|
||||
pageable.getPageSize(),
|
||||
feedResponse.continuationToken(),
|
||||
query.getSort());
|
||||
|
||||
return new PageImpl<>(result, pageRequest, count(query, domainClass, collectionName));
|
||||
}
|
||||
|
@ -391,7 +404,7 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
Assert.hasText(collectionName, "collectionName should not be empty");
|
||||
|
||||
final boolean isCrossPartitionQuery =
|
||||
query.isCrossPartitionQuery(getPartitionKeyNames(domainClass));
|
||||
query.isCrossPartitionQuery(getPartitionKeyNames(domainClass));
|
||||
final Long count = getCountValue(query, isCrossPartitionQuery, collectionName);
|
||||
if (count == null) {
|
||||
throw new DocumentDBAccessException("Failed to get count for collectionName: " + collectionName);
|
||||
|
@ -411,10 +424,10 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
options.enableCrossPartitionQuery(isCrossPartitionQuery);
|
||||
|
||||
return executeQuery(querySpec, containerName, options)
|
||||
.onErrorResume(this::databaseAccessExceptionHandler)
|
||||
.next()
|
||||
.map(r -> r.results().get(0).getLong(COUNT_VALUE_KEY))
|
||||
.block();
|
||||
.onErrorResume(this::databaseAccessExceptionHandler)
|
||||
.next()
|
||||
.map(r -> r.results().get(0).getLong(COUNT_VALUE_KEY))
|
||||
.block();
|
||||
}
|
||||
|
||||
private <T> Mono<T> databaseAccessExceptionHandler(Throwable e) {
|
||||
|
@ -422,15 +435,14 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
}
|
||||
|
||||
private Flux<FeedResponse<CosmosItemProperties>> executeQuery(SqlQuerySpec sqlQuerySpec, String collectionName,
|
||||
FeedOptions options) {
|
||||
FeedOptions options) {
|
||||
return cosmosClient.getDatabase(this.databaseName)
|
||||
.getContainer(collectionName)
|
||||
.queryItems(sqlQuerySpec, options);
|
||||
.getContainer(collectionName)
|
||||
.queryItems(sqlQuerySpec, options);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<String> getPartitionKeyNames(Class<?> domainClass) {
|
||||
final DocumentDbEntityInformation entityInfo = new DocumentDbEntityInformation(domainClass);
|
||||
final DocumentDbEntityInformation<?, ?> entityInfo = entityInfoCreator.apply(domainClass);
|
||||
|
||||
if (entityInfo.getPartitionKeyFieldName() == null) {
|
||||
return new ArrayList<>();
|
||||
|
@ -454,25 +466,26 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
}
|
||||
|
||||
private List<CosmosItemProperties> findDocuments(@NonNull DocumentQuery query,
|
||||
@NonNull Class<?> domainClass,
|
||||
@NonNull String containerName) {
|
||||
@NonNull Class<?> domainClass,
|
||||
@NonNull String containerName) {
|
||||
final SqlQuerySpec sqlQuerySpec = new FindQuerySpecGenerator().generateCosmos(query);
|
||||
final boolean isCrossPartitionQuery =
|
||||
query.isCrossPartitionQuery(getPartitionKeyNames(domainClass));
|
||||
query.isCrossPartitionQuery(getPartitionKeyNames(domainClass));
|
||||
final FeedOptions feedOptions = new FeedOptions();
|
||||
feedOptions.enableCrossPartitionQuery(isCrossPartitionQuery);
|
||||
return cosmosClient
|
||||
.getDatabase(this.databaseName)
|
||||
.getContainer(containerName)
|
||||
.queryItems(sqlQuerySpec, feedOptions)
|
||||
.flatMap(cosmosItemFeedResponse -> Flux.fromIterable(cosmosItemFeedResponse.results()))
|
||||
.collectList()
|
||||
.block();
|
||||
.getDatabase(this.databaseName)
|
||||
.getContainer(containerName)
|
||||
.queryItems(sqlQuerySpec, feedOptions)
|
||||
.flatMap(cosmosItemFeedResponse -> Flux.fromIterable(cosmosItemFeedResponse.results()))
|
||||
.collectList()
|
||||
.block();
|
||||
}
|
||||
|
||||
private CosmosItemResponse deleteDocument(@NonNull CosmosItemProperties cosmosItemProperties,
|
||||
@NonNull List<String> partitionKeyNames,
|
||||
String containerName) {
|
||||
@NonNull List<String> partitionKeyNames,
|
||||
String containerName,
|
||||
@NonNull Class<?> domainClass) {
|
||||
Assert.isTrue(partitionKeyNames.size() <= 1, "Only one Partition is supported.");
|
||||
|
||||
PartitionKey partitionKey = null;
|
||||
|
@ -488,16 +501,34 @@ public class DocumentDbTemplate implements DocumentDbOperations, ApplicationCont
|
|||
}
|
||||
|
||||
final CosmosItemRequestOptions options = new CosmosItemRequestOptions(pk);
|
||||
applyVersioning(domainClass, cosmosItemProperties, options);
|
||||
|
||||
return cosmosClient
|
||||
.getDatabase(this.databaseName)
|
||||
.getContainer(containerName)
|
||||
.getItem(cosmosItemProperties.id(), partitionKey)
|
||||
.delete(options)
|
||||
.block();
|
||||
.getDatabase(this.databaseName)
|
||||
.getContainer(containerName)
|
||||
.getItem(cosmosItemProperties.id(), partitionKey)
|
||||
.delete(options)
|
||||
.block();
|
||||
}
|
||||
|
||||
private <T> T toDomainObject(@NonNull Class<T> domainClass, CosmosItemProperties cosmosItemProperties) {
|
||||
return mappingDocumentDbConverter.read(domainClass, cosmosItemProperties);
|
||||
}
|
||||
|
||||
private void applyVersioning(Class<?> domainClass,
|
||||
CosmosItemProperties cosmosItemProperties,
|
||||
CosmosItemRequestOptions options) {
|
||||
|
||||
if (entityInfoCreator.apply(domainClass).isVersioned()) {
|
||||
final AccessCondition accessCondition = new AccessCondition();
|
||||
accessCondition.type(AccessConditionType.IF_MATCH);
|
||||
accessCondition.condition(cosmosItemProperties.etag());
|
||||
options.accessCondition(accessCondition);
|
||||
}
|
||||
}
|
||||
|
||||
private DocumentDbEntityInformation<?, ?> getDocumentDbEntityInformation(Class<?> domainClass) {
|
||||
return new DocumentDbEntityInformation<>(domainClass);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import com.microsoft.azure.spring.data.cosmosdb.core.mapping.PartitionKey;
|
|||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.annotation.Version;
|
||||
import org.springframework.data.repository.core.support.AbstractEntityInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
@ -31,12 +32,14 @@ import java.util.List;
|
|||
|
||||
public class DocumentDbEntityInformation<T, ID> extends AbstractEntityInformation<T, ID> {
|
||||
|
||||
private static final String ETAG = "_etag";
|
||||
private Field id;
|
||||
private Field partitionKeyField;
|
||||
private String collectionName;
|
||||
private Integer requestUnit;
|
||||
private Integer timeToLive;
|
||||
private IndexingPolicy indexingPolicy;
|
||||
private boolean isVersioned;
|
||||
|
||||
public DocumentDbEntityInformation(Class<T> domainClass) {
|
||||
super(domainClass);
|
||||
|
@ -53,6 +56,7 @@ public class DocumentDbEntityInformation<T, ID> extends AbstractEntityInformatio
|
|||
this.requestUnit = getRequestUnit(domainClass);
|
||||
this.timeToLive = getTimeToLive(domainClass);
|
||||
this.indexingPolicy = getIndexingPolicy(domainClass);
|
||||
this.isVersioned = getIsVersioned(domainClass);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -86,6 +90,10 @@ public class DocumentDbEntityInformation<T, ID> extends AbstractEntityInformatio
|
|||
return this.indexingPolicy;
|
||||
}
|
||||
|
||||
public boolean isVersioned() {
|
||||
return isVersioned;
|
||||
}
|
||||
|
||||
public String getPartitionKeyFieldName() {
|
||||
if (partitionKeyField == null) {
|
||||
return null;
|
||||
|
@ -239,5 +247,13 @@ public class DocumentDbEntityInformation<T, ID> extends AbstractEntityInformatio
|
|||
|
||||
return pathsCollection;
|
||||
}
|
||||
|
||||
private boolean getIsVersioned(Class<T> domainClass) {
|
||||
final Field findField = ReflectionUtils.findField(domainClass, ETAG);
|
||||
return findField != null
|
||||
&& findField.getType() == String.class
|
||||
&& findField.isAnnotationPresent(Version.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE in the project root for
|
||||
* license information.
|
||||
*/
|
||||
package com.microsoft.azure.spring.data.cosmosdb.common;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Domenico Sibilio
|
||||
*
|
||||
*/
|
||||
public class MemoizerUnitTest {
|
||||
private static final String KEY = "key_1";
|
||||
private static final Map<String, AtomicInteger> countMap = new HashMap<>();
|
||||
private static final Function<String, Integer> memoizedFunction =
|
||||
Memoizer.memoize(MemoizerUnitTest::incrCount);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
countMap.put(KEY, new AtomicInteger(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMemoizedFunctionShouldBeCalledOnlyOnce() {
|
||||
IntStream
|
||||
.range(0, 10)
|
||||
.forEach(number -> memoizedFunction.apply(KEY));
|
||||
|
||||
assertEquals(1, countMap.get(KEY).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentMemoizersShouldNotShareTheSameCache() {
|
||||
IntStream
|
||||
.range(0, 10)
|
||||
.forEach(number -> Memoizer.memoize(MemoizerUnitTest::incrCount).apply(KEY));
|
||||
|
||||
assertEquals(10, countMap.get(KEY).get());
|
||||
}
|
||||
|
||||
private static int incrCount(String key) {
|
||||
return countMap.get(key).incrementAndGet();
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
package com.microsoft.azure.spring.data.cosmosdb.core;
|
||||
|
||||
import com.azure.data.cosmos.CosmosClientException;
|
||||
import com.microsoft.azure.documentdb.PartitionKey;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
|
||||
|
@ -23,6 +24,7 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScanner;
|
||||
|
@ -34,29 +36,48 @@ import org.springframework.data.domain.PageRequest;
|
|||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.PageTestUtils.validateLastPage;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.PageTestUtils.validateNonLastPage;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.ADDRESSES;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.DB_NAME;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.FIRST_NAME;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.HOBBIES;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.ID_1;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.ID_2;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.ID_3;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.LAST_NAME;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.NEW_FIRST_NAME;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.NEW_LAST_NAME;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.NOT_EXIST_ID;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.PAGE_SIZE_1;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.PAGE_SIZE_2;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.PAGE_SIZE_3;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.UPDATED_FIRST_NAME;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.PageTestUtils.validateLastPage;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.PageTestUtils.validateNonLastPage;
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.TestConstants.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@PropertySource(value = { "classpath:application.properties" })
|
||||
public class DocumentDbTemplateIT {
|
||||
private static final Person TEST_PERSON = new Person(ID_1, FIRST_NAME, LAST_NAME, HOBBIES,
|
||||
ADDRESSES);
|
||||
ADDRESSES);
|
||||
|
||||
private static final Person TEST_PERSON_2 = new Person(ID_2,
|
||||
NEW_FIRST_NAME,
|
||||
NEW_LAST_NAME, HOBBIES, ADDRESSES);
|
||||
NEW_FIRST_NAME,
|
||||
NEW_LAST_NAME, HOBBIES, ADDRESSES);
|
||||
|
||||
private static final Person TEST_PERSON_3 = new Person(ID_3,
|
||||
NEW_FIRST_NAME,
|
||||
NEW_LAST_NAME, HOBBIES, ADDRESSES);
|
||||
NEW_FIRST_NAME,
|
||||
NEW_LAST_NAME, HOBBIES, ADDRESSES);
|
||||
|
||||
private static final String PRECONDITION_IS_NOT_MET = "is not met";
|
||||
|
||||
private static final String WRONG_ETAG = "WRONG_ETAG";
|
||||
|
||||
@Value("${cosmosdb.uri}")
|
||||
private String documentDbUri;
|
||||
|
@ -68,6 +89,8 @@ public class DocumentDbTemplateIT {
|
|||
private static String collectionName;
|
||||
|
||||
private static boolean initialized;
|
||||
|
||||
private Person insertedPerson;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
@ -76,7 +99,7 @@ public class DocumentDbTemplateIT {
|
|||
public void setup() throws ClassNotFoundException {
|
||||
if (!initialized) {
|
||||
final DocumentDBConfig dbConfig = DocumentDBConfig.builder(documentDbUri,
|
||||
documentDbKey, DB_NAME).build();
|
||||
documentDbKey, DB_NAME).build();
|
||||
final CosmosDbFactory cosmosDbFactory = new CosmosDbFactory(dbConfig);
|
||||
|
||||
final DocumentDbMappingContext mappingContext = new DocumentDbMappingContext();
|
||||
|
@ -86,12 +109,13 @@ public class DocumentDbTemplateIT {
|
|||
mappingContext.setInitialEntitySet(new EntityScanner(this.applicationContext).scan(Persistent.class));
|
||||
|
||||
final MappingDocumentDbConverter dbConverter =
|
||||
new MappingDocumentDbConverter(mappingContext, null);
|
||||
new MappingDocumentDbConverter(mappingContext, null);
|
||||
dbTemplate = new DocumentDbTemplate(cosmosDbFactory, dbConverter, DB_NAME);
|
||||
dbTemplate.createCollectionIfNotExists(personInfo);
|
||||
initialized = true;
|
||||
}
|
||||
dbTemplate.insert(Person.class.getSimpleName(), TEST_PERSON, null);
|
||||
|
||||
insertedPerson = dbTemplate.insert(Person.class.getSimpleName(), TEST_PERSON, null);
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -102,7 +126,7 @@ public class DocumentDbTemplateIT {
|
|||
@Test(expected = DocumentDBAccessException.class)
|
||||
public void testInsertDuplicateId() {
|
||||
dbTemplate.insert(Person.class.getSimpleName(), TEST_PERSON,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -115,20 +139,20 @@ public class DocumentDbTemplateIT {
|
|||
@Test
|
||||
public void testFindById() {
|
||||
final Person result = dbTemplate.findById(Person.class.getSimpleName(),
|
||||
TEST_PERSON.getId(), Person.class);
|
||||
TEST_PERSON.getId(), Person.class);
|
||||
assertEquals(result, TEST_PERSON);
|
||||
|
||||
final Person nullResult = dbTemplate.findById(Person.class.getSimpleName(),
|
||||
NOT_EXIST_ID, Person.class);
|
||||
NOT_EXIST_ID, Person.class);
|
||||
assertThat(nullResult).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindByMultiIds() {
|
||||
dbTemplate.insert(TEST_PERSON_2,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
dbTemplate.insert(TEST_PERSON_3,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_3)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_3)));
|
||||
|
||||
final List<Object> ids = Lists.newArrayList(ID_1, ID_2, ID_3);
|
||||
final List<Person> result = dbTemplate.findByIds(ids, Person.class, collectionName);
|
||||
|
@ -142,14 +166,14 @@ public class DocumentDbTemplateIT {
|
|||
public void testUpsertNewDocument() {
|
||||
// Delete first as was inserted in setup
|
||||
dbTemplate.deleteById(Person.class.getSimpleName(), TEST_PERSON.getId(),
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON)));
|
||||
|
||||
final String firstName = NEW_FIRST_NAME + "_" + UUID.randomUUID().toString();
|
||||
final Person newPerson = new Person(TEST_PERSON.getId(), firstName,
|
||||
NEW_FIRST_NAME, null, null);
|
||||
NEW_FIRST_NAME, null, null);
|
||||
|
||||
dbTemplate.upsert(Person.class.getSimpleName(), newPerson,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(newPerson)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(newPerson)));
|
||||
|
||||
final List<Person> result = dbTemplate.findAll(Person.class);
|
||||
|
||||
|
@ -160,24 +184,47 @@ public class DocumentDbTemplateIT {
|
|||
@Test
|
||||
public void testUpdate() {
|
||||
final Person updated = new Person(TEST_PERSON.getId(), UPDATED_FIRST_NAME,
|
||||
TEST_PERSON.getLastName(), TEST_PERSON.getHobbies(),
|
||||
TEST_PERSON.getShippingAddresses());
|
||||
dbTemplate.upsert(Person.class.getSimpleName(), updated,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(updated)));
|
||||
TEST_PERSON.getLastName(), TEST_PERSON.getHobbies(), TEST_PERSON.getShippingAddresses());
|
||||
updated.set_etag(insertedPerson.get_etag());
|
||||
|
||||
dbTemplate.upsert(Person.class.getSimpleName(), updated, null);
|
||||
|
||||
final Person result = dbTemplate.findById(Person.class.getSimpleName(),
|
||||
updated.getId(), Person.class);
|
||||
updated.getId(), Person.class);
|
||||
|
||||
assertEquals(result, updated);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptimisticLockWhenUpdatingWithWrongEtag() {
|
||||
final Person updated = new Person(TEST_PERSON.getId(), UPDATED_FIRST_NAME,
|
||||
TEST_PERSON.getLastName(), TEST_PERSON.getHobbies(), TEST_PERSON.getShippingAddresses());
|
||||
updated.set_etag(WRONG_ETAG);
|
||||
|
||||
try {
|
||||
dbTemplate.upsert(Person.class.getSimpleName(), updated, null);
|
||||
} catch (DocumentDBAccessException e) {
|
||||
assertThat(e.getCause()).isNotNull();
|
||||
final Throwable cosmosClientException = e.getCause().getCause();
|
||||
assertThat(cosmosClientException).isInstanceOf(CosmosClientException.class);
|
||||
assertThat(cosmosClientException.getMessage()).contains(PRECONDITION_IS_NOT_MET);
|
||||
|
||||
final Person unmodifiedPerson = dbTemplate.findById(Person.class.getSimpleName(),
|
||||
TEST_PERSON.getId(), Person.class);
|
||||
assertThat(unmodifiedPerson.getFirstName()).isEqualTo(insertedPerson.getFirstName());
|
||||
return;
|
||||
}
|
||||
|
||||
fail();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteById() {
|
||||
dbTemplate.insert(TEST_PERSON_2, null);
|
||||
assertThat(dbTemplate.findAll(Person.class).size()).isEqualTo(2);
|
||||
|
||||
dbTemplate.deleteById(Person.class.getSimpleName(), TEST_PERSON.getId(),
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON)));
|
||||
|
||||
final List<Person> result = dbTemplate.findAll(Person.class);
|
||||
assertThat(result.size()).isEqualTo(1);
|
||||
|
@ -190,7 +237,7 @@ public class DocumentDbTemplateIT {
|
|||
assertThat(prevCount).isEqualTo(1);
|
||||
|
||||
dbTemplate.insert(TEST_PERSON_2,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
|
||||
final long newCount = dbTemplate.count(collectionName);
|
||||
assertThat(newCount).isEqualTo(2);
|
||||
|
@ -199,10 +246,10 @@ public class DocumentDbTemplateIT {
|
|||
@Test
|
||||
public void testCountByQuery() {
|
||||
dbTemplate.insert(TEST_PERSON_2,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
|
||||
final Criteria criteria = Criteria.getInstance(CriteriaType.IS_EQUAL, "firstName",
|
||||
Collections.singletonList(TEST_PERSON_2.getFirstName()));
|
||||
Collections.singletonList(TEST_PERSON_2.getFirstName()));
|
||||
final DocumentQuery query = new DocumentQuery(criteria);
|
||||
|
||||
final long count = dbTemplate.count(query, Person.class, collectionName);
|
||||
|
@ -212,7 +259,7 @@ public class DocumentDbTemplateIT {
|
|||
@Test
|
||||
public void testFindAllPageableMultiPages() {
|
||||
dbTemplate.insert(TEST_PERSON_2,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
|
||||
final DocumentDbPageRequest pageRequest = new DocumentDbPageRequest(0, PAGE_SIZE_1, null);
|
||||
final Page<Person> page1 = dbTemplate.findAll(pageRequest, Person.class, collectionName);
|
||||
|
@ -221,7 +268,7 @@ public class DocumentDbTemplateIT {
|
|||
validateNonLastPage(page1, PAGE_SIZE_1);
|
||||
|
||||
final Page<Person> page2 = dbTemplate.findAll(page1.getPageable(), Person.class,
|
||||
collectionName);
|
||||
collectionName);
|
||||
assertThat(page2.getContent().size()).isEqualTo(1);
|
||||
validateLastPage(page2, PAGE_SIZE_1);
|
||||
}
|
||||
|
@ -229,10 +276,10 @@ public class DocumentDbTemplateIT {
|
|||
@Test
|
||||
public void testPaginationQuery() {
|
||||
dbTemplate.insert(TEST_PERSON_2,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
|
||||
final Criteria criteria = Criteria.getInstance(CriteriaType.IS_EQUAL, "firstName",
|
||||
Collections.singletonList(FIRST_NAME));
|
||||
Collections.singletonList(FIRST_NAME));
|
||||
final PageRequest pageRequest = new DocumentDbPageRequest(0, PAGE_SIZE_2, null);
|
||||
final DocumentQuery query = new DocumentQuery(criteria).with(pageRequest);
|
||||
|
||||
|
@ -244,9 +291,9 @@ public class DocumentDbTemplateIT {
|
|||
@Test
|
||||
public void testFindAllWithPageableAndSort() {
|
||||
dbTemplate.insert(TEST_PERSON_2,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
dbTemplate.insert(TEST_PERSON_3,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_3)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_3)));
|
||||
|
||||
final Sort sort = new Sort(Sort.Direction.DESC, "firstName");
|
||||
final PageRequest pageRequest = DocumentDbPageRequest.of(0, PAGE_SIZE_3, null, sort);
|
||||
|
@ -268,19 +315,19 @@ public class DocumentDbTemplateIT {
|
|||
final Person testPerson5 = new Person("id_5", "fred", NEW_LAST_NAME, HOBBIES, ADDRESSES);
|
||||
|
||||
dbTemplate.insert(TEST_PERSON_2,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_2)));
|
||||
dbTemplate.insert(TEST_PERSON_3,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_3)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(TEST_PERSON_3)));
|
||||
dbTemplate.insert(testPerson4,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(testPerson4)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(testPerson4)));
|
||||
dbTemplate.insert(testPerson5,
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(testPerson5)));
|
||||
new PartitionKey(personInfo.getPartitionKeyFieldValue(testPerson5)));
|
||||
|
||||
final Sort sort = new Sort(Sort.Direction.ASC, "firstName");
|
||||
final PageRequest pageRequest = DocumentDbPageRequest.of(0, PAGE_SIZE_3, null, sort);
|
||||
|
||||
final Page<Person> firstPage = dbTemplate.findAll(pageRequest, Person.class,
|
||||
collectionName);
|
||||
collectionName);
|
||||
|
||||
assertThat(firstPage.getContent().size()).isEqualTo(3);
|
||||
validateNonLastPage(firstPage, PAGE_SIZE_3);
|
||||
|
@ -291,7 +338,7 @@ public class DocumentDbTemplateIT {
|
|||
assertThat(firstPageResults.get(2).getFirstName()).isEqualTo(testPerson5.getFirstName());
|
||||
|
||||
final Page<Person> secondPage = dbTemplate.findAll(firstPage.getPageable(), Person.class,
|
||||
collectionName);
|
||||
collectionName);
|
||||
|
||||
assertThat(secondPage.getContent().size()).isEqualTo(2);
|
||||
validateLastPage(secondPage, PAGE_SIZE_3);
|
||||
|
|
|
@ -16,14 +16,15 @@ import org.junit.runner.RunWith;
|
|||
import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.core.query.CriteriaType.IS_EQUAL;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.core.query.CriteriaType.IS_EQUAL;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DocumentDbTemplateIllegalTest {
|
||||
private static final String NULL_STR = null;
|
||||
|
@ -83,9 +84,7 @@ public class DocumentDbTemplateIllegalTest {
|
|||
public void findByIdIllegalArgsShouldFail() throws NoSuchMethodException {
|
||||
final Method method = dbTemplateClass.getDeclaredMethod("findById", Object.class, Class.class);
|
||||
|
||||
checkIllegalArgument(method, null, Person.class);
|
||||
checkIllegalArgument(method, EMPTY_STR, Person.class);
|
||||
checkIllegalArgument(method, WHITESPACES_STR, Person.class);
|
||||
checkIllegalArgument(method, DUMMY_ID, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -6,16 +6,20 @@
|
|||
|
||||
package com.microsoft.azure.spring.data.cosmosdb.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentIndexingPolicy;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.PartitionKey;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.annotation.Version;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(exclude = "_etag")
|
||||
@NoArgsConstructor
|
||||
@DocumentIndexingPolicy(includePaths = TestConstants.ORDER_BY_STRING_PATH)
|
||||
public class Person {
|
||||
private String id;
|
||||
|
@ -25,4 +29,14 @@ public class Person {
|
|||
private String lastName;
|
||||
private List<String> hobbies;
|
||||
private List<Address> shippingAddresses;
|
||||
@Version
|
||||
private String _etag;
|
||||
|
||||
public Person(String id, String firstName, String lastName, List<String> hobbies, List<Address> shippingAddresses) {
|
||||
this.id = id;
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.hobbies = hobbies;
|
||||
this.shippingAddresses = shippingAddresses;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,16 +5,20 @@
|
|||
*/
|
||||
package com.microsoft.azure.spring.data.cosmosdb.repository.support;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.PartitionKey;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.domain.Address;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.domain.Person;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.domain.Student;
|
||||
import lombok.Data;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.annotation.Version;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
public class DocumentDbEntityInformationUnitTest {
|
||||
private static final String ID = "entity_info_test_id";
|
||||
|
@ -54,8 +58,8 @@ public class DocumentDbEntityInformationUnitTest {
|
|||
|
||||
@Test
|
||||
public void testCustomCollectionName() {
|
||||
final DocumentDbEntityInformation<Volunteer, String> entityInformation =
|
||||
new DocumentDbEntityInformation<Volunteer, String>(Volunteer.class);
|
||||
final DocumentDbEntityInformation<VersionedVolunteer, String> entityInformation =
|
||||
new DocumentDbEntityInformation<VersionedVolunteer, String>(VersionedVolunteer.class);
|
||||
|
||||
final String collectionName = entityInformation.getCollectionName();
|
||||
assertThat(collectionName).isEqualTo("testCollection");
|
||||
|
@ -87,6 +91,42 @@ public class DocumentDbEntityInformationUnitTest {
|
|||
final String partitionKeyName = entityInformation.getPartitionKeyFieldName();
|
||||
assertThat(partitionKeyName).isEqualTo("vol_name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVersionedEntity() {
|
||||
final DocumentDbEntityInformation<VersionedVolunteer, String> entityInformation =
|
||||
new DocumentDbEntityInformation<VersionedVolunteer, String>(VersionedVolunteer.class);
|
||||
|
||||
final boolean isVersioned = entityInformation.isVersioned();
|
||||
assertThat(isVersioned).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityShouldNotBeVersionedWithWrongType() {
|
||||
final DocumentDbEntityInformation<WrongVersionType, String> entityInformation =
|
||||
new DocumentDbEntityInformation<WrongVersionType, String>(WrongVersionType.class);
|
||||
|
||||
final boolean isVersioned = entityInformation.isVersioned();
|
||||
assertThat(isVersioned).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntityShouldNotBeVersionedWithoutAnnotationOnEtag() {
|
||||
final DocumentDbEntityInformation<VersionOnWrongField, String> entityInformation =
|
||||
new DocumentDbEntityInformation<VersionOnWrongField, String>(VersionOnWrongField.class);
|
||||
|
||||
final boolean isVersioned = entityInformation.isVersioned();
|
||||
assertThat(isVersioned).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonVersionedEntity() {
|
||||
final DocumentDbEntityInformation<Student, String> entityInformation =
|
||||
new DocumentDbEntityInformation<Student, String>(Student.class);
|
||||
|
||||
final boolean isVersioned = entityInformation.isVersioned();
|
||||
assertThat(isVersioned).isFalse();
|
||||
}
|
||||
|
||||
@Document(collection = "testCollection")
|
||||
class Volunteer {
|
||||
|
@ -139,4 +179,30 @@ public class DocumentDbEntityInformationUnitTest {
|
|||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@Document(collection = "testCollection")
|
||||
class VersionedVolunteer {
|
||||
private String id;
|
||||
private String name;
|
||||
@Version
|
||||
private String _etag;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Document
|
||||
class WrongVersionType {
|
||||
private String id;
|
||||
private String name;
|
||||
private long _etag;
|
||||
}
|
||||
|
||||
@Data
|
||||
@Document
|
||||
class VersionOnWrongField {
|
||||
private String id;
|
||||
@Version
|
||||
private String name;
|
||||
private String _etag;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче