DATACOUCH-142 - Update documentation and provide migration cheatsheet.

The documentation has been updated to reflect the changes in the new Spring Data Couchbase 2.0.

A simple list of major changes to take into account when migrating from 1.x to 2.x has been added.

README.md has also been updated.
This commit is contained in:
Simon Baslé
2015-07-16 19:15:59 +02:00
parent e0972d57b9
commit 93405c5da8
9 changed files with 327 additions and 131 deletions

132
README.md
View File

@@ -1,8 +1,10 @@
# Spring Data Couchbase - v2
This branch is a work in progress, it will contain the do-over of the `Spring-Data Couchbase` connector
for the `Couchbase Java SDK 2.x` generation.
# Spring Data Couchbase 2.0.x
`Spring-Data Couchbase 2.0.x` is the Spring Data connector for the `Couchbase Java SDK 2.x` generation.
Below is the v1 README as of `75bce39`:
Both the SDK and this Spring Data community project are major version changes with lots of differences from their
respective previous versions.
Notably, this version is compatible with `Couchbase Server 4.0`, bringing support for the `N1QL` query language.
# Spring Data Couchbase
@@ -38,7 +40,7 @@ Add the Maven dependency:
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-couchbase</artifactId>
<version>1.2.2.RELEASE</version>
<version>2.0.0.RELEASE</version>
</dependency>
```
@@ -49,7 +51,7 @@ the appropriate dependency version.
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-couchbase</artifactId>
<version>1.3.0.BUILD-SNAPSHOT</version>
<version>2.1.0.BUILD-SNAPSHOT</version>
</dependency>
<repository>
@@ -69,7 +71,7 @@ CouchbaseTemplate is the central support class for Couchbase database operations
### Spring Data Repositories
To simplify the creation of data repositories Spring Data COUCHBASE provides a generic repository programming model. It
To simplify the creation of data repositories Spring Data Couchbase provides a generic repository programming model. It
will automatically create a repository proxy for you that adds implementations of finder methods you specify on an
interface.
@@ -78,19 +80,39 @@ To create a repository on top of a `User` entity, all you need to write is:
```java
public interface UserRepository extends CrudRepository<User, String> {
/**
* Additional custom finder method.
*/
List<User> findByLastname(Query query);
/**
* Return all users emitted by the view user/adults
*/
@View
List<User> findAdults();
/**
* Find all users matching the last name.
*/
@View(viewName="lastNames")
List<User> findByLastname(String lastName);
/**
* Find all the users whose first name contains the word.
*/
List<User> findByFirstnameContains(String word);
}
```
Once you get a reference to that repository bean, you'll find a lot of methods that make it very easy o work with this
Once you get a reference to that repository bean, you'll find a lot of methods that make it very easy to work with this
entity. In addition to the ones provided through the `CrudRepository`, you can add your own methods as well.
In general, every finder method that does not depend on a single key (like `findById`) needs a backing View on the
server side. In the example above, it assumes you have a view named `findByLastname` in the `user` design document. You
In general, every CRUD method that does not depend on a single key (like `findById`) needs a backing View, `all` on the
server side (the design document is by default expected to be the uncapitalized name of the entity, like `user`).
## Custom Repository Methods and Views
Finder methods you define, if annotated with `@View`, also are backed by views. Either you want to return all items from
these views and you can let the method name reflect the view name (like in `findAdults()`, where it'll expect an
`adults` view), or provide simple criteria (you explicitly specify the `viewName` and let the method name determine your
criteria, like in `findByLastname`).
In the example above, it assumes you have a view named `findByLastname` in the `user` design document. You
can customize the view and design document name through the `@View` annotation. Also make sure you publish them into
production before accessing it.
@@ -104,9 +126,10 @@ function (doc, meta) {
}
```
You can pass in custom runtime parameters through the `Query` param.
If you want to use more query parameters than what is supported through query derivation (see `ViewQueryCreator`), you
need to provide the implementation of the finder methods yourself and use the underlying `CouchbaseTemplate`.
To make the `findAll()` and `count` view work, it needs to look like this (and do not forget the `_count` reduce
The `all` view that backs CRUD `findAll()` and `count()` needs to look like this (and do not forget the `_count` reduce
function):
```javascript
@@ -117,8 +140,42 @@ function (doc, meta) {
}
```
The queries issued on execution will be derived from the method name. Extending `CrudRepository` causes CRUD methods
being pulled into the interface so that you can easily save and find single entities and collections of them.
## N1QL and Query Derivation
With the introduction of `N1QL`, Couchbase can now better support query derivation (the mechanism that allows you to
add custom methods that will automatically be implemented as a N1QL query derived from the method's name).
This is the default repository query mechanism, so the associated `@Query` annotation is optional. Here is what it looks
like:
```java
public interface UserRepository extends CrudRepository<User, String> {
/**
* Advanced querying with N1QL derivation
*/
@View
List<User> findByLastnameEqualsIgnoreCaseAndFirstnameStartsWithAndIsAdultTrue(String lastName, String fnamePrefix);
}
```
For instance, calling `find...("Locke", "J")` will get resolved to this N1QL WHERE clause (similar to SQL):
```sql
...WHERE LOWER(lastname) = LOWER("Locke") AND firstname LIKE "J%" AND isAdult = TRUE;
```
You can alternatively write the statement yourself inside the `@Query` annotation, using the `$SELECT_ENTITY$`
placeholder to make sure all necessary fields and metadata are selected by N1QL:
```java
@Query("$SELECT_ENTITY$ WHERE firstname LIKE "%ck%"
List<User> findPatrickAndJackAmongOthers();
```
## Using The Repository
Extending `CrudRepository` causes CRUD methods being pulled into the interface so that you can easily save and find
single entities and collections of them.
You can have Spring automatically create a proxy for the interface by using the following JavaConfig:
@@ -128,7 +185,7 @@ You can have Spring automatically create a proxy for the interface by using the
public class Config extends AbstractCouchbaseConfiguration {
@Override
protected List<String> bootstrapHosts() {
protected List<String> getBootstrapHosts() {
return Arrays.asList("host1", "host2");
}
@@ -145,11 +202,17 @@ public class Config extends AbstractCouchbaseConfiguration {
```
This sets up a connection to a Couchbase cluster and enables the detection of Spring Data repositories (through
`@EnableCouchbaseRepositories). The same configuration would look like this in XML:
`@EnableCouchbaseRepositories`). The same configuration would look like this in XML:
```xml
<couchbase:couchbase id="cb-first" bucket="default" password="" host="localhost" />
<couchbase:template id="cb-template-first" client-ref="cb-first" />
<couchbase:cluster id="cb-first">
<couchbase:node>localhost</couchbase:node>
</couchbase:cluster>
<couchbase:bucket id="cb-bucket-first" cluster-ref="cb-first" bucket="default" password="" />
<couchbase:template id="cb-template-first" bucket-ref="cb-bucket-first" />
<couchbase:repositories couchbase-template-ref="cb-template-first" />
```
@@ -159,26 +222,23 @@ This will find the repository interface and register a proxy object in the conta
@Service
public class MyService {
private final UserRepository userRepository;
private final UserRepository userRepository;
@Autowired
public MyService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public MyService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void doWork() {
userRepository.deleteAll();
public void doWork() {
userRepository.deleteAll();
User user = new User();
user.setLastname("Jackson");
User user = new User();
user.setLastname("Jackson");
user = userRepository.save(user);
user = userRepository.save(user);
Query query = new Query();
query.setKey(ComplexKey.of("Jackson"));
List<User> allUsers = userRepository.findByLastname(query);
}
List<User> allJacksons = userRepository.findByLastname("Jackson");
}
}
```

View File

@@ -1,56 +0,0 @@
[[couchbase.caching]]
= Caching
This chapter describes additional support for caching and `@Cacheable`.
[[caching.usage]]
== Configuration & Usage
Technically, caching is not part of spring-data, but is implemented directly in the spring core. Most database implementations in the spring-data package can't support `@Cacheable`, because it is not possible to store arbitrary data.
Couchbase supports both binary and JSON data, so you can get both out of the same database.
To make it work, you need to add the `@EnableCaching` annotation and configure the `cacheManager` bean:
.`AbstractCouchbaseConfiguration` for Caching
====
[source,java]
----
@Configuration
@EnableCaching
public class Config extends AbstractCouchbaseConfiguration {
// general methods
@Bean
public CouchbaseCacheManager cacheManager() throws Exception {
HashMap<String, CouchbaseClient> instances = new HashMap<String, CouchbaseClient>();
instances.put("persistent", couchbaseClient());
return new CouchbaseCacheManager(instances);
}
}
----
====
The `persistent` identifier can then be used on the `@Cacheable` annotation to identify the cache manager to use (you can have more than one configured).
Once it is set up, you can annotate every method with the `@Cacheable` annotation to transparently cache it in your couchbase bucket. You can also customize how the key is generated.
.Caching example
====
[source,java]
----
@Cacheable(value="persistent", key="'longrunsim-'+#time")
public String simulateLongRun(long time) {
try {
Thread.sleep(time);
} catch(Exception ex) {
System.out.println("This shouldnt happen...");
}
return "I've slept " + time + " miliseconds.;
}
----
====
If you run the method multiple times, you'll see a set operation happening first, followed by multiple get operations and no sleep time (which fakes the expensive execution). You can store whatever you want, if it is JSON of course you can access it through views and look at it in the Web UI.

View File

@@ -6,7 +6,7 @@ This chapter describes the common installation and configuration steps needed wh
[[installation]]
== Installation
All versions intented for production use are distributed across Maven Central and the Spring release repository. As a result, the library can be included like any other maven dependency:
All versions intended for production use are distributed across Maven Central and the Spring release repository. As a result, the library can be included like any other maven dependency:
.Including the dependency through maven
====
@@ -15,7 +15,7 @@ All versions intented for production use are distributed across Maven Central an
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-couchbase</artifactId>
<version>1.0.0.RELEASE</version>
<version>2.0.0.RELEASE</version>
</dependency>
----
====
@@ -31,7 +31,7 @@ You can also grab snapshots from the http://repo.spring.io/libs-snapshot[spring
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-couchbase</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.BUILD-SNAPSHOT</version>
</dependency>
<repository>
@@ -47,7 +47,7 @@ Once you have all needed dependencies on the classpath, you can start configurin
[[configuration-java]]
== Annotation-based Configuration ("JavaConfig")
The annotation based configuration approach is getting more and more popular. It allows you to get rid of XML configuration and treat configuration as part of your code directly. To get started, all you need to do is sublcass the `AbstractCouchbaseConfiguration` and implement the abstract methods.
The annotation based configuration approach is getting more and more popular. It allows you to get rid of XML configuration and treat configuration as part of your code directly. To get started, all you need to do is subclcass the `AbstractCouchbaseConfiguration` and implement the abstract methods.
Please make sure to have cglib support in the classpath so that the annotation based configuration works.
@@ -60,7 +60,7 @@ Please make sure to have cglib support in the classpath so that the annotation b
public class Config extends AbstractCouchbaseConfiguration {
@Override
protected List<String> bootstrapHosts() {
protected List<String> getBootstrapHosts() {
return Collections.singletonList("127.0.0.1");
}
@@ -81,9 +81,13 @@ All you need to provide is a list of Couchbase nodes to bootstrap into (without
The `bucketName` and `password` should be the same as configured in Couchbase Server itself. In the example given, we are connecting to the `beer-sample` bucket which is one of the sample buckets shipped with Couchbase Server and has no password set by default.
Depending on how your environment is setup, the configuration will be automatically picked up by the context or you need to instantiate your own one. How to manage configurations is not scope of this manual, please refer to the spring documentation for more information on that topic.
Depending on how your environment is set up, the configuration will be automatically picked up by the context or you need to instantiate your own one. How to manage configurations is not in scope of this manual, please refer to the spring documentation for more information on that topic.
While not immediately obvious, much more things can be customized and overridden as custom beans from this configuration - we'll touch them in the individual manual sections as needed (for example repositories, validation and custom converters).
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).
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.
[[configuration-xml]]
== XML-based Configuration
@@ -103,12 +107,15 @@ The library provides a custom namespace that you can use in your XML configurati
http://www.springframework.org/schema/data/couchbase
http://www.springframework.org/schema/data/couchbase/spring-couchbase.xsd">
<couchbase:couchbase bucket="beer-sample" password="" host="127.0.0.1" />
<couchbase:cluster>
<couchbase:node>127.0.0.1</couchbase:node>
</couchbase:cluster>
<couchbase:bucket bucketName="beer-sample" bucketPassword=""/>
</beans:beans>
----
====
This code is equivalent to the java configuration approach shown above. It is also possible to configure templates and repositories, which is shown in the appropriate sections.
This code is equivalent to the java configuration approach shown above. You can customize the SDK `CouchbaseEnvironment` via the `<couchbase:env/>` tag, that supports most tuning parameters as attributes. It is also possible to configure templates and repositories, which is shown in the appropriate sections.
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 is correct.
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.

View File

@@ -264,7 +264,7 @@ A populated object can look like:
----
====
If you want to override a converter or implement your own one, this is also possible. The library implements the general Spring Converter pattern. You can plug in custom converters on bean creation time in your configuration. Here's how you can configure it (in your overriden `AbstractCouchbaseConfiguration`):
If you want to override a converter or implement your own one, this is also possible. The library implements the general Spring Converter pattern. You can plug in custom converters on bean creation time in your configuration. Here's how you can configure it (in your overridden `AbstractCouchbaseConfiguration`):
.Custom Converters
====
@@ -304,13 +304,14 @@ There are a few things to keep in mind with custom conversions:
* To make it unambiguous, always use the `@WritingConverter` and `@ReadingConverter` annotations on your converters. Especially if you are dealing with primitive type conversions, this will help to reduce possible wrong conversions.
* If you implement a writing converter, make sure to decode into primitive types, maps and lists only. If you need more complex object types, use the `CouchbaseDocument` and `CouchbaseList` types, which are also understood by the underlying translation engine. Your best bet is to stick with as simple as possible conversions.
* Always put more special converters before generic converters to avoid the case where the wrong converter gets executed.
* For dates, reading converters should be able to read from any `Number` (not just `Long`). This is required for N1QL support.
[[version]]
== Optimistic Locking
Couchbase Server does not support multi-document transactions or rollback. To implement optimistic locking, Couchbase uses a CAS (compare and swap) approach. When a document is mutated, the CAS value also changes. The CAS is opaque to the client, the only thing you need to know is that it changes when the content or a meta information changes too.
In other datastores, similar behavior can be achieved through an arbitrary version field whith a incrementing counter. Since Couchbase supports this in a much better fashion, it is easy to implement. If you want automatic optimistic locking support, all you need to do is add a `@Version` annotation on a long field like this:
In other datastores, similar behavior can be achieved through an arbitrary version field with a incrementing counter. Since Couchbase supports this in a much better fashion, it is easy to implement. If you want automatic optimistic locking support, all you need to do is add a `@Version` annotation on a long field like this:
.A Document with optimistic locking.
====

View File

@@ -1,5 +1,5 @@
= Spring Data Couchbase - Reference Documentation
Michael Nitschinger, Oliver Gierke
Michael Nitschinger, Oliver Gierke, Simon Baslé
:revnumber: {version}
:revdate: {localdate}
:toc:
@@ -17,13 +17,15 @@ include::preface.adoc[]
[[reference]]
= Reference Documentation
= Migrating from Spring-Data-Couchbase 1.x to 2.x
include::migrating.adoc[]
:leveloffset: +1
include::configuration.adoc[]
include::entity.adoc[]
include::{spring-data-commons-docs}/repositories.adoc[]
include::repository.adoc[]
include::template.adoc[]
include::caching.adoc[]
:leveloffset: -1

View File

@@ -0,0 +1,63 @@
[[couchbase.migrating]]
= Migrating from Spring-Data-Couchbase 1.x to 2.x
This chapter is a quick reference of what major changes have been introduced in 2.0.x and gives a high-level overview of things to consider when migrating.
== Configuration
The configuration, xml schema, etc... has changed to take the evolution of the 2.x SDK API into account.
Where a single `CouchbaseClient` bean was previously the only bean declarable, you can now declare a `Cluster` bean (`<couchbase:cluster>`), one or more `Bucket` beans (`<couchbase:bucket>`) and even tune the SDK via a `CouchbaseEnvironment` bean (`<couchbase:env>`). All of these can also be created via Java Config method by extending `AbstractCouchbaseConfig`.
The cluster bean lists the nodes to connect through (and references the environment bean if tuning is necessary) while the bucket beans map to bucket names and passwords and actually opens the connections internally.
You can define more beans that are used for internal configuration of the Spring Data Couchbase module (`MappingContext`, `CouchbaseConverter`, `TranslationService`, ...).
For more information, see <<couchbase.configuration>>.
== Repository Queries
The view-backed query method has evolved and support for N1QL has been introduced. As a result, there are now 4 ways of doing repository queries:
* Simple View query (to return all elements emitted by a view) - @View annotated without `viewName`
* Intermediate View query by query derivation (to provide some criteria for the view) @View annotated with `viewName`
* N1QL with explicit statements inline - @N1QL annotated with value
* N1QL query derivation - @N1QL annotated without value / no annotation (default)
View backed queries are associated with the `@View` annotation, while N1QL backed queries are associated with the `@Query` annotation.
N1QL query derivation is now the default query method (and there the `@Query` annotation is optional).
See <<couchbase.repository.n1ql>> and <<couchbase.repository.views>> for more information.
== Backing Views and View Query Changes
IMPORTANT: The `all` view is still backing most CRUD operations, but custom repository methods are now by default backed by N1QL.
To instead back them with views, use the `@View` annotation explicitly.
Without a `viewName` specified, the view will be guessed from method name (stripping `count` or `find` prefix).
Otherwise, query derivation will be used to parameterize the view query from the method name and parameters.
=== Passing a `ViewQuery` object as a parameter to a custom repository method
This behavior has been removed and the recommended approach is now to either use query derivation (if the query parameters are simple enough) or <<repositories.single-repository-behaviour>>.
For instance, for a view emitting user lastNames, the following:
[source,java]
----
@View
List<User> findByLastname(ViewQuery.from("","").key("test").limit(3));
----
is to be replaced by the (more flexible):
[source,java]
----
@View("byLastName")
List<User> findFirst3ByLastnameEquals(String lastName);
----
=== Reduce in Views
TIP: Reduce is now supported. It will be triggered by prefixing the method name with `count` instead of `find`.
For example: `countByLastnameContains(String word)` instead of `findByLastnameContains(String word)`.
WARNING: Don't forget to specify an `int/long` reduce function (not limited to `_count`) in your view if you plan to use that. Similarly, views backing a query derivation should emit a simple key (not `null` nor a compound key).

View File

@@ -48,7 +48,7 @@ public interface UserRepository extends CrudRepository<User, String> {
Please note that this is just an interface and not an actual class. In the background, when your context gets initialized, actual implementations for your repository descriptions get created and you can access them through regular beans. This means you will save lots of boilerplate code while still exposing full CRUD semantics to your service layer and application.
Now, let's imagine we `@Autowrie` the `UserRepository` to a class that makes use of it. What methods do we have available?
Now, let's imagine we `@Autowire` the `UserRepository` to a class that makes use of it. What methods do we have available?
[cols="2", options="header"]
.Exposed methods on the UserRepository
@@ -90,31 +90,85 @@ Now, let's imagine we `@Autowrie` the `UserRepository` to a class that makes use
| Delete all entities by type in the bucket.
|===
Now thats awesome! Just by defining an interface we get full CRUD functionality on top of our managed entity. All methods suffixed with (*) in the table are backed by Views, which is explained later.
Now that's awesome! Just by defining an interface we get full CRUD functionality on top of our managed entity. All methods suffixed with (*) in the table are backed by Views, which is explained later.
If you are coming from other datastore implementations, you might want to implement the `PagingAndSortingRepository` as well. Note that as of now, it is not supported but will be in the future.
While the exposed methods provide you with a great variety of access patterns, very often you need to define custom ones. You can do this by adding method declarations to your interface, which will be automatically resolved to view requests in the background. Here is an example:
While the exposed methods provide you with a great variety of access patterns, very often you need to define custom ones. You can do this by adding method declarations to your interface, which will be automatically resolved to requests in the background, as we'll see in the next two sections.
.An extended User repository
[[couchbase.repository.n1ql]]
= N1QL based querying
As of version `4.0`, Couchbase Server ships with a new query language called `N1QL`. In `Spring-Data-Couchbase 2.0`, N1QL is the default way of doing queries and will allow you to fully derive queries from a method name.
Prerequisite is to have a N1QL-compatible cluster and to have created a PRIMARY INDEX on the bucket where the entities will be stored.
Here is an example:
.An extended User repository with N1QL queries
====
[source,java]
----
public interface UserRepository extends CrudRepository<User, String> {
@Query("$SELECT_ENTITY$ WHERE role = 'admin'")
List<User> findAllAdmins();
List<User> findByFirstname(Query query);
List<User> findByFirstname(String fname);
}
----
====
Since we've came across views now multiple times and the `findByFirstname(Query query)` exposes a yet unknown parameter, let's cover that next.
Here we see two N1QL-backed ways of querying.
The first one uses the `Query` annotation to provide a N1QL statement inline. Notice the special placeholder `$SELECT_ENTITY` which allows to easily make sure the statement will select all the fields necessary to build the full entity (including document ID and CAS value).
The second one use Spring-Data's query derivation mechanism to build a N1QL query from the method name and parameters. This will produce a query looking like this: `SELECT ... FROM ... WHERE firstName = "valueOfFnameAtRuntime"`. You can combine these criteria, even do a count with a name like `countByFirstname` or a limit with a name like `findFirst3ByLastname`...
Most Spring-Data keywords are supported:
.Supported keywords inside @Query (N1QL) method names
[options = "header, autowidth"]
|===============
|Keyword|Sample|N1QL WHERE clause snippet
|`And`|`findByLastnameAndFirstname`|`lastName = a AND firstName = b`
|`Or`|`findByLastnameOrFirstname`|`lastName = a OR firstName = b`
|`Is,Equals`|`findByField`,`findByFieldEquals`|`field = a`
|`IsNot,Not`|`findByFieldIsNot`|`field != a`
|`Between`|`findByFieldBetween`|`field BETWEEN a AND b`
|`IsLessThan,LessThan,IsBefore,Before`|`findByFieldIsLessThan`,`findByFieldBefore`|`field < a`
|`IsLessThanEqual,LessThanEqual`|`findByFieldIsLessThanEqual`|`field <= a`
|`IsGreaterThan,GreaterThan,IsAfter,After`|`findByFieldIsGreaterThan`,`findByFieldAfter`|`field > a`
|`IsGreaterThanEqual,GreaterThanEqual`|`findByFieldGreaterThanEqual`|`field >= a`
|`IsNull`|`findByFieldIsNull`|`field IS NULL`
|`IsNotNull,NotNull`|`findByFieldIsNotNull`|`field IS NOT NULL`
|`IsLike,Like`|`findByFieldLike`|`field LIKE "a"` - a should be a String containing % and _ (matching n and 1 characters)
|`IsNotLike,NotLike`|`findByFieldNotLike`|`field NOT LIKE "a"` - a should be a String containing % and _ (matching n and 1 characters)
|`IsStartingWith,StartingWith,StartsWith`|`findByFieldStartingWith`|`field LIKE "a%"` - a should be a String prefix
|`IsEndingWith,EndingWith,EndsWith`|`findByFieldEndingWith`|`field LIKE "%a"` - a should be a String suffix
|`IsContaining,Containing,Contains`|`findByFieldContains`|`field LIKE "%a%"` - a should be a String
|`IsNotContaining,NotContaining,NotContains`|`findByFieldNotContaining`|`field NOT LIKE "%a%"` - a should be a String
|`IsIn,In`|`findByFieldIn`|`field IN array` - note that the next parameter value (or its children if a collection/array) should be compatible for storage in a `JsonArray`)
|`IsNotIn,NotIn`|`findByFieldNotIn`|`field NOT IN array` - note that the next parameter value (or its children if a collection/array) should be compatible for storage in a `JsonArray`)
|`IsTrue,True`|`findByFieldIsTrue`|`field = TRUE`
|`IsFalse,False`|`findByFieldFalse`|`field = FALSE`
|`MatchesRegex,Matches,Regex`|`findByFieldMatches`|`REGEXP_LIKE(field, "a")` - note that the ignoreCase is ignored here, a is a regular expression in String form
|`Exists`|`findByFieldExists`|`field IS NOT MISSING` - used to verify that the JSON contains this attribute
|`OrderBy`|`findByFieldOrderByLastnameDesc`|`field = a ORDER BY lastname DESC`
|`IgnoreCase`|`findByFieldIgnoreCase`|`LOWER(field) = LOWER("a")` - a must be a String
|===============
You can use both counting queries and <<repositories.limit-query-result>> features with this approach.
The second way of querying, supported also in older versions of Couchbase Server, is the View-backed one that we'll see in the next section.
[[couchbase.repository.views]]
== Backing Views
This is the historical way of secondary indexing in Couchbase. Views are much more limited in terms of querying flexibility, and each custom method may very well need its own backing view, to be prepared in the cluster beforehand.
As a rule of thumb, all repository access methods which are not "by a specific key" require a backing view to find the one or more matching entities. We'll only cover views to the extend which they are needed, if you need in-depth information about them please refer to the official Couchbase Server manual and the Couchbase Java SDK manual.
We'll only cover views to the extent to which they are needed, if you need in-depth information about them please refer to the official Couchbase Server manual and the Couchbase Java SDK manual.
As a rule of thumb, all repository CRUD access methods which are not "by a specific key" still require a single backing view, by default `all`, to find the one or more matching entities.
IMPORTANT: This is only true for the methods directly defined by the `CrudRepository` interface (the one marked with a `*` in `Table 1.` above), since your additional methods can now be backed by N1QL.
To cover the basic CRUD methods from the `CrudRepository`, one view needs to be implemented in Couchbase Server. It basically returns all documents for the specific entity and also adds the optional reduce function `_count`.
@@ -137,9 +191,35 @@ Note that the important part in this map function is to only include the documen
Also make sure to publish your design documents into production so that they can be picked up by the library! Also, if you are curious why we use `emit(null, null)` in the view: the document id is always sent over to the client implicitly, so we can shave off a view bytes in our view by not duplicating the id. If you use `emit(meta.id, null)` it won't hurt much too.
Implementing your custom repository finder methods works the same way. The `findAllAdmins` calls the `allAdmins` view in the `user` design document. Imagine we have a field on our entity which looks like `boolean isAdmin`. We can write a view like this to expose them (we don't need a reduce function for this one):
[[couchbase.repository.views.querying]]
=== View based querying
.A custom view map function
In `2.0`, since N1QL has been introduced as a more powerful concept, view-backed queries have changed a bit outside of the CRUD methods:
- the `@View` annotation is mandatory.
- if you just want all the results from the view, you can let the framework guess the view name to use by just using the plain annotation `@View`. **You won't be able to customize** the `ViewQuery` (eg. adding limits and specifying a `startkey`) using this method anymore.
- if you want your view query to have restrictions, those can be derived from the method name but in this case you **must** explicitly provide the `viewName` attribute in the annotation.
- View based query derivation is limited to a few keywords and only works on simple keys (not compound keys like `[ age, fname ]`).
- View based query derivation still needs you to include *one* valid property before keywords in the method name.
.An extended User repository with View queries
====
[source,java]
----
public interface UserRepository extends CrudRepository<User, String> {
@View
List<User> findAllAdmins();
@View(viewName="firstNames")
List<User> findByFirstnameStartingWith(String fnamePrefix);
}
----
====
Implementing your custom repository finder methods also needs backing views. The `findAllAdmins` guesses to use the `allAdmins` view in the `user` design document, by convention. Imagine we have a field on our entity which looks like `boolean isAdmin`. We can write a view like this to expose them (we don't need a reduce function for this one, unless you plan to call one by prefixing your method with `count` instead of `find`!):
.The allAdmins map function
====
[source,javascript]
----
@@ -151,9 +231,9 @@ function (doc, meta) {
----
====
By now, we've never actually customized our view at query time. This is where the special `Query` argument comes along - like in our `findByFirstname(Query query)` method.
By now, we've never actually customized our view at query time. This is where the alternative, query derivation, comes along - like in our `findByFirstnameStartingWith(String fnamePrefix)` method.
.A parameterized view map function
.The firstNames view map function
====
[source,javascript]
----
@@ -165,7 +245,7 @@ function (doc, meta) {
----
====
This view not only emits the document id, but also the firstname of every user as the key. We can now run a `Query` which returns us all users with a firstname of "Michael" or "Thomas".
This view not only emits the document id, but also the firstname of every user as the key. We can now run a `ViewQuery` which returns us all users with a firstname of "Michael" or "Michele".
.Query a repository method with custom params.
====
@@ -174,18 +254,30 @@ This view not only emits the document id, but also the firstname of every user a
// Load the bean, or @Autowire it
UserRepository repo = ctx.getBean(UserRepository.class);
// Create the CouchbaseClient Query object
Query query = new Query();
// Filter on those two keys
query.setKeys(ComplexKey.of("Michael", "Thomas"));
// Run the query and get all matching users returned
List<User> users = repo.findByFirstname(query));
// Find all users with first name starting with "Mich"
List<User> users = repo.findByFirstnameStartingWith("Mich");
----
====
On all custom finder methods, you can use the `@View` annotation to both customize the design document and view name (to override the conventions).
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).
Please keep in mind that by default, the `Stale.UPDATE_AFTER` mechanism is used. This means that whatever is in the index gets returned, and then the index gets updated. This strikes a good balance between performance and data freshness. You can tune the behavior through the `setStale()` method on the query object. For more details on behavior, please consult the Couchbase Server and Java SDK documentation directly.
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 <<repositories.single-repository-behaviour>>.
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 view-based query derivation, here are the supported keywords (A and B are method parameters in this table):
.Supported keywords inside @View method names
[options = "header, autowidth"]
|===============
|`Is,Equals`|`findAllByUsername`,`findByFieldEquals`|`key=A` - if only keyword, the method can have no parameter (return all items from the view)
|`Between`|`findByFieldBetween`|`startkey=A&endkey=B`
|`IsLessThan,LessThan,IsBefore,Before`|`findByFieldIsLessThan`,`findByFieldBefore`|`endkey=A`
|`IsLessThanEqual,LessThanEqual`|`findByFieldIsLessThanEqual`|`endkey=A&inclusive_end=true`
|`IsGreaterThanEqual,GreaterThanEqual`|`findByFieldGreaterThanEqual`|`startkey=A`
|`IsStartingWith,StartingWith,StartsWith`|`findByFieldStartingWith`|`startkey="A"&endkey="A\uefff"` - A should be a String prefix
|`IsIn,In`|`findByFieldIn`|`keys=[A]` - A should be a `Collection`/`Array` with elements compatible for storage in a `JsonArray` (or a single element to be stored in a `JsonArray`)
|===============
TIP: Note that the `reduce function` (not always a count) will be activated by prefixing with `count` and that <<repositories.limit-query-result>> 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.

View File

@@ -14,7 +14,34 @@ Since Couchbase Server has different levels of persistence (by default you'll ge
Removing documents through the `remove` methods works exactly the same.
If you want to load documents, you can do that through the `findById` method, which is the fastest and if possible your tool of choice. The find methods for views are `findByView` which converts it into the target entity, but also `queryView` which exposes lower level semantics.
If you want to load documents, you can do that through the `findById` method, which is the fastest and if possible your tool of choice. The find methods for views are `findByView` which converts it into the target entity, but also `queryView` which exposes lower level semantics. Similarly, find methods using N1QL are provided in `findByN1QL` and `queryN1QL`. Additionally, since N1QL allows you to select specific fields in documents (or even across documents using joins), `findByN1QLProjection` will allow you to skip full `Document` conversion and map these fields to an ad-hoc class.
If you really need low-level semantics, the `couchbaseClient` bean is also always in scope.
If you really need low-level semantics, the `couchbaseBucket` is also always in scope through `getCouchbaseBucket()`.
[[couchbase.template.xml]]
== Xml Configuration
The template can be configured via xml, including setting a custom `TranslationService`.
.XML Based Template Declaration
====
[source,xml]
----
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:couchbase="http://www.springframework.org/schema/data/couchbase"
xsi:schemaLocation="http://www.springframework.org/schema/data/couchbase http://www.springframework.org/schema/data/couchbase/spring-couchbase.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<couchbase:env/>
<couchbase:cluster/>
<couchbase:bucket/>
<couchbase:template translation-service-ref="myCustomTranslationService"/>
<bean id="myCustomTranslationService" class="org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService"/>
</beans>
----
====

View File

@@ -202,7 +202,7 @@ public abstract class AbstractCouchbaseConfiguration {
/**
* Scans the mapping base package for classes annotated with {@link Document}.
*
* @throws ClassNotFoundException if intial entity sets could not be loaded.
* @throws ClassNotFoundException if initial entity sets could not be loaded.
*/
protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
String basePackage = getMappingBasePackage();