DATACOUCH-588 - Refactoring part 1 of n. (#278)

* DATACOUCH-588 - Implement pageable and realign repo query

This implements pageable for non-reactive queries and
realigns reactive queries with other spring-data projects
to facilitate the implementaion of pageable (done) and other
types of queries such as projection and distinct (not yet
done)

* DATACOUCH-588 - Implement pageable and realign repo query

This implements pageable for non-reactive queries and
realigns reactive queries with other spring-data projects
to facilitate the implementaion of pageable (done) and other
types of queries such as projection and distinct (not yet
done)

* DATACOUCH-588 - Refactoring part 1 of n.

Co-authored-by: mikereiche <michael.reiche@couchbase.com>
This commit is contained in:
Michael Reiche
2020-12-21 13:48:50 -08:00
committed by GitHub
parent 9c9dde7312
commit 3364e0fa7f
59 changed files with 2389 additions and 241 deletions

View File

@@ -16,8 +16,12 @@
package org.springframework.data.couchbase.core;
import static org.junit.jupiter.api.Assertions.*;
import static org.springframework.data.couchbase.config.BeanNames.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE;
import static org.springframework.data.couchbase.config.BeanNames.REACTIVE_COUCHBASE_TEMPLATE;
import java.io.IOException;
import java.time.Instant;
@@ -99,7 +103,6 @@ class CouchbaseTemplateQueryIntegrationTests extends ClusterAwareIntegrationTest
.consistentWith(QueryScanConsistency.REQUEST_PLUS).all();
for (User u : foundUsers) {
System.out.println(u);
if (!(u.equals(user1) || u.equals(user2))) {
// somebody didn't clean up after themselves.
couchbaseTemplate.removeById().one(u.getId());
@@ -177,8 +180,7 @@ class CouchbaseTemplateQueryIntegrationTests extends ClusterAwareIntegrationTest
Query nonSpecialUsers = new Query(QueryCriteria.where("firstname").notLike("special"));
couchbaseTemplate.removeByQuery(User.class).consistentWith(QueryScanConsistency.REQUEST_PLUS)
.matching(nonSpecialUsers)
.all();
.matching(nonSpecialUsers).all();
assertNull(couchbaseTemplate.findById(User.class).one(user1.getId()));
assertNull(couchbaseTemplate.findById(User.class).one(user2.getId()));

View File

@@ -20,6 +20,12 @@ import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* Airport entity
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Document
public class Airport {
@Id String id;

View File

@@ -18,13 +18,16 @@ package org.springframework.data.couchbase.domain;
import java.util.List;
import com.couchbase.client.java.query.QueryScanConsistency;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.query.QueryScanConsistency;
/**
* template class for Reactive Couchbase operations
*
@@ -64,11 +67,12 @@ public interface AirportRepository extends PagingAndSortingRepository<Airport, S
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
long count();
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} " +
" #{#projectIds != null ? 'AND iata IN $1' : ''} " +
" #{#planIds != null ? 'AND icao IN $2' : ''} " +
" #{#active != null ? 'AND false = $3' : ''} ")
@Query("#{#n1ql.selectEntity} WHERE #{#n1ql.filter} #{#projectIds != null ? 'AND iata IN $1' : ''} "
+ " #{#planIds != null ? 'AND icao IN $2' : ''} #{#active != null ? 'AND false = $3' : ''} ")
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Long countFancyExpression(@Param("projectIds") List<String> projectIds, @Param("planIds") List<String> planIds, @Param("active") Boolean active);
Long countFancyExpression(@Param("projectIds") List<String> projectIds, @Param("planIds") List<String> planIds,
@Param("active") Boolean active);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Page<Airport> findAllByIataNot(String iata, Pageable pageable);
}

View File

@@ -16,31 +16,29 @@
package org.springframework.data.couchbase.domain;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.ClusterOptions;
import com.couchbase.client.java.env.ClusterEnvironment;
import java.lang.reflect.InvocationTargetException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.auditing.DateTimeProvider;
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.config.BeanNames;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;
import org.springframework.data.util.TypeInformation;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import com.couchbase.client.core.deps.com.fasterxml.jackson.databind.DeserializationFeature;
import com.couchbase.client.java.json.JacksonTransformers;
/**
* @author Michael Nitschinger
@@ -190,6 +188,17 @@ public class Config extends AbstractCouchbaseConfiguration {
return converter;
}
@Override
@Bean(name = "couchbaseTranslationService")
public TranslationService couchbaseTranslationService() {
final JacksonTranslationService jacksonTranslationService = new JacksonTranslationService();
jacksonTranslationService.afterPropertiesSet();
// for sdk3, we need to ask the mapper _it_ uses to ignore extra fields...
JacksonTransformers.MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return jacksonTranslationService;
}
@Override
public String typeKey() {
return "t"; // this will override '_class', is passed in to new CustomMappingCouchbaseConverter

View File

@@ -16,13 +16,19 @@
package org.springframework.data.couchbase.domain;
import org.springframework.data.couchbase.repository.Query;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;
import com.couchbase.client.java.query.QueryScanConsistency;
@@ -62,4 +68,25 @@ public interface ReactiveAirportRepository extends ReactiveSortingRepository<Air
@Override
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Mono<Airport> findById(String var1);
// use parameter type PageRequest instead of Pageable. Pageable requires a return type of Page<>
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<Airport> findAllByIataLike(String iata, final PageRequest page);
// use parameter type PageRequest instead of Pageable. Pageable requires a return type of Page<>
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Flux<Airport> findAllByIataLike(String iata);
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Mono<Airport> findByIata(String iata);
// This is not efficient. See findAllByIataLike for efficient reactive paging
default public Mono<Page<Airport>> findAllAirportsPaged(Pageable pageable) {
return count().flatMap(airportCount -> {
return findAll(pageable.getSort())
.buffer(pageable.getPageSize(), (pageable.getPageNumber() * pageable.getPageSize()))
.elementAt(pageable.getPageNumber(), new ArrayList<>())
.map(airports -> new PageImpl<Airport>(airports, pageable, airportCount));
});
}
}

View File

@@ -40,17 +40,19 @@ import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.domain.Address;
import org.springframework.data.couchbase.domain.Airport;
import org.springframework.data.couchbase.domain.AirportRepository;
import org.springframework.data.couchbase.domain.ReactiveUserRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.couchbase.domain.Person;
import org.springframework.data.couchbase.domain.PersonRepository;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.domain.UserRepository;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.util.StreamUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.error.IndexExistsException;
@@ -141,13 +143,16 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
Airport vie = null;
try {
vie = new Airport("airports::vie", "vie", "loww");
airportRepository.save(vie);
vie = airportRepository.save(vie);
List<Airport> airports = airportRepository.findAllByIata("vie");
assertEquals(vie.getId(), airports.get(0).getId());
assertEquals(1, airports.size());
Airport airport1 = airportRepository.findById(airports.get(0).getId()).get();
assertEquals(airport1.getIata(), vie.getIata());
Airport airport2 = airportRepository.findByIata(airports.get(0).getIata());
assertEquals(airport1.getId(), vie.getId());
} finally {
airportRepository.delete(vie);
}
}
@Test
@@ -164,11 +169,8 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
@Test
void count() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
Future[] future = new Future[iatas.length];
ExecutorService executorService = Executors.newFixedThreadPool(iatas.length);
try {
Callable<Boolean>[] suppliers = new Callable[iatas.length];
for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/,
iatas[i].toLowerCase(Locale.ROOT) /* lcao */);
@@ -178,6 +180,11 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
Long count = airportRepository.countFancyExpression(asList("JFK"), asList("jfk"), false);
assertEquals(1, count);
Pageable pageable = PageRequest.of(0, 2);
Page<Airport> aPage = airportRepository.findAllByIataNot("JFK", pageable);
assertEquals(iatas.length - 1, aPage.getTotalElements());
assertEquals(pageable.getPageSize(), aPage.getContent().size());
long airportCount = airportRepository.count();
assertEquals(7, airportCount);

View File

@@ -22,10 +22,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -49,6 +50,7 @@ import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.error.IndexExistsException;
@@ -80,16 +82,21 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends ClusterAwa
@Test
void shouldSaveAndFindAll() {
Airport vie = null;
Airport jfk = null;
try {
vie = new Airport("airports::vie", "vie", "loww");
airportRepository.save(vie).block();
jfk = new Airport("airports::jfk", "JFK", "xxxx");
airportRepository.save(jfk).block();
List<Airport> all = airportRepository.findAll().toStream().collect(Collectors.toList());
assertFalse(all.isEmpty());
assertTrue(all.stream().anyMatch(a -> a.getId().equals("airports::vie")));
assertTrue(all.stream().anyMatch(a -> a.getId().equals("airports::jfk")));
} finally {
airportRepository.delete(vie).block();
airportRepository.delete(jfk).block();
}
}
@@ -103,6 +110,13 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends ClusterAwa
assertEquals(1, airports1.size());
List<Airport> airports2 = airportRepository.findAllByIata("vie").collectList().block();
assertEquals(1, airports2.size());
vie = airportRepository.save(vie).block();
List<Airport> airports = airportRepository.findAllByIata("vie").collectList().block();
assertEquals(1, airports.size());
Airport airport1 = airportRepository.findById(airports.get(0).getId()).block();
assertEquals(airport1.getIata(), vie.getIata());
Airport airport2 = airportRepository.findByIata(airports.get(0).getIata()).block();
assertEquals(airport1.getId(), vie.getId());
} finally {
airportRepository.delete(vie).block();
}
@@ -121,18 +135,35 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends ClusterAwa
@Test
void count() {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
Future[] future = new Future[iatas.length];
ExecutorService executorService = Executors.newFixedThreadPool(iatas.length);
Set<String> iatas = new HashSet();
iatas.add("JFK");
iatas.add("IAD");
iatas.add("SFO");
iatas.add("SJC");
iatas.add("SEA");
iatas.add("LAX");
iatas.add("PHX");
Future[] future = new Future[iatas.size()];
ExecutorService executorService = Executors.newFixedThreadPool(iatas.size());
try {
Callable<Boolean>[] suppliers = new Callable[iatas.length];
for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, iatas[i].toLowerCase() /* lcao */);
Callable<Boolean>[] suppliers = new Callable[iatas.size()];
for (String iata : iatas) {
Airport airport = new Airport("airports::" + iata, iata, iata.toLowerCase() /* lcao */);
airportRepository.save(airport).block();
}
int page = 0;
airportRepository.findAllByIataLike("S%", PageRequest.of(page++, 2)).as(StepVerifier::create) //
.expectNextMatches(a -> {
return iatas.contains(a.getIata());
}).expectNextMatches(a -> iatas.contains(a.getIata())).verifyComplete();
airportRepository.findAllByIataLike("S%", PageRequest.of(page++, 2)).as(StepVerifier::create) //
.expectNextMatches(a -> iatas.contains(a.getIata())).verifyComplete();
Long airportCount = airportRepository.count().block();
assertEquals(iatas.length, airportCount);
assertEquals(iatas.size(), airportCount);
airportCount = airportRepository.countByIataIn("JFK", "IAD", "SFO").block();
assertEquals(3, airportCount);
@@ -144,8 +175,8 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends ClusterAwa
assertEquals(0, airportCount);
} finally {
for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, iatas[i] /* lcao */);
for (String iata : iatas) {
Airport airport = new Airport("airports::" + iata, iata, iata.toLowerCase() /* lcao */);
try {
airportRepository.delete(airport).block();
} catch (DataRetrievalFailureException drfe) {

View File

@@ -15,6 +15,13 @@
*/
package org.springframework.data.couchbase.repository.query;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE;
import java.lang.reflect.Method;
import java.util.Properties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
@@ -37,14 +44,12 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.data.repository.query.*;
import java.lang.reflect.Method;
import java.util.Properties;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* @author Michael Nitschinger
@@ -75,7 +80,8 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
converter.getMappingContext());
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver", "Twist"),
queryMethod, converter, "travel-sample", QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
queryMethod, converter, "travel-sample", new SpelExpressionParser(),
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
Query query = creator.createQuery();
assertEquals(
@@ -93,7 +99,8 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
converter.getMappingContext());
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver", "Twist"),
queryMethod, converter, "travel-sample", QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
queryMethod, converter, "travel-sample", new SpelExpressionParser(),
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
Query query = creator.createQuery();
assertEquals(
@@ -112,7 +119,8 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
try {
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"),
queryMethod, converter, "travel-sample", QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
queryMethod, converter, "travel-sample", new SpelExpressionParser(),
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
} catch (IllegalArgumentException e) {
return;
}
@@ -129,7 +137,8 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
try {
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Oliver"),
queryMethod, converter, "travel-sample", QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
queryMethod, converter, "travel-sample", new SpelExpressionParser(),
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
} catch (IllegalArgumentException e) {
return;
}

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.couchbase.repository.query;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.springframework.data.couchbase.config.BeanNames.COUCHBASE_TEMPLATE;
import java.lang.reflect.Method;
@@ -37,7 +37,8 @@ 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.query.Query;
import org.springframework.data.couchbase.domain.*;
import org.springframework.data.couchbase.domain.Airline;
import org.springframework.data.couchbase.domain.AirlineRepository;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.util.Capabilities;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
@@ -48,7 +49,12 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.data.repository.query.*;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.Parameters;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
@@ -87,10 +93,10 @@ class StringN1qlQueryCreatorTests extends ClusterAwareIntegrationTests {
converter.getMappingContext());
StringN1qlQueryCreator creator = new StringN1qlQueryCreator(getAccessor(getParameters(method), "Continental"),
queryMethod, converter, config().bucketname(), QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
queryMethod, converter, config().bucketname(), new SpelExpressionParser(),
QueryMethodEvaluationContextProvider.DEFAULT, namedQueries);
Query query = creator.createQuery();
System.out.println(query.toN1qlSelectString(couchbaseTemplate.reactive(), Airline.class, false));
try {
Thread.sleep(3000);