From 93405c5da890ceb6d69ee1b76a10d55ac1b5e3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Basl=C3=A9?= Date: Thu, 16 Jul 2015 19:15:59 +0200 Subject: [PATCH] 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. --- README.md | 132 ++++++++++++----- src/main/asciidoc/caching.adoc | 56 -------- src/main/asciidoc/configuration.adoc | 27 ++-- src/main/asciidoc/entity.adoc | 5 +- src/main/asciidoc/index.adoc | 6 +- src/main/asciidoc/migrating.adoc | 63 ++++++++ src/main/asciidoc/repository.adoc | 136 +++++++++++++++--- src/main/asciidoc/template.adoc | 31 +++- .../AbstractCouchbaseConfiguration.java | 2 +- 9 files changed, 327 insertions(+), 131 deletions(-) delete mode 100644 src/main/asciidoc/caching.adoc create mode 100644 src/main/asciidoc/migrating.adoc diff --git a/README.md b/README.md index b068acb1..5c39926c 100644 --- a/README.md +++ b/README.md @@ -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: org.springframework.data spring-data-couchbase - 1.2.2.RELEASE + 2.0.0.RELEASE ``` @@ -49,7 +51,7 @@ the appropriate dependency version. org.springframework.data spring-data-couchbase - 1.3.0.BUILD-SNAPSHOT + 2.1.0.BUILD-SNAPSHOT @@ -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 { - /** - * Additional custom finder method. - */ - List findByLastname(Query query); + /** + * Return all users emitted by the view user/adults + */ + @View + List findAdults(); + + /** + * Find all users matching the last name. + */ + @View(viewName="lastNames") + List findByLastname(String lastName); + + /** + * Find all the users whose first name contains the word. + */ + List 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 { + + /** + * Advanced querying with N1QL derivation + */ + @View + List 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 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 bootstrapHosts() { + protected List 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 - - + + localhost + + + + + + ``` @@ -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 allUsers = userRepository.findByLastname(query); - - } + List allJacksons = userRepository.findByLastname("Jackson"); + } } ``` diff --git a/src/main/asciidoc/caching.adoc b/src/main/asciidoc/caching.adoc deleted file mode 100644 index d2d0f903..00000000 --- a/src/main/asciidoc/caching.adoc +++ /dev/null @@ -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 instances = new HashMap(); - 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. - diff --git a/src/main/asciidoc/configuration.adoc b/src/main/asciidoc/configuration.adoc index f95c794b..d4e8e435 100644 --- a/src/main/asciidoc/configuration.adoc +++ b/src/main/asciidoc/configuration.adoc @@ -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 org.springframework.data spring-data-couchbase - 1.0.0.RELEASE + 2.0.0.RELEASE ---- ==== @@ -31,7 +31,7 @@ You can also grab snapshots from the http://repo.spring.io/libs-snapshot[spring org.springframework.data spring-data-couchbase - 1.1.0.BUILD-SNAPSHOT + 2.1.0.BUILD-SNAPSHOT @@ -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 bootstrapHosts() { + protected List 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"> - + + 127.0.0.1 + + ---- ==== -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 `` 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. diff --git a/src/main/asciidoc/entity.adoc b/src/main/asciidoc/entity.adoc index 26b1e86f..b28ca7a7 100644 --- a/src/main/asciidoc/entity.adoc +++ b/src/main/asciidoc/entity.adoc @@ -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. ==== diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index f5ce97ca..bfd49548 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -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 diff --git a/src/main/asciidoc/migrating.adoc b/src/main/asciidoc/migrating.adoc new file mode 100644 index 00000000..2226ebea --- /dev/null +++ b/src/main/asciidoc/migrating.adoc @@ -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 (``), one or more `Bucket` beans (``) and even tune the SDK via a `CouchbaseEnvironment` bean (``). 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 <>. + +== 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 <> and <> 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 <>. + +For instance, for a view emitting user lastNames, the following: + +[source,java] +---- +@View +List findByLastname(ViewQuery.from("","").key("test").limit(3)); +---- + +is to be replaced by the (more flexible): + +[source,java] +---- +@View("byLastName") +List 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). \ No newline at end of file diff --git a/src/main/asciidoc/repository.adoc b/src/main/asciidoc/repository.adoc index fed39222..cbed5953 100644 --- a/src/main/asciidoc/repository.adoc +++ b/src/main/asciidoc/repository.adoc @@ -48,7 +48,7 @@ public interface UserRepository extends CrudRepository { 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 { + @Query("$SELECT_ENTITY$ WHERE role = 'admin'") List findAllAdmins(); - List findByFirstname(Query query); + List 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 <> 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 { + + @View + List findAllAdmins(); + + @View(viewName="firstNames") + List 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 users = repo.findByFirstname(query)); +// Find all users with first name starting with "Mich" +List 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 <>. +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 <> 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 diff --git a/src/main/asciidoc/template.adoc b/src/main/asciidoc/template.adoc index 9b05fbb4..14411e85 100644 --- a/src/main/asciidoc/template.adoc +++ b/src/main/asciidoc/template.adoc @@ -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] +---- + + + + + + + + + + + + +---- +==== 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 26721d25..d2529a83 100644 --- a/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java +++ b/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java @@ -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> getInitialEntitySet() throws ClassNotFoundException { String basePackage = getMappingBasePackage();