DATACOUCH-185 - view-based CRUD methods use default consistency
The CRUD methods that are backed by a view in SimpleCouchbaseRepository now take the default view consistency into account. This includes findAll(), findAll(keys), count() and deleteAll(). PagingAndSortingRepository findAll methods already took the default N1QL consistency into account. This is verified in an unit test and has been made more explicit in the repository documentation.
This commit is contained in:
@@ -378,15 +378,20 @@ One aspect that is often needed and doesn't have a direct equivalent in the Spri
|
||||
`query consistency`. In both view-based queries and N1QL, you have this concept that the secondary index can return stale
|
||||
data, because the latest version hasn't been indexed yet. This gives the best performance at the expense of consistency.
|
||||
|
||||
Note that weaker consistencies can lead to data being returned that doesn't match the criteria of a derived query.
|
||||
|
||||
If one wants to have stronger consistency, there are two possibilities described in the next sections.
|
||||
|
||||
==== Configure it on a global level
|
||||
The global consistency used by generated queries (views and N1QL) is defined at the template level,
|
||||
using `Consistency` enumeration (like `Consistency.READ_YOUR_OWN_WRITE`):
|
||||
A global consistency can be defined using the `Consistency` enumeration (eg. `Consistency.READ_YOUR_OWN_WRITE`):
|
||||
|
||||
- in xml, this is done via the `consistency` attribute on `<couchbase:template>`.
|
||||
- in javaConfig, this is done by overriding the `getDefaultConsistency()` method.
|
||||
|
||||
By default it is `Consistency.UPDATE_AFTER` (which means speed is prioritized over consistency, but the view index will be updated after each request).
|
||||
|
||||
IMPORTANT: This is **only used in repositories**, either for index-backed methods automatically provided by the repository interface (`findAll()`, `findAll(keys)`, `count()`, `deleteAll()`...) or methods you define in your specific interface using query derivation.
|
||||
|
||||
==== Provide an implementation
|
||||
Provide the implementation and directly use `queryView` and `queryN1QL` methods on the template with a specific consistency
|
||||
(see <<repositories.single-repository-behaviour>>).
|
||||
|
||||
@@ -134,6 +134,7 @@ public class SimpleCouchbaseRepository<T, ID extends Serializable> implements Co
|
||||
final ResolvedView resolvedView = determineView();
|
||||
ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName());
|
||||
query.reduce(false);
|
||||
query.stale(getCouchbaseOperations().getDefaultConsistency().viewConsistency());
|
||||
return couchbaseOperations.findByView(query, entityInformation.getJavaType());
|
||||
}
|
||||
|
||||
@@ -142,6 +143,7 @@ public class SimpleCouchbaseRepository<T, ID extends Serializable> implements Co
|
||||
final ResolvedView resolvedView = determineView();
|
||||
ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName());
|
||||
query.reduce(false);
|
||||
query.stale(getCouchbaseOperations().getDefaultConsistency().viewConsistency());
|
||||
JsonArray keys = JsonArray.create();
|
||||
for (ID id : ids) {
|
||||
keys.add(id);
|
||||
@@ -156,6 +158,7 @@ public class SimpleCouchbaseRepository<T, ID extends Serializable> implements Co
|
||||
final ResolvedView resolvedView = determineView();
|
||||
ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName());
|
||||
query.reduce(true);
|
||||
query.stale(getCouchbaseOperations().getDefaultConsistency().viewConsistency());
|
||||
|
||||
ViewResult response = couchbaseOperations.queryView(query);
|
||||
|
||||
@@ -172,6 +175,8 @@ public class SimpleCouchbaseRepository<T, ID extends Serializable> implements Co
|
||||
final ResolvedView resolvedView = determineView();
|
||||
ViewQuery query = ViewQuery.from(resolvedView.getDesignDocument(), resolvedView.getViewName());
|
||||
query.reduce(false);
|
||||
query.stale(getCouchbaseOperations().getDefaultConsistency().viewConsistency());
|
||||
|
||||
|
||||
ViewResult response = couchbaseOperations.queryView(query);
|
||||
for (ViewRow row : response) {
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package org.springframework.data.couchbase.repository;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.couchbase.client.java.Bucket;
|
||||
import com.couchbase.client.java.document.json.JsonObject;
|
||||
import com.couchbase.client.java.query.N1qlQuery;
|
||||
import com.couchbase.client.java.view.ViewQuery;
|
||||
import com.couchbase.client.java.view.ViewResult;
|
||||
import com.couchbase.client.java.view.ViewRow;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.query.Consistency;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
|
||||
import org.springframework.data.couchbase.repository.support.N1qlCouchbaseRepository;
|
||||
import org.springframework.data.couchbase.repository.support.ViewMetadataProvider;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
public class RepositoryIndexUsageTest {
|
||||
|
||||
private static final org.springframework.data.couchbase.core.query.Consistency CONSISTENCY = Consistency.STRONGLY_CONSISTENT;
|
||||
|
||||
private CouchbaseOperations couchbaseOperations;
|
||||
private N1qlCouchbaseRepository<String, String> repository;
|
||||
|
||||
@Before
|
||||
public void initMocks() {
|
||||
ViewRow mockCountRow1 = mock(ViewRow.class);
|
||||
when(mockCountRow1.value()).thenReturn("100");
|
||||
ViewRow mockCountRow2 = mock(ViewRow.class);
|
||||
when(mockCountRow2.value()).thenReturn("200");
|
||||
List<ViewRow> allCountRows = Arrays.asList(mockCountRow1, mockCountRow2);
|
||||
|
||||
ViewResult mockCountResult = mock(ViewResult.class);
|
||||
when(mockCountResult.iterator()).thenReturn(allCountRows.iterator());
|
||||
|
||||
Bucket mockBucket = mock(Bucket.class);
|
||||
when(mockBucket.name()).thenReturn("mockBucket");
|
||||
|
||||
CouchbaseConverter mockConverter = mock(CouchbaseConverter.class);
|
||||
when(mockConverter.getTypeKey()).thenReturn("mockType");
|
||||
|
||||
couchbaseOperations = mock(CouchbaseOperations.class);
|
||||
when(couchbaseOperations.getDefaultConsistency()).thenReturn(CONSISTENCY);
|
||||
when(couchbaseOperations.getCouchbaseBucket()).thenReturn(mockBucket);
|
||||
when(couchbaseOperations.getConverter()).thenReturn(mockConverter);
|
||||
when(couchbaseOperations.findByView(any(ViewQuery.class), any(Class.class))).thenReturn(allCountRows);
|
||||
when(couchbaseOperations.findByN1QL(any(N1qlQuery.class), any(Class.class))).thenReturn(Collections.emptyList());
|
||||
when(couchbaseOperations.queryView(any(ViewQuery.class))).thenReturn(mockCountResult);
|
||||
when(couchbaseOperations.queryN1QL(any(N1qlQuery.class))).thenReturn(null);
|
||||
|
||||
CouchbaseEntityInformation metadata = mock(CouchbaseEntityInformation.class);
|
||||
when(metadata.getJavaType()).thenReturn(String.class);
|
||||
|
||||
repository = new N1qlCouchbaseRepository<String, String>(metadata, couchbaseOperations);
|
||||
repository.setViewMetadataProvider(mock(ViewMetadataProvider.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAllUsesViewWithConfiguredConsistency() {
|
||||
String expectedQueryParams = "reduce=false&stale=false";
|
||||
repository.findAll();
|
||||
|
||||
verify(couchbaseOperations, never()).queryView(any(ViewQuery.class));
|
||||
verify(couchbaseOperations, never()).findByN1QL(any(N1qlQuery.class), any(Class.class));
|
||||
verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class));
|
||||
ArgumentCaptor<ViewQuery> queryCaptor = ArgumentCaptor.forClass(ViewQuery.class);
|
||||
verify(couchbaseOperations).findByView(queryCaptor.capture(), any(Class.class));
|
||||
String sQuery = queryCaptor.getValue().toString();
|
||||
assertEquals(expectedQueryParams, sQuery);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAllKeysUsesViewWithConfiguredConsistency() {
|
||||
String expectedQueryParams = "reduce=false&stale=false";
|
||||
repository.findAll(Collections.singleton("someKey"));
|
||||
|
||||
verify(couchbaseOperations, never()).queryView(any(ViewQuery.class));
|
||||
verify(couchbaseOperations, never()).findByN1QL(any(N1qlQuery.class), any(Class.class));
|
||||
verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class));
|
||||
ArgumentCaptor<ViewQuery> queryCaptor = ArgumentCaptor.forClass(ViewQuery.class);
|
||||
verify(couchbaseOperations).findByView(queryCaptor.capture(), any(Class.class));
|
||||
String sQuery = queryCaptor.getValue().toString();
|
||||
assertEquals(expectedQueryParams, sQuery);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountUsesViewWithConfiguredConsistencyAndReduces() {
|
||||
String expectedQueryParams = "reduce=true&stale=false";
|
||||
repository.count();
|
||||
|
||||
verify(couchbaseOperations, never()).findByView(any(ViewQuery.class), any(Class.class));
|
||||
verify(couchbaseOperations, never()).findByN1QL(any(N1qlQuery.class), any(Class.class));
|
||||
verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class));
|
||||
ArgumentCaptor<ViewQuery> queryCaptor = ArgumentCaptor.forClass(ViewQuery.class);
|
||||
verify(couchbaseOperations).queryView(queryCaptor.capture());
|
||||
String sQuery = queryCaptor.getValue().toString();
|
||||
assertEquals(expectedQueryParams, sQuery);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCountParsesAndAddsLongValuesFromRows() {
|
||||
long count = repository.count();
|
||||
assertEquals(300L, count);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteAllUsesViewWithConfiguredConsistency() {
|
||||
String expectedQueryParams = "reduce=false&stale=false";
|
||||
repository.deleteAll();
|
||||
|
||||
verify(couchbaseOperations, never()).findByView(any(ViewQuery.class), any(Class.class));
|
||||
verify(couchbaseOperations, never()).findByN1QL(any(N1qlQuery.class), any(Class.class));
|
||||
verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class));
|
||||
ArgumentCaptor<ViewQuery> queryCaptor = ArgumentCaptor.forClass(ViewQuery.class);
|
||||
verify(couchbaseOperations).queryView(queryCaptor.capture());
|
||||
String sQuery = queryCaptor.getValue().toString();
|
||||
assertEquals(expectedQueryParams, sQuery);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAllSortedUsesN1qlWithConfiguredConsistencyAndOrderBy() {
|
||||
String expectedOrderClause = "ORDER BY `length` ASC";
|
||||
Sort sort = new Sort(Sort.Direction.ASC, "length");
|
||||
repository.findAll(sort);
|
||||
|
||||
verify(couchbaseOperations, never()).findByView(any(ViewQuery.class), any(Class.class));
|
||||
verify(couchbaseOperations, never()).queryView(any(ViewQuery.class));
|
||||
verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class));
|
||||
ArgumentCaptor<N1qlQuery> queryCaptor = ArgumentCaptor.forClass(N1qlQuery.class);
|
||||
verify(couchbaseOperations).findByN1QL(queryCaptor.capture(), any(Class.class));
|
||||
|
||||
JsonObject query = queryCaptor.getValue().n1ql();
|
||||
assertEquals(CONSISTENCY.n1qlConsistency().n1ql(), query.getString("scan_consistency"));
|
||||
String statement = query.getString("statement");
|
||||
assertTrue("Expected " + expectedOrderClause + " in " + statement, statement.contains(expectedOrderClause));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindAllPagedUsesUsesN1qlConfiguredConsistencyAndLimitOffset() {
|
||||
String expectedLimitClause = "LIMIT 10 OFFSET 0";
|
||||
repository.findAll(new PageRequest(0, 10));
|
||||
|
||||
verify(couchbaseOperations, never()).findByView(any(ViewQuery.class), any(Class.class));
|
||||
verify(couchbaseOperations, never()).queryView(any(ViewQuery.class));
|
||||
verify(couchbaseOperations, never()).queryN1QL(any(N1qlQuery.class));
|
||||
ArgumentCaptor<N1qlQuery> queryCaptor = ArgumentCaptor.forClass(N1qlQuery.class);
|
||||
verify(couchbaseOperations).findByN1QL(queryCaptor.capture(), any(Class.class));
|
||||
|
||||
JsonObject query = queryCaptor.getValue().n1ql();
|
||||
assertEquals(CONSISTENCY.n1qlConsistency().n1ql(), query.getString("scan_consistency"));
|
||||
String statement = query.getString("statement");
|
||||
assertTrue("Expected " + expectedLimitClause + " in " + statement, statement.contains(expectedLimitClause));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user