I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE COURSE

1. Introduction

After our introduction to Spring Data Couchbase, in this second tutorial we focus on the support for entity validation (JSR-303), optimistic locking, and different levels of query consistency for a Couchbase document database.

2. Entity Validation

Spring Data Couchbase provides support for JSR-303 entity validation annotations. In order to take advantage of this feature, first we add the JSR-303 library to the dependencies section of our Maven project:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>

Then we add an implementation of JSR-303. We will use the Hibernate implementation:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.2.4.Final</version>
</dependency>

Finally, we add a validator factory bean and corresponding Couchbase event listener to our Couchbase configuration:

@Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
    return new LocalValidatorFactoryBean();
}

@Bean
public ValidatingCouchbaseEventListener validatingCouchbaseEventListener() {
    return new ValidatingCouchbaseEventListener(localValidatorFactoryBean());
}

The equivalent XML configuration looks like this:

<bean id="validator"
  class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

<bean id="validatingEventListener" 
  class="org.springframework.data.couchbase.core.mapping.event.ValidatingCouchbaseEventListener"/>

Now we add JSR-303 annotations to our entity classes. When a constraint violation is encountered during a persistence operation, the operation will fail, throwing a ConstraintViolationException.

Here is a sample of the constraints that we can enforce involving our Student entities:

@Field
@NotNull
@Size(min=1, max=20)
@Pattern(regexp="^[a-zA-Z .'-]+$")
private String firstName;

...
@Field
@Past
private DateTime dateOfBirth;

3. Optimistic Locking

Spring Data Couchbase does not support multi-document transactions similar to those you can achieve in other Spring Data modules such as Spring Data JPA (via the @Transactional annotation), nor does it provide a rollback feature.

However it does support optimistic locking in much the same way as other Spring Data modules through the use of the @Version annotation:

@Version
private long version;

Under the covers, Couchbase uses what is known as a “compare and swap” (CAS) mechanism to achieve optimistic locking at the datastore level.

Each document in Couchbase has an associated CAS value that is modified automatically any time the document’s metadata or contents are altered. The use of the @Version annotation on a field causes that field to be populated with the current CAS value whenever a document is retrieved from Couchbase.

When you attempt to save the document back to Couchbase, this field is checked against the current CAS value in Couchbase. If the values do not match, the persistence operation will fail with an OptimisticLockingException.

It is extremely important to note that you should never attempt to access or modify this field in your code.

4. Query Consistency

When implementing a persistence layer over Couchbase, you have to consider the possibility of stale reads and writes. This is because when documents are inserted, updated, or deleted, it may take some time before the backing views and indexes are updated to reflect these changes.

And if you have a large dataset backed by a cluster of Couchbase nodes, this can become a significant problem, especially for a OLTP system.

Spring Data provides a robust level of consistency for some repository and template operations, plus a couple of options that let you determine the level of read and write consistency that is acceptable for your application.

4.1. Levels of Consistency

Spring Data allows you to specify various levels of query consistency and staleness for your application via the Consistency enum found in the org.springframework.data.couchbase.core.query package.

This enum defines the following levels of query consistency and staleness, from least to most strict:

  • EVENTUALLY_CONSISTENT
    • stale reads are allowed
    • indexes are updated according to Couchbase standard algorithm
  • UPDATE_AFTER
    • stale reads are allowed
    • indexes are updated after each request
  • DEFAULT_CONSISTENCY (same as READ_YOUR_OWN_WRITES)
  • READ_YOUR_OWN_WRITES
    • stale reads are not allowed
    • indexes are updated after each request
  • STRONGLY_CONSISTENT
    • stale reads are not allowed
    • indexes are updated after each statement

4.2. Default Behavior

Consider the case where you have documents that have been deleted from Couchbase, and the backing views and indexes have not been fully updated.

The CouchbaseRepository built-in method deleteAll() safely ignores documents that were found by the backing view but whose deletion is not yet reflected by the view.

Likewise, the CouchbaseTemplate built-in methods findByView and findBySpatialView offer a similar level of consistency by not returning documents that were initially found by the backing view but which have since been deleted.

For all other template methods, built-in repository methods, and derived repository query methods, according to the official Spring Data Couchbase 2.1.x documentation as of this writing, Spring Data uses a default consistency level of Consistency.READ_YOUR_OWN_WRITES.

It is worth noting that earlier versions of the library used a default of Consistency.UPDATE_AFTER.

Whichever version you are using, if you have any reservations about blindly accepting the default consistency level being provided, Spring offers two methods by which you can declaratively control the consistency level(s) being used, as the following subsections will describe.

4.3. Global Consistency Setting

If you are using Couchbase repositories and your application calls for a stronger level of consistency, or if it can tolerate a weaker level, then you may override the default consistency setting for all repositories by overriding the getDefaultConsistency() method in your Couchbase configuration.

Here is how you can override global consistency level in your Couchbase configuration class:

@Override
public Consistency getDefaultConsistency() {
    return Consistency.STRONGLY_CONSISTENT;
}

Here is the equivalent XML configuration:

<couchbase:template consistency="STRONGLY_CONSISTENT"/>

Note that the price of stricter levels of consistency is increased latency at query time, so be sure to tailor this setting based on the needs of your application.

For example, a data warehouse or reporting application in which data is often appended or updated only in a batch would be a good candidate for EVENTUALLY_CONSISTENT, whereas an OLTP application should probably tend towards the more strict levels such as READ_YOUR_OWN_WRITES or STRONGLY_CONSISTENT.

4.4. Custom Consistency Implementation

If you need more finely tuned consistency settings, you can override the default consistency level on a query-by-query basis by providing your own repository implementation for any queries whose consistency level you want to control independently and making use of the queryView and/or queryN1QL methods provided by CouchbaseTemplate.

Let’s implement a custom repository method called findByFirstNameStartsWith for our Student entity for which we do not want to allow stale reads.

First, create an interface containing the custom method declaration:

public interface CustomStudentRepository {
    List<Student> findByFirstNameStartsWith(String s);
}

Next, implement the interface, setting the Stale setting from the underlying Couchbase Java SDK to the desired level:

public class CustomStudentRepositoryImpl implements CustomStudentRepository {

    @Autowired
    private CouchbaseTemplate template;

    public List<Student> findByFirstNameStartsWith(String s) {
        return template.findByView(ViewQuery.from("student", "byFirstName")
          .startKey(s)
          .stale(Stale.FALSE),
          Student.class);
    }
}

Finally, by having your standard repository interface extend both the generic CrudRepository interface and your custom repository interface, clients will have access to all the built-in and derived methods of your standard repository interface, plus any custom methods you implemented in your custom repository class:

public interface StudentRepository extends CrudRepository<Student, String>,
  CustomStudentRepository {
    ...
}

5. Conclusion

In this tutorial, we showed how to implement JSR-303 entity validation and achieve optimistic locking capability when using the Spring Data Couchbase community project.

We also discussed the need for understanding query consistency in Couchbase, and we introduced the different levels of consistency provided by Spring Data Couchbase.

Finally, we explained the default consistency levels used by Spring Data Couchbase globally and for a few specific methods, and we demonstrated ways to override the global default consistency setting as well as how to override consistency settings on a query-by-query basis by providing your own custom repository implementations.

You can view the complete source code for this tutorial in the GitHub project.

To learn more about Spring Data Couchbase, visit the official Spring Data Couchbase project site.

I just announced the new Spring 5 modules in REST With Spring:

>> CHECK OUT THE LESSONS