Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreThis is a cross-post blog from Simon BASLÉ from Couchbase. You can find him on twitter (
@simonbasle
) or github. Learn more about Couchbase and the Couchbase Java SDK on the developer portal. Thanks again, Simon and great job to you and your team! -Josh
Spring Data Couchbase 2.0 is a rewrite of the original Spring Data Couchbase 1.4.x connector. It is based on the Couchbase Java 2.2 SDK and makes heavy use of the new query language N1QL (which was introduced in Couchbase Server 4.0) to offer more features for Spring Data users.
The first Milestone has been released last august, then a Release Candidate followed, and since then additional features (and bugfixes) were implemented a GA release can now be unleashed on the public.
Let's take a quick tour of what changed (with a ⭐ to ⭐⭐⭐ notation of how awesome and significant we think each feature is ?):
The main differences between the 1.x generation of Spring Data Couchbase and its 2.x version are:
CouchbaseTemplate
s that each connect to a different bucket, or even to different clusters!)Of course you can still access the lower level API by using the CouchbaseTemplate
rather than the CouchbaseRepository
interface, and you can even access the underlying Bucket
from the SDK.
⭐⭐⭐
The big new feature in Couchbase 4.0 is N1QL, a SQL extension that works on JSON documents (so it added JSON-related specificities to SQL).
This is especially great for the Repository
pattern and query derivation in Spring Data, because the vast majority of query derivation keywords can be easily translated to N1QL.
N1QL is now the default backing Couchbase feature for Repository methods. You can also elect to use the @Query interface if you want to be explicit on the query executed.
public interface UserRepository extends Repository<User, String> {
User findByUsernameEquals(String username);
List<User> findByUsernameContains(String contains);
@Query //optional for N1QL query derivation but more explicit
List<User> findByAgeBetween(int minAge, int maxAge);
}
⭐⭐
One big change in this version is that now, Repository Queries (aka custom repository methods) that are based on views are more in line with the Spring Data philosophy. They also have to be annotated explicitly with @View(viewName="something")
.
This means that nothing Couchbase-specific should leak into your repository interface. Instead, what you can do is use query derivation mechanisms for most of the queries.
Query derivation is also possible to a small extent, with a few keywords being accepted in a view-backed method.
public interface UserRepository extends Repository<User, String> {
@Override
@View(designDocument = "user", viewName = "customFindAllView")
Iterable<User> findAll();
@View(viewName = "customFindByNameView")
User findByUsernameIs(String lowKey);
@View(viewName = "customFindByNameView")
List<User> findByUsernameBetween(String lowKey, String highKey);
}
⭐
Another new thing that wasn't previously supported is the execution of the reduce function if you have one. Now, in order to execute it, you simply set the reduce
flag to true in the @View
annotation.
You could also prefix your method with "count" instead of "find" if that is meaningful to you (ie. you actually use the "count" reduce function).
Note that the reduce function in Couchbase can be something else than the preexisting _count one, and could even return something else than a long like a JsonObject
, like for built-in _stats
.
Similarly, adding the variation "topX" or "firstX" in a method name will result in an additional limit being set on the request (eg. findFirst5ByLastName
will limit the list to 5 results).
⭐⭐⭐
One thing that comes up often when using asynchronously populated secondary indexes like views and GSI (the new secondary index engine backing N1QL), is the need to immediately read the modifications from your previous write operations.
This implies that the view/N1QL shouldn't answer as long as the data is still in the process of being indexed, so this sacrifices some performance in favor of consistency.
The opposite (and current default for Spring Data Couchbase) is to favor performance by accepting stale data to be returned.
We added global semantics for configuring all queries (view-based or N1QL-based) that are constructed by the framework through query derivation, by providing a small abstraction around the concept of Consistency.
This is done by overriding the AbstractCouchbaseConfiguration
's getDefaultConsistency()
method. Consistency
is an enum that lets you choose between READ_YOUR_OWN_WRITES
, STRONGLY_CONSISTENT
, UPDATE_AFTER
and EVENTUALLY_CONSISTENT
. Please refer to the official documentation for more information on how they work exactly and what their impact is at query time.
You can also do that in XML by using the consistency attribute on the <couchbase:template>
tag.
Since GA, CRUD methods in repositories now also take the default configured consistency into account.
⭐
Some users have reported issues with Spring Data and the Couchbase Mobile side of things, with the Sync Gateway rejecting documents containing fields prefixed by an underscore.
This is problematic for Spring Data, since by default it stores the type information in a _class
field :(
The solution is to allow, through the configuration, to modify the name of that type information field. You can do so by overriding the typeKey()
method in AbstractCouchbaseConfiguration
. For instance, you can use the constant MappingCouchbaseConverter.TYPEKEY_SYNCGATEWAY_COMPATIBLE
(which is "javaClass
").
This field is the one used by generated N1QL queries to filter only documents corresponding to the repository's entity.
Pageable
/PageRequest
in N1QL derived queries⭐⭐
Using N1QL, for queries that are generated through query derivation, Pageable
and Sort
parameters are now supported.
PagingAndSortingRepository
based on N1QL.findAll
methods that rely on N1QL for paging and/or sorting. Uses the default configured consistency.⭐⭐⭐
Query Couchbase using coordinates! Provided your entity has a Point
(or x
and y
) location, you can find it using:
findByLocationWithin(Box area)
findByLocationWithin(Circle area)
, findByLocationWithin(Point center, Distance radius)
.findByLocationWithin(Polygon area)
, findByLocationWithin(Point[] polygon)
.findByLocationNear(Point near, Distance maxDistance)
.Circle and polygon-like queries are performed fast as bounding box approximations on the server then the false positives are eliminated by the framework before presenting results.
You can take advantage of the multidimensional aspect of Couchbase Spatial Views to add extra dimensions to your queries (e.g. stores that open late at night within a city...).
public interface DimensionalPartyRepository extends CrudRepository<Party, String> {
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation")
List<Party> findByLocationNear(Point p, Distance d);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation")
List<Party> findByLocationWithin(Box boundingBox);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation")
List<Party> findByLocationWithin(Polygon zone);
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocationAndAttendees", dimensions = 3)
List<Party> findByLocationWithinAndAttendeesGreaterThan(Polygon zone, double minAttendees);
}
Note: if you want to reuse annotations, you can do that too (works for @View
and @Query
too):
public interface DimensionalPartyRepository extends CrudRepository<Party, String> {
//define your own meta-annotation
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocation", dimensions = 2)
@Retention(RetentionPolicy.RUNTIME)
@interface IndexedByLocation { }
//use it :)
@IndexedByLocation
List<Party> findByLocationNear(Point p, Distance d);
@IndexedByLocation
List<Party> findByLocationWithin(Box boundingBox);
@IndexedByLocation
List<Party> findByLocationWithin(Polygon zone);
//here we use a variation with 3 dimensions, so we need to revert to @Dimensional
@Dimensional(designDocument = "partyGeo", spatialViewName = "byLocationAndAttendees", dimensions = 3)
List<Party> findByLocationWithinAndAttendeesGreaterThan(Polygon zone, double minAttendees);
}
N1QL
@Query
now have SpEL support⭐⭐⭐
Inline queries can use SpEL notation to:
#{#n1ql.selectEntity}
to generate a SELECT ... FROM ...
clause, and #{#n1ql.filter}
in the WHERE
clause to limit query to the correct entity.⭐⭐
⚠️ IMPORTANT: this is considered as an aid during development/testing and discouraged in production
In order to make sure that N1QL indexing of the entities in a given repository is activated in a dev or pre-production environment, one can annotate it with @N1qlPrimaryIndexed
(which enables bucket-wide freeform querying) and @N1qlSecondaryIndexed
(which will index only the documents corresponding to the entity type, similarly to the WHERE clause produced by SpEL #{#n1ql.filter}
).
Also, the backing view for CRUD operation can be automatically created by annotating the repository with @ViewIndexed
(you'll need to provide the design document name, which should correspond to the entity's simple class name with a lowercase first letter).
This feature must additionally be opted-in by redefining the indexManager
bean in the AbstractCouchbaseConfiguration
.
String
) are now supported when using a single-row projection⭐⭐
This is especially targeted at inline N1QL queries with aggregation functions like COUNT(*)
, AVG(field)
, etc... The query must return a single row with a single projection.
⭐⭐
Use either named parameters or positional parameter, but not both. Syntax for named parameters is $paramName
, requiring that each method parameter be annotated with @Param("paramName")
.
⭐
Other features include:
couchbase
", in order to avoid clashes with other stores.CouchbaseTemplate
@Document
, as a long
+ timeUnit
A few bugfixes and improvements over RC1 have also been implemented.
⭐⭐⭐
Documentation has also been improved, adding Couchbase-oriented examples on how to add the implementation of a custom method to a repository, how to change the base class of all repositories, how to deal with SpEL in inline queries, ...
The Spring Cache support has been moved out of the Spring Data repository. It is still there and we plan on improving on it. You can for now find it on a Couchbase repository on github but it should soon reintegrate the official Spring family of projects.
You can add the following to your project's pom.xml
to get this GA Release (in the dependencies
section:
<!--<dependencies>-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-couchbase</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<!--</dependencies>-->
We hope you enjoy this release and all the new features it brings to the table. Next step will be to re-attach to the Hopper
release train with a version 2.1
expected before Summer.