A newer version of this software is available
You are viewing the documentation for an older version of this software. To find the documentation for the current version, visit the Couchbase documentation home page.
This guide provides information for developers who want to use the Couchbase Java SDK to build applications that use Couchbase Server.
Awesome that you want to learn more about Couchbase! This is the right place to start your journey. This chapter will teach you the basics of Couchbase and how to interact with it through the Java Client SDK.
If you haven’t already, download the latest
Couchbase Server 2.0 release (or later) and install it. While following the download
instructions and setup wizard, make sure install the beer-sample
default
bucket. It contains sample data of beers and breweries, which we’ll be using in
our examples here. If you’ve already installed Couchbase Server 2.0 and didn’t
install the beer-sample
bucket (or if you deleted it), just open the Web-UI
and navigate to Settings/Sample Buckets
. Activate the beer-sample
checkbox
and click Create
. In the right hand corner you’ll see a notification box that
will disappear once the bucket is ready to be used.
Here’s a quick outline of what you’ll learn in this chapter:
Create a project in your favorite IDE and set up the dependencies.
Write a simple program to demonstrate connecting to Couchbase and saving some documents.
Write a program to demonstrate using Create, Read, Update, Delete (CRUD) operations on documents in combination with JSON serialization and deserialization.
Explore some of the API methods that will take you further than what you’ve seen previously.
From here on, we’ll assume that you have a Couchbase Server 2.0 release running and the “beer-sample” bucket configured. If you need any help on setting up everything, there is plenty of documentation available:
Using the Couchbase Web Console, for information on using the Couchbase Administrative Console,
Couchbase CLI, for the command line interface,
Couchbase REST API, for creating and managing Couchbase resources.
The TCP/IP port allocation on Windows by default includes a restricted number of ports available for client communication. For more information on this issue, including information on how to adjust the configuration and increase the available ports, see MSDN: Avoiding TCP/IP Port Exhaustion.
In general, there are two options available on how to include the Client SDK in
your project. You can either manually include all dependencies in your
CLASSPATH
or (if you want to make your life easier) use
Maven.
To include the libraries ( jar
files) directly in your project,
download the archive and add
all jar
files to your CLASSPATH
of the system/project. Most IDEs also allow
you add specific jar
files to your project. Make sure to have the following
dependencies in your CLASSPATH
:
couchbase-client-1.1.9.jar
spymemcached-2.9.1.jar
commons-codec-1.5.jar
httpcore-4.1.1.jar
netty-3.5.5.Final.jar
httpcore-nio-4.1.1.jar
jettison-1.1.jar
Previous releases are also available as zip archives as well: * Couchbase Java Client 1.1.8 * Couchbase Java Client 1.1.7 * Couchbase Java Client 1.1.6 * Couchbase Java Client 1.1.5 * Couchbase Java Client 1.1.4 * Couchbase Java Client 1.1.3 * Couchbase Java Client 1.1.2 * Couchbase Java Client 1.1.1 * Couchbase Java Client 1.1.0
If you don’t like to handle dependencies on your own, you can use a build
manager to handle them for you. Couchbase provides a
Maven repository that you can use which includes the
dependencies automatically. The root URL of the repository is located under
http://files.couchbase.com/maven2/.
Depending on the build manager you’re using, the exact syntax to include it may
vary. Here is an example on how to do it in Maven by updating your pom.xml
.
<repositories>
<repository>
<id>couchbase</id>
<name>Couchbase Maven Repository</name>
<layout>default</layout>
<url>http://files.couchbase.com/maven2/</url>
</repository>
</repositories>
<dependency>
<groupId>couchbase</groupId>
<artifactId>couchbase-client</artifactId>
<version>1.1.9</version>
</dependency>
If you are coming from Scala and want to manage your dependencies through
sbt, then you can do it this way (in your
build.sbt
):
resolvers += "Couchbase Maven Repository" at "http://files.couchbase.com/maven2"
libraryDependencies += "couchbase" % "couchbase-client" % "1.1.9"
In this example here we’ll use the NetBeans IDE, but of
course any other will work as well. After installing the IDE, go to File -> New
Project
, select Maven
and Java Application
. Give it a name (like
“examples”) and change the location to a directory where you want to place the
files. We’ll use the com.couchbase
namespace, but you can use your own if you
like (just make sure to change it later in the source files when you copy them).
Now that you’ve created your project, it’s time to add the Couchbase Maven
repository. The easiest way to do this is to go to Window -> Services
, spot
the Maven Repositories
tree, right click on it and click Add Repository
. Use
the following settings (or modify the directly
):
ID: couchbase
Name: Couchbase Maven Repository
Jump back to your new project and right click on Dependencies
, and then Add
Dependency
. For now, we only need to add the Couchbase SDK itself, because the
transitive dependencies will be fetched automatically. Use the following
settings:
Group ID: couchbase
Artifact ID: couchbase-client
Version: 1.1.9
Now all dependencies are in place and we can move forward to our first application with Couchbase!
To follow the tradition of programming tutorials, we’ll start with “Hello Couchbase”. In the first example, we’ll connect to the Cluster, set a simple document, retrieve it and print it out. This first example contains the full sourcecode, but in later example we’ll omit the imports and assume we’re already connected to the cluster.
Listing 1: Hello Couchbase!
package com.couchbase.examples;
import com.couchbase.client.CouchbaseClient;
import java.net.URI;
import java.util.ArrayList;
public class App {
public static void main(String[] args) {
ArrayList<URI> nodes = new ArrayList<URI>();
// Add one or more nodes of your cluster (exchange the IP with yours)
nodes.add(URI.create("http://127.0.0.1:8091/pools"));
// Try to connect to the client
CouchbaseClient client = null;
try {
client = new CouchbaseClient(nodes, "default", "");
} catch (Exception e) {
System.err.println("Error connecting to Couchbase: " + e.getMessage());
System.exit(1);
}
// Set your first document with a key of "hello" and a value of "couchbase!"
int timeout = 0; // 0 means store forever
client.set("hello", timeout, "couchbase!");
// Return the result and cast it to string
String result = (String)client.get("hello");
System.out.println(result);
// Shutdown the client
client.shutdown();
}
}
While this code should be very easy to grasp, there is a lot going on worth a little more discussion:
Connecting: the CouchbaseClient accepts a List of URIs
that point to nodes in
the Cluster. While passing in only one URI
is fine, it is strongly recommended
to add two or three (of course if your cluster has more than one node). It is
important to understand that this list does not have to contain all nodes in the
cluster - you just need to provide a few so that during the initial bootstrap
phase the Client is able to connect to the server. After this has happened, the
Client automatically fetches the cluster configuration and keeps it up to date,
even when the cluster topology changes. This means that you don’t need to change
your application config at all when you need to resize your cluster. Also keep
in mind to use a URI exactly like http://[YOUR-NODE]:8091/pools
(and not just
the IP-Address for example) - this is sometimes called the bootstrap URI.
The next two arguments are for the bucket
and the password
. The bucket is
the container for all your documents. Inside a bucket, a key - the identifier
for a document - must be unique. In production environments, it is recommended
to use a password on a bucket (this can be configured during bucket creation),
but when you are just starting out using the default
bucket without a password
is fine. Note that the beer-sample
bucket also doesn’t have a password, so
just change the bucket name and you’re set.
Set
and get
: these two operations are one of the most fundamental ones. You
can use set
to create or override a document inside your bucket and get
to
read it back afterwards. There are lots of arguments and variations, but if you
just use them as shown in the previous example will get you pretty far. Note
that the get
operation doesn’t care what you read out of the bucket, so you
need to cast it into the format you want (in our case we did store a string, so
it makes sense to convert it back later).
Disconnecting: at the end of the program (or when you shutdown your server
instance), you should use the shutdown
method to prevent the loss of data. If
used without arguments, it will wait until all outstanding operations have been
finished (but no new ones will be accepted). You can also call it with a maximum
waiting time which makes sense if you (potentially) don’t want to wait forever.
By default, the logger is configured to log from INFO
upwards. This means that
by default, a decent amount of helpful information is logged automatically. In
our example above, the logs look like this:
2012-12-03 18:57:45.777 INFO com.couchbase.client.CouchbaseConnection: Added {QA sa=/127.0.0.1:11210, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2012-12-03 18:57:45.788 INFO com.couchbase.client.CouchbaseConnection: Connection state changed for sun.nio.ch.SelectionKeyImpl@76f8968f
2012-12-03 18:57:45.807 INFO com.couchbase.client.ViewConnection: Added localhost to connect queue
2012-12-03 18:57:45.808 INFO com.couchbase.client.CouchbaseClient: viewmode property isn't defined. Setting viewmode to production mode
couchbase!
2012-12-03 18:57:45.925 INFO com.couchbase.client.CouchbaseConnection: Shut down Couchbase client
2012-12-03 18:57:45.929 INFO com.couchbase.client.ViewConnection: Node localhost has no ops in the queue
2012-12-03 18:57:45.929 INFO com.couchbase.client.ViewNode: I/O reactor terminated for localhost
You can see to which nodes the client connects (hopefully every node in the cluster), which viewmode is used (more on that later) and other helpful output. These logs provide vital information when a issue needs to be debugged (be it through the community forums or through paid Couchbase Support).
A document in Couchbase Server consists of a unique key
, a value
and us
stored in a bucket
. A document can be anything, but it is recommended to use
the JSON format. JSON is very convenient for storing structured data with little
overhead, and is also used inside the View engine. This means that if you want
to get most out of Couchbase Server 2.0, use JSON.
The Java SDK doesn’t come with a JSON library bundled and leaves it up to you to
choose your favorite one. If you are not sure which one to use, the GSON
library is a very good choice (and it’s
what we’ll use in later examples). You can download it directly (and include it
in your CLASSPATH
) or get it through Maven.
The following chapter introduces the basic operations that you can use as the fundamental building blocks of your application.
Couchbase Server provides a set of commands to store documents. The commands are very similar to each other and differ only in their meaning on the server-side. These are:
set
: Stores a document in Couchbase Server (identified by its unique key) and
overrides the previous document (if there was one).
add
: Adds a document in Couchbase Server (identified by its unique key) and
fails if there is already a document with the same key stored.
replace
: Replaces a document in Couchbase Server (identified by its unique
key) and fails if there is no document with the given key already in place.
The SDK provides overloaded methods for these operations, but to start out here are the simplest forms:
String key = "mykey";
int timeout = 0;
String doc = "something";
OperationFuture<Boolean> setResult = client.set(key, timeout, doc);
OperationFuture<Boolean> addResult = client.add(key, timeout, doc);
OperationFuture<Boolean> replaceResult = client.replace(key, timeout, doc);
Couchbase Server takes performance very seriously, so all these operations are
non-blocking (asynchronous). They return immediately and are handled in a
different thread behind the scenes. If you want to explicitly wait until the
operation has finished, you can use the get()
method on the returned future.
If you do so, your calling thread will wait until the operation has been
fulfilled by the server and is marked as done
. If something goes wrong, the
return value of the future will be false
and you can use the
future.getStatus().getMessage()
method to see why it failed.
The second param on all these mutation operations is a timeout
. When set to
0
, the document will be stored forever (or until deleted manually). If set to
(for example) 10
, the document will be deleted automatically after 10 seconds.
If you want to use Couchbase Server as a session store, this comes in very handy
(amongst lots of other use cases).
With Couchbase Server 2.0, you have two ways of fetching your documents: either
by the unique key through the get
method, or through Views. Since Views are
more complex, let’s tackle get
first:
Object get = client.get("mykey");
Since Couchbase doesn’t really know what you’ve stored as a document, you get a
Object
back. If you store JSON documents, the actual document will be a
String, so you can safely convert it to a string:
String json = (String) client.get("mykey");
If no document was found, null
is returned. It is important to check for
null
, to prevent NullPointerExceptions
later down the stack.
With Couchbase Server 2.0, the very powerful ability to query your documents through secondary indexes ( Views ) has been added to your toolbelt. This guide gets you started on how to use them through the Java SDK, if you want to learn more please refer to the chapter in the Couchbase Server 2.0 documentation.
Once you created your View in the UI, you can query it from the SDK in three
steps. First, you grab the View definition from the cluster, second you create a
Query
object and third, you query the cluster with both the View
and the
Query
objects. In its simplest form, it looks like this:
// 1: Get the View definition from the cluster
View view = client.getView("beer", "brewery_beers");
// 2: Create the query object
Query query = new Query();
// 3: Query the cluster with the view and query information
ViewResponse result = client.query(view, query);
The getView()
method needs both the name of the Design Document
and the
View
to load up the proper definition from the cluster. This is needed to
determine if there is a view with the given information and also if it contains
a reduce function (or is even a spatial view).
Views can be queried with a large amount of options. All supported options are
available as setter methods on the Query
object. Here are some of them:
setIncludeDocs(boolean)
: Used to define if the complete documents should be
included in the result.
setReduce(boolean)
: Used to enable/disable the reduce function (if there is
one defined on the server).
setLimit(int)
: Limit the number of results that should be returned.
setDescending(boolean)
: Revert the sorting order of the result set.
setStale(Stale)
: Can be used to define the tradeoff between performance and
freshness of the data.
setDebug(boolean)
: Prints out debugging information in the logs.
Now that we have our View information and the Query object in place, we can
issue the query
command, which actually triggers the scatter-gather data
loading process on the Cluster. The resulting information is encapsulated inside
the ViewResponse
object. We can use it to iterate over the results and print
out some details (here is a more complete example which also includes the full
documents and only fetches the first five results):
View view = client.getView("beer", "brewery_beers");
Query query = new Query();
query.setIncludeDocs(true).setLimit(5); // include all docs and limit to 5
ViewResponse result = client.query(view, query);
// Iterate over the result and print the key of each document:
for(ViewRow row : result) {
System.out.println(row.getId());
// The full document (as String) is available through row.getDocument();
}
In the logs, you can see the corresponding document keys automatically sorted (ascending):
21st_amendment_brewery_cafe
21st_amendment_brewery_cafe-21a_ipa
21st_amendment_brewery_cafe-563_stout
21st_amendment_brewery_cafe-amendment_pale_ale
21st_amendment_brewery_cafe-bitter_american
If you want to get rid of a document, you can use the delete
operation:
OperationFuture<Boolean> delete = client.delete("key");
Again, delete
is an asynchronous operation and therefore returns a
OperationFuture
on which you can block through the get()
method. If you try
to delete a document that is not there, the result of the OperationFuture
will
be false
.
One benefit of working with JSON documents is that you can map them (nearly) directly from/to objects in your application. Unlike with Java object serialization, you need to do some more work to convert your object to its appropriate JSON representation. Fortunately, libraries like GSON (as mentioned before) are here to help.
In the following example, we define a simple Beer
class that implicitly
carries the JSON fields as attributes. We can then use GSON to convert from the
Object to JSON and back. The example is a little bit contrived, but it shows the
roundtrip from Object to JSON back to Object pretty well that you can use
throughout your application.
public class App {
// Model the Beer
static class Beer {
String name;
String type;
String category;
float abv;
// Convenience method to generate a key
public String getKey() {
return type + "-" + name;
}
}
public static void main(String args[]) throws Exception {
// Connect
CouchbaseClient client = new CouchbaseClient(
Arrays.asList(URI.create("http://127.0.0.1:8091/pools")),
"beer-sample",
""
);
// Instantiate GSON
Gson gson = new Gson();
// Create a beer
Beer myAle = new Beer();
myAle.name = "cool-ale";
myAle.type ="beer";
myAle.category = "Stouts and Ales";
myAle.abv = 5.2f;
// Convert the beer to JSON
String json = gson.toJson(myAle);
// Store in Couchbase
client.set(myAle.getKey(), 0, json);
// Read it back
String readJson = (String) client.get(myAle.getKey());
// Create a object out of it
Beer sameBeer = gson.fromJson(readJson, Beer.class);
// Now use it like:
System.out.println(sameBeer.category);
// Disconnect
client.shutdown(3, TimeUnit.SECONDS);
System.exit(0);
}
}
You can also use the same pattern when you query a View, and include the
complete docs through setIncludeDocs(true)
. Based on the previous Beer
class, imagine a View that returns all beers stored in the bucket. We could
query the View and immediately create Objects out of them:
View view = client.getView("beer", "only_beers");
Query query = new Query();
query.setIncludeDocs(true);
ViewResponse result = client.query(view, query);
for(ViewRow row : result) {
Beer beer = gson.fromJson(row.getDocument(), Beer.class);
System.out.println(beer.name);
}
The previous chapters introduced Couchbase Server and how to use it through the Java SDK. With this knowledge at hand, you are already able to build powerful applications on top of Couchbase Server 2.0 and Java. The next chapter digs a bit deeper and introduces advanced concepts that can help you along the way.
This chapter introduces some techniques topics that you can use to further extend your Couchbase vocabulary.
If you need to coordinate shared access on documents, Couchbase helps you with
two approaches. Depending on the application you may need to use both of them,
but in general it is better (if feasible) to lean towards CAS
because it
provides the better performance characteristics.
Optimistic Locking with cas()
: Each document has a unique identifier
associated with it (the CAS
value), which changes when the document itself is
mutated. You can fetch the CAS
value for a given key and then use the cas()
operation to update it. The update will only succeed, when the CAS
value is
still the same. This is why it’s called optimistic locking: someone else can
still read and try to update the document, but it will fail once the CAS
value
has changed. Here is a example on how to do it with the Java SDK:
// Reads the document with the CAS value.
String key = "eagle_brewing-golden";
CASValue<Object> beerWithCAS = client.gets(key);
// Updates the document and tries to store it back.
Beer beer = gson.fromJson((String)beerWithCAS.getValue(), Beer.class);
beer.name = "Some other Name";
CASResponse cas = client.cas(key, beerWithCAS.getCas(), gson.toJson(beer));
The CASResponse
is a ENUM which identifies the status of the cas()
operation. If it succeeds, CASResponse.OK
is returned. If CASResponse.EXISTS
is returned, the CAS has changed in the meantime. A common approach is to run
the script shown above (the whole fetch-and-update process) in a loop until the
operation did succeed. Depending on your application, you may need to do
conflict management as well.
Note that this also means that all your application need to follow the same code
path (cooperative locking). If you use set()
somewhere else in the code on the
same document, it will work even if the CAS
itself is out of date (that’s
because the normal set()
method doesn’t care about those values at all). Of
course, the CAS
itself changes then and the cas()
operation would fail
afterwards.
Pessimistic Locking with getAndLock()
: If you want to lock a document
completely (or an object graph), you can use the getAndLock()
method. Other
threads can still run get()
queries against the document, but set()
operations without a CAS
will fail.
// Get with Lock
String key = "eagle_brewing-golden";
CASValue<Object> beer = client.getAndLock(key, 20);
String beerJson = (String) beer.getValue();
// Try to set without the lock
OperationFuture<Boolean> result = client.set(key, 0, beerJson);
System.out.println(result.get()); // Prints false
// Try to set with the CAS aquired
CASResponse cas = client.cas(key, beer.getCas(), beerJson);
System.out.println(cas); // Prints OK
Once you update the document, the lock will be released. There is also the
unlock()
method available through which you can unlock the document. You are
required to define how long the lock should be active (the maximum is 30
seconds).
By default, the OperationFutures
return when Couchbase Server has accepted the
command and stored it in memory (disk persistence and replication is handled
asynchronously by the cluster). That’s one of the reason why it’s so fast. For
most use-cases, that’s the behavior that you need. Sometimes though, you want to
trade in performance for data-safety and wait until the document has been saved
to disk and/or replicated to other hosts.
The Java SDK provides overloaded commands for all necessary operations (that is
set()
, add()
, replace()
, cas()
and delete()
). They all accept a
combindation of PersistTo
and ReplicateTo
enums, which define on how many
other servers you want it to persist/replicate. Here is an example on how to
make sure that the document has been persisted on its master node, but also
replicated to at least one of its replicas.
OperationFuture<Boolean> oper = client.set(
"important",
0,
"document",
PersistTo.MASTER,
ReplicateTo.ONE
);
Boolean success = oper.get();
if(success == false) {
System.err.println(oper.getStatus().getMessage());
}
It is very important to understand that the persistence checks are separate from
the actual operation. This means that when the OperationFuture
returns false,
it can be the case that the operation itself succeeded. It is up to the
application layer to determine what went wrong and what to do afterwards. This
may seem problematic at first, but imagine the following scenario: because of a
power outage in the datacenter, the nodes with the replica copies for the key
went down. Now the persistence constraint will fail temporarily, but you can
still serve your application because the main node is still available. If you
need different behavior, you can add a “rollback” wrapper around it and delete
the saved document if persistence problems arise.
Most of the setters you can use on the Query
object accept Booleans or Enums.
Some of them accept strings as well, which means the value given would be
encoded and sent over the wire. The problem is that Couchbase Server expects
valid JSON strings for some of them, which means you need to take care of proper
JSON handling yourself. To make your life easier, all of these commands are
overloaded and accept instances of the ComplexKey
class in addition to plain
Strings. These ComplexKey
instances accept any kind of Java objects and handle
the proper JSON translation for you.
To create ComplexKeys
, you can use the of()
factory method like this as it
accepts a variable amount of java objects and/or types:
Query query = new Query();
// Generates valid ["Hello","World",5.12] JSON on the wire.
ComplexKey rangeStart = ComplexKey.of("Hello", "World", 5.12);
// Pass it in like this
query.setRangeStart(rangeStart);
For more information about using views for indexing and querying from Couchbase Server, here are some useful resources:
General Information: Couchbase Server Manual: Views and Indexes.
Sample Patterns: to see examples and patterns you can use for views, see Couchbase Views, Sample Patterns.
Timestamp Pattern: many developers frequently ask about extracting information based on date or time. To find out more, see Couchbase Views, Sample Patterns.
You should now be well equipped to start exploring Couchbase Server on your own. If you want to dig deeper and see a full-fledged application on top of Couchbase Server 2.0, head over to the Web Application Tutorial. Also, the server documentation and the developer documentation provide useful information for your day-to-day work with Couchbase. Finally, the API docs of the Java SDK can be found here. And JavaDoc is also available.
In this chapter we will build on the foundations introduced in the Getting
Started
guide and build a full-blown web application on top of it. Make sure to have the
beer-sample
bucket around, because the application will allow you to display
and manage beers and breweries.
The full sourcecode is available through couchbaselabs on GitHub. Note that the sample application over there provides more content than described in this tutorial, but it should be easy for you to poke around if you read this tutorial here.
If you want to get up and running really quickly, here is how to get it done using Jetty. Note that this short guide assumes you’re running MacOS or Linux. If you’re running windows, you need to modify the paths accordingly. Also, make sure to have at least maven installed.
Download Couchbase Server 2.0 (or later) and install it. Make sure to install the beer-sample dataset when you run the wizard, because the tutorial application will work with it.
Add the following views (and design documents) to the beer-sample
bucket (and
publish them to production afterwards):
Design document name is beer
and view name is by_name
.
function (doc, meta) {
if(doc.type && doc.type == "beer") {
emit(doc.name, null);
}
}
Design document name is brewery
and view name is by_name
.
function (doc, meta) {
if(doc.type && doc.type == "brewery") {
emit(doc.name, null);
}
}
Clone the repository from
GitHub and cd
into the
directory:
$ git clone git://github.com/couchbaselabs/beersample-java.git
Cloning into ‘beersample-java’…
remote: Counting objects: 153, done.
remote: Compressing objects: 100% (92/92), done.
remote: Total 153 (delta 51), reused 124 (delta 22)
Receiving objects: 100% (153/153), 81.97 KiB | 120 KiB/s, done.
Resolving deltas: 100% (51/51), done.
$ cd beersample-java
Finally, use maven to run the application inside the Jetty container:
$ mvn jetty:run
…. snip ….
Dec 17, 2012 1:50:16 PM com.couchbase.beersample.ConnectionManager contextInitialized
INFO: Connecting to Couchbase Cluster
2012-12-17 13:50:16.621 INFO com.couchbase.client.CouchbaseConnection: Added {QA sa=/127.0.0.1:11210, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2012-12-17 13:50:16.624 INFO com.couchbase.client.CouchbaseConnection: Connection state changed for sun.nio.ch.SelectionKeyImpl@2e2a730e
2012-12-17 13:50:16.635 WARN net.spy.memcached.auth.AuthThreadMonitor: Incomplete authentication interrupted for node {QA sa=localhost/127.0.0.1:11210, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=8}
2012-12-17 13:50:16.662 WARN net.spy.memcached.auth.AuthThread: Authentication failed to localhost/127.0.0.1:11210
2012-12-17 13:50:16.662 INFO net.spy.memcached.protocol.binary.BinaryMemcachedNodeImpl: Removing cancelled operation: SASL auth operation
2012-12-17 13:50:16.664 INFO net.spy.memcached.auth.AuthThread: Authenticated to localhost/127.0.0.1:11210
2012-12-17 13:50:16.666 INFO com.couchbase.client.ViewConnection: Added localhost to connect queue
2012-12-17 13:50:16.667 INFO com.couchbase.client.CouchbaseClient: viewmode property isn’t defined. Setting viewmode to production mode
2012-12-17 13:50:16.866:INFO::Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server
Now, navigate to http://localhost:8080/welcome and enjoy the application.
This tutorial uses Servlets and JSPs in combination with Couchbase Server 2.0 to
display and manage beers and breweries found in the beer-sample
dataset. The
easiest way is to develop it with your IDE (like Eclipse or NetBeans) and then
publish it automatically to your application server (Apache Tomcat or GlassFish)
through a war
archive. The code used here is designed to be as portable as
possible, but it may be the case that you need to tweak one or two things if you
have a slightly different version or a customized setup.
Inside your IDE, go ahead and create a new Web Project
, either with or without
Maven support. The getting started guide provides information on how to include
the Couchbase SDK and all the needed dependencies in your project (the same
concepts apply here). Also make sure to include Google GSON or your favorite
JSON library as well. This turorial uses a directory structure like this:
|-target
|-src
|---main
|-----java
|-------com
|---------couchbase
|-----------beersample
|-----resources
|-----webapp
|-------WEB-INF
|---------beers
|---------breweries
|---------maps
|---------tags
|---------welcome
|-------css
|-------js
If you’re using Maven, then you’ll also have a pom.xml
in the root directory.
Here is a sampe pom.xml
for your reference (the full source is available from
the repository mentioned at the beginning of the tutorial):
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.couchbase</groupId>
<artifactId>beersample-java</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>beersample-java</name>
<repositories>
<repository>
<id>couchbase</id>
<name>Couchbase Maven Repository</name>
<layout>default</layout>
<url>http://files.couchbase.com/maven2/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>couchbase</groupId>
<artifactId>couchbase-client</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
In order to make the application look pretty, we’re incorporating jQuery and
Twitter Bootstrap. You can either download the libraries and put it in their
appropriate css and js directories (under webapp
), or clone the project
repository and use it from there. Either way, make sure to have the following
files in place:
css/bootstrap.min.css (the minified twitter bootstrap library)
css/bootstrap-responsive.min.css (the minified responsive layout classes from bootstrap)
From here on, you should have a barebones web application configured that has
all the dependencies included. We’ll now move on and configure the beer-sample
bucket the way we need it.
The beer-sample
bucket comes with a small set of views already predefined, but
to make our application function correctly we need some more. This is also a
very good chance to explore the view management possibilities inside the Web-UI.
Since we want to list beers and breweries by their name, we need to define one
view for each. Head over to the Web-UI and click on the Views
menu. Select
beer-sample
from the dropdown list to switch to the correct bucket. Now click
on Development Views
and then Create Development View
to define your first
view. You need to give it the name of both the design document and the actual
view. Insert the following names:
Design Document Name: _design/dev_beer
View Name: by_name
The next step is to define the map
and (optional) reduce
functions. In our
examples, we won’t use the reduce
functions at all but you can play around and
see what happens. Insert the following map
function (that’s JavaScript) and
click Save
.
function (doc, meta) {
if(doc.type && doc.type == "beer") {
emit(doc.name, null);
}
}
Every map
function takes the full document ( doc
) and its associated
metadata ( meta
) as the arguments. You are then free to inspect this data and
emit
a result when you want to have it in your index. In our case we emit the
name of the beer ( doc.name
) when the document both has a type field and the
type is beer
. We don’t need to emit a value - that’s we we are using null
here. It’s always advisable to keep the index as small as possible. Resist the
urge to include the full document through emit(meta.id, doc)
, because it will
increase the size of your view indexes. If you need to access the full document
(or large parts), then use the setIncludeDocs(true)
directive which will do a
get()
call against the document ID in the background. The resulting retrieval
of the document may be slightly out of sync with your view, but it will be fast
and efficient.
Now we need to do (nearly) the same for our breweries. Since you already know how to do this, here is all the information you need to create it:
Design Document Name: _design/dev_brewery
View Name: by_name
Map Function:
function (doc, meta) {
if(doc.type && doc.type == "brewery") {
emit(doc.name, null);
}
}
The final step that you need to do is to push the design documents in
production. While the design documents are in development
, the index is only
applied on the local node. Since we want to have the index on the whole dataset,
click the Publish
button on both design documents (and accept any info popup
that warns you from overriding the old one).
For more information about using views for indexing and querying from Couchbase Server, here are some useful resources:
General Information: Couchbase Server Manual: Views and Indexes.
Sample Patterns: to see examples and patterns you can use for views, see Couchbase Views, Sample Patterns.
Timestamp Pattern: many developers frequently ask about extracting information based on date or time. To find out more, see Couchbase Views, Sample Patterns.
To tell the application server where and how the incoming HTTP requests should
be routed, we need to define a web.xml
inside the WEB-INF
directory.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<listener>
<listener-class>com.couchbase.beersample.ConnectionManager</listener-class>
</listener>
<servlet>
<servlet-name>WelcomeServlet</servlet-name>
<servlet-class>com.couchbase.beersample.WelcomeServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>BreweryServlet</servlet-name>
<servlet-class>com.couchbase.beersample.BreweryServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>BeerServlet</servlet-name>
<servlet-class>com.couchbase.beersample.BeerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>WelcomeServlet</servlet-name>
<url-pattern>/welcome</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>BreweryServlet</servlet-name>
<url-pattern>/breweries/*</url-pattern>
<url-pattern>/breweries</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>BeerServlet</servlet-name>
<url-pattern>/beers/*</url-pattern>
<url-pattern>/beers</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>welcome</welcome-file>
</welcome-file-list>
</web-app>
Don’t try to run this yet, because none of these classes are already
implemented. The listener
directive references the ConnectionMananger
class,
which we’ll implement to manage the connection instance to our Couchbase
cluster. The servlet
directives define our servlet classes that we’re going to
use (three of them) and the following servlet-mapping
directives map HTTP URLs
to them. The final welcome-file-list
directive is used to tell the application
server where to route the root URL ( "/"
).
For now, comment out all servlet
, servlet-mapping
and welcome-file-list
directives, because the application server will complain if they’re not
implemented. When you implement the appropriate servlets, remove the comments
accordingly (if you don’t know how comment inside an XML file, use the <!--
and -->
characters). If you plan to add your own servlets, don’t forget to add
and map them inside the web.xml
properly!
The first class we’re going to implement is the ConnectionManager
in the
src/main/java/com/couchbase/beersample
directory. This is a
ServletContextListener
which starts the CouchbaseClient
on startup and
closes the connection when the application is shut down. Here is the full class,
the discussion follows afterwards (the comments and imports have been removed to
shorten the listing a bit).
package com.couchbase.beersample;
public class ConnectionManager implements ServletContextListener {
private static CouchbaseClient client;
private static final Logger logger = Logger.getLogger(
ConnectionManager.class.getName());
@Override
public void contextInitialized(ServletContextEvent sce) {
logger.log(Level.INFO, "Connecting to Couchbase Cluster");
ArrayList<URI> nodes = new ArrayList<URI>();
nodes.add(URI.create("http://127.0.0.1:8091/pools"));
try {
client = new CouchbaseClient(nodes, "beer-sample", "");
} catch (IOException ex) {
logger.log(Level.SEVERE, ex.getMessage());
}
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
logger.log(Level.INFO, "Disconnecting from Couchbase Cluster");
client.shutdown();
}
public static CouchbaseClient getInstance() {
return client;
}
}
The contextInitialized
and the contextDestroyed
method are called on startup
and shutdown. When the application starts, the CouchbaseClient
is initialized
with the list of nodes, the bucket name and the password (empty). Note that in a
production deployment, you want to fetch these environment-dependent settings
from a config file. The getInstance()
method will be called from the servlets
to obtain the CouchbaseClient
instance.
When you publish your application, you should see in the server logs that Couchbase is correctly connecting to the bucket. If an exception is raised during this phase, it means that your settings are wrong or you don’t have no Couchbase Server running at the given nodes.
INFO: Connecting to Couchbase Cluster
SEVERE: 2012-12-05 14:39:00.419 INFO com.couchbase.client.CouchbaseConnection: Added {QA sa=/127.0.0.1:11210, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
SEVERE: 2012-12-05 14:39:00.426 INFO com.couchbase.client.CouchbaseConnection: Connection state changed for sun.nio.ch.SelectionKeyImpl@1b554a4
SEVERE: 2012-12-05 14:39:00.458 INFO net.spy.memcached.auth.AuthThread: Authenticated to localhost/127.0.0.1:11210
SEVERE: 2012-12-05 14:39:00.487 INFO com.couchbase.client.ViewConnection: Added localhost to connect queue
SEVERE: 2012-12-05 14:39:00.489 INFO com.couchbase.client.CouchbaseClient: viewmode property isn't defined. Setting viewmode to production mode
INFO: WEB0671: Loading application [com.couchbase_beersample-java_war_1.0-SNAPSHOT] at [/]
INFO: com.couchbase_beersample-java_war_1.0-SNAPSHOT was successfully deployed in 760 milliseconds.
The first servlet that we’re going to implement is the WelcomeServlet
, so go
ahead and remove the appropriate comments inside the web.xml
file (don’t
forget to enable the welcome-file-list
as well). When the user visits the
application, we’ll show him a nice greeting and give him all available options
to choose.
Since there is no Couchbase interaction involved, we just tell it to render the JSP template (imports and comments removed).
package com.couchbase.beersample;
public class WelcomeServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/welcome/index.jsp")
.forward(request, response);
}
}
The index.jsp uses formatting-magic from twiter bootstrap to look pretty. Aside from that, it shows a nice greeting and links to the servlets who provide the actual functionality.
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<t:layout>
<jsp:body>
<div class="span6">
<div class="span12">
<h4>Browse all Beers</h4>
<a href="/beers" class="btn btn-warning">Show me all beers</a>
<hr />
</div>
<div class="span12">
<h4>Browse all Breweries</h4>
<a href="/breweries" class="btn btn-info">Take me to the breweries</a>
</div>
</div>
<div class="span6">
<div class="span12">
<h4>About this App</h4>
<p>Welcome to Couchbase!</p>
<p>This application helps you to get started on application
development with Couchbase. It shows how to create, update and
delete documents and how to work with JSON documents.</p>
<p>The official tutorial can be found
<a href="http://www.couchbase.com/docs/couchbase-sdk-java-1.1/tutorial.html">here</a>!</p>
</div>
</div>
</jsp:body>
</t:layout>
There is one more “unusual” thing here: it uses taglibs, which allow us to use
the same layout for all pages. Since haven’t created it yet, it’s time for it
now. Create a layout.tag
file in the /WEB-INF/tags
directory that looks like
this:
<%@tag description="Page Layout" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Couchbase Java Beer-Sample</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="The Couchbase Java Beer-Sample App">
<meta name="author" content="Couchbase, Inc. 2012">
<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/beersample.css" rel="stylesheet">
<link href="/css/bootstrap-responsive.min.css" rel="stylesheet">
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<div class="container-narrow">
<div class="masthead">
<ul class="nav nav-pills pull-right">
<li><a href="/welcome">Home</a></li>
<li><a href="/beers">Beers</a></li>
<li><a href="/breweries">Breweries</a></li>
</ul>
<h2 class="muted">Couchbase Beer-Sample</h2>
</div>
<hr>
<div class="row-fluid">
<div class="span12">
<jsp:doBody/>
</div>
</div>
<hr>
<div class="footer">
<p>© Couchbase, Inc. 2012</p>
</div>
</div>
<script src="/js/jquery.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/beersample.js"></script>
</body>
</html>
Again, nothing fancy here. We just need it in place to make everything look pretty afterwards. When you now deploy your application, you should see through the logs that it is connecting to the Couchbase cluster and from the browser a nice web page greeting you.
Now we’re getting to the real meat of the tutorial. First, uncomment the
BeerServlet
and its corresponding tags inside the web.xml
. We’ll make use of
a view to list all beers and make them easily searchable. We’ll also provide a
form to create and/or edit beers and finally delete them.
Here is the barebones structure of our BeerServlet
, which will be filled with
live data soon (again, comments and imports are removed).
package com.couchbase.beersample;
public class BeerServlet extends HttpServlet {
final CouchbaseClient client = ConnectionManager.getInstance();
final Gson gson = new Gson();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
if(request.getPathInfo() == null) {
handleIndex(request, response);
} else if(request.getPathInfo().startsWith("/show")) {
handleShow(request, response);
} else if(request.getPathInfo().startsWith("/delete")) {
handleDelete(request, response);
} else if(request.getPathInfo().startsWith("/edit")) {
handleEdit(request, response);
} else if(request.getPathInfo().startsWith("/search")) {
handleSearch(request, response);
}
} catch (InterruptedException ex) {
Logger.getLogger(BeerServlet.class.getName()).log(
Level.SEVERE, null, ex);
} catch (ExecutionException ex) {
Logger.getLogger(BeerServlet.class.getName()).log(
Level.SEVERE, null, ex);
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
}
private void handleIndex(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
}
private void handleShow(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
}
private void handleDelete(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException,
InterruptedException,
ExecutionException {
}
private void handleEdit(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
}
private void handleSearch(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
}
}
Since our web.xml
uses wildcards ( *
) to route every /beer
-related to
this servlet, we need to inspect the path through getPathInfo()
and dispatch
the request to a helper method that does the actual work. The doPost()
method
will be used to analyze and store the results of the web-form to edit and create
beers (since the form is sent through a POST request).
The first functionality we’ll implement is to list the top 20 beers in a table.
We can use the beer/by_name
view we’ve created at the beginning to get a
sorted list of all beers. The following code belongs to the handleIndex
method:
// Fetch the View
View view = client.getView("beer", "by_name");
// Set up the Query object
Query query = new Query();
// We the full documents and only the top 20
query.setIncludeDocs(true).setLimit(20);
// Query the Cluster
ViewResponse result = client.query(view, query);
// This ArrayList will contain all found beers
ArrayList<HashMap<String, String>> beers = new ArrayList<HashMap<String, String>>();
// Iterate over the found documents
for(ViewRow row : result) {
// Use Google GSON to parse the JSON into a HashMap
HashMap<String, String> parsedDoc = gson.fromJson((String)row.getDocument(), HashMap.class);
// Create a HashMap which will be stored in the beers list.
HashMap<String, String> beer = new HashMap<String, String>();
beer.put("id", row.getId());
beer.put("name", parsedDoc.get("name"));
beer.put("brewery", parsedDoc.get("brewery_id"));
beers.add(beer);
}
// Pass all found beers to the JSP layer
request.setAttribute("beers", beers);
// Render the index.jsp template
request.getRequestDispatcher("/WEB-INF/beers/index.jsp")
.forward(request, response);
The index action queries the view, parses the results with GSON into a HashMap
and eventually forwards the ArrayList
to the JSP layer. We’ll now implement
the index.jsp
template which will iterate over the ArrayList
and print it
out in a nicely-formatted table:
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<t:layout>
<jsp:body>
<h3>Browse Beers</h3>
<form class="navbar-search pull-left">
<input id="beer-search" type="text" class="search-query" placeholder="Search for Beers">
</form>
<table id="beer-table" class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Brewery</th>
<th></th>
</tr>
</thead>
<tbody>
<c:forEach items="${beers}" var="beer">
<tr>
<td><a href="/beers/show/${beer.id}">${beer.name}</a></td>
<td><a href="/breweries/show/${beer.brewery}">To Brewery</a></td>
<td>
<a class="btn btn-small btn-warning" href="/beers/edit/${beer.id}">Edit</a>
<a class="btn btn-small btn-danger" href="/beers/delete/${beer.id}">Delete</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</jsp:body>
</t:layout>
We’re using JSP tags to iterate
over the beers and use their properties ( name
and id
) to fill the rows in
the table. On the website you should now see a table with a list of beers with
Edit
and Delete
buttons on the right. There is also a link to the associated
brewery that you can click on. Let’s implement the delete action next, since its
very easy to do with Couchbase:
private void handleDelete(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException, InterruptedException, ExecutionException {
// Split the Request-Path and get the Beer ID out of it
String beerId = request.getPathInfo().split("/")[2];
// Try to delete the document and store the OperationFuture
OperationFuture<Boolean> delete = client.delete(beerId);
// If the Future succeeded (returned true), redirect to /beers
if(delete.get()) {
response.sendRedirect("/beers");
}
}
The delete method deletes a document from the cluster based on the given
document key. Here, we wait on the OperationFuture
to return (through the
get()
method) and if the delete was successful (when true
is returned), we
redirect to the index action.
Now that we can delete a document, it makes sense to also be able to edit it.
The edit action is very similar to the delete action, but it reads the document
based on the given ID
instead of deleting it. We also need to parse the String
representation of the JSON document into a Java structure, so we can use it in
the template. We again make use of the excellent Google GSON library to handle
this for us.
private void handleEdit(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// Extract the Beer ID from the URL
String[] beerId = request.getPathInfo().split("/");
// If there is a Beer ID
if(beerId.length > 2) {
// Read the Document (as a JSON string)
String document = (String) client.get(beerId[2]);
HashMap<String, String> beer = null;
if(document != null) {
// Convert the String into a HashMap
beer = gson.fromJson(document, HashMap.class);
beer.put("id", beerId[2]);
// Forward the beer to the view
request.setAttribute("beer", beer);
}
request.setAttribute("title", "Modify Beer \"" + beer.get("name") + "\"");
} else {
request.setAttribute("title", "Create a new beer");
}
request.getRequestDispatcher("/WEB-INF/beers/edit.jsp").forward(request, response);
}
If the document could be successfully loaded, it gets parsed into a HashMap
and then forwarded to the edit.jsp template. Also, we define a title variable
that is used inside the template to determine if we want to edit a document or
create a new one (that is when no Beer ID passed to the edit method). Here is
the corresponding edit.jsp template:
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<t:layout>
<jsp:body>
<h3>${title}</h3>
<form method="post" action="/beers/edit/${beer.id}">
<fieldset>
<legend>General Info</legend>
<div class="span12">
<div class="span6">
<label>Name</label>
<input type="text" name="beer_name" placeholder="The name of the beer." value="${beer.name}">
<label>Description</label>
<input type="text" name="beer_description" placeholder="A short description." value="${beer.description}">
</div>
<div class="span6">
<label>Style</label>
<input type="text" name="beer_style" placeholder="Bitter? Sweet? Hoppy?" value="${beer.style}">
<label>Category</label>
<input type="text" name="beer_category" placeholder="Ale? Stout? Lager?" value="${beer.category}">
</div>
</div>
</fieldset>
<fieldset>
<legend>Details</legend>
<div class="span12">
<div class="span6">
<label>Alcohol (ABV)</label>
<input type="text" name="beer_abv" placeholder="The beer's ABV" value="${beer.abv}">
<label>Biterness (IBU)</label>
<input type="text" name="beer_ibu" placeholder="The beer's IBU" value="${beer.ibu}">
</div>
<div class="span6">
<label>Beer Color (SRM)</label>
<input type="text" name="beer_srm" placeholder="The beer's SRM" value="${beer.srm}">
<label>Universal Product Code (UPC)</label>
<input type="text" name="beer_upc" placeholder="The beer's UPC" value="${beer.upc}">
</div>
</div>
</fieldset>
<fieldset>
<legend>Brewery</legend>
<div class="span12">
<div class="span6">
<label>Brewery</label>
<input type="text" name="beer_brewery_id" placeholder="The brewery" value="${beer.brewery_id}">
</div>
</div>
</fieldset>
<div class="form-actions">
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</form>
</jsp:body>
</t:layout>
It’s a little bit longer, but just because we have lots of fields on our beer documents. Note how the beer attributes are used inside the value attributes of the HTML input fields. The unique ID is also used in the form method to dispatch it to the correct URL on submit.
The last thing we need to implement to make the form submission work is the
actual form parsing and storing itself. Since the form submission happens
through a POST request, we need to implement the doPost()
method on our
servlet.
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Parse the Beer ID
String beerId = request.getPathInfo().split("/")[2];
HashMap<String, String> beer = beer = new HashMap<String, String>();
Enumeration<String> params = request.getParameterNames();
// Iterate over all POST params
while(params.hasMoreElements()) {
String key = params.nextElement();
if(!key.startsWith("beer_")) {
continue;
}
String value = request.getParameter(key);
// Store them in a HashMap with key and value
beer.put(key.substring(5), value);
}
// Add two more fields
beer.put("type", "beer");
beer.put("updated", new Date().toString());
// Set (add or override) the document (converted to JSON with GSON)
client.set(beerId, 0, gson.toJson(beer));
// Redirect to the show page
response.sendRedirect("/beers/show/" + beerId);
}
The code iterates over all POST fields and stores them in a HashMap
. We then
use the set command to store the Document inside the cluster and use Google GSON
to translate out HashMap
to a JSON string. In this case, we could also wait
for a OperationFuture
response and for example return an error if the set
failed.
The last line redirects to a show method, which just shows all fields of the document. Since the patterns are the same as before, here is the show method without any further ado:
private void handleShow(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
// Extract the Beer ID
String beerId = request.getPathInfo().split("/")[2];
String document = (String) client.get(beerId);
if(document != null) {
// Parse the JSON and set it for the template if a document was found
HashMap<String, String> beer = gson.fromJson(document, HashMap.class);
request.setAttribute("beer", beer);
}
// render the show.jsp template
request.getRequestDispatcher("/WEB-INF/beers/show.jsp")
.forward(request, response);
}
The ID is again extracted and if a document is found (get returns null when it can’t find a document for the given ID), it gets parsed into a HashMap and forwarded to the show.jsp template. The templat then just prints out all keys and values in a table:
<%@taglib prefix="t" tagdir="/WEB-INF/tags" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<t:layout>
<jsp:body>
<h3>Show Details for Beer "${beer.name}"</h3>
<table class="table table-striped">
<tbody>
<c:forEach items="${beer}" var="item">
<tr>
<td><strong>${item.key}</strong></td>
<td>${item.value}</td>
</tr>
</c:forEach>
</tbody>
</table>
</jsp:body>
</t:layout>
In the index.jsp template, you may have noticed the search box at the top. We can use to dynamically filter our table based on the user input. We’ll use nearly the same code for it as in the index method, aside from the fact that we make use of range queries to define a beginning and end to search for.
Before we implement the actual Java method, we need to put the following snippet
inside the js/beersample.js
file (if you haven’t already at the beginning of
the tutorial) to listen on searchbox changes and update the table with the
resulting JSON (which will be returned from the search method):
$("#beer-search").keyup(function() {
var content = $("#beer-search").val();
if(content.length >= 0) {
$.getJSON("/beers/search", {"value": content}, function(data) {
$("#beer-table tbody tr").remove();
for(var i=0;i<data.length;i++) {
var html = "<tr>";
html += "<td><a href=\"/beers/show/"+data[i].id+"\">"+data[i].name+"</a></td>";
html += "<td><a href=\"/breweries/show/"+data[i].brewery+"\">To Brewery</a></td>";
html += "<td>";
html += "<a class=\"btn btn-small btn-warning\" href=\"/beers/edit/"+data[i].id+"\">Edit</a>\n";
html += "<a class=\"btn btn-small btn-danger\" href=\"/beers/delete/"+data[i].id+"\">Delete</a>";
html += "</td>";
html += "</tr>";
$("#beer-table tbody").append(html);
}
});
}
});
The code waits for keyup events on the search field and if they happen does a AJAX query to the search method on the servlet. The servlet computes the result and sends it back as JSON. The JavaScript then clears the table, iterates over the result and creates new rows. The search method looks like this:
private void handleSearch(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
// Exctract the searched value
String startKey = request.getParameter("value").toLowerCase();
// Prepare a query against the by_name view
View view = client.getView("beer", "by_name");
Query query = new Query();
// Define the query params
query.setIncludeDocs(true) // include the full documents
.setLimit(20) // only show 20 results
.setRangeStart(ComplexKey.of(startKey)) // Start the search at the given search value
.setRangeEnd(ComplexKey.of(startKey + "\uefff")); // End the search at the given search plus the unicode "end"
// Query the view
ViewResponse result = client.query(view, query);
ArrayList<HashMap<String, String>> beers = new ArrayList<HashMap<String, String>>();
// Iterate over the results
for(ViewRow row : result) {
// Parse the Document to a HashMap
HashMap<String, String> parsedDoc = gson.fromJson((String)row.getDocument(), HashMap.class);
// Create a new Beer out of it
HashMap<String, String> beer = new HashMap<String, String>();
beer.put("id", row.getId());
beer.put("name", parsedDoc.get("name"));
beer.put("brewery", parsedDoc.get("brewery_id"));
beers.add(beer);
}
// Return a JSON representation of all Beers
response.setContentType("application/json");
PrintWriter out = response.getWriter();
out.print(gson.toJson(beers));
out.flush();
}
You can use the setRangeStart()
and setRangeEnd()
methods to define which
key range from the index should be returned. If we’ve just provded the start
range key, then we’d get all documents starting from our search value. Since we
want only those beginning with the search value, we can use the special
"\uefff"
UTF-8 character at the end which means “end here”. You need to get
used to it in the first place, but its very fast and efficient when accessing
the view.
The tutorial presented an easy approach to start a web application with Couchbase Server 2.0 as the underlying data source. If you want to dig a little bit deeper, the full sourcecode on couchbaselabs on GitHub has more servlets and code to learn from. This may be extended and updated from time to time.
Of course, this is only the starting point for Couchbase, but together with the Getting Started Guide, you should now be well equipped to start exploring Couchbase Server on your own. Have fun working with Couchbase!
The Client libraries provides an interface to both Couchbase and Memcached
clients using a consistent interface. The interface between your Java
application and your Couchbase or Memcached servers is provided through the
instantiation of a single object class, CouchbaseClient
.
Creating a new object based on this class opens the connection to each configured server and handles all the communication with the server(s) when setting, retrieving and updating values. A number of different methods are available for creating the object specifying the connection address and methods.
You can connect to specific Couchbase buckets (in place of using the default
bucket, or a hostname/port combination configured on the Couchbase cluster) by
using the Couchbase URI
for one or more Couchbase nodes, and specifying the
bucket name and password (if required) when creating the new CouchbaseClient
object.
For example, to connect to the local host and the default
bucket:
List<URI> uris = new LinkedList<URI>();
uris.add(URI.create("http://127.0.0.1:8091/pools"));
try {
client = new CouchbaseClient(uris, "default", "");
} catch (Exception e) {
System.err.println("Error connecting to Couchbase: " + e.getMessage());
System.exit(0);
}
The format of this constructor is:
CouchbaseClient(URIs,BUCKETNAME,BUCKETPASSWORD)
Where:
URIS
is a List
of URIs to the Couchbase nodes. The format of the URI is the
hostname, port and path /pools
.
BUCKETNAME
is the name of the bucket on the cluster that you want to use.
Specified as a String
.
BUCKETPASSWORD
is the password for this bucket. Specified as a String
.
The returned CouchbaseClient
object can be used as with any other
CouchbaseClient
object.
If you want to use SASL to provide secure connectivity to your Couchbase server
then you could create a CouchbaseConnectionFactory
that defines the SASL
connection type, userbucket and password.
The connection to Couchbase uses the underlying protocol for SASL. This is
similar to the earlier example except that we use the
CouchbaseConnectionFactory
.
List<URI> baseURIs = new ArrayList<URI>();
baseURIs.add(base);
CouchbaseConnectionFactory cf = new
CouchbaseConnectionFactory(baseURIs,
"userbucket", "password");
client = new CouchbaseClient((CouchbaseConnectionFactory) cf);
A final approach to creating the connection is using the
CouchbaseConnectionFactoryBuilder
and CouchbaseConnectionFactory
classes.
It’s possible to ovverride some of the default paramters that are defined in the
CouchbaseConnectionFactoryBuilder
for a variety of reasons and customize the
connection for the session depending on expected load on the server and
potential network traffic.
For example, in the following program snippet, we instatiate a new
CouchbaseConnectionFactoryBuilder
and use the setOpTimeout
method to change
the default value to 10000ms (or 10 secs).
We subsequently use the buildCouchbaseConnection
specifying the bucket name,
password and an username (which is not being used any more) to get a
CouchbaseConnectionFactory
object. We then create a CouchbaseClient
object.
List<URI> baseURIs = new ArrayList<URI>();
baseURIs.add(base);
CouchbaseConnectionFactoryBuilder cfb = new
CouchbaseConnectionFactoryBuilder();
// Ovveride default values on CouchbaseConnectionFactoryBuilder
// For example - wait up to 10 seconds for an operation to succeed
cfb.setOpTimeout(10000);
CouchbaseConnectionFactory cf =
cfb.buildCouchbaseConnection(baseURIs, "default", "", "");
client = new CouchbaseClient((CouchbaseConnectionFactory) cf);
For example, the following code snippet will set the OpTimeOut value to 10000 secs. before creating the connection as we saw in the code above.
cfb.setOpTimeout(10000);
These parameters can be set at runtime by setting a property on the command line (such as -DopTimeout=1000 ) or via properties in a file cbclient.properties in that order of precedence.
The following parameters can be set as summarized in the table below. We provide the parameter name, a brief description, the default value and why the particular parameter might need to be modified.
Parameter | Description | Default value | When to Override the default value |
---|---|---|---|
opTimeout |
Time in millisecs for an operation to Timeout | 2500 millisecs. | You can set this value higher when there is heavy network traffic and timeouts happen frequently. |
timeoutExceptionThreshold |
Number of operations to timeout before the node is deemed down | 998 | You can set this value lower to deem a node is down earlier. |
readBufSize |
Read Buffer Size | 16384 | You can set this value higher or lower to optimize the reads. |
opQueueMaxBlockTime |
The maximum time to block waiting for op queue operations to complete, in milliseconds. | 10000 millisecs. | The default has been set with the expectation that most requests are interactive and waiting for more than a few seconds is thus more undesirable than failing the request. However, this value could be lowered for operations not to block for this time. |
shouldOptimize |
Optimize behavior for the network | False | You can set this value to be true if the performance should be optimized for the network as in cases where there are some known issues with the network that may be causing adverse effects on applications. |
maxReconnectDelay |
Maximum number of milliseconds to wait between reconnect attempts. | 30000 millisecs. | You can set this value lower when there is intermittent and frequent connection failures. |
MinReconnectInterval |
A default minimum reconnect interval in millisecs. | 1100 | This means that if a reconnect is needed, it won’t try to reconnect more frequently than default value. The internal connections take up to 500ms per request. You can set this to higher to try reconnecting less frequently. |
obsPollInterval |
Wait for the specified interval before the Observe operation polls the nodes. | 400 | Set this higher or lower depending on whether the polling needs to happen less or more frequently depending on the tolerance limits for the Observe operation as compared to other operations. |
obsPollMax |
The maximum times to poll the master and replica(s) to meet the desired durability requirements. | 10 | You could set this value higher if the Observe operations do not complete after the normal polling. |
The preferred method for closing a connection is to cleanly shutdown the active
connection with a timeout using the shutdown()
method with an optional timeout
period and unit specification. The following will shutdown the active connection
to all the configured servers after 60 seconds:
client.shutdown(60, TimeUnit.SECONDS);
The unit specification relies on the TimeUnit
object enumerator, which
supports the following values:
Constant | Description |
---|---|
TimeUnit.NANOSECONDS |
Nanoseconds (10 -9 s). |
TimeUnit.MICROSECONDS |
Microseconds (10 -6 s). |
TimeUnit.MILLISECONDS |
Milliseconds (10 -3 s). |
TimeUnit.SECONDS |
Seconds. |
The method returns a boolean
value indicating whether the shutdown request
completed successfully.
You also can shutdown an active connection immediately by using the shutdown()
method to your Couchbase object instance. For example:
client.shutdown();
In this form the shutdown()
method returns no value.
The couchbase-client
and spymemcached
libraries support the full suite of
API calls to Couchbase.
The Java Client Libraries support the core Couchbase API methods as direct calls to the Couchbase server through the API call. These direct methods can be used to provide instant storage, retrieval and updating of Couchbase key/value pairs.
For example, the get()
is a synchronous operation:
Object myObject = client.get("someKey");
In the example code above, the client get()
call will wait until a response
has been received from the appropriately configured Couchbase servers before
returning the required value or an exception.
A list of the synchronous methods are shown in .
Method | Title |
---|---|
client.append(casunique, key, value) |
Append a value to an existing key |
client.append(casunique, key, value, transcoder) |
Append a value to an existing key |
client.cas(key, casunique, value) |
Compare and set |
client.cas(key, casunique, expiry, value, transcoder) |
Compare and set with a custom transcoder and expiry |
client.cas(key, casunique, value, transcoder) |
Compare and set with a custom transcoder |
client.decr(key, offset) |
Decrement the value of an existing numeric key |
client.decr(key, offset, default) |
Decrement the value of a key, setting the initial value if the key didn’t already exist |
client.decr(key, offset, default, expiry) |
Decrement the value of a key, setting the initial value if the key didn’t already exist, with an expiry |
client.getAndTouch(key, expiry) |
Get a value and update the expiration time for a given key |
client.getAndTouch(key, expiry, transcoder) |
Get a value and update the expiration time for a given key using a custom transcoder |
client.get(key) |
Get a single key |
client.getAndLock(key [, getl-expiry ], transcoder) |
Get and lock |
client.getBulk(keycollection) |
Get multiple keys |
client.getBulk(keyn) |
Get multiple keys |
client.getBulk(transcoder, keyn) |
Get multiple keys using a custom transcoder |
client.getBulk(keycollection, transcoder) |
Get multiple keys using a custom transcoder |
client.get(key, transcoder) |
Get a single key using a custom transcoder |
client.gets(key) |
Get single key value with CAS value |
client.gets(key, transcoder) |
Get single key value with CAS value using custom transcoder |
client.getStats() |
Get the statistics from all connections |
client.getStats(statname) |
Get the statistics from all connections |
client.getView(ddocname, viewname) |
Create a view object |
client.incr(key, offset) |
Increment the value of an existing numeric key |
client.incr(key, offset, default) |
Increment the value of an existing numeric key |
client.incr(key, offset, default, expiry) |
Increment the value of an existing numeric key |
client.new CouchbaseClient([ url ] [, urls ] [, username ] [, password ]) |
Create connection to Couchbase Server |
client.query(view, query) |
Query a view |
Query.new() |
Create a query object |
client.unlock(key, casunique) |
Unlock |
In addition, the librares also support a range of asynchronous methods that can be used to store, update and retrieve values without having to explicitly wait for a response.
The asynchronous methods use a Future object or its appropriate implementation which is returned by the initial method call for the operation. The communication with the Couchbase server will be handled by the client libraries in the background so that the main program loop can continue. You can recover the status of the operation by using a method to check the status on the returned Future object. For example, rather than synchronously getting a key, an asynchronous call might look like this:
GetFuture getOp = client.asyncGet("someKey");
A list of the asynchronous methods are shown in .
Method | Title |
---|---|
client.add(key, expiry, value) |
Add a value with the specified key that does not already exist |
client.add(key, expiry, value, persistto) |
Add a value using the specified key and observe it being persisted on master and more node(s). |
client.add(key, expiry, value, persistto, replicateto) |
Add a value using the specified key and observe it being persisted on master and more node(s) and being replicated to one or more node(s). |
client.add(key, expiry, value, replicateto) |
Add a value using the specified key and observe it being replicated to one or more node(s). |
client.add(key, expiry, value, transcoder) |
Add a value that does not already exist using custom transcoder |
client.asyncCAS(key, casunique, value) |
Asynchronously compare and set a value |
client.asyncCAS(key, casunique, expiry, value, transcoder) |
Asynchronously compare and set a value with custom transcoder and expiry |
client.asyncCAS(key, casunique, value, transcoder) |
Asynchronously compare and set a value with custom transcoder |
client.asyncDecr(key, offset) |
Asynchronously decrement the value of an existing key |
client.asyncGetAndTouch(key, expiry) |
Asynchronously get a value and update the expiration time for a given key |
client.asyncGetAndTouch(key, expiry, transcoder) |
Asynchronously get a value and update the expiration time for a given key using a custom transcoder |
client.asyncGet(key) |
Asynchronously get a single key |
client.asyncGetBulk(keycollection) |
Asynchronously get multiple keys |
client.asyncGetBulk(keyn) |
Asynchronously get multiple keys |
client.asyncGetBulk(transcoder, keyn) |
Asynchronously get multiple keys using a custom transcoder |
client.asyncGetBulk(keycollection, transcoder) |
Asynchronously get multiple keys using a custom transcoder |
client.asyncGet(key, transcoder) |
Asynchronously get a single key using a custom transcoder |
client.asyncGetLock(key [, getl-expiry ]) |
Asynchronously get a lock. |
client.asyncGetLock(key [, getl-expiry ], transcoder) |
Asynchronously get a lock with transcoder. |
client.asyncGets(key) |
Asynchronously get single key value with CAS value |
client.asyncGets(key, transcoder) |
Asynchronously get single key value with CAS value using custom transcoder |
client.asyncIncr(key, offset) |
Asynchronously increment the value of an existing key |
client.delete(key) |
Delete the specified key |
client.getAndLock(key [, getl-expiry ]) |
Get and lock Asynchronously |
client.prepend(casunique, key, value) |
Prepend a value to an existing key using the default transcoder |
client.prepend(casunique, key, value, transcoder) |
Prepend a value to an existing key using a custom transcoder |
client.replace(key, expiry, value) |
Update an existing key with a new value |
client.replace(key, expiry, value, persistto) |
Replace a value using the specified key and observe it being persisted on master and more node(s). |
client.replace(key, expiry, value, persistto, replicateto) |
Replace a value using the specified key and observe it being persisted on master and more node(s) and being replicated to one or more node(s). |
client.replace(key, expiry, value, replicateto) |
Replace a value using the specified key and observe it being replicated to one or more node(s). |
client.replace(key, expiry, value, transcoder) |
Update an existing key with a new value using a custom transcoder |
client.set(key, expiry, value) |
Store a value using the specified key |
client.set(key, expiry, value, persistto) |
Store a value using the specified key and observe it being persisted on master and more node(s). |
client.set(key, expiry, value, persistto, replicateto) |
Store a value using the specified key and observe it being persisted on master and more node(s) and being replicated to one or more node(s). |
client.set(key, expiry, value, replicateto) |
Store a value using the specified key and observe it being replicated to one or more node(s). |
client.set(key, expiry, value, transcoder) |
Store a value using the specified key and a custom transcoder. |
client.touch(key, expiry) |
Update the expiry time of an item |
This will populate the Future object GetFuture
with the response from the
server. The Future object class is defined
here.
The primary methods are:
cancel()
Attempts to Cancel the operation if the operation has not already been completed.
get()
Waits for the operation to complete. Gets the object returned by the operation as if the method was synchronous rather than asynchronous.
get(timeout, TimeUnit)
Gets the object waiting for a maximum time specified by timeout
and the
corresponding TimeUnit
.
isDone()
The operation has been completed successfully.
For example, you can use the timeout method to obtain the value or cancel the operation:
GetFuture getOp = client.asyncGet("someKey");
Object myObj;
try {
myObj = getOp.get(5, TimeUnit.SECONDS);
} catch(TimeoutException e) {
getOp.cancel(false);
}
Alternatively, you can do a blocking wait for the response by using the get()
method:
Object myObj;
myObj = getOp.get();
All of the Java client library methods use the default Whalin transcoder that provides compatilibility with memcached clients for the serialization of objects from the object type into a byte array used for storage within Couchbase.
You can also use a custom transcoder for the serialization of objects. This can be used to serialize objects in a format that is compatible with other languages or environments.
You can customize the transcoder by implementing a new Transcoder interface and then using this when storing and retrieving values. The Transcoder will be used to encode and decode objects into binary strings. All of the methods that store, retrieve or update information have a version that supports a custom transcoder.
All values in Couchbase and Memcached can be set with an expiry value. The expiry value indicates when the item should be expired from the database and can be set when an item is added or updated.
Within spymemcached
the expiry value is expressed in the native form of an
integer as per the Memcached protocol specification. The integer value is
expressed as the number of seconds, but the interpretation of the value is
different based on the value itself:
Expiry is less than 30*24*60*60
(30 days)
The value is interpreted as the number of seconds from the point of storage or update.
Expiry is greater than 30*24*60*60
The value is interpreted as the number of seconds from the epoch (January 1st, 1970).
Expiry is 0
This disables expiry for the item.
For example:
client.set("someKey", 3600, someObject);
The value will have an expiry time of 3600 seconds (one hour) from the time the item was stored.
The statement:
client.set("someKey", 1307458800, someObject);
Will set the expiry time as June 7th 2011, 15:00 (UTC).
API Call | client.new CouchbaseClient([ url ] [, urls ] [, username ] [, password ])
|
---|---|
Asynchronous | no |
Description | Create a connection to Couchbase Server with given parameters, such as node URL. The connection obtains the cluster configuration from the first host to which it has connected. Further communication operates directly with each node in the cluster as required. |
Returns | (none) |
Arguments | |
String url | URL for Couchbase Server Instance, or node. |
String urls | Linked list containing one or more URLs as strings. |
String username | Username for Couchbase bucket. |
String password | Password for Couchbase bucket. |
The Couchbase Java Client Library store operations set information within the Couchbase database. These are distinct from the update operations in that the key does not have to exist within the Couchbase database before being stored.
The add()
method adds a value to the database with the specified key, but will
fail if the key already exists in the database.
API Call | client.add(key, expiry, value)
|
---|---|
Asynchronous | yes |
Description | Add a value with the specified key that does not already exist. Will fail if the key/value pair already exist. |
Returns | Future<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Object value | Value to be stored |
The add()
method adds a value to the database using the specified key.
client.add("someKey", 0, someObject);
Unlike Set Operations the operation can fail (and return false) if the specified key already exists.
For example, the first operation in the example below may complete if the key does not already exist, but the second operation will always fail as the first operation will have set the key:
OperationFuture<Boolean> addOp = client.add("someKey",0,"astring");
System.out.printf("Result was %b",addOp.get());
addOp = client.add("someKey",0,"anotherstring");
System.out.printf("Result was %b",addOp.get());
API Call | client.add(key, expiry, value, transcoder)
|
---|---|
Asynchronous | yes |
Description | Add a value with the specified key that does not already exist. Will fail if the key/value pair already exist. |
Returns | Future<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Object value | Value to be stored |
Transcoder |
Transcoder class to be used to serialize value |
This method is identical to the add()
method, but supports the use of a custom
transcoder for serialization of the object value. For more information on
transcoding, see Object Serialization
(Transcoding).
The set()
operations store a value into Couchbase or Memcached using the
specified key and value. The value is stored against the specified key, even if
the key already exists and has data. This operation overwrites the existing with
the new data. The store operation in this case is asynchronous.
API Call | client.set(key, expiry, value)
|
---|---|
Asynchronous | yes |
Description | Store a value using the specified key, whether the key already exists or not. Will overwrite a value if the given key/value already exists. |
Returns | OperationFuture<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Object value | Value to be stored |
The first form of the set()
method stores the key, sets the expiry (use 0 for
no expiry), and the corresponding object value using the built in transcoder for
serialization.
The simplest form of the command is:
client.set("someKey", 3600, someObject);
The Boolean
return value from the asynchronous operation return value will be
true if the value was stored. For example:
OperationFuture<Boolean> setOp = client.set("someKey",0,"astring");
System.out.printf("Result was %b",setOp.get());
API Call | client.set(key, expiry, value, transcoder)
|
---|---|
Asynchronous | yes |
Description | Store a value using the specified key and a custom transcoder. |
Returns | OperationFuture<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Object value | Value to be stored |
Transcoder |
Transcoder class to be used to serialize value |
The second form of the set()
method supports the use of a custom transcoder
for serialization of the object value. For more information on transcoding, see
Object Serialization (Transcoding).
API Call | client.set(key, expiry, value, persistto)
|
---|---|
Asynchronous | yes |
Description | Store a value using the specified key and observe it being persisted on master and more node(s). |
Returns | OperationFuture<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Object value | Value to be stored |
enum persistto | Specify the number of nodes on which the document must be persisted to before returning. |
API Call | client.set(key, expiry, value, replicateto)
|
---|---|
Asynchronous | yes |
Description | Store a value using the specified key and observe it being replicated to one or more node(s). |
Returns | OperationFuture<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Object value | Value to be stored |
enum replicateto | Specify the number of nodes on which the document must be replicated to before returning |
API Call | client.set(key, expiry, value, persistto, replicateto)
|
---|---|
Asynchronous | yes |
Description | Store a value using the specified key and observe it being persisted on master and more node(s) and being replicated to one or more node(s). |
Returns | OperationFuture<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Object value | Value to be stored |
enum persistto | Specify the number of nodes on which the document must be persisted to before returning. |
enum replicateto | Specify the number of nodes on which the document must be replicated to before returning |
This method is identical to the set()
method, but supports the ability to
observe the persistence on the master and replicas and the propagation to the
replicas. Using these methods above, it’s possible to set the persistence
requirements for the data on the nodes.
The persistence requirements can be specified in terms of how the data should be
persisted on the master and the replicas using PeristTo
and how the data
should be propagated to the replicas using ReplicateTo
respectively.
The client library will poll the server until the persistence requirements are met. The method will return FALSE if the requirements are impossible to meet based on the configuration (inadequate number of replicas) or even after a set amount of retries the persistence requirements could not be met.
The program snippet below illustrates how to specify a requirement that the data should be persisted on 4 nodes (master and three replicas).
// Perist to all four nodes including master
OperationFuture<Boolean> setOp =
c.set("key", 0, "value", PersistTo.FOUR);
System.out.printf("Result was %b", setOp.get());
The peristence requirements can be specified for both the master and replicas. In the case above, it’s required that the key and value is persisted on all the 4 nodes (including replicas).
In the following, the requirement is specified as requiring persistence to the master and propagation of the data to the three replicas. This requirement is weaker than requring the data to be persisted on all four nodes including the three replicas.
// Perist to master and propagate the data to three replicas
OperationFuture<Boolean> setOp =
c.set("key", 0, "value", PersistTo.MASTER, ReplicateTo.THREE);
System.out.printf("Result was %b", setOp.get());
Similar requirements can used with the add() and replace() mutation operations.
The retrieve operations get information from the Couchbase database. A summary of the available API calls is listed below.
The synchronous get()
methods allow for direct access to a given key/value
pair.
API Call | client.get(key)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | Object ( Binary object ) |
Arguments | |
String key | Document ID used to identify the value |
The get()
method obtains an object stored in Couchbase using the default
transcoder for serialization of the object.
For example:
Object myObject = client.get("someKey");
Transcoding of the object assumes the default transcoder was used when the value was stored. The returned object can be of any type.
If the request key does no existing in the database then the returned value is null.
API Call | client.get(key, transcoder)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | T ( Transcoded object ) |
Arguments | |
String key | Document ID used to identify the value |
Transcoder |
Transcoder class to be used to serialize value |
The second form of the get()
retrieves a value from Couchbase using a custom
transcoder.
For example to obtain an integer value using the IntegerTranscoder:
Transcoder<Integer> tc = new IntegerTranscoder();
Integer ic = client.get("someKey", tc);
The asynchronous asyncGet()
methods allow to retrieve a given value for a key
without waiting actively for a response.
API Call | client.asyncGet(key)
|
---|---|
Asynchronous | yes |
Description | Get one or more key values |
Returns | Future<Object> ( Asynchronous request value, as Object ) |
Arguments | |
String key | Document ID used to identify the value |
Exceptions | |
TimeoutException |
Value could not be retrieved |
The first form of the asyncGet()
method obtains a value for a given key
returning a Future object so that the value can be later retrieved. For example,
to get a key with a stored String value:
GetFuture<Object> getOp =
client.asyncGet("samplekey");
String username;
try {
username = (String) getOp.get(5, TimeUnit.SECONDS);
} catch(Exception e) {
getOp.cancel(false);
}
API Call | client.asyncGet(key, transcoder)
|
---|---|
Asynchronous | yes |
Description | Get one or more key values |
Returns | Future<T> ( Asynchronous request value, as Transcoded Object ) |
Arguments | |
String key | Document ID used to identify the value |
Transcoder |
Transcoder class to be used to serialize value |
The second form is identical to the first, but includes the ability to use a custom transcoder on the stored value.
The Get-and-Touch (GAT) methods obtain a value for a given key and update the expiry time for the key. This can be useful for session values and other information where you want to set an expiry time, but don’t want the value to expire while the value is still in use.
API Call | client.getAndTouch(key, expiry)
|
---|---|
Asynchronous | no |
Description | Get a value and update the expiration time for a given key |
Introduced Version | 1.0 |
Returns | CASValue ( Check and set object ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
The first form of the getAndTouch()
obtains a given value and updates the
expiry time. For example, to get session data and renew the expiry time to five
minutes:
session = client.getAndTouch("sessionid",300);
API Call | client.getAndTouch(key, expiry, transcoder)
|
---|---|
Asynchronous | no |
Description | Get a value and update the expiration time for a given key |
Introduced Version | 1.0 |
Returns | CASValue ( Check and set object ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Transcoder |
Transcoder class to be used to serialize value |
The second form supports the use of a custom transcoder for the stored value information.
API Call | client.asyncGetAndTouch(key, expiry)
|
---|---|
Asynchronous | yes |
Description | Get a value and update the expiration time for a given key |
Introduced Version | 1.0 |
Returns | Future<CASValue<Object>> ( Asynchronous request value, as CASValue, as Object ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
The asynchronous methods obtain the value and update the expiry time without requiring you to actively wait for the response. The returned value is a CAS object with the embedded value object.
Future<CASValue<Object>> future = client.asyncGetAndTouch("sessionid", 300);
CASValue casv;
try {
casv = future.get(5, TimeUnit.SECONDS);
} catch(Exception e) {
future.cancel(false);
}
API Call | client.asyncGetAndTouch(key, expiry, transcoder)
|
---|---|
Asynchronous | yes |
Description | Get a value and update the expiration time for a given key |
Introduced Version | 1.0 |
Returns | Future<CASValue<T>> ( Asynchronous request value, as CASValue as Transcoded object ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Transcoder |
Transcoder class to be used to serialize value |
The second form of the asynchronous method supports the use of a custom transcoder for the stored object.
The gets()
methods obtain a CAS value for a given key. The CAS value is used
in combination with the corresponding Check-and-Set methods when updating a
value. For example, you can use the CAS value with the append()
operation to
update a value only if the CAS value you supply matches. For more information
see Append Methods and Check-and-Set
Methods.
API Call | client.gets(key)
|
---|---|
Asynchronous | no |
Description | Get single key value with CAS value |
Returns | CASValue ( Check and set object ) |
Arguments | |
String key | Document ID used to identify the value |
The gets()
method obtains a CASValue
for a given key. The CASValue holds the
CAS to be used when performing a Check-And-Set operation, and the corresponding
value for the given key.
For example, to obtain the CAS and value for the key someKey
:
CASValue status = client.gets("someKey");
System.out.printf("CAS is %s\n",status.getCas());
System.out.printf("Result was %s\n",status.getValue());
The CAS value can be used in a CAS call such as append()
:
client.append(status.getCas(),"someKey", "appendedstring");
API Call | client.gets(key, transcoder)
|
---|---|
Asynchronous | no |
Description | Get single key value with CAS value |
Returns | CASValue ( Check and set object ) |
Arguments | |
String key | Document ID used to identify the value |
Transcoder |
Transcoder class to be used to serialize value |
The second form of the gets()
method supports the use of a custom transcoder.
API Call | client.asyncGets(key)
|
---|---|
Asynchronous | yes |
Description | Get single key value with CAS value |
Returns | Future<CASValue<Object>> ( Asynchronous request value, as CASValue, as Object ) |
Arguments | |
String key | Document ID used to identify the value |
The asyncGets()
method obtains the CASValue
object for a stored value
against the specified key, without requiring an explicit wait for the returned
value.
For example:
Future<CASValue<Object>> future = client.asyncGets("someKey");
System.out.printf("CAS is %s\n",future.get(5,TimeUnit.SECONDS).getCas());
Note that you have to extract the CASValue from the Future response, and then the CAS value from the corresponding object. This is performed here in a single statement.
API Call | client.asyncGets(key, transcoder)
|
---|---|
Asynchronous | yes |
Description | Get single key value with CAS value |
Returns | Future<CASValue<T>> ( Asynchronous request value, as CASValue as Transcoded object ) |
Arguments | |
String key | Document ID used to identify the value |
Transcoder |
Transcoder class to be used to serialize value |
The final form of the asyncGets()
method supports the use of a custom
transcoder.
The bulk getBulk()
methods allow you to get one or more items from the
database in a single request. Using the bulk methods is more efficient than
multiple single requests as the operation can be conducted in a single network
call.
API Call | client.getBulk(keycollection)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | Map<String,Object> ( Map of Strings/Objects ) |
Arguments | |
Collection |
One or more keys used to reference a value |
The first format accepts a String
Collection
as the request argument which
is used to specify the list of keys that you want to retrieve. The return type
is Map
between the keys and object values.
For example:
Map<String,Object> keyvalues = client.getBulk(keylist);
System.out.printf("A is %s\n",keyvalues.get("keyA"));
System.out.printf("B is %s\n",keyvalues.get("keyB"));
System.out.printf("C is %s\n",keyvalues.get("keyC"));
The returned map will only contain entries for keys that exist from the original request. For example, if you request the values for three keys, but only one exists, the resultant map will contain only that one key/value pair.
API Call | client.getBulk(keycollection, transcoder)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | Map<String,T> ( Map of Strings/Transcoded objects ) |
Arguments | |
Collection |
One or more keys used to reference a value |
Transcoder |
Transcoder class to be used to serialize value |
The second form of the getBulk()
method supports the same Collection
argument, but also supports the use of a custom transcoder on the returned
values.
The specified transcoder will be used for every value requested from the database.
API Call | client.getBulk(keyn)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | Map<String,Object> ( Map of Strings/Objects ) |
Arguments | |
String… keyn | One or more keys used to reference a value |
The third form of the getBulk()
method supports a variable list of arguments
with each interpreted as the key to be retrieved from the database.
For example, the equivalent of the Collection
method operation using this
method would be:
Map<String,Object> keyvalues = client.getBulk("keyA","keyB","keyC");
System.out.printf("A is %s\n",keyvalues.get("keyA"));
System.out.printf("B is %s\n",keyvalues.get("keyB"));
System.out.printf("C is %s\n",keyvalues.get("keyC"));
The return Map
will only contain entries for keys that exist. Non-existent
keys will be silently ignored.
API Call | client.getBulk(transcoder, keyn)
|
---|---|
Asynchronous | no |
Description | Get one or more key values |
Returns | Map<String,T> ( Map of Strings/Transcoded objects ) |
Arguments | |
Transcoder |
Transcoder class to be used to serialize value |
String… keyn | One or more keys used to reference a value |
The fourth form of the getBulk()
method uses the variable list of arguments
but supports a custom transcoder.
Note that unlike other formats of the methods used for supporting custom transcoders, the transcoder specification is at the start of the argument list, not the end.
API Call | client.asyncGetBulk(keycollection)
|
---|---|
Asynchronous | yes |
Description | Get one or more key values |
Returns | BulkFuture<Map<String,Object>> ( Map of Strings/Objects ) |
Arguments | |
Collection |
One or more keys used to reference a value |
The asynchronous getBulk()
method supports a Collection
of keys to be
retrieved, returning a BulkFuture object (part of the spymemcached
package)
with the returned map of key/value information. As with other asynchronous
methods, the benefit is that you do not need to actively wait for the response.
The BulkFuture
object operates slightly different in context to the standard
Future
object. Whereas the Future
object gets a returned single value, the
BulkFuture
object will return an object containing as many keys as have been
returned. For very large queries requesting large numbers of keys this means
that multiple requests may be required to obtain every key from the original
list.
For example, the code below will obtain as many keys as possible from the
supplied Collection
.
BulkFuture<Map<String,Object>> keyvalues = client.asyncGetBulk(keylist);
Map<String,Object> keymap = keyvalues.getSome(5,TimeUnit.SECONDS);
System.out.printf("A is %s\n",keymap.get("keyA"));
System.out.printf("B is %s\n",keymap.get("keyB"));
System.out.printf("C is %s\n",keymap.get("keyC"));
API Call | client.asyncGetBulk(keycollection, transcoder)
|
---|---|
Asynchronous | yes |
Description | Get one or more key values |
Returns | BulkFuture<Map<String,T>> ( Map of Strings/Transcoded objects ) |
Arguments | |
Collection |
One or more keys used to reference a value |
Transcoder |
Transcoder class to be used to serialize value |
The second form of the asynchronous getBulk()
method supports the custom
transcoder for the returned values.
API Call | client.asyncGetBulk(keyn)
|
---|---|
Asynchronous | yes |
Description | Get one or more key values |
Returns | BulkFuture<Map<String,Object>> ( Map of Strings/Objects ) |
Arguments | |
String… keyn | One or more keys used to reference a value |
The third form is identical to the multi-argument key request method
(seecollection based
asyncBulkGet()
), except that
the operation occurs asynchronously.
API Call | client.asyncGetBulk(transcoder, keyn)
|
---|---|
Asynchronous | yes |
Description | Get one or more key values |
Returns | BulkFuture<Map<String,T>> ( Map of Strings/Transcoded objects ) |
Arguments | |
Transcoder |
Transcoder class to be used to serialize value |
String… keyn | One or more keys used to reference a value |
The final form of the asyncGetBulk()
method supports a custom transcoder with
the variable list of keys supplied as arguments.
API Call | client.asyncGetLock(key [, getl-expiry ], transcoder)
|
---|---|
Asynchronous | yes |
Description | Get the value for a key, lock the key from changes |
Returns | Future<CASValue<T>> ( Asynchronous request value, as CASValue as Transcoded object ) |
Arguments | |
String key | Document ID used to identify the value |
int getl-expiry | Expiry time for lock |
Default | |
Maximum | |
Transcoder |
Transcoder class to be used to serialize value |
API Call | client.asyncGetLock(key [, getl-expiry ])
|
---|---|
Asynchronous | yes |
Description | Get the value for a key, lock the key from changes |
Returns | Future<CASValue<Object>> ( Asynchronous request value, as CASValue, as Object ) |
Arguments | |
String key | Document ID used to identify the value |
int getl-expiry | Expiry time for lock |
Default | |
Maximum |
API Call | client.getAndLock(key [, getl-expiry ], transcoder)
|
---|---|
Asynchronous | no |
Description | Get the value for a key, lock the key from changes |
Returns | CASValue<T> ( CASValue as Transcoded object ) |
Arguments | |
String key | Document ID used to identify the value |
int getl-expiry | Expiry time for lock |
Default | |
Maximum | |
Transcoder |
Transcoder class to be used to serialize value |
Exceptions | |
OperationTimeoutException |
Exception timeout occured while waiting for value. |
RuntimeException |
Exception object specifying interruption while waiting for value. |
The simplest form of the method is without the transcoder as below.
API Call | client.getAndLock(key [, getl-expiry ])
|
---|---|
Asynchronous | yes |
Description | Get the value for a key, lock the key from changes |
Returns | CASValue<Object> ( CASValue as Object ) |
Arguments | |
String key | Document ID used to identify the value |
int getl-expiry | Expiry time for lock |
Default | |
Maximum | |
Exceptions | |
OperationTimeoutException |
Exception timeout occured while waiting for value. |
RuntimeException |
Exception object specifying interruption while waiting for value. |
CASValue<Object> casv = client.getAndLock("keyA", 3);
Will lock keyA for 3 seconds or until an Unlock is issued.
API Call | client.unlock(key, casunique)
|
---|---|
Asynchronous | no |
Description | Unlock a previously locked key by providing the corresponding CAS value that was returned during the lock |
Returns | Boolean ( Boolean (true/false) ) |
Arguments | |
String key | Document ID used to identify the value |
long casunique | Unique value used to verify a key/value combination |
Exceptions | |
InterruptedException |
Interrupted Exception while waiting for value. |
OperationTimeoutException |
Exception timeout occured while waiting for value. |
RuntimeException |
Exception object specifying interruption while waiting for value. |
CASValue<Object> casv = client.getAndLock("keyA", 3);
//Use CAS Value to Unlock
client.unlock("getunltest", casv.getCas());
The update methods support different methods of updating and changing existing information within Couchbase. A list of the available methods is listed below.
The append()
methods allow you to add information to an existing key/value
pair in the database. You can use this to add information to a string or other
data after the existing data.
The append()
methods append raw serialized data on to the end of the existing
data in the key. If you have previously stored a serialized object into
Couchbase and then use append, the content of the serialized object will not be
extended. For example, adding an Array
of integers into the database, and then
using append()
to add another integer will result in the key referring to a
serialized version of the array, immediately followed by a serialized version of
the integer. It will not contain an updated array with the new integer appended
to it. De-serialization of objects that have had data appended may result in
data corruption.
API Call | client.append(casunique, key, value)
|
---|---|
Asynchronous | no |
Description | Append a value to an existing key |
Returns | Object ( Binary object ) |
Arguments | |
long casunique | Unique value used to verify a key/value combination |
String key | Document ID used to identify the value |
Object value | Value to be stored |
The append()
appends information to the end of an existing key/value pair. The
append()
function requires a CAS value. For more information on CAS values,
see CAS get Methods.
For example, to append a string to an existing key:
CASValue<Object> casv = client.gets("samplekey");
client.append(casv.getCas(),"samplekey", "appendedstring");
You can check if the append operation succeeded by using the return
OperationFuture
value:
OperationFuture<Boolean> appendOp =
client.append(casv.getCas(),"notsamplekey", "appendedstring");
try {
if (appendOp.get().booleanValue()) {
System.out.printf("Append succeeded\n");
}
else {
System.out.printf("Append failed\n");
}
}
catch (Exception e) {
...
}
API Call | client.append(casunique, key, value, transcoder)
|
---|---|
Asynchronous | no |
Description | Append a value to an existing key |
Returns | Object ( Binary object ) |
Arguments | |
long casunique | Unique value used to verify a key/value combination |
String key | Document ID used to identify the value |
Object value | Value to be stored |
Transcoder |
Transcoder class to be used to serialize value |
The second form of the append()
method supports the use of custom transcoder.
The prepend()
methods insert information before the existing data for a given
key. Note that as with the append()
method, the information will be inserted
before the existing binary data stored in the key, which means that
serialization of complex objects may lead to corruption when using prepend()
.
API Call | client.prepend(casunique, key, value)
|
---|---|
Asynchronous | yes |
Description | Prepend a value to an existing key |
Returns | Future<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
long casunique | Unique value used to verify a key/value combination |
String key | Document ID used to identify the value |
Object value | Value to be stored |
The prepend()
inserts information before the existing data stored in the
key/value pair. The prepend()
function requires a CAS value. For more
information on CAS values, see CAS get
Methods.
For example, to prepend a string to an existing key:
CASValue<Object> casv = client.gets("samplekey");
client.prepend(casv.getCas(),"samplekey", "prependedstring");
You can check if the prepend operation succeeded by using the return
OperationFuture
value:
OperationFuture<Boolean> prependOp =
client.prepend(casv.getCas(),"notsamplekey", "prependedstring");
try {
if (prependOp.get().booleanValue()) {
System.out.printf("Prepend succeeded\n");
}
else {
System.out.printf("Prepend failed\n");
}
}
catch (Exception e) {
...
}
API Call | client.prepend(casunique, key, value, transcoder)
|
---|---|
Asynchronous | yes |
Description | Prepend a value to an existing key |
Returns | Future<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
long casunique | Unique value used to verify a key/value combination |
String key | Document ID used to identify the value |
Object value | Value to be stored |
Transcoder |
Transcoder class to be used to serialize value |
The secondary form of the prepend()
method supports the use of a custom
transcoder for updating the key/value pair.
The check-and-set methods provide a mechanism for updating information only if the client knows the check (CAS) value. This can be used to prevent clients from updating values in the database that may have changed since the client obtained the value. Methods for storing and updating information support a CAS method that allows you to ensure that the client is updating the version of the data that the client retrieved.
The check value is in the form of a 64-bit integer which is updated every time the value is modified, even if the update of the value does not modify the binary data. Attempting to set or update a key/value pair where the CAS value does not match the value stored on the server will fail.
The cas()
methods are used to explicitly set the value only if the CAS
supplied by the client matches the CAS on the server, analogous to the Set
Operations method.
With all CAS operations, the CASResponse
value returned indicates whether the
operation succeeded or not, and if not why. The CASResponse
is an Enum
with
three possible values:
EXISTS
The item exists, but the CAS value on the database does not match the value supplied to the CAS operation.
NOT_FOUND
The specified key does not exist in the database. An add()
method should be
used to add the key to the database.
OK
The CAS operation was successful and the updated value is stored in Couchbase
API Call | client.cas(key, casunique, value)
|
---|---|
Asynchronous | no |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | CASResponse ( Check and set object ) |
Arguments | |
String key | Document ID used to identify the value |
long casunique | Unique value used to verify a key/value combination |
Object value | Value to be stored |
The first form of the cas()
method allows for an item to be set in the
database only if the CAS value supplied matches that stored on the server.
For example:
CASResponse casr = client.cas("caskey", casvalue, "new string value");
if (casr.equals(CASResponse.OK)) {
System.out.println("Value was updated");
}
else if (casr.equals(CASResponse.NOT_FOUND)) {
System.out.println("Value is not found");
}
else if (casr.equals(CASResponse.EXISTS)) {
System.out.println("Value exists, but CAS didn't match");
}
API Call | client.cas(key, casunique, value, transcoder)
|
---|---|
Asynchronous | no |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | CASResponse ( Check and set object ) |
Arguments | |
String key | Document ID used to identify the value |
long casunique | Unique value used to verify a key/value combination |
Object value | Value to be stored |
Transcoder |
Transcoder class to be used to serialize value |
The second form of the method supports using a custom transcoder for storing a value.
API Call | client.cas(key, casunique, expiry, value, transcoder)
|
---|---|
Asynchronous | no |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | CASResponse ( Check and set object ) |
Arguments | |
String key | Document ID used to identify the value |
long casunique | Unique value used to verify a key/value combination |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Object value | Value to be stored |
Transcoder |
Transcoder class to be used to serialize value |
This form of the cas()
method updates both the key value and the expiry time
for the value. For information on expiry values, see Expiry
Values.
For example the following attempts to set the key caskey
with an updated
value, setting the expiry times to 3600 seconds (one hour).
Transcoder<Integer> tc = new IntegerTranscoder();
CASResponse casr = client.cas("caskey", casvalue, 3600, 1200, tc);
API Call | client.asyncCAS(key, casunique, value)
|
---|---|
Asynchronous | yes |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | Future<CASResponse> ( Asynchronous request value, as CASResponse ) |
Arguments | |
String key | Document ID used to identify the value |
long casunique | Unique value used to verify a key/value combination |
Object value | Value to be stored |
Performs an asynchronous CAS operation on the given key/value. You can use this method to set a value using CAS without waiting for the response. The following example requests an update of a key, timing out after 5 seconds if the operation was not successful.
Future<CASResponse> future = client.asyncCAS("someKey", casvalue, "updatedvalue");
CASResponse casr;
try {
casr = future.get(5, TimeUnit.SECONDS);
} catch(TimeoutException e) {
future.cancel(false);
}
API Call | client.asyncCAS(key, casunique, value, transcoder)
|
---|---|
Asynchronous | yes |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | Future<CASResponse> ( Asynchronous request value, as CASResponse ) |
Arguments | |
String key | Document ID used to identify the value |
long casunique | Unique value used to verify a key/value combination |
Object value | Value to be stored |
Transcoder |
Transcoder class to be used to serialize value |
Performs an asynchronous CAS operation on the given key/value using a custom transcoder. The example below shows the update of an existing value using a custom Integer transcoder.
Transcoder<Integer> tc = new IntegerTranscoder();
Future<CASResponse> future = client.asyncCAS("someKey", casvalue, 1200, tc);
CASResponse casr;
try {
casr = future.get(5, TimeUnit.SECONDS);
} catch(TimeoutException e) {
future.cancel(false);
}
API Call | client.asyncCAS(key, casunique, expiry, value, transcoder)
|
---|---|
Asynchronous | yes |
Description | Compare and set a value providing the supplied CAS key matches |
Returns | Future<CASResponse> ( Asynchronous request value, as CASResponse ) |
Arguments | |
String key | Document ID used to identify the value |
long casunique | Unique value used to verify a key/value combination |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Object value | Value to be stored |
Transcoder |
Transcoder class to be used to serialize value |
The final form of the asyncCAS()
method supports a custom transcoder and
setting the associated expiry value. For example, to update a value and set the
expiry to 60 seconds:
Transcoder<Integer> tc = new IntegerTranscoder();
Future<CASResponse> future = client.asyncCAS("someKey", casvalue, 60, 1200, tc);
CASResponse casr;
try {
casr = future.get(5, TimeUnit.SECONDS);
} catch(TimeoutException e) {
future.cancel(false);
}
The delete()
method deletes an item in the database with the specified key.
Delete operations are asynchronous only.
API Call | client.delete(key)
|
---|---|
Asynchronous | yes |
Description | Delete a key/value |
Returns | OperationFuture<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
String key | Document ID used to identify the value |
For example, to delete an item you might use code similar to the following:
OperationFuture<Boolean> delOp =
client.delete("samplekey");
try {
if (delOp.get().booleanValue()) {
System.out.printf("Delete succeeded\n");
}
else {
System.out.printf("Delete failed\n");
}
}
catch (Exception e) {
System.out.println("Failed to delete " + e);
}
The decrement methods reduce the value of a given key if the corresponding value can be parsed to an integer value. These operations are provided at a protocol level to eliminate the need to get, update, and reset a simple integer value in the database. All the Java Client Library methods support the use of an explicit offset value that will be used to reduce the stored value in the database.
API Call | client.decr(key, offset)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | long ( Numeric value ) |
Arguments | |
String key | Document ID used to identify the value |
int offset | Integer offset value to increment/decrement (default 1) |
The first form of the decr()
method accepts the keyname and offset value to be
used when reducing the server-side integer. For example, to decrement the server
integer dlcounter
by 5:
client.decr("dlcounter",5);
The return value is the updated value of the specified key.
API Call | client.decr(key, offset, default)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | long ( Numeric value ) |
Arguments | |
String key | Document ID used to identify the value |
int offset | Integer offset value to increment/decrement (default 1) |
int default | Default value to increment/decrement if key does not exist |
The second form of the decr()
method will decrement the given key by the
specified offset
value if the key already exists, or set the key to the
specified default
value if the key does not exist. This can be used in
situations where you are recording a counter value but do not know whether the
key exists at the point of storage.
For example, if the key dlcounter
does not exist, the following fragment will
return 1000:
long newcount =
client.decr("dlcount",1,1000);
System.out.printf("Updated counter is %d\n",newcount);
A subsequent identical call will return the value 999 as the key dlcount
already exists.
API Call | client.decr(key, offset, default, expiry)
|
---|---|
Asynchronous | no |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | long ( Numeric value ) |
Arguments | |
String key | Document ID used to identify the value |
int offset | Integer offset value to increment/decrement (default 1) |
int default | Default value to increment/decrement if key does not exist |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
The third form of the decr()
method the decrement operation, with a default
value and with the addition of setting an expiry time on the stored value. For
example, to decrement a counter, using a default of 1000 if the value does not
exist, and an expiry of 1 hour (3600 seconds):
long newcount =
client.decr("dlcount",1,1000,3600);
For information on expiry values, see Expiry Values.
API Call | client.asyncDecr(key, offset)
|
---|---|
Asynchronous | yes |
Description | Decrement the value of an existing numeric key. The Couchbase Server stores numbers as unsigned values. Therefore the lowest you can decrement is to zero. |
Returns | long ( Numeric value ) |
Arguments | |
String key | Document ID used to identify the value |
int offset | Integer offset value to increment/decrement (default 1) |
The asynchronous form of the asyncDecr()
method enables you to decrement a
value without waiting for a response. This can be useful in situations where you
do not need to know the updated value or merely want to perform a decrement and
continue processing.
For example, to asynchronously decrement a given key:
OperationFuture<Long> decrOp =
client.asyncDecr("samplekey",1,1000,24000);
The increment methods enable you to increase a given stored integer value. These are the incremental equivalent of the decrement operations and work on the same basis; updating the value of a key if it can be parsed to an integer. The update operation occurs on the server and is provided at the protocol level. This simplifies what would otherwise be a two-stage get and set operation.
API Call | client.incr(key, offset)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | long ( Numeric value ) |
Arguments | |
String key | Document ID used to identify the value |
int offset | Integer offset value to increment/decrement (default 1) |
The first form of the incr()
method accepts the keyname and offset (increment)
value to be used when increasing the server-side integer. For example, to
increment the server integer dlcounter
by 5:
client.incr("dlcounter",5);
The return value is the updated value of the specified key.
API Call | client.incr(key, offset, default)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | long ( Numeric value ) |
Arguments | |
String key | Document ID used to identify the value |
int offset | Integer offset value to increment/decrement (default 1) |
int default | Default value to increment/decrement if key does not exist |
The second form of the incr()
method supports the use of a default value which
will be used to set the corresponding key if that value does not already exist
in the database. If the key exists, the default value is ignored and the value
is incremented with the provided offset value. This can be used in situations
where you are recording a counter value but do not know whether the key exists
at the point of storage.
For example, if the key dlcounter
does not exist, the following fragment will
return 1000:
long newcount =
client.incr("dlcount",1,1000);
System.out.printf("Updated counter is %d\n",newcount);
A subsequent identical call will return the value 1001 as the key dlcount
already exists and the value (1000) is incremented by 1.
API Call | client.incr(key, offset, default, expiry)
|
---|---|
Asynchronous | no |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | long ( Numeric value ) |
Arguments | |
String key | Document ID used to identify the value |
int offset | Integer offset value to increment/decrement (default 1) |
int default | Default value to increment/decrement if key does not exist |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
The third format of the incr()
method supports setting an expiry value on the
given key, in addition to a default value if key does not already exist.
For example, to increment a counter, using a default of 1000 if the value does not exist, and an expiry of 1 hour (3600 seconds):
long newcount =
client.incr("dlcount",1,1000,3600);
For information on expiry values, see Expiry Values.
API Call | client.asyncIncr(key, offset)
|
---|---|
Asynchronous | yes |
Description | Increment the value of an existing numeric key. Couchbase Server stores numbers as unsigned numbers, therefore if you try to increment an existing negative number, it will cause an integer overflow and return a non-logical numeric result. If a key does not exist, this method will initialize it with the zero or a specified value. |
Returns | Future<Long> ( Asynchronous request value, as Long ) |
Arguments | |
String key | Document ID used to identify the value |
int offset | Integer offset value to increment/decrement (default 1) |
The asyncIncr()
method supports an asynchronous increment on the value for a
corresponding key. Asynchronous increments are useful when you do not want to
immediately wait for the return value of the increment operation.
OperationFuture<Long> incrOp =
client.asyncIncr("samplekey",1,1000,24000);
The replace()
methods update an existing key/value pair in the database. If
the specified key does not exist, then the operation will fail.
API Call | client.replace(key, expiry, value)
|
---|---|
Asynchronous | yes |
Description | Update an existing key with a new value |
Returns | Future<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Object value | Value to be stored |
The first form of the replace()
method updates an existing value setting while
supporting the explicit setting of the expiry time on the item. For example to
update the samplekey
:
OperationFuture<Boolean> replaceOp =
client.replace("samplekey","updatedvalue",0);
The return value is a OperationFuture
value with a Boolean
base.
API Call | client.replace(key, expiry, value, transcoder)
|
---|---|
Asynchronous | yes |
Description | Update an existing key with a new value |
Returns | Future<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
Object value | Value to be stored |
Transcoder |
Transcoder class to be used to serialize value |
The second form of the replace()
method is identical o the first, but also
supports using a custom Transcoder in place of the default transcoder.
The touch()
methods allow you to update the expiration time on a given key.
This can be useful for situations where you want to prevent an item from
expiring without resetting the associated value. For example, for a session
database you might want to keep the session alive in the database each time the
user accesses a web page without explicitly updating the session value, keeping
the user’s session active and available.
API Call | client.touch(key, expiry)
|
---|---|
Asynchronous | yes |
Description | Update the expiry time of an item |
Returns | Future<Boolean> ( Asynchronous request value, as Boolean ) |
Arguments | |
String key | Document ID used to identify the value |
int expiry | Expiry time for key. Values larger than 30*24*60*60 seconds (30 days) are interpreted as absolute times (from the epoch). |
The first form of the touch()
provides a simple key/expiry call to update the
expiry time on a given key. For example, to update the expiry time on a session
for another 5 minutes:
OperationFuture<Boolean> touchOp =
client.touch("sessionid",300);
The Couchbase Java Client Library includes support for obtaining statistic
information from all of the servers defined within a CouchbaseClient
object. A
summary of the commands is provided below.
API Call | client.getStats()
|
---|---|
Asynchronous | no |
Description | Get the database statistics |
Returns | Object ( Binary object ) |
Arguments | |
None |
The first form of the getStats()
command gets the statistics from all of the
servers configured in your CouchbaseClient
object. The information is returned
in the form of a nested Map, first containing the address of configured server,
and then within each server the individual statistics for that server.
API Call | client.getStats(statname)
|
---|---|
Asynchronous | no |
Description | Get the database statistics |
Returns | Object ( Binary object ) |
Arguments | |
String statname | Group name of a statistic for selecting individual statistic value |
The second form of the getStats()
command gets the specified group of
statistics from all of the servers configured in your CouchbaseClient object.
The information is returned in the form of a nested Map, first containing the
address of configured server, and then within each server the individual
statistics for that server.
Couchbase Server 2.0 extends the querying mechanisms by not only allowding key-based lookups, but also allowing you to query your datasets through a flexible mechanism called views. Those views are based on a common data aggregation approach called map/reduce. With Couchbase Server 2.0 you are able to keep using all of the Couchbase code you already have, and upgrade certain parts of it to use JSON documents without any hassles. In doing this, you can easily add the power of Views and querying those views to your applications.
For more information about using views for indexing and querying from Couchbase Server, here are some useful resources:
For more information on Views, how they operate, and how to write effective map/reduce queries, see Couchbase Server 2.0: Views and Couchbase Sever 2.0: Writing Views.
Sample Patterns: to see examples and patterns you can use for views, see Couchbase Views, Sample Patterns.
Timestamp Pattern: many developers frequently ask about extracting information based on date or time. To find out more, see Couchbase Views, Sample Patterns.
The View
Object is obtained by calling the getView
method which provides
access to the view on the server.
API Call | client.getView(ddocname, viewname)
|
---|---|
Asynchronous | no |
Description | Create a view object to be used when querying a view. |
Returns | (none) |
Arguments | |
String ddocname | Design document name |
String viewname | View name within a design document |
View view = client.getView(docName, viewName)
Then obtain a new Query
object.
API Call | Query.new()
|
---|---|
Asynchronous | no |
Description | Create a query object to be used when querying a view. |
Returns | (none) |
Arguments | |
None |
Query query = new Query();
Once, the View and Query objects are available, the results of the server view can be accessed as below.
API Call | client.query(view, query)
|
---|---|
Asynchronous | no |
Description | Query a view within a design doc |
Returns | (none) |
Arguments | |
View view | View object associated with a server view |
Query query | View object associated with a server view |
ViewResponse = client.query(view, query);
Before accessing the View, a list of options can be set with the query object (here is a short list of the most commonly used ones).
setKey(java.lang.String key)
to set the starting Key.
setRangeStart(java.lang.String startKey)
to set the starting Key.
setRangeEnd(java.lang.String endKey)
to set the ending Key.
setRange(java.lang.String startKey, java.lang.String endKey)
to set the starting and ending key, both.
setDescending(boolean descending)
to set the descending flat to true or false.
setIncludeDocs(boolean include)
to Include the original JSON document with the query.
setReduce(boolean reduce)
where the reduce function is included or excluded based on the Flag.
setStale(Stale stale)
where the possible values for stale are FALSE
, UPDATE_AFTER
and OK
as
noted in the Release Notes.
The format of the returned information of the query method is:
ViewResponse
or any of the other inherited objects such as
ViewResponseWithDocs
, ViewResponseNoDocs
, ViewResponseReduced
.
The ViewResponse
method provides a iterator()
method for iterating through
the rows as a ViewRow
interface. The ViewResponse
method also provides a
getMap()
method where the result is available as a map.
The following methods are available on the ViewRow
interface.
getId()
to get the Id of the associated row.
getKey()
to get the Key of the associated Key/Value pair of the result.
getValue()
to get the Value of the associated Key/Value pair of the result.
getDocument()
to get the document associated with the row.
For usage of these classes, please refer to the Tutorial which has been enhanced to include Views.
This Couchbase SDK Java provides a complete interface to Couchbase Server through the Java programming language. For more on Couchbase Server and Java read our Java SDK Getting Started Guide followed by our in-depth Couchbase and Java tutorial. We require Java SE 6 (or higher) for running the Couchbase Client Library.
This section covers the following topics:
Logging from the Java SDK
Handling Timeouts
Bulk Load and Exponential Backoff
Retrying After Receiving a Temporary Failure
Occasionally when you are troubleshooting an issue with a clustered deployment, you may find it helpful to use additional information from the Couchbase Java SDK logging. The SDK uses JDK logging and this can be configured by specifying a runtime define and adding some additional logging properties. There are two ways to set up Java SDK logging:
Use spymemcached to log from the Java SDK. Since the SDK uses spymemcached and is compatible with spymemcached, you can use the logging provided to output SDK-level information.
Set your JDK properties to log Couchbase Java SDK information.
Provide logging from your application.
To provide logging via spymemcached:
System.setProperty("net.spy.log.LoggerImpl", "net.spy.memcached.compat.log.SunLogger");
or
System.setProperty("net.spy.log.LoggerImpl", "net.spy.memcached.compat.log.Log4JLogger");
The default logger simply logs everything to the standard error stream. To provide logging via the JDK, if you are running a command-line Java program, you can run the program with logging by setting a property:
-Djava.util.logging.config.file=logging.properties
The other alternative is create a logging.properties
and add it to your in
your classpath:
logging.properties
handlers = java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
com.couchbase.client.vbucket.level = FINEST
com.couchbase.client.vbucket.config.level = FINEST
com.couchbase.client.level = FINEST
The final option is to provide logging from your actual Java application. If you are writing your application in an IDE which manages command-line operations for you, it may be easier if you express logging in your application code. Here is an example:
// Tell things using spymemcached logging to use internal SunLogger API
Properties systemProperties = System.getProperties();
systemProperties.put("net.spy.log.LoggerImpl", "net.spy.memcached.compat.log.SunLogger");
System.setProperties(systemProperties);
Logger.getLogger("net.spy.memcached").setLevel(Level.FINEST);
Logger.getLogger("com.couchbase.client").setLevel(Level.FINEST);
Logger.getLogger("com.couchbase.client.vbucket").setLevel(Level.FINEST);
//get the top Logger
Logger topLogger = java.util.logging.Logger.getLogger("");
// Handler for console (reuse it if it already exists)
Handler consoleHandler = null;
//see if there is already a console handler
for (Handler handler : topLogger.getHandlers()) {
if (handler instanceof ConsoleHandler) {
//found the console handler
consoleHandler = handler;
break;
}
}
if (consoleHandler == null) {
//there was no console handler found, create a new one
consoleHandler = new ConsoleHandler();
topLogger.addHandler(consoleHandler);
}
//set the console handler to fine:
consoleHandler.setLevel(java.util.logging.Level.FINEST);
The Java client library has a set of synchronous and asynchronous methods. While it does not happen in most situations, occasionally network IO can become congested, nodes can fail, or memory pressure can lead to situations where an operation can timeout.
When a timeout occurs, most of the synchronous methods on the client will return a RuntimeException showing a timeout as the root cause. Since the asynchronous operations give more specific control over how long it takes for an operation to be successful or unsuccessful, asynchronous operations throw a checked TimeoutException.
As an application developer, it is best to think about what you would do after this timeout. This may be something such as showing the user a message, it may be doing nothing, or it may be going to some other system for additional data.
In some cases you might want to retry the operation, but you should consider this carefully before performing the retry in your code; sometimes a retry may exacerbate the underlying problem that caused the timeout. If you choose to do a retry, providing in the form of a backoff or exponential backoff is advisable. This can be thought of as a pressure relief valve for intermittent resource problems. For more information on backoff and exponential backoff, see Bulk Load and Exponential Backoff.
If your application creates a large number of asynchronous operations, you may also encounter timeouts immediately in response to the requests. When you perform an asynchronous operation, Couchbase Java SDK creates an object and puts the object into a request queue. The object and the request are stored in Java runtime memory, in other words, they are stored in local to your Java application runtime memory and require some amount of Java Virtual Machine IO to be serviced.
Rather than write so many asynchronous operations that can overwhelm a JVM and generate out of memory errors for the JVM, you can rely on SDK-level timeouts. The default behavior of the Java SDK is to start to immediately timeout asynchronous operations if the queue of operations to be sent to the server is overwhelmed.
You can also choose to control the volume of asynchronous requests that are issued by your application by setting a timeout for blocking. You might want to do this for a bulk load of data so that you do not overwhelm your JVM. The following is an example:
List<URI> baselist = new ArrayList<URI>();
baselist.add(new URI("http://localhost:8091/pools"));
CouchbaseConnectionFactoryBuilder cfb = new CouchbaseConnectionFactoryBuilder();
cfb.setOpQueueMaxBlockTime(5000); // wait up to 5 seconds when trying to enqueue an operation
CouchbaseClient myclient = new CouchbaseClient(cfb.buildCouchbaseConnection(baselist, "default", "default", ""));
When you bulk load data to Couchbase Server, you can accidentally overwhelm available memory in the Couchbase cluster before it can store data on disk. If this happens, Couchbase Server will immediately send a response indicating the operation cannot be handled at the moment but can be handled later.
This is sometimes referred to as “handling Temp OOM”, where where OOM means out of memory. Note though that the actual temporary failure could be sent back for reasons other than OOM. However, temporary OOM is the most common underlying cause for this error.
To handle this problem, you could perform an exponential backoff as part of your bulk load. The backoff essentially reduces the number of requests sent to Couchbase Server as it receives OOM errors:
package com.couchbase.sample.dataloader;
import com.couchbase.client.CouchbaseClient;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import net.spy.memcached.internal.OperationFuture;
import net.spy.memcached.ops.OperationStatus;
/**
*
* The StoreHandler exists mainly to abstract the need to store things
* to the Couchbase Cluster even in environments where we may receive
* temporary failures.
*
* @author ingenthr
*/
public class StoreHandler {
CouchbaseClient cbc;
private final List<URI> baselist;
private final String bucketname;
private final String password;
/**
*
* Create a new StoreHandler. This will not be ready until it's initialized
* with the init() call.
*
* @param baselist
* @param bucketname
* @param password
*/
public StoreHandler(List<URI> baselist, String bucketname, String password) {
this.baselist = baselist; // TODO: maybe copy this?
this.bucketname = bucketname;
this.password = password;
}
/**
* Initialize this StoreHandler.
*
* This will build the connections for the StoreHandler and prepare it
* for use. Initialization is separated from creation to ensure we would
* not throw exceptions from the constructor.
*
*
* @return StoreHandler
* @throws IOException
*/
public StoreHandler init() throws IOException {
// I prefer to avoid exceptions from constructors, a legacy we're kind
// of stuck with, so wrapped here
cbc = new CouchbaseClient(baselist, bucketname, password);
return this;
}
/**
*
* Perform a regular, asynchronous set.
*
* @param key
* @param exp
* @param value
* @return the OperationFuture<Boolean> that wraps this set operation
*/
public OperationFuture<Boolean> set(String key, int exp, Object value) {
return cbc.set(key, exp, cbc);
}
/**
* Continuously try a set with exponential backoff until number of tries or
* successful. The exponential backoff will wait a maximum of 1 second, or
* whatever
*
* @param key
* @param exp
* @param value
* @param tries number of tries before giving up
* @return the OperationFuture<Boolean> that wraps this set operation
*/
public OperationFuture<Boolean> contSet(String key, int exp, Object value,
int tries) {
OperationFuture<Boolean> result = null;
OperationStatus status;
int backoffexp = 0;
try {
do {
if (backoffexp > tries) {
throw new RuntimeException("Could not perform a set after "
+ tries + " tries.");
}
result = cbc.set(key, exp, value);
status = result.getStatus(); // blocking call, improve if needed
if (status.isSuccess()) {
break;
}
if (backoffexp > 0) {
double backoffMillis = Math.pow(2, backoffexp);
backoffMillis = Math.min(1000, backoffMillis); // 1 sec max
Thread.sleep((int) backoffMillis);
System.err.println("Backing off, tries so far: " + backoffexp);
}
backoffexp++;
if (!status.isSuccess()) {
System.err.println("Failed with status: " + status.getMessage());
}
} while (status.getMessage().equals("Temporary failure"));
} catch (InterruptedException ex) {
System.err.println("Interrupted while trying to set. Exception:"
+ ex.getMessage());
}
if (result == null) {
throw new RuntimeException("Could not carry out operation."); // rare
}
// note that other failure cases fall through. status.isSuccess() can be
// checked for success or failure or the message can be retrieved.
return result;
}
}
There is also a setting you can provide at the connection-level for Couchbase Java SDK that will also help you avoid too many asynchronous requests:
List<URI> baselist = new ArrayList<URI>();
baselist.add(new URI("http://localhost:8091/pools"));
CouchbaseConnectionFactoryBuilder cfb = new CouchbaseConnectionFactoryBuilder();
cfb.setOpTimeout(10000); // wait up to 10 seconds for an operation to succeed
cfb.setOpQueueMaxBlockTime(5000); // wait up to 5 seconds when trying to enqueue an operation
CouchbaseClient myclient = new CouchbaseClient(cfb.buildCouchbaseConnection(baselist, "default", "default", ""));
If you send too many requests all at once to Couchbase, you can create a out of memory problem, and the server will send back a temporary failure message. The message indicates you can retry the operation, however the server will not slow down significantly; it just does not handle the request. In contrast, other database systems will become slower for all operations under load.
This gives your application a bit more control since the temporary failure messages gives you the opportunity to provide a backoff mechanism and retry operations in your application logic.
Generally speaking, there is no reason to adjust any Java Virtual Machine parameters when using the Couchbase Java Client. In fact, in general you should not start with specific tuning, but instead should use defaults from the application server first, then measure application metrics such as throughput and response time. Then, if there is a need to make an improvement, make adjustments and re-measure.
The recommendations here are based on the Oracle (formerly Sun) HotSpot Virtual Machine and derivations such as the Java Virtual Machine shipped with Mac OS X and the OpenJDK project. Other Java virtual machines likely behave similarly.
It should be noted that by default, garbage collection times may easily go over 1sec. This can lead to higher than expected response times or even timeouts, as the default timeout is 2.5 seconds. This is true with simple tests even on systems with lots of CPUs and a good amount of memory.
The reason for this is that for the most part, by default, the JVM is weighted toward throughput instead of latency. Of course, much of this can be controlled with GC tuning on the JVM. With the hotspot JVM, look to this whitepaper: http://www.oracle.com/technetwork/java/javase/memorymanagement-whitepaper-150215.pdf
In the referenced whitepaper, the Concurrent Mark Sweep collector is recommended if your applciation needs short pauses. It also recommends advising the JVM to try to shorten pause times. Given the Couchbase client’s 2.5 second default timeout, with our basic testing we found the following to be useful:
-XX:+UseConcMarkSweepGC -XX:MaxGCPauseMillis=850
The whitepaper refers to a couple of tools which may be useful in gathering information on JVM GC performance. For example, adding -XX:+PrintGCDetails and -XX:+PrintGCTimeStamps are a simple way to generate log messages which you may correlate to application behavior. The logs may show a full GC event taking, perhaps, several seconds during which no processing occurs and operations may timeout. Adjusting parameters related to how to perform a full GC, which collector to use, how long to pause the VM during GC and even adding incremental mode may help, depending on your application’s workload. One other common tool for getting information is JConsole (http://docs.oracle.com/javase/6/docs/technotes/guides/management/jconsole.html). JConsole is more of an interactive tool, but it may help you identify changes you may want to make in the different memory spaces used by the JVM to further reduce the need to run a GC on the old generation.
There is a CPU time tradeoff when setting these tuning parameters. There are also other parameters which may provide additional help referenced in the whitepaper.
If you happen to be using JDK 7 update 4 or later, the G1 collector may be an even better option. Again, you should be guided by measuring performance from the application level.
Even with these, our testing showed some GCs near a half a second. While the Couchbase Client allows tuning of the timeout time to drop as low as you wish, we do not recommend dropping it much below one second unless you are planning to tune other parts of the system beyond the JVM.
For example, most people run applications on networks that do not offer any guarantee around response time. If the network is oversubscribed or minor blips occur on the network, there can be TCP retransmissions. While many TCP implementations may ignore it, RFC 2988 specifies rounding up to 1sec when calculating TCP retransmit timeouts.
Achieving either maximum throughput or minimum per-operation latency can be enhanced with JVM tuning, supported by overall system tuning at the extremes.
The following sections provide release notes for individual release versions of Couchbase Client Library Java. To browse or submit new issues, see Couchbase Client Library Java Issues Tracker.
This 1.1.8 release is the eighth bugfix release for the 1.1 series. The main additions (aside from the regular bugfixes) are support for “replica read” and “delete with CAS” through the upgrade of spymemcached to 2.9.1.
New Features and Behavior Changes in 1.1.8
Spymemcached has been upgraded to 2.9.1, which adds the capability to use delete with CAS. This allows better handling of concurrent deletes.
Here is a simple example. Note that in a production environment, proper retry on CAS failure needs to be implemented.
CASValue<Object> casvalue = c.gets("mykey");
boolean goodToDelete = yourCheckIfDocCanBeDeleted(casvalue);
if (goodToDelete) {
c.delete("mykey", casvalue.getCas());
}
Alongside with the Couchbase Server 2.1 release, the 1.1.8 version of the SDK adds support for “replica read”. This allows the developer to choose a tradeoff between data consistency and availability. Note that this operation is not intended for read scaling, but instead for fault tolerance.
Here is a (simple) example on how to use it. If asynchronous get is used, the future needs to be inspected and properly acted upon its resulting status.
Object result;
try {
result = client.get("mykey");
} catch (Exception ex) {
// Read from all replicas and return the first responding value
result = client.getFromReplica("mykey");
}
if(result != null) {
// Do something with the eventually consistent value.
}
The underlying code will “fan out” to all of the currently available replicas and return the value from the first that responds. This is because we know that the cluster is in a unstable state and the original response is already delayed until cancelled or timeouted.
Note that because of the eventually consistent nature, it can be the case that
the value has not been replicated to one of the replicas, leading to a valid
null as reponse. Also, be aware that since the cluster is currently not in a
“stable” state, wildly calling getFromReplica
for every get that failed will
only result in even more load against it.
Issues : JCBC-76
Fixes in 1.1.8
The flush()
method on the CouchbaseClient()
object can now no longer be
called after the shutdown()
method has been executed.
Issues : JCBC-323
If memcached-type buckets are used, the client now checks for inactive nodes and triggers hard reconfiguration if no new configuration arrives on time. This makes sure that clients connect to memcached-type buckets now eventually recover when the streaming connection node is removed / failed over.
Issues : JCBC-319
The code now makes sure that rebalance/reconnection is only called once at a time, making sure that there are not more connections opened than needed.
Issues : JCBC-318
Known Issues in 1.1.8
The 1.1.7 release is the seventh bugfix release for the 1.1 series. This release mostly brings better stability during rebalance and failover phases. Also, fail-fast exceptions have been implemented to disallow certain operations on memcache buckets.
New Features and Behavior Changes in 1.1.7
For lots of HTTP responses (Views, DesignDocuments) we now retry until timeout when its not a 200 success. This especially helps for transient errors and server redirects. This should bring better stability for http requests during the rebalance process.
Issues : JCBC-313
The SDK now “fails fast” when one tries to use persistence options (ReplicateTo, PersistTo) with memcached-type buckets. This is not supported and now immediately throws a exception instead of failing further down the stack.
Issues : JCBC-250
Spymemcached has been upgraded to 2.9.0. One of the major enhancements is support for SLF4J, which is now also available for the Java SDK.
Fixes in 1.1.7
In cases where there is no master partition available for the key (so in the internal vbucket map its ID is -1), now we correctly cancel the operation and also trigger reconfiguration (to make sure the system eventually recovers).
Issues : JCBC-312
Possible ConcurrentModificationException
in the IO thread are caught and we
are making sure that those type of exceptions dont terminate it. There are rare
occurences where they can occur internally, but they are temporary. Note that
they still get logged properly.
Issues : JCBC-309
Known Issues in 1.1.7
The 1.1.6 release is the sixth bugfix release for the 1.1 series. Improvements have been made around view query handling and pagination. Also, new operations without the need to provide a TTL have been added for convenience.
New Features and Behavior Changes in 1.1.6
It is now possible to pass a single “null” value in when using the ComplexKey.of() method. This will be translated to a “null” (without the quotes) in the Query string.
Issues : JCBC-177
An optimization has been implemented so when ReplicateTo.ZERO and PersistTo.ZERO are used, the persistence constraint checks are avoided completely (and treated the same as if they are not present at all). Previously, a potentially costly but not needed “observe” call has been executed and therefore decreased performance.
Issues : JCBC-268
The View Paginator has been completely refactored internally. It is now possible to use it for reduced views as well. Note that it should be more efficient because only one HTTP call is done behind the scenes instead of two for every iteration.
Because of the current API, in odd cases, Pagination with numbers in strings on view keys can lead to infinte loops. To address this issue, a new method has been added which allows one to force a certain key type even if its not a string value. Please see the Paginator#forceKeyType() method for more information. In general, this will be not needed and should only be used when those inifite loops occur.
Also note that this is considered “band aid” and is in place because we don’t want to change the API completely in the 1.1 series. For a future minor release (most likely 1.2), the API for views will change a bit so keys don’t directly get casted to strings and therefore loose the type information needed to address this issue. We will then depcrecate this method.
Issues : JCBC-241
set, add and replace methods have been overloaded so you don’t need to provide a TTL of 0 all the time if it is not needed. You can now use shorther method calles like:
client.set("key", "{"name":"My JSON doc with no TTL"}");
Issues : JCBC-284
Fixes in 1.1.6
An issue when parsing view query params has been fixed. Previously, a key like “123ABC” has been incorrectly sent as key=123 over the wire (ComplexKex and setKeys have been working correctly).
Issues : JCBC-288
The 1.1.5 release is the fifth bugfix release for the 1.1 series. It fixes a bug around the ConnectionFactoryBuilder and includes better interoperability with Netty.
New Features and Behavior Changes in 1.1.5
The distributed javadoc jar file has been renamed from “javadocs” to “javadoc” for better standard compliance and IDE compatibility.
Issues : JCBC-232
Fixes in 1.1.5
If the CouchbaseClient object was used inside a Netty worker thread (for example when used together with the Play! framework), Netty would complain because the Client object blocked when loading the configuration (also using Netty) and this is not allowed. The blocking mechanism has now been moved out to a separate thread, so Netty won’t complain anymore and the CouchbaseClient object can now be used without issues inside worker threads.
Issues : JCBC-135
The CouchbaseConnectionFactoryBuilder has been modified so that the same default settings are used as if the ConnectionFactory is initialized directly. Previously, this could lead to a bug where when the FactoryBuilder was used to connect to the cluster, a silently dying streaming connection was never detected and not reestablished.
Issues : JCBC-278
This issue only affects connections to memcache-type buckets. In some cases, it could’ve been the case that when nodes are added, they were not instantly marked as active and put on the reconnect queue. This could lead to some odd race conditions and a failure state that could not be recovered properly. This has been fixed.
Issues : JCBC-271
The 1.1.4 release is the fourth bugfix release for the 1.1 series. It mainly fixes a bug introduced in 1.1.3 that comes up when trying to use it with buckets of type “Memcached”.
Fixes in 1.1.4
When using the 1.1.3 release in combination with a bucket of type “memcached”, the initial client construction fails with an Exception. This was because of the previously introduced backoff on warmup, which uses the number of vBuckets to determine the warmup state. The problem associated with this enhancement is that “memcached” buckets do not use the concept of vbuckets at all. This has been fixed, warmup is now ignored for those bucket types.
Issues : JCBC-261
When using the ConnectionFactory during CouchbaseClient initialization, it could happen that some of the properties were not correctly initialized wih default values and this can lead to NPEs when those values get accessed later. Proper default values are now used automatically when not provided otherwise.
Issues : JCBC-257
The 1.1.3 release is the third bugfix release for the 1.1 series. Most of the improvements center around persistence and replication constraints, other small bugs have also been fixed.
ATTENTION: do not upgrade to this release when using buckets of type “memcached”. The code will throw an exception during the construction of the CouchbaseClient object. Regular “couchbase” buckets are not affected and we recommend all deployments on the 1.1-branch to upgrade. A proper fix for this known issue is under way and a 1.1.4 release is due shortly.
New Features and Behavior Changes in 1.1.3
Spymemcached has been updated to 2.8.12. This version includes corresponding enhancements and bugfixes that are needed by this release.
When running a cluster and during a failure scenario all nodes provided on the bootstrap list are not accessible, the Client now more intelligently backoffs for a certain amount of time and sleeps to save precious CPU resources. Before this change, the Client did try as fast as it can. The maximum interval between backoff is 10 seconds.
Issues : JCBC-27
When an operation is now cancelled, a CancellationException is now thrown instead of a RuntimeException. Since CancellationExceptions inherit from RuntimeExceptions, this is not an API change and old code will still work, but new code can benefit by distinguishing it from others explicitely.
Issues : JCBC-210
Fixes in 1.1.3
Various bugfixes have been implemented that improve the stability and fault-tolerance of mutation commands in combination with replication and persistence contstaints (that is, all methods that take ReplicateTo and/or PersistTo ENUMs such as set, replace, add or delete). If you see Exceptions in older versions in combination with those commands, there is a strong possibility that they have been addressed here.
Also, methods with those signatures incorrectly only accepted Strings as values. Since they just call the underlying methods, this is wrong and has been corrected. You can now use all kinds ob Objects with those methods and make use of the built-in transcoding mechanisms.
Known Issues in 1.1.3
A bug in the newly implemented warmup backoff algorithm causes the 1.1.3 release to malfunction when using it in combination with “memcached” type buckets. Regular “couchbase” buckets are not affected.
Issues : JCBC-261
The 1.1.2 release is the second bugfix release for the 1.1 series. Failover and rebalance resiliency have been improved, and also a bug regarding persistence constraints and replicas has been fixed. Spymemcached has been upgraded to 2.8.11, which now does not try to reconnect when the node is not part of the cluster anymore.
New Features and Behavior Changes in 1.1.2
Spymemcached has been upgraded to 2.8.11. 2.8.11 fixes a important bug that can happen when using commands like “stats” and memcached-buckets together, where the node list was not updated correctly. This may also affect broadcast operations. In addition, when using authenticated buckets the code now only tries to reconnect when the node is still part of the cluster (by checking against the node map). This is not an issue on its own, but should give the IO thread more time handling IO and not trying to reconnect when not necessary.
When the Streaming connection dies and no node is available to reconnect, then the client tried as fast as it can to reestablish the connection. This may lead to unnecessary CPU usage. The fix implements a backoff algorithm that increases from 1 second to max 10 seconds between retries. There is no maximum time where it stops, so once one of the nodes in the list is back online, it will be able to reconnect. INFO-level log messages indicate the reconnect attempts.
Issues : JCBC-227
When writing view operations to nodes, previously when one node had no connection available, ultimately the operation was cancelled. Now, when one node is not able to provide a connection in a reasonable time (max of 5 seconds), then a different node is tried. After a max. of 6 retries, the operation is considered cancelled (this would mean 6*5=30 seconds).
In general, this will slightly improve performance, but also help when nodes have problems fulfilling requests and when shutting down.
Fixes in 1.1.2
When the Couchbase Server closes the HTTP Chunked connection properly (during a rebalance finish), it can be the case that a different Netty event is generated that has not been caught preivously. This led to a case where the streaming connection never got reestablished fully, leading to a potentially “deaf” client.
Issues : JCBC-219
When running against a 1-node cluster with replicas enabled (which is done by default) and using the persistence constraints with ReplicateTo.One or more, it created an IndexOutOfBoundsException. The client now properly checks for the replica count when using “observe”. Note that this does not affect 1-node buckets when replication has been disabled completely.
Issues : JCBC-223
The 1.1.1 release is the first bugfix release for the 1.1 series. It brings more resiliency on failover, adds more flexibility for view query params and adds a (by default disabled) stats-based throttling mechanism.
New Features and Behavior Changes in 1.1.1
An adaptive throttling mechanism has been implemented that is particularily useful when running bulk load operations while maintaining a healthy throughput from a different running application at the same time. The throttler is disabled by default and can be enabled through properties.
The adaptive throttling mechanisms works on top of memory thresholds that it
fetches every N operations from the cluster. If the memory is higher than a
certain level, operations are throttled until everything is back to normal. See
the cbclient.properties.dist
for all available options.
Note that this code should normally not enabled by default, because it negatively impacts the throughput and latency against your cluster (which is okay if you want the tradeoff in particular situations).
Issues : JCBC-212
Behind the scenes, property management has been refactored into a centralized
class. While this doesn’t change anything in the first place, all properties
should now have the cbclient.
prefix, so that they are picked up
automatically. As always, when there is a cbclient.properties
file around in
the CLASSPATH, it is picked up automatically.
The only notable difference (for backwards compatibility) is the viewmode
property, which can be used with or without the cbclient.
prefix.
Spymemcached has been updated to 2.8.10, which includes a fix that improves timeouts during node failure situations.
When working with the ComplexKey
class for view queries, it is now possible to
use all kinds of numbers (not only doubles).
Issues : JCBC-190
Fixes in 1.1.1
A bug in the reconnection logic has been fixed that would negatively impact the reconnect threshold when a node is considered down but a cluster config update has not yet reached the client (or the entry node has been failed over/killed).
Also, possible hanging nodes (that is the socket is open but the process may be dead) are now properly detected and should not cause infinited timeouts and block the client.
Known Issues in 1.1.1
The flush()
command now works over HTTP, but is currently not working because
of an open issue inside Couchbase Server 2.0 (MB-7381). A workaround is to use
the ClusterManager with Administrator privileges in the meantime.
Issues : JCBC-144
The 1.1.0 release adds all features and tools neded to work against Couchbase Server 2.0 with Java.
This especially includes support for the brand-new view engine. The following list includes the major features and bugfixes compared to the 1.0.* releases. For more detailed release notes, see the developer preview and beta releases.
Also, the Getting Started Guide and the Tutorial have been updated and can be used together with the 1.1.0 release.
New Features and Behavior Changes in 1.1.0
This release adds the possibility of providing a durability setting that allows to make sure data is replicated but not persisted. This may speed up operations while allowing to maintain a reasonable safety net at the same time (wait until the operation has been replicated to the given number of nodes). Also, every command takes either ReplicateTo, PersistTo or both.
Here is an usage example of an add operation which makes sure to replicate to at least one node:
// With ReplicateTo only
client.add("mykey", 0, "value", ReplicateTo.ONE);
// Identical to this
client.add("mykey", 0, "value", PersistTo.ZERO, ReplicateTo.ONE);
Support for spatial view queries has been added to the SDK. Not that at this stage, spatial view queries are expermimental and should be treated as such. Since spatial queries differ a little bit from classic map/reduce ones, a new “bbox” param has been added to the Query object as well to accomodate the needs. Note that there has been taken lots of care not to break the current API by adding this feature. The main difference to normal map/reduce is to use the “getSpatialView” method instead of the “getView” method on the CouchbaseClient object. Here is a short example on how to use it:
SpatialView view = client.getSpatialView("my_design", "my_spatial_view");
Query query = new Query();
ViewResponse response = client.query(view, query);
for(ViewRow row : response) {
// Work with bbox data: row.getBbox();
// Work with geometry data: row.getGeometry();
// Work with the value: row.getValue();
}
Issues : JCBC-136
ComplexKey querying support has been enhanced and is now more flexible when it comes to Long values, nulls and others.
View Query Timeouts can now be configured through the
CouchbaseConnectionFactoryBuilder (using the setViewTimeout()
method).
Issues : JCBC-168
A new ComplexKey class has ben added as a utility to define view query options. This makes it possible to convert Java types into their corresponding JSON representations as easy as possible. Also, this avoids the need to encode the values by hand to encode them properly. Passing in a string does still work, but the ComplexKey approach is recommended when working with arrays or other more complex JSON structures.
Here is a short example on how to use it properly:
// JSON Result: 100
ComplexKey.of(100);
// JSON Result: "Hello"
ComplexKey.of("Hello");
// JSON Result: ["Hello", "World"]
ComplexKey.of("Hello", "World");
// JSON Result: [1349360771542,1]
ComplexKey.of(new Date().getTime(), 1);
When querying a view it looks like this:
long now = new Date().getTime();
long tomorrow = now + 86400;
Query query = new Query();
query.setRange(ComplexKey.of(now), ComplexKey.of(tomorrow));
// Converts to: ?startkey=1349362647742&endkey=1349362734142
The Views functionality has been added to the Couchbase-client library. Views is available starting with Couchbase Server 2.0. For more details on this feature refer to Couchbase Server Manual.
The purpose of a view is take the structured data stored within your Couchbase Server database as JSON documents, extract the fields and information that is needed, and to produce an index of the selected information. The result is a view on the stored data.
The view that is created during this process can be iterated, selected and
queried with the information in the database from the raw data objects that have
been stored using Java objects such as View
, Query
, ViewFuture
,
ViewResponse
and ViewRow
.
The following code fragment illustrates how to use the objects and access the view.
Query query = new Query();
query.setReduce(false);
query.setIncludeDocs(true);
query.setStale(Stale.FALSE);
// Specify the design and document (by default production mode)
View view = client.getView("chat", "messages");
if (view == null) {
// Take corrective action
}
ViewResponse result = client.query(view, query);
Iterator<ViewRow> itr = result.iterator();
ViewRow row;
while (itr.hasNext()) {
row = itr.next();
String doc = (String) row.getDocument();
// Do something for each row
}
Starting from this beta release, it is now possible to manage design documents through the SDK as well. Design documents can be created, loaded and deleted through their corresponding methods from the CouchbaseClient class. Use the “createDesignDoc()” and “deleteDesignDoc()” methods for creating or deleting design documents as a whole. Here is a short example on how to use it:
List<ViewDesign> views = new ArrayList<ViewDesign>();
List<SpatialViewDesign> spviews = new ArrayList<SpatialViewDesign>();
ViewDesign view1 = new ViewDesign(
"view1",
"function(a, b) {}"
);
views.add(view1);
SpatialViewDesign spview = new SpatialViewDesign(
"spatialfoo",
"function(map) {}"
);
spviews.add(spview);
DesignDocument doc = new DesignDocument("mydesign", views, spviews);
Boolean success = client.createDesignDoc(doc);
Note that creating design documents may take some time, so make sure to wait some time and poll with “getDesignDocument()” to see if it is already correctly loaded.
Issues : JCBC-63
Fixes in 1.1.0
When no replicas are defined in the cluster and a node fails (or more nodes fail at the same time than replicas are defined), then it is possible that there is no single master responsible for a given vbucket. This has been a problem in previous tests and is now handled that a RuntimeException is thrown which a explanatory message. Since the cluster may now be in a very bad state, it is up to the developer at the application level to ensure the correct behavior of the following operation (since this may be very different depending on the application requirements).
Issues : JCBC-123
Netty has been upgraded to 3.5.5.
Issues : JCBC-106
Operation Status message has been changed from hard coded value to be based on tunable parameters.
Issues : JCBC-107
When using memcache-type buckets instead of couchbase-type buckets, the reconfiguration should now work as expected. The client previously tried to compare the vbuckets all the time, but since memcache-type buckets don’t contain vbuckets it failed (now only the server nodes themselves are compared).
Issues : JCBC-35
Known Issues in 1.1.0
The flush()
command now works over HTTP, but is currently not working because
of an open issue inside Couchbase Server 2.0 (MB-7381). A workaround is to use
the ClusterManager with Administrator privileges in the meantime.
Issues : JCBC-144
New Features and Behavior Changes in 1.1-beta
The “delete” command now also supports persitence constraints like set, add, replace or CAS do.
client.delete("my_key", PersistTo.MASTER, ReplicateTo.ONE);
Issues : JCBC-162
To assist with better debugging on view queries, a new method on the “Query” class, “setDebug(boolean)” has been introduced. When it is set to true, the client will log the view results including debug information to the INFO level, which is by default turned on. This can be very helpful on debugging misbehaving view queries and to provide vital information for support tickets.
Issues : JCBC-158
The regular view timeout has been increased from 60 to 75 seconds (because the server-side view timeout is 60 seconds and now the corresponding timeout on the client is slightly higher). It is now possible to configure it through the CouchbaseConnectionFactoryBuilder. Use the corresponding “setViewTimeout” method on it if you want to change the setting. Note that the lower threshold is set to 500ms, and it will print a warning if it’s lower than 2500ms because this can lead to undesired effects in production.
Issues : JCBC-153
Support for spatial view queries has been added to the SDK. Not that at this stage, spatial view queries are expermimental and should be treated as such. Since spatial queries differ a little bit from classic map/reduce ones, a new “bbox” param has been added to the Query object as well to accomodate the needs. Note that there has been taken lots of care not to break the current API by adding this feature. The main difference to normal map/reduce is to use the “getSpatialView” method instead of the “getView” method on the CouchbaseClient object. Here is a short example on how to use it:
SpatialView view = client.getSpatialView("my_design", "my_spatial_view");
Query query = new Query();
ViewResponse response = client.query(view, query);
for(ViewRow row : response) {
// Work with bbox data: row.getBbox();
// Work with geometry data: row.getGeometry();
// Work with the value: row.getValue();
}
Issues : JCBC-136
To “fail fast” on observe constraints (when operations like “set” are used with PersistTo and/or ReplicateTo), the client now checks if there are enough nodes in the cluster to theoretically fulfill the request. This means if PersistTo.THREE is used when there are only two nodes in the cluster, the operations may succeed but the observation surely fails because the constraint can’t be fulfilled. In this case, the OperationFuture would return false and the message respons with something like this: “Currently, there are less nodes in the cluster than required to satisfy the replication/persistence constraint.”. Keep in mind that replication constraints always require one additional node to be fulfilled correctly. So a ReplicateTo.TWO with only two nodes in the cluster will fail.
Issues : JCBC-148
Starting from this beta release, it is now possible to manage design documents through the SDK as well. Design documents can be created, loaded and deleted through their corresponding methods from the CouchbaseClient class. Use the “createDesignDoc()” and “deleteDesignDoc()” methods for creating or deleting design documents as a whole. Here is a short example on how to use it:
List<ViewDesign> views = new ArrayList<ViewDesign>();
List<SpatialViewDesign> spviews = new ArrayList<SpatialViewDesign>();
ViewDesign view1 = new ViewDesign(
"view1",
"function(a, b) {}"
);
views.add(view1);
SpatialViewDesign spview = new SpatialViewDesign(
"spatialfoo",
"function(map) {}"
);
spviews.add(spview);
DesignDocument doc = new DesignDocument("mydesign", views, spviews);
Boolean success = client.createDesignDoc(doc);
Note that creating design documents may take some time, so make sure to wait some time and poll with “getDesignDocument()” to see if it is already correctly loaded.
Issues : JCBC-63
The “getViews” method has been renamed to “getDesignDocument” and the arguments have been changed to make it easier to use in combination with “createDesignDocument” and “deleteDesignDocument”. The method takes the name of the design document and returns an instance of a “DesignDocument”. This can be modified and then passed back to “createDesignDocument” to update it accordingly. Here is a quick example:
DesignDocument design = client.getDesignDocument("rawdesign");
design.getName(); // Returns the name of the design document
design.getViews(); // Contains the stored views
design.getSpatialViews(); // contains the stored spatial views
Issues : JCBC-147
Fixes in 1.1-beta
When a view with a defined reduce-function is used, the client now implicitly sets “setReduce()” to “true”. This behavour is now much more intuitive and also irons-out some bugs associated with it.
Issues : JCBC-150
Pagination support got a lot more attention in the previous weeks and therefore some bugs have been ironed out. NullPointerExceptions should not be raised anymore. Note that is currently not possible to paginate over reduced or spatial results, this will be added in a future release.
Issues : JCBC-40
When no replicas are defined in the cluster and a node fails (or more nodes fail at the same time than replicas are defined), then it is possible that there is no single master responsible for a given vbucket. This has been a problem in previous tests and is now handled that a RuntimeException is thrown which a explanatory message. Since the cluster may now be in a very bad state, it is up to the developer at the application level to ensure the correct behavior of the following operation (since this may be very different depending on the application requirements).
Issues : JCBC-123
It is now possible to read every kind of included document through views. Previously all documents were casted to strings when fetched with “setIncludeDocs(true)”, which did not allow it to be (for example) serialized java objects. This is now possible since those objects are not implicitly casted to strings anymore. Note that this only applies to the included documents, view results are still interpreted as strings (JSON).
Issues : JCBC-125
When using memcache-type buckets instead of couchbase-type buckets, the reconfiguration should now work as expected. The client previously tried to compare the vbuckets all the time, but since memcache-type buckets don’t contain vbuckets it failed (now only the server nodes themselves are compared).
Issues : JCBC-35
Additional checkpoints have been added to make sure all ViewConnection threads are properly closed during the shutdown phase.
Issues : JCBC-94
New Features and Behavior Changes in 1.1-dp4
It is now possible to create and delete buckets from the client directly, without the need to switch to the UI or use the REST API. The functionality is provided through the new ClusterManager class. Here is a short example on how to use it:
// Connect with cluster admin and password
ClusterManager manager = new ClusterManager(uris, "Administrator", "password");
// Create a new bucket with authentication
manager.createSaslBucket(BucketType.COUCHBASE, "saslbucket", 100, 0, "password");
// Delete the bucket again
manager.deleteBucket("saslbucket");
See the documentation for more details and usage examples.
Issues : JCBC-64
This release adds the possibility of providing a durability setting that allows to make sure data is replicated but not persisted. This may speed up operations while allowing to maintain a reasonable safety net at the same time (wait until the operation has been replicated to the given number of nodes). Also, every command takes either ReplicateTo, PersistTo or both.
Here is an usage example of an add operation which makes sure to replicate to at least one node:
// With ReplicateTo only
client.add("mykey", 0, "value", ReplicateTo.ONE);
// Identical to this
client.add("mykey", 0, "value", PersistTo.ZERO, ReplicateTo.ONE);
A new ComplexKey class has ben added as a utility to define view query options. This makes it possible to convert Java types into their corresponding JSON representations as easy as possible. Also, this avoids the need to encode the values by hand to encode them properly. Passing in a string does still work, but the ComplexKey approach is recommended when working with arrays or other more complex JSON structures.
Here is a short example on how to use it properly:
// JSON Result: 100
ComplexKey.of(100);
// JSON Result: "Hello"
ComplexKey.of("Hello");
// JSON Result: ["Hello", "World"]
ComplexKey.of("Hello", "World");
// JSON Result: [1349360771542,1]
ComplexKey.of(new Date().getTime(), 1);
When querying a view it looks like this:
long now = new Date().getTime();
long tomorrow = now + 86400;
Query query = new Query();
query.setRange(ComplexKey.of(now), ComplexKey.of(tomorrow));
// Converts to: ?startkey=1349362647742&endkey=1349362734142
Fixes in 1.1-dp4
When a HTTP operation is cancelled, it is now ensured that the corresponding HTTP request is also cancelled. This prevents a possible issue where the calling thread is blocked longer than needed.
Issues : JCBC-30
The CouchbaseClient now does not try to establish view connections on memcache buckets. This makes it possible to connect to memcache buckets on the 1.1 series again.
Issues : JCBC-121
Behind the scenes, the HTTP query parameters for the views are now properly encoded. This means that passing characters like spaces don’t break view queries anymore. This fix also ensures that query parameters generated through ComplexKey.of work properly.
Issues : JCBC-126
The update_seq param has been removed from the possible Query options because it has also been removed from Couchbase Server 2.0 as well.
Fixes in 1.1-dp3
Default Observe poll latency has been changed to 100ms.
Issues : JCBC-109
The options with Views as documented in http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views-writing-querying-errorcontrol.html is available. The options STOP and CONTINUE can be set as below.
query.setOnError(OnError.CONTINUE);
Issues : JCBC-25
Netty has been upgraded to 3.5.5.
Issues : JCBC-106
Operation Status message has been changed from hard coded value to be based on tunable parameters.
Issues : JCBC-107
Known Issues in 1.1-dp3
The Paginator object has been changed to handle this. The following code listing illustrates how to use the Paginator object and iterate through the pages and between the rows.
Paginator result = client.paginatedQuery(view, query, 15);
while (result.hasNext()) {
ViewResponse response = result.next();
for (ViewRow row: response) {
System.out.println("Next Row: " + row.getId());
}
System.out.println("<=== Page ====>");
}
Issues : JCBC-40
New Features and Behavior Changes in 1.1-dp2
The set()
and delete()
methods now support the ability to observe the
persistence on the master and replicas. Using these methods, it’s possible to
set the persistence requirements for the data on the nodes.
These methods are supported in Couchbase server build 1554 or higher.
The persistence requirements can be specified in terms of how the data should be
persisted on the master and the replicas using PeristTo
and ReplicateTo
respectively.
The client library will poll the server until the persistence requirements are met. The method will return FALSE if the requirements are impossible to meet based on the configuration (inadequate number of replicas) or even after a set amount of retries the persistence requirements could not be met.
The program snippet below illustrates how to specify a requirement that the data should be persisted on 4 nodes (master and three replicas).
// Perist to all four nodes including master
OperationFuture<Boolean> setOp =
c.set("key", 0, "value", PersistTo.FOUR);
System.out.printf("Result was %b", setOp.get());
The program snippet below illustrates how to specify a requirement that the data deletion should be persisted on 4 nodes (master and three replicas).
// Perist of delete to all four nodes including master
OperationFuture<Boolean> deleteOp =
c.delete("key", PersistTo.FOUR);
System.out.printf("Result was %b",deleteOp.get());
The program snippet below illustrates how to specify a requirement that the data deletion should be persisted on 4 nodes (master and three replicas).
// Perist of delete to all four nodes including master
OperationFuture<Boolean> deleteOp =
c.delete("key", PersistTo.FOUR);
System.out.printf("Result was %b",deleteOp.get());
The peristence requirements can be specified for both the master and replicas. In the case above, it’s required that the key and value is persisted on all the 4 nodes (including replicas).
The same persistence requirement can be specified in a slightly different form as below.
// Perist to master and three replicas
OperationFuture<Boolean> setOp =
c.set("key", 0, "value", PersistTo.MASTER, ReplicateTo.THREE);
System.out.printf("Result was %b", setOp.get());
The same persistence requirement can be specified in a slightly different form as below.
// Perist of delete to master and three replicas
OperationFuture<Boolean> deleteOp =
c.delete("key", PersistTo.MASTER, ReplicateTo.THREE);
System.out.printf("Result was %b", deleteOp.get());
Fixes in 1.1-dp2
The Java client library throws exception for non-200 http view responses.
Issues : JCBC-72
The issue is now fixed.
Issues : JCBC-20
The Getting Started has a brief explanation of JSON and has a simple example of persisting JSON data.
Issues : JCBC-97
This issue is now fixed.
Issues : JCBC-68
This issue is now fixed.
Issues : JCBC-69
Known Issues in 1.1-dp2
unlock() method does not check for server errors. The method should check for the error and raise an exception.
Issues : SPY-97
New Features and Behavior Changes in 1.1-dp
The Views functionality has been added to the Couchbase-client library. Views is available starting with Couchbase Server 2.0. For more details on this feature refer to Couchbase Server Manual.
The purpose of a view is take the structured data stored within your Couchbase Server database as JSON documents, extract the fields and information that is needed, and to produce an index of the selected information. The result is a view on the stored data.
The view that is created during this process can be iterated, selected and
queried with the information in the database from the raw data objects that have
been stored using Java objects such as View
, Query
, ViewFuture
,
ViewResponse
and ViewRow
.
The following code fragment illustrates how to use the objects and access the view.
Query query = new Query();
query.setReduce(false);
query.setIncludeDocs(true);
query.setStale(Stale.FALSE);
// Specify the design and document (by default production mode)
View view = client.getView("chat", "messages");
if (view == null) {
// Take corrective action
}
ViewResponse result = client.query(view, query);
Iterator<ViewRow> itr = result.iterator();
ViewRow row;
while (itr.hasNext()) {
row = itr.next();
String doc = (String) row.getDocument();
// Do something for each row
}
Fixes in 1.1-dp
CouchbaseConnectionFactory
which was not being closed properly has been
fixed. The TapConnectionProvider
patch has been integrated.