diff --git a/src/integration/java/org/springframework/data/couchbase/IntegrationTestApplicationConfig.java b/src/integration/java/org/springframework/data/couchbase/IntegrationTestApplicationConfig.java index 8dee2744..2457c63d 100644 --- a/src/integration/java/org/springframework/data/couchbase/IntegrationTestApplicationConfig.java +++ b/src/integration/java/org/springframework/data/couchbase/IntegrationTestApplicationConfig.java @@ -13,61 +13,67 @@ import org.springframework.core.env.Environment; import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.core.WriteResultChecking; +import org.springframework.data.couchbase.core.view.Consistency; @Configuration public class IntegrationTestApplicationConfig extends AbstractCouchbaseConfiguration { - @Autowired - private Environment springEnv; + @Autowired + private Environment springEnv; - @Bean - public String couchbaseAdminUser() { - return springEnv.getProperty("couchbase.adminUser", "Administrator"); - } + @Bean + public String couchbaseAdminUser() { + return springEnv.getProperty("couchbase.adminUser", "Administrator"); + } - @Bean - public String couchbaseAdminPassword() { - return springEnv.getProperty("couchbase.adminUser", "password"); - } + @Bean + public String couchbaseAdminPassword() { + return springEnv.getProperty("couchbase.adminUser", "password"); + } - @Override - protected List getBootstrapHosts() { - return Collections.singletonList(springEnv.getProperty("couchbase.host", "127.0.0.1")); - } + @Override + protected List getBootstrapHosts() { + return Collections.singletonList(springEnv.getProperty("couchbase.host", "127.0.0.1")); + } - @Override - protected String getBucketName() { - return springEnv.getProperty("couchbase.bucket", "default"); - } + @Override + protected String getBucketName() { + return springEnv.getProperty("couchbase.bucket", "default"); + } - @Override - protected String getBucketPassword() { - return springEnv.getProperty("couchbase.password", ""); - } + @Override + protected String getBucketPassword() { + return springEnv.getProperty("couchbase.password", ""); + } - //TODO maybe create the bucket if doesn't exist + //TODO maybe create the bucket if doesn't exist - @Override - protected CouchbaseEnvironment getEnvironment() { - return DefaultCouchbaseEnvironment.builder() - .connectTimeout(10000) - .kvTimeout(10000) - .queryTimeout(10000) - .viewTimeout(10000) - .build(); - } + @Override + protected CouchbaseEnvironment getEnvironment() { + return DefaultCouchbaseEnvironment.builder() + .connectTimeout(10000) + .kvTimeout(10000) + .queryTimeout(10000) + .viewTimeout(10000) + .build(); + } - @Override - public CouchbaseTemplate couchbaseTemplate() throws Exception { - CouchbaseTemplate template = super.couchbaseTemplate(); - template.setWriteResultChecking(WriteResultChecking.LOG); - return template; - } + @Override + public CouchbaseTemplate couchbaseTemplate() throws Exception { + CouchbaseTemplate template = super.couchbaseTemplate(); + template.setWriteResultChecking(WriteResultChecking.LOG); + return template; + } //change the name of the field that will hold type information - @Override - public String typeKey() { - return "javaClass"; - } + @Override + public String typeKey() { + return "javaClass"; + } + + @Override + protected Consistency getDefaultConsistency() { + return Consistency.READ_YOUR_OWN_WRITES; + } } diff --git a/src/integration/java/org/springframework/data/couchbase/config/CouchbaseTemplateParserIntegrationTests.java b/src/integration/java/org/springframework/data/couchbase/config/CouchbaseTemplateParserIntegrationTests.java index 8a8d9002..6691315d 100644 --- a/src/integration/java/org/springframework/data/couchbase/config/CouchbaseTemplateParserIntegrationTests.java +++ b/src/integration/java/org/springframework/data/couchbase/config/CouchbaseTemplateParserIntegrationTests.java @@ -30,65 +30,67 @@ import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.ClassPathResource; import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; +import org.springframework.data.couchbase.core.view.Consistency; import org.springframework.data.couchbase.repository.User; /** * @author Michael Nitschinger + * @author Simon Baslé */ public class CouchbaseTemplateParserIntegrationTests { - DefaultListableBeanFactory factory; - BeanDefinitionReader reader; + DefaultListableBeanFactory factory; + BeanDefinitionReader reader; - @Before - public void setUp() { - factory = new DefaultListableBeanFactory(); - reader = new XmlBeanDefinitionReader(factory); - } + @Before + public void setUp() { + factory = new DefaultListableBeanFactory(); + reader = new XmlBeanDefinitionReader(factory); + } - @Test - public void readsCouchbaseTemplateAttributesCorrectly() { - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-bean.xml")); + @Test + public void readsCouchbaseTemplateAttributesCorrectly() { + reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-bean.xml")); - BeanDefinition definition = factory.getBeanDefinition("couchbaseTemplate"); - assertEquals(2, definition.getConstructorArgumentValues().getArgumentCount()); + BeanDefinition definition = factory.getBeanDefinition("couchbaseTemplate"); + assertEquals(2, definition.getConstructorArgumentValues().getArgumentCount()); - factory.getBean("couchbaseTemplate"); - } + factory.getBean("couchbaseTemplate"); + } - @Test - public void readsCouchbaseTemplateWithTranslationServiceAttributesCorrectly() { - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-with-translation-service-bean.xml")); + @Test + public void readsCouchbaseTemplateWithTranslationServiceAttributesCorrectly() { + reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-with-translation-service-bean.xml")); - BeanDefinition definition = factory.getBeanDefinition("couchbaseTemplate"); - assertEquals(3, definition.getConstructorArgumentValues().getArgumentCount()); + BeanDefinition definition = factory.getBeanDefinition("couchbaseTemplate"); + assertEquals(3, definition.getConstructorArgumentValues().getArgumentCount()); - factory.getBean("couchbaseTemplate"); - } + factory.getBean("couchbaseTemplate"); + } - /** - * Test case for DATACOUCH-47. - */ - @Test - public void allowsMultipleBuckets() { - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-multi-bucket-bean.xml")); + /** + * Test case for DATACOUCH-47. + */ + @Test + public void allowsMultipleBuckets() { + reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-multi-bucket-bean.xml")); - factory.getBean("cb-template-first"); - factory.getBean("cb-template-second"); - } + factory.getBean("cb-template-first"); + factory.getBean("cb-template-second"); + } - /** - * Test case for DATACOUCH-134 in xml: field for storing type information can be renamed. - */ - @Test - public void testTypeFieldCanBeChosen() { - reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-typekey.xml")); - CouchbaseTemplate template = factory.getBean("couchbaseTemplate", CouchbaseTemplate.class); + /** + * Test case for DATACOUCH-134 in xml: field for storing type information can be renamed. + */ + @Test + public void testTypeFieldCanBeChosen() { + reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-typekey.xml")); + CouchbaseTemplate template = factory.getBean("couchbaseTemplate", CouchbaseTemplate.class); - assertTrue(template.getConverter() instanceof MappingCouchbaseConverter); - MappingCouchbaseConverter converter = ((MappingCouchbaseConverter) template.getConverter()); + assertTrue(template.getConverter() instanceof MappingCouchbaseConverter); + MappingCouchbaseConverter converter = ((MappingCouchbaseConverter) template.getConverter()); - assertEquals("javaXmlClass", converter.getTypeKey()); + assertEquals("javaXmlClass", converter.getTypeKey()); User u = new User("specialSaveUser", "John Locke"); template.save(u); @@ -99,5 +101,37 @@ public class CouchbaseTemplateParserIntegrationTests { assertNull(uJson.get(MappingCouchbaseConverter.TYPEKEY_DEFAULT)); assertEquals("org.springframework.data.couchbase.repository.User", uJson.getString("javaXmlClass")); assertEquals("John Locke", uJson.getString("username")); - } + } + + /** + * Test case for DATACOUCH-148, choosing an alternative default for view/N1QL staleness. + */ + @Test + public void shouldParseCustomStaleness() { + reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-consistency.xml")); + CouchbaseTemplate template = factory.getBean("template", CouchbaseTemplate.class); + + assertEquals(Consistency.READ_YOUR_OWN_WRITES, template.getDefaultConsistency()); + assertNotEquals(Consistency.DEFAULT_CONSISTENCY, template.getDefaultConsistency()); + } + + /** + * Test case for DATACOUCH-148, choosing an unknown value for view/N1QL staleness. + */ + @Test + public void shouldIgnoreBadCustomStaleness() { + reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-consistency.xml")); + CouchbaseTemplate template = factory.getBean("templateBad", CouchbaseTemplate.class); + + assertEquals(Consistency.DEFAULT_CONSISTENCY, template.getDefaultConsistency()); + } + + @Test + public void shouldHaveDefaultsForStaleness() { + //use another resource where staleness isn't customized + reader.loadBeanDefinitions(new ClassPathResource("configurations/couchbase-template-with-translation-service-bean.xml")); + CouchbaseTemplate template = factory.getBean("couchbaseTemplate", CouchbaseTemplate.class); + + assertEquals(Consistency.DEFAULT_CONSISTENCY, template.getDefaultConsistency()); + } } diff --git a/src/integration/resources/configurations/couchbase-consistency.xml b/src/integration/resources/configurations/couchbase-consistency.xml new file mode 100644 index 00000000..0b960946 --- /dev/null +++ b/src/integration/resources/configurations/couchbase-consistency.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/asciidoc/configuration.adoc b/src/main/asciidoc/configuration.adoc index cd265977..3a8a31e1 100644 --- a/src/main/asciidoc/configuration.adoc +++ b/src/main/asciidoc/configuration.adoc @@ -85,10 +85,12 @@ Depending on how your environment is set up, the configuration will be automatic Additionally, the SDK environment can be tuned by overriding the `getEnvironment()` method to return a properly tuned `CouchbaseEnvironment`. -While not immediately obvious, much more things can be customized and overridden as custom beans from this configuration (for example repositories, validation and custom converters). +While not immediately obvious, much more things can be customized and overridden as custom beans from this configuration (for example repositories, query consistency, validation and custom converters). TIP: If you use `SyncGateway` and `CouchbaseMobile`, you may run into problem with fields prefixed by `_`. Since Spring Data Couchbase by default stores the type information as a `_class` attribute this can be problematic. Override `typeKey()` (for example to return `MappingCouchbaseConverter.TYPEKEY_SYNCGATEWAY_COMPATIBLE`) to change the name of said attribute. +TIP: For generated queries, if you want strong consistency (at the expense of performance), you can override `getDefaultConsistency()` and return `Consistency.READ_YOUR_OWN_WRITES`. + [[configuration-xml]] == XML-based Configuration @@ -121,5 +123,7 @@ The library provides a custom namespace that you can use in your XML configurati ==== This code is equivalent to the java configuration approach shown above. You can customize the SDK `CouchbaseEnvironment` via the `` tag, that supports most tuning parameters as attributes. It is also possible to configure templates and repositories, which is shown in the appropriate sections. +IMPORTANT: The XML configuration **must** include the `clusterInfo` credentials, in order to be able to detect N1QL feature. + If you start your application, you should see Couchbase INFO level logging in the logs, indicating that the underlying Couchbase Java SDK is connecting to the database. If any errors are reported, make sure that the given credentials and host information are correct. diff --git a/src/main/asciidoc/repository.adoc b/src/main/asciidoc/repository.adoc index 91dc7ffa..b09cff39 100644 --- a/src/main/asciidoc/repository.adoc +++ b/src/main/asciidoc/repository.adoc @@ -264,7 +264,7 @@ List users = repo.findByFirstnameStartingWith("Mich"); On all these derived custom finder methods, you have to use the `@View` annotation with at least the view name specified (and you can also override the design document name, otherwise determined by convention). IMPORTANT: For any other usage and customization of the `ViewQuery` that goes beyond that, recommended approach is to provide an implementation that uses the underlying template, like described in <>. -Please keep in mind that one typical parameter that you cannot tune using both view-based approaches is the `Stale` mechanism. You would need to do the implementation yourself in order to tune the behavior through the `setStale()` method on the `ViewQuery` object. For more details on behavior, please consult the Couchbase Server and Java SDK documentation directly. +For more details on behavior, please consult the Couchbase Server and Java SDK documentation directly. For view-based query derivation, here are the supported keywords (A and B are method parameters in this table): @@ -282,4 +282,28 @@ For view-based query derivation, here are the supported keywords (A and B are me TIP: Note that the `reduce function` (not always a count) will be activated by prefixing with `count` and that <> is also supported. -WARNING: Compound keys are not supported, and neither are Or composition, Ignore Case and Order By. You have to include a valid entity property in the naming of your method. \ No newline at end of file +WARNING: Compound keys are not supported, and neither are Or composition, Ignore Case and Order By. You have to include a valid entity property in the naming of your method. + +[[couchbase.repository.consistency]] +== Querying with consistency +One aspect that is often needed and doesn't have a direct equivalent in the Spring Data query derivation mechanism is +`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. + +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`): + + - in xml, this is done via the `consistency` attribute on ``. + - in javaConfig, this is done by overriding the `getDefaultConsistency()` method. + +=== Provide an implementation +Provide the implementation and directly use `queryView` and `queryN1QL` methods on the template with a specific consistency +(see <>). + + - one can specify the consistency on those via their respective query classes, according to the Couchbase Java SDK documentation. + - for example for views `ViewQuery.stale(Stale.FALSE)` + - for example for N1QL `Query.simple("SELECT * FROM default", QueryParams.build().consistency(ScanConsistency.REQUEST_PLUS));` + diff --git a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java index 48a7a1d7..7faf66c3 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -27,6 +27,8 @@ import com.couchbase.client.java.CouchbaseCluster; import com.couchbase.client.java.cluster.ClusterInfo; import com.couchbase.client.java.env.CouchbaseEnvironment; import com.couchbase.client.java.env.DefaultCouchbaseEnvironment; +import com.couchbase.client.java.query.Query; +import com.couchbase.client.java.view.ViewQuery; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Bean; @@ -41,6 +43,7 @@ import org.springframework.data.couchbase.core.convert.translation.JacksonTransl import org.springframework.data.couchbase.core.convert.translation.TranslationService; import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; import org.springframework.data.couchbase.core.mapping.Document; +import org.springframework.data.couchbase.core.view.Consistency; import org.springframework.data.mapping.model.CamelCaseAbbreviatingFieldNamingStrategy; import org.springframework.data.mapping.model.FieldNamingStrategy; import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; @@ -136,11 +139,16 @@ public abstract class AbstractCouchbaseConfiguration { /** * Creates a {@link CouchbaseTemplate}. * + * This uses {@link #couchbaseClusterInfo()}, {@link #couchbaseClient()}, {@link #mappingCouchbaseConverter()}, + * , {@link #translationService()} and {@link #getDefaultConsistency()} for construction. + * * @throws Exception on Bean construction failure. */ @Bean(name = BeanNames.COUCHBASE_TEMPLATE) public CouchbaseTemplate couchbaseTemplate() throws Exception { - return new CouchbaseTemplate(couchbaseClusterInfo(), couchbaseClient(), mappingCouchbaseConverter(), translationService()); + CouchbaseTemplate template = new CouchbaseTemplate(couchbaseClusterInfo(), couchbaseClient(), mappingCouchbaseConverter(), translationService()); + template.setDefaultConsistency(getDefaultConsistency()); + return template; } /** @@ -257,4 +265,14 @@ public abstract class AbstractCouchbaseConfiguration { protected FieldNamingStrategy fieldNamingStrategy() { return abbreviateFieldNames() ? new CamelCaseAbbreviatingFieldNamingStrategy() : PropertyNameFieldNamingStrategy.INSTANCE; } -} + + /** + * Configures the default consistency for generated {@link ViewQuery view queries} + * and {@link Query N1QL queries} in repositories. + * + * @return the {@link Consistency consistency} to apply by default on generated queries. + */ + protected Consistency getDefaultConsistency() { + return Consistency.DEFAULT_CONSISTENCY; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java b/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java index 344b6988..865eab8a 100644 --- a/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java +++ b/src/main/java/org/springframework/data/couchbase/config/CouchbaseTemplateParser.java @@ -16,6 +16,8 @@ package org.springframework.data.couchbase.config; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import org.springframework.beans.factory.support.AbstractBeanDefinition; @@ -23,6 +25,7 @@ import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.data.couchbase.core.CouchbaseTemplate; +import org.springframework.data.couchbase.core.view.Consistency; import org.springframework.util.StringUtils; /** @@ -34,6 +37,8 @@ import org.springframework.util.StringUtils; */ public class CouchbaseTemplateParser extends AbstractSingleBeanDefinitionParser { + private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseTemplateParser.class); + /** * Resolve the bean ID and assign a default if not set. * @@ -82,6 +87,17 @@ public class CouchbaseTemplateParser extends AbstractSingleBeanDefinitionParser if (StringUtils.hasText(translationServiceRef)) { bean.addConstructorArgReference(translationServiceRef); } + + String consistencyValue = element.getAttribute("consistency"); + if (consistencyValue != null) { + try { + Consistency consistency = Consistency.valueOf(consistencyValue); + bean.addPropertyValue("defaultConsistency", consistency); + } catch (IllegalArgumentException e) { + //bad consistency, leave default and log + LOGGER.warn("Parsed bad consistency value " + consistencyValue + " in xml template configuration, using default"); + } + } } } diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java index a36ecfe7..342b625d 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseOperations.java @@ -33,6 +33,7 @@ import com.couchbase.client.java.view.ViewResult; import org.springframework.data.couchbase.core.convert.CouchbaseConverter; import org.springframework.data.couchbase.core.convert.translation.TranslationService; +import org.springframework.data.couchbase.core.view.Consistency; /** * Defines common operations on the Couchbase data source, most commonly implemented by {@link CouchbaseTemplate}. @@ -341,4 +342,12 @@ public interface CouchbaseOperations { */ CouchbaseConverter getConverter(); + /** + * Returns the {@link Consistency consistency} parameter to be used by default for generated queries (views and N1QL) + * in repositories. Defaults to {@link Consistency#DEFAULT_CONSISTENCY}. + * + * @return the consistency to use for generated repository queries. + */ + Consistency getDefaultConsistency(); + } diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java index 287421c3..9f5c0612 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplate.java @@ -36,8 +36,10 @@ import com.couchbase.client.java.document.json.JsonObject; import com.couchbase.client.java.error.CASMismatchException; import com.couchbase.client.java.error.TranscodingException; import com.couchbase.client.java.query.Query; +import com.couchbase.client.java.query.QueryParams; import com.couchbase.client.java.query.QueryResult; import com.couchbase.client.java.query.QueryRow; +import com.couchbase.client.java.query.consistency.ScanConsistency; import com.couchbase.client.java.util.features.CouchbaseFeature; import com.couchbase.client.java.view.ViewQuery; import com.couchbase.client.java.view.ViewResult; @@ -65,6 +67,7 @@ import org.springframework.data.couchbase.core.mapping.event.BeforeConvertEvent; import org.springframework.data.couchbase.core.mapping.event.BeforeDeleteEvent; import org.springframework.data.couchbase.core.mapping.event.BeforeSaveEvent; import org.springframework.data.couchbase.core.mapping.event.CouchbaseMappingEvent; +import org.springframework.data.couchbase.core.view.Consistency; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; @@ -100,6 +103,9 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP protected final MappingContext, CouchbasePersistentProperty> mappingContext; + //default value is in case the template isn't constructed through configuration mechanisms that use the setter. + private Consistency configuredConsistency = Consistency.DEFAULT_CONSISTENCY; + public CouchbaseTemplate(final ClusterInfo clusterInfo, final Bucket client) { this(clusterInfo, client, null, null); } @@ -576,4 +582,13 @@ public class CouchbaseTemplate implements CouchbaseOperations, ApplicationEventP public CouchbaseConverter getConverter() { return this.converter; } + + @Override + public Consistency getDefaultConsistency() { + return configuredConsistency; + } + + public void setDefaultConsistency(Consistency consistency) { + this.configuredConsistency = consistency; + } } diff --git a/src/main/java/org/springframework/data/couchbase/core/view/Consistency.java b/src/main/java/org/springframework/data/couchbase/core/view/Consistency.java new file mode 100644 index 00000000..3f3a767f --- /dev/null +++ b/src/main/java/org/springframework/data/couchbase/core/view/Consistency.java @@ -0,0 +1,72 @@ +/* + * Copyright 2012-2015 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.core.view; + +import com.couchbase.client.java.query.consistency.ScanConsistency; +import com.couchbase.client.java.view.Stale; + +/** + * Enumeration of different consistency configurations to be used by the queries generated by the + * framework. + * + * Each consistency can be translated to a {@link Stale} (for the {@link #viewConsistency() views}) + * and {@link ScanConsistency} (for the {@link #n1qlConsistency() N1QL queries}). + * + * @author Simon Baslé + */ +public enum Consistency { + + /** READ_YOUR_OWN_WRITES is {@link Stale#FALSE} and {@link ScanConsistency#STATEMENT_PLUS} */ + READ_YOUR_OWN_WRITES(Stale.FALSE, ScanConsistency.STATEMENT_PLUS), + /** STRONGLY_CONSISTENT is {@link Stale#FALSE} and {@link ScanConsistency#REQUEST_PLUS} */ + STRONGLY_CONSISTENT(Stale.FALSE, ScanConsistency.REQUEST_PLUS), + /** UPDATE_AFTER is {@link Stale#UPDATE_AFTER} and {@link ScanConsistency#NOT_BOUNDED} */ + UPDATE_AFTER(Stale.UPDATE_AFTER, ScanConsistency.NOT_BOUNDED), + /** EVENTUALLY_CONSISTENT is {@link Stale#TRUE} and {@link ScanConsistency#NOT_BOUNDED} */ + EVENTUALLY_CONSISTENT(Stale.TRUE, ScanConsistency.NOT_BOUNDED); + + /** + * The static default Consistency ({@link #UPDATE_AFTER}). + */ + public static final Consistency DEFAULT_CONSISTENCY = UPDATE_AFTER; + + private final Stale viewConsistency; + private final ScanConsistency n1qlConsistency; + + Consistency(Stale viewConsistency, ScanConsistency n1qlConsistency) { + this.viewConsistency = viewConsistency; + this.n1qlConsistency = n1qlConsistency; + } + + /** + * Returns the {@link Stale view consistency} corresponding to this {@link Consistency}. + * + * @return the view consistency. + */ + public Stale viewConsistency() { + return viewConsistency; + } + + /** + * Returns the {@link ScanConsistency N1QL consistency} corresponding to this {@link Consistency}. + * + * @return the N1QL consistency. + */ + public ScanConsistency n1qlConsistency() { + return n1qlConsistency; + } +} diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQuery.java index 5cfaf2aa..e1660e54 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQuery.java @@ -20,7 +20,9 @@ import java.util.List; import com.couchbase.client.java.document.json.JsonArray; import com.couchbase.client.java.query.Query; +import com.couchbase.client.java.query.QueryParams; import com.couchbase.client.java.query.Statement; +import com.couchbase.client.java.query.consistency.ScanConsistency; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,18 +59,20 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery { Statement statement = getStatement(accessor); JsonArray queryPlaceholderValues = getPlaceholderValues(accessor); - Query query = buildQuery(statement, queryPlaceholderValues); + Query query = buildQuery(statement, queryPlaceholderValues, + getCouchbaseOperations().getDefaultConsistency().n1qlConsistency()); return executeDependingOnType(query, queryMethod, queryMethod.isPageQuery(), queryMethod.isModifyingQuery(), queryMethod.isSliceQuery()); } - protected static Query buildQuery(Statement statement, JsonArray queryPlaceholderValues) { + protected static Query buildQuery(Statement statement, JsonArray queryPlaceholderValues, ScanConsistency scanConsistency) { + QueryParams queryParams = QueryParams.build().consistency(scanConsistency); Query query; if (!queryPlaceholderValues.isEmpty()) { - query = Query.parameterized(statement, queryPlaceholderValues); + query = Query.parameterized(statement, queryPlaceholderValues, queryParams); } else { - query = Query.simple(statement); + query = Query.simple(statement, queryParams); } if (LOG.isDebugEnabled()) { diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java b/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java index c18a9930..e7a60c35 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/ViewBasedCouchbaseQuery.java @@ -68,7 +68,8 @@ public class ViewBasedCouchbaseQuery implements RepositoryQuery { boolean isReduce = methodName.startsWith("count"); String viewName = StringUtils.uncapitalize(methodName.replaceFirst("find|count", "")); - ViewQuery simpleQuery = ViewQuery.from(designDoc, viewName); + ViewQuery simpleQuery = ViewQuery.from(designDoc, viewName) + .stale(operations.getDefaultConsistency().viewConsistency()); if (isReduce) { simpleQuery.reduce(isReduce); return executeReduce(simpleQuery, designDoc, viewName); @@ -80,7 +81,8 @@ public class ViewBasedCouchbaseQuery implements RepositoryQuery { protected Object deriveAndExecute(Object[] runtimeParams) { String designDoc = designDocName(method); String viewName = method.getViewAnnotation().viewName(); - ViewQuery baseQuery = ViewQuery.from(designDoc, viewName); + ViewQuery baseQuery = ViewQuery.from(designDoc, viewName) + .stale(operations.getDefaultConsistency().viewConsistency()); try { PartTree tree = new PartTree(method.getName(), method.getEntityInformation().getJavaType()); diff --git a/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-2.0.xsd b/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-2.0.xsd index b816b689..aa822f88 100644 --- a/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-2.0.xsd +++ b/src/main/resources/org/springframework/data/couchbase/config/spring-couchbase-2.0.xsd @@ -112,6 +112,18 @@ The reference to a CouchbaseConverter instance. + + + + + + + + + + + diff --git a/src/test/java/org/springframework/data/couchbase/UnitTestApplicationConfig.java b/src/test/java/org/springframework/data/couchbase/UnitTestApplicationConfig.java index 13bb93d0..aeab03e2 100644 --- a/src/test/java/org/springframework/data/couchbase/UnitTestApplicationConfig.java +++ b/src/test/java/org/springframework/data/couchbase/UnitTestApplicationConfig.java @@ -20,6 +20,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; import org.springframework.data.couchbase.core.CouchbaseTemplate; import org.springframework.data.couchbase.core.WriteResultChecking; +import org.springframework.data.couchbase.core.view.Consistency; @Configuration public class UnitTestApplicationConfig extends AbstractCouchbaseConfiguration { @@ -73,4 +74,9 @@ public class UnitTestApplicationConfig extends AbstractCouchbaseConfiguration { template.setWriteResultChecking(WriteResultChecking.LOG); return template; } + + @Override + protected Consistency getDefaultConsistency() { + return Consistency.READ_YOUR_OWN_WRITES; + } } diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQueryTest.java b/src/test/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQueryTest.java index a684aba8..1f7126f8 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQueryTest.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/AbstractN1qlBasedQueryTest.java @@ -11,11 +11,16 @@ import com.couchbase.client.java.document.json.JsonArray; import com.couchbase.client.java.document.json.JsonObject; import com.couchbase.client.java.query.ParameterizedQuery; import com.couchbase.client.java.query.Query; +import com.couchbase.client.java.query.QueryParams; import com.couchbase.client.java.query.SimpleQuery; import com.couchbase.client.java.query.Statement; +import com.couchbase.client.java.query.consistency.ScanConsistency; import org.junit.Test; +import org.mockito.Matchers; import org.mockito.Mockito; +import org.springframework.data.couchbase.core.CouchbaseOperations; +import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.QueryMethod; public class AbstractN1qlBasedQueryTest { @@ -23,12 +28,12 @@ public class AbstractN1qlBasedQueryTest { @Test public void testEmptyArgumentsShouldProduceSimpleQuery() throws Exception { Statement st = select("*"); - Query query = AbstractN1qlBasedQuery.buildQuery(st, JsonArray.empty()); + Query query = AbstractN1qlBasedQuery.buildQuery(st, JsonArray.empty(), ScanConsistency.NOT_BOUNDED); JsonObject queryObject = query.n1ql(); assertTrue(query instanceof SimpleQuery); assertEquals(st.toString(), query.statement().toString()); - assertNull(query.params()); + assertEquals(QueryParams.build().consistency(ScanConsistency.NOT_BOUNDED), query.params()); assertFalse(queryObject.containsKey("args")); } @@ -38,12 +43,12 @@ public class AbstractN1qlBasedQueryTest { List params = new ArrayList(2); params.add("test"); JsonArray placeholderValues = JsonArray.from(params); - Query query = AbstractN1qlBasedQuery.buildQuery(st, placeholderValues); + Query query = AbstractN1qlBasedQuery.buildQuery(st, placeholderValues, ScanConsistency.NOT_BOUNDED); JsonObject queryObject = query.n1ql(); assertTrue(query instanceof ParameterizedQuery); assertEquals(st.toString(), query.statement().toString()); - assertNull(query.params()); + assertEquals(QueryParams.build().consistency(ScanConsistency.NOT_BOUNDED), query.params()); assertTrue(queryObject.containsKey("args")); JsonArray args = queryObject.getArray("args"); assertEquals(1, args.size()); @@ -57,12 +62,12 @@ public class AbstractN1qlBasedQueryTest { params.add(123L); params.add("test"); JsonArray placeholderValues = JsonArray.from(params); - Query query = AbstractN1qlBasedQuery.buildQuery(st, placeholderValues); + Query query = AbstractN1qlBasedQuery.buildQuery(st, placeholderValues, ScanConsistency.NOT_BOUNDED); JsonObject queryObject = query.n1ql(); assertTrue(query instanceof ParameterizedQuery); assertEquals(st.toString(), query.statement().toString()); - assertNull(query.params()); + assertEquals(QueryParams.build().consistency(ScanConsistency.NOT_BOUNDED), query.params()); assertTrue(queryObject.containsKey("args")); JsonArray args = queryObject.getArray("args"); assertEquals(2, args.size());