diff --git a/src/main/asciidoc/autokeygeneration.adoc b/src/main/asciidoc/autokeygeneration.adoc index 16c64fa6..8ddf7cce 100644 --- a/src/main/asciidoc/autokeygeneration.adoc +++ b/src/main/asciidoc/autokeygeneration.adoc @@ -37,37 +37,6 @@ public class User { ---- ==== -Common prefix and suffix for all entities keys can be added to `CouchbaseTemplate` directly. -Once added to the `CouchbaseTemplate`, they become immutable. -These settings are always applied irrespective of the `GeneratedValue` annotation. - -.Common key settings in CouchbaseTemplate -==== -[source,java] ----- -@Autowired -CouchbaseTemplate couchbaseTemplate; -... -couchbaseTemplate.keySettings(KeySettings.build().prefix("ApplicationA").suffix("Server1").delimiter("::")); ----- -==== - -Key will be auto-generated only for operations with direct entity input like insert, update, save, delete using entity. -For other operations requiring just the key, it can be generated using `CouchbaseTemplate`. - -.Standalone key generation in CouchbaseTemplate -==== -[source,java] ----- -@Autowired -CouchbaseTemplate couchbaseTemplate; -... -String id = couchbaseTemplate.getGeneratedId(entity); -... -repo.exists(id); ----- -==== - [[couchbase.autokeygeneration.usingattributes]] == Key generation using attributes diff --git a/src/main/asciidoc/reactiverepository.adoc b/src/main/asciidoc/reactiverepository.adoc index a58959c0..b4f0c368 100644 --- a/src/main/asciidoc/reactiverepository.adoc +++ b/src/main/asciidoc/reactiverepository.adoc @@ -11,25 +11,17 @@ So make sure you’ve got a sound understanding of the basic concepts explained [[couchbase.reactiverepository.libraries]] == Reactive Composition Libraries -Couchbase Java SDK 2.x has taken a reactive programming approach since its inception using an early reactive extension to JVM, https://github.com/ReactiveX/RxJava/tree/1.x/[RxJava1]. -It provides RxJava1 observable sequence API to compose asynchronous database operations. +The Couchbase Java SDK 3.x moved from RxJava to Reactor, so it blends in very nicely with the reactive spring ecosystem. Reactive Couchbase repositories provide project Reactor wrapper types and can be used by simply extending from one of the library-specific repository interfaces: -* ReactiveCrudRepository - -* ReactiveSortingRepository - -Spring-data-couchbase converts RxJava 1 observables to reactor types by using reactive-streams adapters from https://github.com/ReactiveX/[RxJavaReactiveStreams] -for convenience since these conversions can easily clutter application code. -This transformation happens on same thread. -It also provides direct access to RxJava1 observable sequence API from SDK through RxJavaCouchbaseOperations methods. + * ReactiveCrudRepository + * ReactiveSortingRepository [[couchbase.reactiverepository.usage]] == Usage -To access domain entities stored in a Couchbase bucket you can leverage our sophisticated repository support that eases implementing those quite significantly. -To do so, simply create an interface for your repository: +Let's create a simple entity to start with: .Sample Person entity ==== @@ -48,7 +40,7 @@ public class Person { ---- ==== -We have a quite simple domain object here. +A corresponding repository implementation may look like this: .Basic repository interface to persist Person entities ==== @@ -71,28 +63,16 @@ For JavaConfig use the `@EnableReactiveCouchbaseRepositories` annotation. The annotation carries the very same attributes like the namespace element. If no base package is configured the infrastructure will scan the package of the annotated configuration class. +Also note that if you are using it in a spring boot setup you likely can omit the annotation since it is autoconfigured for you. + .JavaConfig for repositories ==== [source,java] ---- @Configuration @EnableReactiveCouchbaseRepositories -class ApplicationConfig extends AbstractReactiveCouchbaseConfiguration { - - @Override - protected List getBootstrapHosts() { - return Collections.singletonList("127.0.0.1"); - } - - @Override - protected String getBucketName() { - return "default"; - } - - @Override - protected String getPassword() { - return ""; - } +class ApplicationConfig extends AbstractCouchbaseConfiguration { + // ... (see configuration for details) } ---- ==== diff --git a/src/main/asciidoc/repository.adoc b/src/main/asciidoc/repository.adoc index a9ce76e2..94f1d285 100644 --- a/src/main/asciidoc/repository.adoc +++ b/src/main/asciidoc/repository.adoc @@ -3,14 +3,7 @@ The goal of Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores. -There are three backing mechanisms in Couchbase for repositories, described in the sections of this chapter: - -- <> -- <> -- <> - -CRUD operations are still mostly backed by Couchbase views (see <>). -Such views (and, for N1QL, equivalent indexes) can be automatically built, but note this is **discouraged in production** and can be an **expensive operation** (see <>). +By default, operations are backed by Key/Value if they are single-document operations and the ID is known. For all other operations by default N1QL queries are generated, and as a result proper indexes must be created for performant data access. Note that you can tune the consistency you want for your queries (see <>) and have different repositories backed by different buckets (see <>) @@ -21,6 +14,8 @@ While support for repositories is always present, you need to enable them in gen If you extend `AbstractCouchbaseConfiguration`, just use the `@EnableCouchbaseRepositories` annotation. It provides lots of possible options to narrow or customize the search path, one of the most common ones is `basePackages`. +Also note that if you are running inside spring boot, the autoconfig support already sets up the annotation for you so you only need to use it if you want to override the defaults. + .Annotation-Based Repository Setup ==== [source,java] @@ -35,16 +30,6 @@ public class Config extends AbstractCouchbaseConfiguration { An advanced usage is described in <>. -XML-based configuration is also available: - -.XML-Based Repository Setup -==== -[source,xml] ----- - ----- -==== - [[couchbase.repository.usage]] == Usage @@ -87,13 +72,13 @@ What methods do we have available? | boolean exists(String id) | Check if a given entity exists by its unique id. -| Iterable findAll() (*) +| Iterable findAll() | Find all entities by this type in the bucket. | Iterable findAll(Iterable ids) | Find all entities by this type and the given list of ids. -| long count() (*) +| long count() | Count the number of entities in the bucket. | void delete(String id) @@ -105,13 +90,12 @@ What methods do we have available? | void delete(Iterable entities) | Delete all given entities. -| void deleteAll() (*) +| void deleteAll() | Delete all entities by type in the bucket. |=== 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. 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 sections. @@ -122,13 +106,7 @@ You can do this by adding method declarations to your interface, which will be a [[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. -DML queries are supported from Couchbase server version `4.1`. - -WARNING: If it is detected at configuration time that the cluster doesn't support N1QL while there are `@Query` annotated methods or non-annotated methods in your repository interface, a `UnsupportedCouchbaseFeatureException` will be thrown. +Prerequisite is to have created a PRIMARY INDEX on the bucket where the entities will be stored. Here is an example: @@ -283,7 +261,7 @@ Most Spring-Data keywords are supported: You can use both counting queries and <> features with this approach. -With N1QL, another possible interface for the repository is the `PagingAndSortingRepository` one (which extends CRUDRepository). +With N1QL, another possible interface for the repository is the `PagingAndSortingRepository` one (which extends `CrudRepository`). It adds two methods: [cols="2",options="header"] @@ -303,476 +281,82 @@ TIP: You can also use `Page` and `Slice` as method return types as well with a N NOTE: If pageable and sort parameters are used with inline queries, there should not be any order by, limit or offset clause in the inline query itself otherwise the server would reject the query as malformed. -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. - -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 3.` 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`. - -Since every view has a design document and view name, by convention we default to `all` as the view name and the uncapitalized (lowercase first letter) entity name as the design document name. -So if your entity is named `UserInfo`, then the code expects the `all` view in the `userInfo` design document. -It needs to look like this: - -.The all view map function -==== -[source,javascript] ----- -// do not forget the _count reduce function! -function (doc, meta) { - if (doc._class == "namespace.to.entity.UserInfo") { - emit(meta.id, null); - } -} ----- -==== - -Note that the important part in this map function is to only include the document IDs which correspond to our entity. -Because the library always adds the `_class` property, this is a quick and easy way to do it. -If you have another property in your JSON which does the same job (like a explicit `type` field), then you can use that as well - you don't have to stick to `_class` all the time. - -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(meta.id, null)` in the view despite the document id being always sent over to the client implicitly, it is so the view can be queried with a list of ids, eg. in the `findAll(Iterable ids)` CRUD method. [[couchbase.repository.indexing]] === Automatic Index Management -We've seen that the repositories default methods can be backed by two broad kind of features: views and N1QL (in the case of paging and sorting). -In order for the CRUD operations to work, the adequate view must have been created beforehand, and this is usually left for the user to do. -First because view creation (and index creation) is an expensive operation that can take quite some time if the quantity of documents is high. -Second, because in production it is considered best practice to avoid administration of the cluster elements like buckets, indexes and view by an application code. +By default, it is expected that the user creates and manages optimal indexes for their queries. Especially in the early stages of development, it can come in handy to automatically create indexes to get going quickly. -In the case where the index creation cost isn't considered too high and you are not in a production environment, it can be triggered automatically instead, in two steps. -You will first need to annotate the repositories you want managed with the relevant annotation(s): +For N1QL, the following annotations are provided which need to be attached to the entity (either on the class or the field): -- `@ViewIndexed` will create a view like the "all" view previously seen, to list all entities in the bucket. -- `@N1qlPrimaryIndexed` can be used to ensure a general-purpose PRIMARY INDEX is available in N1QL. -- `@N1qlSecondaryIndexed` will create a more specific N1QL index that does the same kind of filtering on entity type that the view does. -It'll allow for efficient listing of all documents that correspond to a Repository's associated domain object. + - `@QueryIndexed`: Placed on a field to signal that this field should be part of the index + - `@CompositeQueryIndex`: Placed on the class to signal that an index on more than one field (composite) should be created. + - `@CompositeQueryIndexes`: If more than one `CompositeQueryIndex` should be created, this annotation will take a list of them. -Secondly, you'll need to opt-in to this feature by customizing the `indexManager()` bean of your env-specific `AbstractCouchbaseConfiguration` to take certain types of annotations into account. -This is done through the `IndexManager(boolean processViews, boolean processN1qlPrimary, boolean processN1qlSecondary)` constructor. -Set the flags for the category of annotations you want processed to true, or false to deactivate the automatic creation feature. +For example, this is how you define a composite index on an entity: -The `@Profile` annotation is one possible Spring annotation to be used to differentiate configurations (or individual beans) per environment. - -.A Dev configuration where only @ViewIndexed annotations will be processed. +.Composite index on two fields with ordering ==== [source,java] ---- -@Configuration -public class ExampleDevApplicationConfig extends AbstractCouchbaseConfiguration { +@Document +@CompositeQueryIndex(fields = {"id", "name desc"}) +public class Airline { + @Id + String id; - // note a few other overrides are actually needed + @QueryIndexed + String name; + + @PersistenceConstructor + public Airline(String id, String name) { + this.id = id; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } - //this is for dev so it is ok to auto-create indexes - @Override - public IndexManager indexManager() { - return new IndexManager(true, false, false); - } } ---- ==== -[[couchbase.repository.views.querying]] -=== View based querying +By default, index creation is disabled. If you want to enable it you need to override it on the configuration: -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 UserInfo repository with View queries +.Enable auto index creation ==== [source,java] ---- -public interface UserRepository extends CrudRepository { - - @View - List findAllAdmins(); - - @View(viewName="firstNames") - List findByFirstnameStartingWith(String fnamePrefix); +@Override +protected boolean autoIndexCreation() { + return true; } ---- ==== -Implementing your custom repository finder methods also needs backing views. -The `findAllAdmins` guesses to use the `allAdmins` view in the `userInfo` 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] ----- -function (doc, meta) { - if (doc._class == "namespace.to.entity.UserInfo" && doc.isAdmin) { - emit(null, null); - } -} ----- -==== - -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. - -.The firstNames view map function -==== -[source,javascript] ----- -function (doc, meta) { - if (doc._class == "namespace.to.entity.UserInfo") { - emit(doc.firstname, null); - } -} ----- -==== - -This view not only emits the document id, but also the firstname of every UserInfo 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. -==== -[source,java] ----- -// Load the bean, or @Autowire it -UserRepository repo = ctx.getBean(UserRepository.class); - -// Find all users with first name starting with "Mich" -List users = repo.findByFirstnameStartingWith("Mich"); ----- -==== - -On all these derived custom finder methods, you have to use the `@View` annotation with at least the view name specified (and you can also override the design document name, otherwise determined by convention). - -IMPORTANT: For any other usage and customization of the `ViewQuery` that goes beyond that, recommended approach is to provide an implementation that uses the underlying template, like described in <>. -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`) -|=============== - -Note that both `reduce functions` and <> are also supported. - -TIP: In order to trigger a `reduce`, you can use the `count` prefix instead of `find`. -But sometimes is doesn't make much sense (eg. because you actually use the `_stats` built in function, which returns a JSON object). -So alternatively you can also explicitly ask for reduce to be executed by setting `reduce = true` in the `@View` annotation. -Be sure to specify a return type that make sense for the reduce function of your view. - -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. - -Last method of querying in Couchbase (from Couchbase Server 4.0, like for N1QL) is querying for dimensional data through *Spatial Views*, as we'll see in the next section. - -[[couchbase.repository.spatial]] -=== Spatial View based querying - -Couchbase can accommodate multi-dimensional data and query it with the use of special views, the Spatial Views. -Such views allows to perform multi-dimensional queries, not only limited to geographical data. - -Integration of these views in `Spring Data Couchbase` repositories is done through the `@Dimensional` annotation. -Like `@View`, the annotation allows to indicate usage of a Spatial View as the backing mechanism for the annotated method. -The annotation requires you to give the name of the `designDocument` and the `spatialViewName` to use. -Additionally, you should specify the number of `dimensions` the view works with (unless it is the default classical 2). - -Multi-dimensionality concept is interesting, it means you can craft views that allows you to answer questions like "find all shops that are within Manhattan and open between 14:00 and 23:00" (the third dimension of the view being the opening hours). - -Couchbase's Spatial View support querying through ranges that represent "lowest" and "highest" values in each dimension, so for 2D it represents a bounding box, with the southwest-most point [x,y] as `startRange` and northeast-most point [x,y] as `endRange`. - -TIP: Even though Couchbase Spatial View engine only support Bounding Box querying, the Spring Data Couchbase framework will attempt to remove false positives for you when querying with a `Polygon` or a `Circle` (in TRACE log level each false positive elimination will be logged). -Note that a point on the edge of a `Polygon` is *not* considered within (whereas it is when dealing with a `Circle`). - -The following query derivation keywords and parameters relative to geographical data in Spring Data are supported for Spatial Views: - -.Supported keywords inside @Dimensional method names -[options = "header,autowidth"] -|=============== -|Keyword|Sample|Remarks -|`Within,IsWithin`|`findByLocationWithin`| -|`Near,IsNear`|`findByLocationNear`|expects a `Point` and a `Distance`, will approximate to bounding box -|`Between`|`findByLocationWithinAndOpeningHoursBetween`|useful for dimensions beyond 2, adds two numerical values to the startRange and endRange respectively -|`GreaterThan,GreaterThanEqual,After`|`findByLocationWithinAndOpeningHoursAfter`|useful for dimensions beyond 2, adds a numerical value to the startRange -|`LessThan,LessThanEqual,Before`|`findByLocationWithinAndOpeningHoursBefore`|useful for dimensions beyond 2, adds a numerical value to the endRange -|=============== - -IMPORTANT: For "within" types of queries, the expected parameters map to geographical 2D data. -Classes from the `org.springframework.data.geo` package are usually expected, but Polygon and Boxes can also be expressed as arrays of `Point`s. - -Further dimensions are supported through keywords other than Within and Near and require numerical input. - [[couchbase.repository.consistency]] === Querying with consistency -One aspect that is often needed and doesn't have a direct equivalent in the Spring Data query derivation mechanism is -`query consistency`. -In both view-based queries and N1QL, you have this concept that the secondary index can return stale data, because the latest version hasn't been indexed yet. -This gives the best performance at the expense of consistency. +By default repository queries that use N1QL use the `NOT_BOUNDED` scan consistency. This means that results return quickly, but the data from the index may not yet contain data from previously written operations (called eventual consistency). If you need "ready your own write" semantics for a query, you need to use the `@ScanConsistency` annotation. Here is an example: -Note that weaker consistencies can lead to data being returned that doesn't match the criteria of a derived query. -One trickier case is when documents are deleted from Couchbase but views have not yet caught up to the deletion. -With weak consistency this can mean that a view would return IDs that are not in the database anymore, leading to null entities. -The `CouchbaseTemplate`s `findByView` and `findBySpatialView` methods will remove such stale deleted entities from their result in order to avoid having nulls in the returned collections. -Similarly, `CouchbaseRepository`'s `deleteAll` method will ignore documents that the backing view provided but the SDK remove operation couldn't find. - -If one wants to have stronger consistency, there are three possibilities described in the next sections. - -==== Configure it on a global level - -A global consistency can be defined using the `Consistency` enumeration (eg. `Consistency.READ_YOUR_OWN_WRITE`): - -- in xml, this is done via the `consistency` attribute on ``. -- in javaConfig, this is done by overriding the `getDefaultConsistency()` method. - -By default it is `Consistency.READ_YOUR_OWN_WRITES` (which means consistency is prioritized over speed, especially when a large number of documents has been created recently). - -IMPORTANT: This is **only used in repositories**, either for index-backed methods automatically provided by the repository interface (`findAll()`, `findAll(keys)`, `count()`, `deleteAll()`...) or methods you define in your specific interface using query derivation. - -==== Set consistency for N1QL queries - -For N1QL queries, the `ScanConsistency` can be set using the `@WithConsistency` annotation. -This is independent of whether the query is derived from the method name or specified using `@Query`. -If the annotation is omitted, the global default consistency is used. -Consider the three queries in the following example: - -.Setting the scan consistency on repository methods +.Using a different scan consistency ==== [source,java] ---- -public interface UserRepository extends CrudRepository { +@Repository +public interface AirportRepository extends PagingAndSortingRepository { - List findByLastname(String name); <1> + @Override + @ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) + Iterable findAll(); - @WithConsistency(ScanConsistency.NOT_BOUNDED) - List findByFirstname(String name); <2> - - @WithConsistency(ScanConsistency.NOT_BOUNDED) - @Query("#{#n1ql.selectEntity} WHERE role = 'admin' AND #{#n1ql.filter}") - List findAllAdmins(); <3> } ---- ==== -<1> Since neither `@Query` nor `@WithConsistency` is specified, a N1QL query is derived from the method name and executed with default consistency. -<2> The N1QL query is derived automatically but executed with the given consistency, i.e. `NOT_BOUNDED`. -<3> The N1QL query is specified using `@Query` and is being executed with the given consistency. - -IMPORTANT: The `@WithConsistency` annotation is currently only evaluated **for N1QL queries**. -View queries use the global default consistency. - -==== Provide an implementation - -Provide the implementation and directly use `queryView` and `queryN1QL` methods on the template with a specific consistency (see <>). - -- one can specify the consistency on those via their respective query classes, according to the Couchbase Java SDK documentation. -- for example for views `ViewQuery.stale(Stale.FALSE)` -- for example for N1QL `Query.simple("SELECT * FROM default", QueryParams.build().consistency(ScanConsistency.REQUEST_PLUS));` - -[[couchbase.repository.multibucket]] -== Working with multiple buckets - -The Java Config version allows you to define multiple `Bucket` and `CouchbaseTemplate`, but in order to have different repositories use different underlying buckets/templates, you need to follow these steps: - -* in your `AbstractCouchbaseConfiguration` implementation, override the `configureRepositoryOperationsMapping` method. -* mutate the provided `RepositoryOperationsMapping` as needed (it defaults to mapping everything to the default template). -* configure the mapping by chaining calls to `map`, `mapEntity` and `setDefault`. -** `map` maps a specific repository interface to the `CouchbaseOperations` it should use -** `mapEntity` maps all unmapped repositories of a domain type / entity class to a common `CouchbaseOperations` -** `setDefault` maps all remaining unmapped repositories to a default - `CouchaseOperations` (the default, using `couchbaseTemplate` bean unless modified). - -The idea is that the framework will look for an entry corresponding to the repository's interface when instantiating it. -If none is found it will look at the mapping for the repository's domain type. -Eventually it will fallback to the default setting. -Here is an example: - -.Example of configuring multiple templates and repositories. -==== -[source,java] ----- -@Configuration -@EnableCouchbaseRepositories -public class ConcreteCouchbaseConfig extends AbstractCouchbaseConfig { - - //the default bucket and template must be created, implement abstract methods here to that end - - //we want all User objects to be stored in a second bucket - //let's define the bucket reference... - @Bean - public Bucket userBucket() { - return couchbaseCluster().openBucket("users", ""); - } - - //... then the template (inspired by couchbaseTemplate() method)... - @Bean - public CouchbaseTemplate userTemplate() { - CouchbaseTemplate template = new CouchbaseTemplate( - couchbaseClusterInfo(), //reuse the default bean - userBucket(), //the bucket is non-default - mappingCouchbaseConverter(), translationService() //default beans here as well - ); - template.setDefaultConsistency(getDefaultConsistency()); - return template; - } - - //... then finally make sure all repositories of Users will use it - @Override - public void configureRepositoryOperationsMapping(RepositoryOperationsMapping baseMapping) { - baseMapping //this is already using couchbaseTemplate as default - .mapEntity(User.class, userTemplate()); //every repository dealing with User will be backed by userTemplate() - } -} ----- -==== - -[[couchbase.repository.changing-repository-behaviour]] -== Changing repository behaviour - -Sometimes you don't simply want the repository to create methods for you, but instead you want to tune the base repository's behaviour. -You can either do that for *all* repositories - by changing the _base class_ for them - or just for a single repository - by adding custom implementations for either new or existing methods - (see <> for a generic introduction to these concepts). - -=== Couchbase specifics about changing the base class - -This follows the standard procedure for changing all repositories' base class: - -. Create an generic interface for your base that extends `CouchbaseRepository` (CRUD) or `CouchbasePagingAndSortingRepository`. -Declare any method you want to add to all repositories there. -. Create an implementation (eg. `MyRepositoryImpl`). -This should extend one the concrete base classes (`SimpleCouchbaseRepository` or `N1qlCouchbaseRepository`) and you can also override existing methods from the Spring Data interfaces. -. Declare your repository interfaces as extending `MyRepository` instead of eg. `CRUDRepository` or `CouchbaseRepository`. -. In the `@EnableCouchbaseRepositories` annotation of your configuration, use the `repositoryBaseClass` parameter. - -Here is a complete example that you can find in `RepositoryBaseTest` in the integration tests: - -.Changing repository base class -[source,java] ----- -@NoRepositoryBean <1> -public interface MyRepository extends CouchbaseRepository { <2> - - int sharedCustomMethod(ID id); <3> -} - -public class MyRepositoryImpl - extends N1qlCouchbaseRepository <4> - implements MyRepository { <5> - - public MyRepositoryImpl(CouchbaseEntityInformation metadata, CouchbaseOperations couchbaseOperations) { <6> - super(metadata, couchbaseOperations); - } - - @Override - public int sharedCustomMethod(ID id) { - //... implement common behavior <7> - } -} - -@EnableCouchbaseRepositories(repositoryBaseClass = MyRepositoryImpl.class) <8> -public class MyConfig extends AbstractCouchbaseConfiguration { /** ... */ } ----- -<1> This annotation prevents picking this custom interface as a repository declaration. -<2> The new base interface extends one from Spring Data Couchbase. -<3> This method will be available in all repositories. -<4> Custom base implementation relies on the existing bases... -<5> ...and also implements new interface (so that common methods are exposed). -<6> Constructors that follow the signature of superconstructor will be picked up by the framework. -<7> Custom functionality to be implemented by the user (eg. return string's length). -<8> Weaving it all in by changing the repository base class. - -=== Couchbase specifics about adding methods to a single repository - -Again following the standard procedure for custom repository methods, here is a complete example that you can find in `RepositoryCustomMethodTest` in the integration tests: - -.Adding and overriding methods in a single repository -[source,java] ----- -public interface MyRepositoryCustom { - long customCountItems(); <1> -} - -public interface MyRepository extends CrudRepository, MyRepositoryCustom { } <2> - -public class MyRepositoryImpl implements MyRepositoryCustom { <3> - - @Autowired - RepositoryOperationsMapping templateProvider; <4> - - @Override - public long customCountItems() { - CouchbaseOperations template = templateProvider.resolve(MyRepository.class, Item.class); <5> - - CouchbasePersistentEntity itemPersistenceEntity = (CouchbasePersistentEntity) - template.getConverter() - .getMappingContext() - .getPersistentEntity(MyItem.class); - - CouchbaseEntityInformation itemEntityInformation = - new MappingCouchbaseEntityInformation(itemPersistenceEntity); - - Statement countStatement = N1qlUtils.createCountQueryForEntity( <6> - template.getCouchbaseBucket().name(), - template.getConverter(), - itemEntityInformation); - - ScanConsistency consistency = template.getDefaultConsistency().n1qlConsistency(); <7> - N1qlParams queryParams = N1qlParams.build().consistency(consistency); - N1qlQuery query = N1qlQuery.simple(countStatement, queryParams); - - List countFragments = template.findByN1QLProjection(query, CountFragment.class); <8> - - if (countFragments == null || countFragments.isEmpty()) { - return 0L; - } else { - return countFragments.get(0).count * -1L; <9> - } - } - - public long count() { <10> - return 100; - } -} ----- -<1> This method is to be added with a user-provided implementation for a single repository. -<2> This is the declaration of the customized repository, both a CRUD and exposing the custom interface. -<3> This is the implementation of the custom interface. -<4> The custom implementation doesn't have access to the original base implementation, so use dependency injection to get access to necessary resources. -<5> Here is a couchbase specificity: if you need to use the `CouchbaseTemplate`, be sure to use the one that would be associated with the customized repository or associated entity type. -<6> We use `N1QLUtils` to prepare a complete `N1QL` statement for counting. -It relies on the information above that we got from the correct template. -<7> We want to make sure that the default consistency configured in the associated template is used for this query. -<8> Using `CouchbaseTemplate.findByN1qlProjection`, we execute the count query and store the single aggregation result into a `CountFragment`. -<9> Now we return this count result with a twist: it is negated. -<10> *TIP*: You can actually also change implementation of methods from the `CRUDRepository` interface! - -By storing 3 items using a `MyRepository` instance and calling `count()` then `customCountItems()`, we'd obtain - ----- -100 --3 ----- === DTO Projections