DATACOUCH-574 - Support countBy...() query method calls in repository.

This commit is contained in:
mikereiche
2020-06-23 13:31:02 -07:00
parent b7543dfaf4
commit 2e2aecf5f1
8 changed files with 122 additions and 31 deletions

View File

@@ -163,6 +163,16 @@ public class CouchbaseQueryMethod extends QueryMethod {
return StringUtils.hasText(query) ? query : null; return StringUtils.hasText(query) ? query : null;
} }
/**
* indicates if the method begins with "count"
*
* @return true if the method begins with "count", indicating that .count() should be called instead of one() or
* all().
*/
public boolean isCountQuery() {
return getName().toLowerCase().startsWith("count");
}
@Override @Override
public String toString() { public String toString() {
return super.toString(); return super.toString();

View File

@@ -59,15 +59,16 @@ public class N1qlRepositoryQueryExecutor {
Query query; Query query;
ExecutableFindByQueryOperation.ExecutableFindByQuery q; ExecutableFindByQueryOperation.ExecutableFindByQuery q;
if (queryMethod.hasN1qlAnnotation()) { if (queryMethod.hasN1qlAnnotation()) {
query = new StringN1qlQueryCreator(accessor, queryMethod, operations.getConverter(), query = new StringN1qlQueryCreator(accessor, queryMethod, operations.getConverter(), operations.getBucketName(),
operations.getBucketName(), QueryMethodEvaluationContextProvider.DEFAULT, QueryMethodEvaluationContextProvider.DEFAULT, namedQueries).createQuery();
namedQueries).createQuery();
} else { } else {
final PartTree tree = new PartTree(queryMethod.getName(), domainClass); final PartTree tree = new PartTree(queryMethod.getName(), domainClass);
query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter()).createQuery(); query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter()).createQuery();
} }
q = (ExecutableFindByQueryOperation.ExecutableFindByQuery) operations.findByQuery(domainClass).matching(query); q = (ExecutableFindByQueryOperation.ExecutableFindByQuery) operations.findByQuery(domainClass).matching(query);
if (queryMethod.isCollectionQuery()) { if (queryMethod.isCountQuery()) {
return q.count();
} else if (queryMethod.isCollectionQuery()) {
return q.all(); return q.all();
} else { } else {
return q.oneValue(); return q.oneValue();

View File

@@ -63,15 +63,16 @@ public class ReactiveN1qlRepositoryQueryExecutor {
Query query; Query query;
ReactiveFindByQueryOperation.ReactiveFindByQuery q; ReactiveFindByQueryOperation.ReactiveFindByQuery q;
if (queryMethod.hasN1qlAnnotation()) { if (queryMethod.hasN1qlAnnotation()) {
query = new StringN1qlQueryCreator(accessor, queryMethod, operations.getConverter(), query = new StringN1qlQueryCreator(accessor, queryMethod, operations.getConverter(), operations.getBucketName(),
operations.getBucketName(), QueryMethodEvaluationContextProvider.DEFAULT, QueryMethodEvaluationContextProvider.DEFAULT, namedQueries).createQuery();
namedQueries).createQuery();
} else { } else {
final PartTree tree = new PartTree(queryMethod.getName(), domainClass); final PartTree tree = new PartTree(queryMethod.getName(), domainClass);
query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter()).createQuery(); query = new N1qlQueryCreator(tree, accessor, queryMethod, operations.getConverter()).createQuery();
} }
q = (ReactiveFindByQueryOperation.ReactiveFindByQuery) operations.findByQuery(domainClass).matching(query); q = (ReactiveFindByQueryOperation.ReactiveFindByQuery) operations.findByQuery(domainClass).matching(query);
if (queryMethod.isCollectionQuery()) { if (queryMethod.isCountQuery()) {
return q.count();
} else if (queryMethod.isCollectionQuery()) {
return q.all(); return q.all();
} else { } else {
return q.one(); return q.one();

View File

@@ -25,6 +25,12 @@ import org.springframework.stereotype.Repository;
import com.couchbase.client.java.query.QueryScanConsistency; import com.couchbase.client.java.query.QueryScanConsistency;
/**
* template class for Reactive Couchbase operations
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Repository @Repository
public interface AirportRepository extends PagingAndSortingRepository<Airport, String> { public interface AirportRepository extends PagingAndSortingRepository<Airport, String> {
@@ -37,4 +43,8 @@ public interface AirportRepository extends PagingAndSortingRepository<Airport, S
@Query("#{#n1ql.selectEntity} where iata = $1") @Query("#{#n1ql.selectEntity} where iata = $1")
List<Airport> getAllByIata(String iata); List<Airport> getAllByIata(String iata);
long countByIataIn(String... iata);
long countByIcaoAndIataIn(String icao, String... iata);
} }

View File

@@ -69,7 +69,7 @@ public class Config extends AbstractCouchbaseConfiguration {
} }
} }
String clusterGet( String methodName, String defaultValue){ String clusterGet(String methodName, String defaultValue) {
if (clusterAware != null) { if (clusterAware != null) {
try { try {
return (String) clusterAware.getMethod(methodName).invoke(null); return (String) clusterAware.getMethod(methodName).invoke(null);
@@ -82,22 +82,22 @@ public class Config extends AbstractCouchbaseConfiguration {
@Override @Override
public String getConnectionString() { public String getConnectionString() {
return clusterGet( "connectionString", connectionString ); return clusterGet("connectionString", connectionString);
} }
@Override @Override
public String getUserName() { public String getUserName() {
return clusterGet( "username", username ); return clusterGet("username", username);
} }
@Override @Override
public String getPassword() { public String getPassword() {
return clusterGet( "password", password ); return clusterGet("password", password);
} }
@Override @Override
public String getBucketName() { public String getBucketName() {
return clusterGet( "bucketName", bucketname ); return clusterGet("bucketName", bucketname);
} }
@Bean(name = "auditorAwareRef") @Bean(name = "auditorAwareRef")
@@ -113,23 +113,30 @@ public class Config extends AbstractCouchbaseConfiguration {
@Override @Override
public void configureReactiveRepositoryOperationsMapping(ReactiveRepositoryOperationsMapping baseMapping) { public void configureReactiveRepositoryOperationsMapping(ReactiveRepositoryOperationsMapping baseMapping) {
try { try {
ReactiveCouchbaseTemplate personTemplate = myReactiveCouchbaseTemplate(myCouchbaseClientFactory("protected"),new MappingCouchbaseConverter()); // comment out references to 'protected' and 'mybucket' - they are only to show how multi-bucket would work
baseMapping.mapEntity(Person.class, personTemplate); // Person goes in "protected" bucket // ReactiveCouchbaseTemplate personTemplate =
ReactiveCouchbaseTemplate userTemplate = myReactiveCouchbaseTemplate(myCouchbaseClientFactory("mybucket"),new MappingCouchbaseConverter()); // myReactiveCouchbaseTemplate(myCouchbaseClientFactory("protected"),new MappingCouchbaseConverter());
baseMapping.mapEntity(User.class, userTemplate); // User goes in "mybucket" // baseMapping.mapEntity(Person.class, personTemplate); // Person goes in "protected" bucket
// everything else goes in getBucketName() ( which is travel-sample ) // ReactiveCouchbaseTemplate userTemplate = myReactiveCouchbaseTemplate(myCouchbaseClientFactory("mybucket"),new
// MappingCouchbaseConverter());
// baseMapping.mapEntity(User.class, userTemplate); // User goes in "mybucket"
// everything else goes in getBucketName() ( which is travel-sample )
} catch (Exception e) { } catch (Exception e) {
throw e; throw e;
} }
} }
@Override @Override
public void configureRepositoryOperationsMapping(RepositoryOperationsMapping baseMapping) { public void configureRepositoryOperationsMapping(RepositoryOperationsMapping baseMapping) {
try { try {
CouchbaseTemplate personTemplate = myCouchbaseTemplate(myCouchbaseClientFactory("protected"),new MappingCouchbaseConverter()); // comment out references to 'protected' and 'mybucket' - they are only to show how multi-bucket would work
baseMapping.mapEntity(Person.class, personTemplate); // Person goes in "protected" bucket // CouchbaseTemplate personTemplate = myCouchbaseTemplate(myCouchbaseClientFactory("protected"),new
CouchbaseTemplate userTemplate = myCouchbaseTemplate(myCouchbaseClientFactory("mybucket"),new MappingCouchbaseConverter()); // MappingCouchbaseConverter());
baseMapping.mapEntity(User.class, userTemplate); // User goes in "mybucket" // baseMapping.mapEntity(Person.class, personTemplate); // Person goes in "protected" bucket
// everything else goes in getBucketName() ( which is travel-sample ) // CouchbaseTemplate userTemplate = myCouchbaseTemplate(myCouchbaseClientFactory("mybucket"),new
// MappingCouchbaseConverter());
// baseMapping.mapEntity(User.class, userTemplate); // User goes in "mybucket"
// everything else goes in getBucketName() ( which is travel-sample )
} catch (Exception e) { } catch (Exception e) {
throw e; throw e;
} }
@@ -152,7 +159,7 @@ public class Config extends AbstractCouchbaseConfiguration {
// do not use couchbaseClientFactory for the name of this method, otherwise the value of that bean will // do not use couchbaseClientFactory for the name of this method, otherwise the value of that bean will
// will be used instead of this call being made ( bucketname is an arg here, instead of using bucketName() ) // will be used instead of this call being made ( bucketname is an arg here, instead of using bucketName() )
public CouchbaseClientFactory myCouchbaseClientFactory(String bucketName) { public CouchbaseClientFactory myCouchbaseClientFactory(String bucketName) {
return new SimpleCouchbaseClientFactory(getConnectionString(),authenticator(), bucketName ); return new SimpleCouchbaseClientFactory(getConnectionString(), authenticator(), bucketName);
} }
// convenience constructor for tests // convenience constructor for tests

View File

@@ -21,9 +21,16 @@ import reactor.core.publisher.Flux;
import org.springframework.data.couchbase.repository.ScanConsistency; import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.reactive.ReactiveSortingRepository; import org.springframework.data.repository.reactive.ReactiveSortingRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;
import com.couchbase.client.java.query.QueryScanConsistency; import com.couchbase.client.java.query.QueryScanConsistency;
/**
* template class for Reactive Couchbase operations
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@Repository @Repository
public interface ReactiveAirportRepository extends ReactiveSortingRepository<Airport, String> { public interface ReactiveAirportRepository extends ReactiveSortingRepository<Airport, String> {
@@ -33,4 +40,7 @@ public interface ReactiveAirportRepository extends ReactiveSortingRepository<Air
Flux<Airport> findAllByIata(String iata); Flux<Airport> findAllByIata(String iata);
Mono<Long> countByIataIn(String... iatas);
Mono<Long> countByIcaoAndIataIn(String icao, String... iatas);
} }

View File

@@ -104,6 +104,15 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
airportCount = airportRepository.count(); airportCount = airportRepository.count();
assertEquals(7, airportCount); assertEquals(7, airportCount);
airportCount = airportRepository.countByIataIn("JFK", "IAD", "SFO");
assertEquals(3, airportCount);
airportCount = airportRepository.countByIcaoAndIataIn("jfk", "JFK", "IAD", "SFO", "XXX");
assertEquals(1, airportCount);
airportCount = airportRepository.countByIataIn("XXX");
assertEquals(0, airportCount);
} finally { } finally {
for (int i = 0; i < iatas.length; i++) { for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, iatas[i] /* lcao */); Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, iatas[i] /* lcao */);
@@ -113,7 +122,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
} }
@Test @Test
void threadSafeParametersTest() { void threadSafeParametersTest() throws Exception {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" }; String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
Future[] future = new Future[iatas.length]; Future[] future = new Future[iatas.length];
ExecutorService executorService = Executors.newFixedThreadPool(iatas.length); ExecutorService executorService = Executors.newFixedThreadPool(iatas.length);
@@ -121,7 +130,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
try { try {
Callable<Boolean>[] suppliers = new Callable[iatas.length]; Callable<Boolean>[] suppliers = new Callable[iatas.length];
for (int i = 0; i < iatas.length; i++) { for (int i = 0; i < iatas.length; i++) {
Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, iatas[i] /* lcao */); Airport airport = new Airport("airports::" + iatas[i], iatas[i] /*iata*/, iatas[i].toLowerCase() /* lcao */);
airportRepository.save(airport); airportRepository.save(airport);
final int idx = i; final int idx = i;
suppliers[i] = () -> { suppliers[i] = () -> {
@@ -143,8 +152,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
for (int i = 0; i < iatas.length; i++) { for (int i = 0; i < iatas.length; i++) {
future[i].get(); future[i].get();
} }
} catch (Exception e) {
throw new RuntimeException(e);
} finally { } finally {
executorService.shutdown(); executorService.shutdown();
for (int i = 0; i < iatas.length; i++) { for (int i = 0; i < iatas.length; i++) {
@@ -155,7 +163,7 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
} }
@Test @Test
void threadSafeStringParametersTest() { void threadSafeStringParametersTest() throws Exception {
String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" }; String[] iatas = { "JFK", "IAD", "SFO", "SJC", "SEA", "LAX", "PHX" };
Future[] future = new Future[iatas.length]; Future[] future = new Future[iatas.length];
ExecutorService executorService = Executors.newFixedThreadPool(iatas.length); ExecutorService executorService = Executors.newFixedThreadPool(iatas.length);
@@ -185,8 +193,6 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
for (int i = 0; i < iatas.length; i++) { for (int i = 0; i < iatas.length; i++) {
future[i].get(); future[i].get();
} }
} catch (Exception e) {
throw new RuntimeException(e);
} finally { } finally {
executorService.shutdown(); executorService.shutdown();
for (int i = 0; i < iatas.length; i++) { for (int i = 0; i < iatas.length; i++) {

View File

@@ -19,6 +19,10 @@ package org.springframework.data.couchbase.repository;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -38,6 +42,12 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import com.couchbase.client.core.error.IndexExistsException; import com.couchbase.client.core.error.IndexExistsException;
/**
* template class for Reactive Couchbase operations
*
* @author Michael Nitschinger
* @author Michael Reiche
*/
@SpringJUnitConfig(ReactiveCouchbaseRepositoryQueryIntegrationTests.Config.class) @SpringJUnitConfig(ReactiveCouchbaseRepositoryQueryIntegrationTests.Config.class)
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED) @IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegrationTests { public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegrationTests {
@@ -73,6 +83,42 @@ public class ReactiveCouchbaseRepositoryQueryIntegrationTests extends ClusterAwa
System.err.println(airports); System.err.println(airports);
} }
@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() /* lcao */);
airportRepository.save(airport).block();
}
try {
Thread.sleep(1000);
} catch (InterruptedException ie) {}
Long airportCount = null;
airportCount = airportRepository.count().block();
assertEquals(7, airportCount);
airportCount = airportRepository.countByIataIn("JFK", "IAD", "SFO").block();
assertEquals(3, airportCount);
airportCount = airportRepository.countByIcaoAndIataIn("jfk", "JFK", "IAD", "SFO", "XXX").block();
assertEquals(1, airportCount);
airportCount = airportRepository.countByIataIn("XXX").block();
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 */);
airportRepository.delete(airport);
}
}
}
@Configuration @Configuration
@EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase") @EnableReactiveCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration { static class Config extends AbstractCouchbaseConfiguration {