A newer version of this documentation is available.

View Latest

Retrieving documents

Describes how to load documents using the various get() methods.

Regular reads

To read a document use the get() method. Either pass in the id of the Document or the Document from which the id is taken from.

JsonDocument loadedFromId = bucket.get("id");
JsonDocument loadedFromDoc = bucket.get(JsonDocument.create("id"));

Both methods have the same effect. The latter method is helpful if you are already dealing with Document instances in your code and you don’t want to extract the ID out of them on your own.

When only the ID is passed in, there is no way to figure out which Document type should be used, so JsonDocument is selected as a sensible default. If you want to override this, you can pass in a specific Document type like this:

LegacyDocument loaded = bucket.get("legacyId", LegacyDocument.class);

If the document is not found, the blocking API returns null.:

JsonDocument found = bucket.get("notexisting");
if (found == null) {
// doc not found
} else {
// doc found
}

If you are dealing with asynchronous code, an empty observable is returned instead. This aligns with how Observable objects are supposed to work by contract, but also makes it easier to deal with the implementation later. If no document is returned, the subsequent operations are just not executed, which avoids having null checks all over the place (if for example a Document would be returned but with the content set to null).

Observable<JsonDocument> updateIfFound = bucket.async().get(potentialNonExistingKey)
	.map(doc -> doc.content())
	.filter(jsonObject -> jsonObject.containsKey("test"))
	.subscribe(
		//onNext only invoked if the key could be retrieved
		data -> System.out.println("Data exists and has test key"),
		error::printStackTrace,
		() -> System.out.println("Completed"));

Reading from replica

A regular read always reads the document from its master node. If this node is down or not available, the document cannot be loaded. Reading from replica allows you to load the document from one or more replica nodes instead.

When replica reads are used, always use them under the assumption that the data returned is stale. There is no way to guarantee that the data is up-to-date on the replica node unless the proper durability requirements have been set and succeeded on write operations. Only use replica reads if you understand the implications.

You can either read the data from one specific replica or all of the available replicas:

// Read from all available replicas and the master node and return all responding
bucket.getFromReplica("id", ReplicaMode.ALL);

// Read only from the first replica
bucket.getFromReplica("id", ReplicaMode.FIRST);

// Read only from the second replica
bucket.getFromReplica("id", ReplicaMode.SECOND);

// Read only from the third replica
bucket.getFromReplica("id", ReplicaMode.THIRD);
If ReplicaMode.ALL is used, requests are sent to the master node and all configured replicas.

The main goal is to get responses back as fast as possible, but because more requests are sent, more responses can arrive. You can use this to either compare all of the responding documents and draw conclusions, or just pick the first one arriving:

bucket
	.async()
    .getFromReplica("id", ReplicaMode.ALL)
    .first()
    .subscribe();

In addition, you can add operations to filter based on some of your assumptions. Imagine you have a version field in your document and you want to only use the replica information if it this specific version:

bucket
	.async()
    .getFromReplica("id", ReplicaMode.ALL)
    .filter(document -> document.content().getInt("version") > 5)
    .first()
    .subscribe();

Reading and locking

Reading and locking works very similar to a regular read, but in addition the Document is write locked (not read locked) on the server side for the given amount of time.

// Get and lock for 10 seconds
JsonDocument doc = bucket.getAndLock("id", 10);
You can only write lock a document for a maximum of 30 seconds. If an invalid lock time (less than 0 or greater than 30 seconds) is provided, 15 seconds is used as the default.

The Document is unlocked under the following conditions:

  • The unlock() command is used.

  • The Document is replaced with the correct CAS value.

  • 30 seconds are over, and the server unlocks it for you.

The following example shows the case with optimistic locking, where the locked document is automatically released on error so it can be used by other clients.

JsonDocument doc = JsonDocument.create(
	"stats",
	JsonObject.empty().put("sold", 0).put("bought", 0)
);
bucket.upsert(doc);

doc = bucket.getAndLock("stats", 20);
try {
    doc.content().put("sold", doc.content().getInt("sold") + 1);
    // Fake a processing error
    if (new Random().nextInt(100) < 30) {
        throw new RuntimeException("processing error");
    }
} catch (RuntimeException ex) {
    bucket.unlock("stats", doc.cas());
}

Reading and touching

Reading and touching works very similar to a regular read, but it also refreshes the expiration time of the document to the specified value.

// Get and set the new expiration time to 4 seconds
JsonDocument doc = bucket.getAndTouch("id", 4);

You can also use the touch() command if you do not want to read the document and just refresh its expiration time.

If you specify an expiration time greater than 30 days in seconds (60 seconds * 60 minutes * 24 hours * 30 days = 2,592,000 seconds), it is considered an absolute timestamp instead of a relative one.