From 9e1f730f667894ef89ed1b437083df51428ff7aa Mon Sep 17 00:00:00 2001 From: Michael Nitschinger Date: Tue, 11 Mar 2014 18:12:27 +0100 Subject: [PATCH] DATACOUCH-74 - Initial Documentation --- src/docbkx/caching.xml | 68 ++++++ src/docbkx/configuration.xml | 141 +++++++++++ src/docbkx/entity.xml | 453 +++++++++++++++++++++++++++++++++++ src/docbkx/index.xml | 55 +++++ src/docbkx/preface.xml | 42 ++++ src/docbkx/repository.xml | 253 +++++++++++++++++++ src/docbkx/template.xml | 38 +++ 7 files changed, 1050 insertions(+) create mode 100644 src/docbkx/caching.xml create mode 100644 src/docbkx/configuration.xml create mode 100644 src/docbkx/entity.xml create mode 100644 src/docbkx/index.xml create mode 100644 src/docbkx/preface.xml create mode 100644 src/docbkx/repository.xml create mode 100644 src/docbkx/template.xml diff --git a/src/docbkx/caching.xml b/src/docbkx/caching.xml new file mode 100644 index 00000000..cfe0d45d --- /dev/null +++ b/src/docbkx/caching.xml @@ -0,0 +1,68 @@ + + + + Caching + + + This chapter describes additional support for caching and @Cacheable. + + +
+ 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: + + + <code>AbstractCouchbaseConfiguration</code> for Caching + 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 + + + + 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. + +
+ +
\ No newline at end of file diff --git a/src/docbkx/configuration.xml b/src/docbkx/configuration.xml new file mode 100644 index 00000000..24ddb701 --- /dev/null +++ b/src/docbkx/configuration.xml @@ -0,0 +1,141 @@ + + + + Installation & Configuration + + + This chapter describes the common installation and configuration steps needed when working with the + library. + + +
+ 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: + + + Including the dependency through maven + + + org.springframework.data + spring-data-couchbase + 1.0.0.RELEASE + + ]]> + + + This will pull in several dependencies, including the underlying Couchbase Java SDK, common Spring + dependencies and also Jackson as the JSON mapping infrastructure. + + You can also grab snapshots from the + spring snapshot repository and milestone releases + from the milestone repository. Here is an example + on how to use the current SNAPSHOT dependency: + + + Using a snapshot version + + + org.springframework.data + spring-data-couchbase + 1.1.0.BUILD-SNAPSHOT + + + + spring-libs-snapshot + Spring Snapshot Repository + http://repo.spring.io/libs-snapshot + + ]]> + + + Once you have all needed dependencies on the classpath, you can start configuring it. Both Java and + XML config are supported. The next sections describe both approaches in detail. +
+ +
+ 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. + + Please make sure to have cglib support in the classpath so that the annotation based configuration works. + + + Extending the <code>AbstractCouchbaseConfiguration</code> + bootstrapHosts() { + return Collections.singletonList("127.0.0.1"); + } + + @Override + protected String getBucketName() { + return "beer-sample"; + } + + @Override + protected String getBucketPassword() { + return ""; + } +} + ]]> + + + All you need to provide is a list of Couchbase nodes to bootstrap into (without any ports, just the IP + address or hostname). Please note that while one host is sufficient in development, it is recommended to + add 3 to 5 bootstrap nodes here. Couchbase will pick up all nodes from the cluster automatically, but it + could be the case that the only node you've provided is experiencing issues while you are starting the + application. + + 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. + + While not immediately obvious, much more things can be customized and overriden as custom beans from + this configuration - we'll touch them in the individual manual sections as needed (for example repositories, + validation and custom converters). +
+ +
+ XML-based Configuration + + The library provides a custom namespace that you can use in your XML configuration: + + + Basic XML configuration + + + + + + ]]> + + + 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. + + 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. +
+ +
\ No newline at end of file diff --git a/src/docbkx/entity.xml b/src/docbkx/entity.xml new file mode 100644 index 00000000..325d9b90 --- /dev/null +++ b/src/docbkx/entity.xml @@ -0,0 +1,453 @@ + + + + Modeling Entities + + + This chapter describes how to model Entities and explains their counterpart representation in Couchbase + Server itself. + + +
+ Documents and Fields + + All entities should be annotated with the @Document annotation. Also, every field in the entity + should be annotated with the @Field annotation. While this is - strictly speaking - optional, it + helps to reduce edge cases and clearly shows the intent and design of the entity. + + There is also a special @Id annotation which needs to be always in place. Best practice is + to also name the property id. Here is a very simple User entity: + + + A simple Document with Fields + + + + Couchbase Server supports automatic expiration for documents. The library implements support for it through + the @Document annotation. You can set a expiry value which translates to the number of + seconds until the document gets removed automatically. If you want to make it expire in 10 seconds after mutation, + set it like @Document(expiry = 10). + + If you want a different representation of the field name inside the document in contrast to the field + name used in your entity, you can set a different name on the @Field annotation. For example if + you want to keep your documents small you can set the firstname field to @Field("fname"). In the + JSON document, you'll see {"fname": ".."} instead of {"firstname": ".."}. + + The @Id annotation needs to be present because every document in Couchbase needs a unique + key. This key needs to be any string with a length of maximum 250 characters. Feel free to use whatever fits + your use case, be it a UUID, an email address or anything else. +
+ +
+ Datatypes and Converters + + The storage format of choice is JSON. It is great, but like many data representations it allows less + datatypes than you could express in Java directly. Therefore, for all non-primitive types some form of + conversion to and from supported types needs to happen. + + For the following entity field types, you don't need to add special handling: + + + Primitive Types + + + + + Java Type + JSON Representation + + + + + string + string + + + boolean + boolean + + + byte + number + + + short + number + + + int + number + + + long + number + + + float + number + + + double + number + + + null + Ignored on write + + + +
+ + Since JSON supports objects ("maps") and lists, Map and List types can be converted + naturally. If they only contain primitive field types from the last paragraph, you don't need to add special + handling too. Here is an example: + + + A Document with Map and List + firstnames; + + @Field + private Map childrenAges; + + public User(String id, List firstnames, Map childrenAges) { + this.id = id; + this.firstnames = firstnames; + this.childrenAges = childrenAges; + } + +} + ]]> + + + Storing a user with some sample data could look like this as a JSON representation: + + + A Document with Map and List - JSON + + + + You don't need to break everything down to primitive types and Lists/Maps all the time. Of course, you can + also compose other objects out of those primitive values. Let's modify the last example so that we want to + store a List of Children: + + + A Document with composed objects + firstnames; + + @Field + private List children; + + public User(String id, List firstnames, List children) { + this.id = id; + this.firstnames = firstnames; + this.children = children; + } + + static class Child { + private String name; + private int age; + + Child(String name, int age) { + this.name = name; + this.age = age; + } + + } + +} + ]]> + + + A populated object can look like: + + + A Document with composed objects - JSON + + + + Most of the time, you also need to store a temporal value like a Date. Since it can't be stored + directly in JSON, a conversion needs to happen. The library implements default converters for Date, + Calendar and JodaTime types (if on the classpath). All of those are represented by default in the + document as a unix timestamp (number). You can always override the default behavior with custom converters as + shown later. Here is an example: + + + A Document with Date and Calendar + + + + A populated object can look like: + + + A Document with Date and Calendar - JSON + + + + + 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): + + + Custom Converters + { + INSTANCE; + + @Override + public Bar convert(Foo source) { + return /* do your conversion here */; + } + +} + +@ReadingConverter +public static enum BarToFooConverter implements Converter { + INSTANCE; + + @Override + public Foo convert(Bar source) { + return /* do your conversion here */; + } + +} + ]]> + + + 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. + + + + +
+ +
+ 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: + + + A Document with optimistic locking. + + + + If you load a document through the template or repository, the version field will be automatically populated + with the current CAS value. It is important to note that you shouldn't access the field or even change it + on your own. Once you save the document back, it will either succeed or fail with a + OptimisticLockingFailureException. If you get such an exception, the further approach depends + on what you want to achieve application wise. You should either retry the complete load-update-write cycle + or propagate the error to the upper layers for proper handling. + +
+ +
+ Validation + + The library supports JSR 303 validation, which is based on annotations directly in your entities. Of course + you can add all kinds of validation in your service layer, but this way its nicely coupled to your actual + entities. + + To make it work, you need to include two additional dependencies. JSR 303 and a library that implements it, + like the one supported by hibernate: + + + Validation dependencies + + javax.validation + validation-api + + + org.hibernate + hibernate-validator + + ]]> + + + Now you need to add two beans to your configuration: + + + Validation beans + + + + Now you can annotate your fields with JSR303 annotations. If a validation on save() fails, + a ConstraintViolationException is thrown. + + + Sample Validation Annotation + + + +
+ +
\ No newline at end of file diff --git a/src/docbkx/index.xml b/src/docbkx/index.xml new file mode 100644 index 00000000..cc0b527c --- /dev/null +++ b/src/docbkx/index.xml @@ -0,0 +1,55 @@ + + + + + Spring Data Couchbase - Reference Documentation + + version; + + + + Michael + + Nitschinger + + + Software Engineer + + Couchbase, Inc. + + + michael.nitschinger@couchbase.com + + + + + Copies of this document may be made for your own use and for + distribution to others, provided that you do not charge any fee for such + copies and further provided that each copy contains this Copyright + Notice, whether distributed in print or electronically. + + + + 2014 + + The original authors. + + + Spring Data Couchbase - Reference Documentation + + + + + + + + Reference Documentation + + + + + + + + \ No newline at end of file diff --git a/src/docbkx/preface.xml b/src/docbkx/preface.xml new file mode 100644 index 00000000..fbde49b2 --- /dev/null +++ b/src/docbkx/preface.xml @@ -0,0 +1,42 @@ + + + + Preface + + + This reference documentation describes the general usage of the Spring Data Couchbase library. + + +
+ Project Information + + + + Version control - git://github.com/spring-projects/spring-data-couchbase.git + + + + Bugtracker - https://jira.springsource.org/browse/DATACOUCH + + + + Release repository - http://repo.spring.io/libs-release + + + + Milestone repository - http://repo.spring.io/libs-milestone + + + + Snapshot repository - http://repo.spring.io/libs-snapshot + + +
+ +
\ No newline at end of file diff --git a/src/docbkx/repository.xml b/src/docbkx/repository.xml new file mode 100644 index 00000000..8f7795bb --- /dev/null +++ b/src/docbkx/repository.xml @@ -0,0 +1,253 @@ + + + + Repositories + + + 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. + + +
+ Configuration + + While support for repositories is always present, you need to enable them in general or for a specific + namespace. 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. + + + Annotation-Based Repository Setup + + + + XML-based configuration is also available: + + + XML-Based Repository Setup + + ]]> + + +
+ +
+ Usage + + In the simplest case, your repository will extend the CrudRepository<T, String>, where + T is the entity that you want to expose. Let's look at a repository for a user: + + + A User repository + { +} + ]]> + + + 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? + + + Exposed methods on the UserRepository + + + + + Method + Description + + + + + User save(User entity) + Save the given entity. + + + Iterable<User> save(Iterable<User> entity) + Save the list of entities. + + + User findOne(String id) + Find a entity by its unique id. + + + boolean exists(String id) + Check if a given entity exists by its unique id. + + + Iterable<User> findAll() (*) + Find all entities by this type in the bucket. + + + Iterable<User> findAll(Iterable<String> ids) + Find all entities by this type and the given list of ids. + + + long count() (*) + Count the number of entities in the bucket. + + + void delete(String id) + Delete the entity by its id. + + + void delete(User entity) + Delete the entity. + + + void delete(Iterable<User> entities) + Delete all given entities. + + + void deleteAll() (*) + 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. + + 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: + + + An extended User repository + { + + List findAllAdmins(); + + List findByFirstname(Query query); +} + + ]]> + + + Since we've came across views now multiple times and the findByFirstname(Query query) exposes + a yet unknown parameter, let's cover that next. +
+ +
+ Backing Views + + 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. + + 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 lower-cased entity name as the design document name. So if your entity is named User, then + the code expects the all view in the user design document. It needs to look like this: + + + The all view map function + + + + 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(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): + + + A custom view map function + + + + 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 adding it we + can customize the query at runtime. Let's write our view for this: + + + A parameterized view map function + + + + 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". + + + Query a repository method with custom params. + users = repo.findByFirstname(query)); + ]]> + + + On all custom finder methods, you can use the @View annotation to both customize the design + document and view name (to override the conventions). + + 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. + +
+ +
\ No newline at end of file diff --git a/src/docbkx/template.xml b/src/docbkx/template.xml new file mode 100644 index 00000000..20438ed9 --- /dev/null +++ b/src/docbkx/template.xml @@ -0,0 +1,38 @@ + + + + Template & direct Operations + + + The template provides lower level access to the underlying database and also serves as the foundation for + repositories. Any time a repository is too high-level for you needs chances are good that the templates will + serve you well. + + +
+ Supported Operations + + The template can be accessed through the couchbaseTemplate bean out of your context. Once + you've got a reference to it, you can run all kinds of operations against it. Other than through a repository, + in a template you need to always specifiy the target entity type which you want to get converted. + + To mutate documents, you'll find save, insert and update methods + exposed. Saving will insert or update the document, insert will fail if it has been created already and update + only works against documents that have already been created. + + Since Couchbase Server has different levels of persistence (by default you'll get a positive response if + it has been acknowledged in the managed cache), you can provide higher durability options through the overloaded + PersistTo and/or ReplicateTo options. The behaviour is part of the Couchbase Java SDK, + please refer to the official documentation for more details. + + Remvoing docouments 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 querView which exposes lower level semantics. + + If you really need low-level semantics, the couchbaseClient bean is also always in scope. +
+ +
\ No newline at end of file