Reinstate the getDefaultConsistency() method in the Configuration. (#1244)

Closes #1243.

Co-authored-by: mikereiche <michael.reiche@couchbase.com>
This commit is contained in:
Michael Reiche
2021-10-04 10:53:37 -07:00
committed by GitHub
parent f4daeb6143
commit ada1c80d52
9 changed files with 188 additions and 17 deletions

View File

@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import com.couchbase.client.java.query.QueryScanConsistency;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
@@ -157,7 +158,8 @@ public abstract class AbstractCouchbaseConfiguration {
@Bean(name = BeanNames.COUCHBASE_TEMPLATE)
public CouchbaseTemplate couchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
MappingCouchbaseConverter mappingCouchbaseConverter, TranslationService couchbaseTranslationService) {
return new CouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter, couchbaseTranslationService);
return new CouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter, couchbaseTranslationService,
getDefaultConsistency());
}
public CouchbaseTemplate couchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
@@ -168,8 +170,8 @@ public abstract class AbstractCouchbaseConfiguration {
@Bean(name = BeanNames.REACTIVE_COUCHBASE_TEMPLATE)
public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
MappingCouchbaseConverter mappingCouchbaseConverter, TranslationService couchbaseTranslationService) {
return new ReactiveCouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter,
couchbaseTranslationService);
return new ReactiveCouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter, couchbaseTranslationService,
getDefaultConsistency());
}
public ReactiveCouchbaseTemplate reactiveCouchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory,
@@ -379,4 +381,8 @@ public abstract class AbstractCouchbaseConfiguration {
}
}
public QueryScanConsistency getDefaultConsistency() {
return null;
}
}

View File

@@ -19,6 +19,8 @@ package org.springframework.data.couchbase.core;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Defines common operations on the Couchbase data source, most commonly implemented by {@link CouchbaseTemplate}.
*/
@@ -44,4 +46,8 @@ public interface CouchbaseOperations extends FluentCouchbaseOperations {
*/
CouchbaseClientFactory getCouchbaseClientFactory();
/**
* Returns the default consistency to use for queries
*/
QueryScanConsistency getConsistency();
}

View File

@@ -28,11 +28,11 @@ import org.springframework.data.couchbase.core.index.CouchbasePersistentEntityIn
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Implements lower-level couchbase operations on top of the SDK with entity mapping capabilities.
@@ -50,6 +50,7 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContex
private final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
private final ReactiveCouchbaseTemplate reactiveCouchbaseTemplate;
private @Nullable CouchbasePersistentEntityIndexCreator indexCreator;
private QueryScanConsistency scanConsistency;
public CouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
this(clientFactory, converter, new JacksonTranslationService());
@@ -57,11 +58,17 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContex
public CouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter,
final TranslationService translationService) {
this(clientFactory, converter, translationService, null);
}
public CouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter,
final TranslationService translationService, QueryScanConsistency scanConsistency) {
this.clientFactory = clientFactory;
this.converter = converter;
this.templateSupport = new CouchbaseTemplateSupport(converter, translationService);
this.reactiveCouchbaseTemplate = new ReactiveCouchbaseTemplate(clientFactory, converter, translationService);
this.reactiveCouchbaseTemplate = new ReactiveCouchbaseTemplate(clientFactory, converter, translationService,
scanConsistency);
this.scanConsistency = scanConsistency;
this.mappingContext = this.converter.getMappingContext();
if (mappingContext instanceof CouchbaseMappingContext) {
CouchbaseMappingContext cmc = (CouchbaseMappingContext) mappingContext;
@@ -136,6 +143,11 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationContex
return clientFactory;
}
@Override
public QueryScanConsistency getConsistency() {
return scanConsistency;
}
/**
* Provides access to a {@link Collection} on the configured {@link CouchbaseClientFactory}.
*

View File

@@ -20,6 +20,8 @@ import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* Defines common operations on the Couchbase data source, most commonly implemented by
* {@link ReactiveCouchbaseTemplate}.
@@ -47,8 +49,12 @@ public interface ReactiveCouchbaseOperations extends ReactiveFluentCouchbaseOper
CouchbaseClientFactory getCouchbaseClientFactory();
/**
* @@return the pseudoArgs from the ThreadLocal field of the CouchbaseOperations
* @return the pseudoArgs from the ThreadLocal field of the CouchbaseOperations
*/
PseudoArgs<?> getPseudoArgs();
/**
* @return the default consistency to use for queries
*/
QueryScanConsistency getConsistency();
}

View File

@@ -28,6 +28,7 @@ import org.springframework.data.couchbase.core.convert.translation.TranslationSe
import org.springframework.data.couchbase.core.support.PseudoArgs;
import com.couchbase.client.java.Collection;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* template class for Reactive Couchbase operations
@@ -44,6 +45,7 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
private final PersistenceExceptionTranslator exceptionTranslator;
private final ReactiveCouchbaseTemplateSupport templateSupport;
private ThreadLocal<PseudoArgs<?>> threadLocalArgs = new ThreadLocal<>();
private QueryScanConsistency scanConsistency;
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter) {
this(clientFactory, converter, new JacksonTranslationService());
@@ -51,10 +53,16 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter,
final TranslationService translationService) {
this(clientFactory, converter, translationService, null);
}
public ReactiveCouchbaseTemplate(final CouchbaseClientFactory clientFactory, final CouchbaseConverter converter,
final TranslationService translationService, QueryScanConsistency scanConsistency) {
this.clientFactory = clientFactory;
this.converter = converter;
this.exceptionTranslator = clientFactory.getExceptionTranslator();
this.templateSupport = new ReactiveCouchbaseTemplateSupport(converter, translationService);
this.scanConsistency = scanConsistency;
}
@Override
@@ -165,4 +173,12 @@ public class ReactiveCouchbaseTemplate implements ReactiveCouchbaseOperations, A
return threadLocalArgs == null ? null : threadLocalArgs.get();
}
/**
* {@inheritDoc}
*/
@Override
public QueryScanConsistency getConsistency() {
return scanConsistency;
}
}

View File

@@ -201,7 +201,8 @@ public class ReactiveFindByQueryOperationSupport implements ReactiveFindByQueryO
@Override
public QueryOptions buildOptions(QueryOptions options) {
QueryOptions opts = query.buildQueryOptions(options, scanConsistency);
QueryScanConsistency qsc = scanConsistency != null ? scanConsistency : template.getConsistency();
QueryOptions opts = query.buildQueryOptions(options, qsc);
return opts;
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.data.couchbase.core;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -26,6 +25,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.support.PseudoArgs;
import org.springframework.data.couchbase.core.support.TemplateUtils;
import org.springframework.util.Assert;
import com.couchbase.client.java.query.QueryOptions;
import com.couchbase.client.java.query.QueryScanConsistency;
@@ -45,8 +45,7 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
@Override
public <T> ReactiveRemoveByQuery<T> removeByQuery(Class<T> domainType) {
return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY,null, null,
null, null);
return new ReactiveRemoveByQuerySupport<>(template, domainType, ALL_QUERY, null, null, null, null);
}
static class ReactiveRemoveByQuerySupport<T> implements ReactiveRemoveByQuery<T> {
@@ -94,7 +93,8 @@ public class ReactiveRemoveByQueryOperationSupport implements ReactiveRemoveByQu
}
private QueryOptions buildQueryOptions(QueryOptions options) {
return query.buildQueryOptions(options, scanConsistency);
QueryScanConsistency qsc = scanConsistency != null ? scanConsistency : template.getConsistency();
return query.buildQueryOptions(options, qsc);
}
@Override

View File

@@ -43,6 +43,10 @@ import com.couchbase.client.java.query.QueryScanConsistency;
@Repository
public interface AirportRepository extends CouchbaseRepository<Airport, String> {
// NOT_BOUNDED to test ScanConsistency
// @ScanConsistency(query = QueryScanConsistency.NOT_BOUNDED)
Airport iata(String iata);
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
List<Airport> findAll();

View File

@@ -16,13 +16,17 @@
package org.springframework.data.couchbase.repository;
import static com.couchbase.client.java.query.QueryScanConsistency.NOT_BOUNDED;
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -39,13 +43,17 @@ import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.RemoveResult;
import org.springframework.data.couchbase.core.query.N1QLExpression;
import org.springframework.data.couchbase.core.query.Query;
import org.springframework.data.couchbase.core.query.QueryCriteria;
@@ -194,14 +202,85 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
}
}
@Test
public void saveNotBounded() {
// save() followed by query with NOT_BOUNDED will result in not finding the document
Airport vie = new Airport("airports::vie", "vie", "low9");
Airport airport2 = null;
for (int i = 1; i <= 100; i++) {
// set version == 0 so save() will be an upsert, not a replace
Airport saved = airportRepository.save(vie.clearVersion());
try {
airport2 = airportRepository.iata(saved.getIata());
if (airport2 == null) {
break;
}
} catch (DataRetrievalFailureException drfe) {
airport2 = null; //
} finally {
// airportRepository.delete(vie);
// instead of delete, use removeResult to test QueryOptions.consistentWith()
RemoveResult removeResult = couchbaseTemplate.removeById().one(vie.getId());
assertEquals(vie.getId(), removeResult.getId());
assertTrue(removeResult.getCas() != 0);
assertTrue(removeResult.getMutationToken().isPresent());
Airport airport3 = airportRepository.iata(vie.getIata());
assertNull(airport3, "should have been removed");
}
}
assertNull(airport2, "airport2 should have likely been null at least once");
Airport saved = airportRepository.save(vie.clearVersion());
couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).all();
airport2 = airportRepository.iata(vie.getIata());
RemoveResult removeResult = couchbaseTemplate.removeById().one(saved.getId());
assertNotNull(airport2, "airport2 should have been found");
}
@Test
public void saveNotBoundedRequestPlus() {
ApplicationContext ac = new AnnotationConfigApplicationContext(ConfigRequestPlus.class);
// the Config class has been modified, these need to be loaded again
CouchbaseTemplate couchbaseTemplateRP = (CouchbaseTemplate) ac.getBean(COUCHBASE_TEMPLATE);
AirportRepository airportRepositoryRP = (AirportRepository) ac.getBean("airportRepository");
// save() followed by query with NOT_BOUNDED will result in not finding the document
Airport vie = new Airport("airports::vie", "vie", "low9");
Airport airport2 = null;
for (int i = 1; i <= 100; i++) {
// set version == 0 so save() will be an upsert, not a replace
Airport saved = airportRepositoryRP.save(vie.clearVersion());
try {
airport2 = airportRepositoryRP.iata(saved.getIata());
if (airport2 == null) {
break;
}
} catch (DataRetrievalFailureException drfe) {
airport2 = null; //
} finally {
// airportRepository.delete(vie);
// instead of delete, use removeResult to test QueryOptions.consistentWith()
RemoveResult removeResult = couchbaseTemplateRP.removeById().one(vie.getId());
assertEquals(vie.getId(), removeResult.getId());
assertTrue(removeResult.getCas() != 0);
assertTrue(removeResult.getMutationToken().isPresent());
Airport airport3 = airportRepositoryRP.iata(vie.getIata());
assertNull(airport3, "should have been removed");
}
}
assertNotNull(airport2, "airport2 should have never been null");
Airport saved = airportRepositoryRP.save(vie.clearVersion());
List<Airport> airports = couchbaseTemplateRP.findByQuery(Airport.class).withConsistency(NOT_BOUNDED).all();
RemoveResult removeResult = couchbaseTemplateRP.removeById().one(saved.getId());
assertFalse(!airports.isEmpty(), "airports should have been empty");
}
@Test
void findByTypeAlias() {
Airport vie = null;
try {
vie = new Airport("airports::vie", "vie", "loww");
vie = airportRepository.save(vie);
List<Airport> airports = couchbaseTemplate.findByQuery(Airport.class)
.withConsistency(QueryScanConsistency.REQUEST_PLUS)
List<Airport> airports = couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS)
.matching(new Query(QueryCriteria.where(N1QLExpression.x("_class")).is("airport"))).all();
assertFalse(airports.isEmpty(), "should have found aiport");
} finally {
@@ -252,7 +331,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
userRepository.save(user1);
userRepository.save(user2);
List<User> users = userRepository.findByLastname("Wilson").collect(Collectors.toList());
assertEquals(2,users.size());
assertEquals(2, users.size());
assertTrue(users.contains(user1));
assertTrue(users.contains(user2));
userRepository.delete(user1);
@@ -268,7 +347,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
airportRepository.saveAll(
Arrays.stream(iatas).map((iata) -> new Airport("airports::" + iata, iata, iata.toLowerCase(Locale.ROOT)))
.collect(Collectors.toSet()));
couchbaseTemplate.findByQuery(Airport.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).all();
couchbaseTemplate.findByQuery(Airport.class).withConsistency(REQUEST_PLUS).all();
Long count = airportRepository.countFancyExpression(asList("JFK"), asList("jfk"), false);
assertEquals(1, count);
@@ -427,7 +506,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
void couchbaseRepositoryQuery() throws Exception {
User user = new User("1", "Dave", "Wilson");
userRepository.save(user);
couchbaseTemplate.findByQuery(User.class).withConsistency(QueryScanConsistency.REQUEST_PLUS)
couchbaseTemplate.findByQuery(User.class).withConsistency(REQUEST_PLUS)
.matching(QueryCriteria.where("firstname").is("Dave").and("`1`").is("`1`")).all();
String input = "findByFirstname";
Method method = UserRepository.class.getMethod(input, String.class);
@@ -493,6 +572,47 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
public DateTimeProvider testDateTimeProvider() {
return new AuditingDateTimeProvider();
}
}
@Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
@EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef")
static class ConfigRequestPlus extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
@Bean(name = "auditorAwareRef")
public NaiveAuditorAware testAuditorAware() {
return new NaiveAuditorAware();
}
@Bean(name = "dateTimeProviderRef")
public DateTimeProvider testDateTimeProvider() {
return new AuditingDateTimeProvider();
}
@Override
public QueryScanConsistency getDefaultConsistency() {
return REQUEST_PLUS;
}
}
}