Common patterns and use cases for FoundationDB Java bindings.
Store and retrieve user data with structured keys:
import com.apple.foundationdb.*;
import com.apple.foundationdb.subspace.Subspace;
import com.apple.foundationdb.tuple.Tuple;
Subspace users = new Subspace(Tuple.from("users"));
// Create user
db.run(tr -> {
long userId = 1001L;
byte[] profileKey = users.pack(Tuple.from(userId, "profile"));
byte[] emailKey = users.pack(Tuple.from(userId, "email"));
tr.set(profileKey, "Alice Smith".getBytes());
tr.set(emailKey, "alice@example.com".getBytes());
return null;
});
// Get user profile
String profile = db.run(tr -> {
byte[] key = users.pack(Tuple.from(1001L, "profile"));
byte[] value = tr.get(key).join();
return value != null ? new String(value) : null;
});
// List all users
List<Long> userIds = db.read(tr -> {
List<Long> result = new ArrayList<>();
Range range = users.range(Tuple.from(1001L));
for (KeyValue kv : tr.getRange(range)) {
Tuple key = users.unpack(kv.getKey());
if (key.size() == 2 && key.getString(1).equals("profile")) {
result.add(key.getLong(0));
}
}
return result;
});Implement atomic counters:
import java.nio.ByteBuffer;
// Increment counter atomically
db.run(tr -> {
byte[] key = "counter".getBytes();
byte[] increment = ByteBuffer.allocate(8).putLong(1).array();
tr.mutate(MutationType.ADD, key, increment);
return null;
});
// Get counter value
long count = db.run(tr -> {
byte[] value = tr.get("counter".getBytes()).join();
return value != null ? ByteBuffer.wrap(value).getLong() : 0L;
});Create and query secondary indexes:
Subspace users = new Subspace(Tuple.from("users"));
Subspace emailIndex = new Subspace(Tuple.from("email_index"));
// Write user with index
db.run(tr -> {
long userId = 1001L;
String email = "alice@example.com";
// Primary data
byte[] userKey = users.pack(Tuple.from(userId));
tr.set(userKey, "Alice".getBytes());
// Index entry
byte[] indexKey = emailIndex.pack(Tuple.from(email, userId));
tr.set(indexKey, new byte[0]);
return null;
});
// Query by email
Long userId = db.read(tr -> {
Range emailRange = emailIndex.range(Tuple.from("alice@example.com"));
for (KeyValue kv : tr.getRange(emailRange)) {
Tuple indexKey = emailIndex.unpack(kv.getKey());
return indexKey.getLong(1); // Return userId
}
return null;
});Process multiple operations efficiently:
// Batch write
db.run(tr -> {
for (int i = 0; i < 1000; i++) {
byte[] key = ("item:" + i).getBytes();
byte[] value = ("value:" + i).getBytes();
tr.set(key, value);
}
return null;
});
// Batch read
Map<String, String> results = db.read(tr -> {
Map<String, String> map = new HashMap<>();
List<CompletableFuture<byte[]>> futures = new ArrayList<>();
List<String> keys = new ArrayList<>();
for (int i = 0; i < 100; i++) {
String keyStr = "item:" + i;
keys.add(keyStr);
futures.add(tr.get(keyStr.getBytes()));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
for (int i = 0; i < futures.size(); i++) {
byte[] value = futures.get(i).join();
if (value != null) {
map.put(keys.get(i), new String(value));
}
}
return map;
});Isolate data per tenant:
import com.apple.foundationdb.tuple.Tuple;
// Create tenant
TenantManagement.createTenant(db, "tenant1".getBytes()).join();
// Use tenant
Tenant tenant = db.openTenant("tenant1".getBytes());
tenant.run(tr -> {
// Operations are isolated to this tenant
tr.set("key".getBytes(), "value".getBytes());
return null;
});
// Read from tenant
String value = tenant.read(tr -> {
byte[] result = tr.get("key".getBytes()).join();
return result != null ? new String(result) : null;
});Use async operations for better concurrency:
// Async transaction
CompletableFuture<Void> future = db.runAsync(tr -> {
return tr.get("key1".getBytes())
.thenCompose(value1 -> {
tr.set("key2".getBytes(), value1);
return tr.get("key3".getBytes());
})
.thenCompose(value3 -> {
tr.set("key4".getBytes(), value3);
return tr.commit();
});
});
future.thenRun(() -> System.out.println("Transaction completed"));Implement pagination for large datasets:
// Paginated range query
List<KeyValue> getPage(Database db, byte[] startKey, int pageSize) {
return db.read(tr -> {
KeySelector begin = KeySelector.firstGreaterOrEqual(startKey);
KeySelector end = KeySelector.firstGreaterOrEqual("end:".getBytes());
AsyncIterable<KeyValue> range = tr.getRange(
begin, end, pageSize, false, StreamingMode.WANT_ALL
);
return range.asList().join();
});
}
// Usage
byte[] lastKey = "start:".getBytes();
List<KeyValue> page = getPage(db, lastKey, 50);Monitor keys for changes:
// Set up watch
CompletableFuture<Void> watch = db.run(tr -> {
CompletableFuture<Void> w = tr.watch("config:refresh".getBytes());
tr.commit().join();
return w;
});
// Watch triggers when key changes
watch.thenRun(() -> {
System.out.println("Configuration changed, reloading...");
// Reload configuration
});Manual transaction management with retry:
boolean success = false;
int retries = 0;
int maxRetries = 10;
while (!success && retries < maxRetries) {
try (Transaction tr = db.createTransaction()) {
// Perform operations
tr.set("key".getBytes(), "value".getBytes());
tr.commit().join();
success = true;
} catch (FDBException e) {
if (e.isRetryable()) {
retries++;
Thread.sleep(100 * retries); // Exponential backoff
} else {
throw e;
}
}
}Use directory layer for hierarchical organization:
import com.apple.foundationdb.directory.*;
DirectoryLayer dir = new DirectoryLayer();
// Create directory
DirectorySubspace appDir = dir.createOrOpen(
db, Arrays.asList("myapp", "users")
).join();
// Use directory as subspace
db.run(tr -> {
byte[] key = appDir.pack(Tuple.from("user123"));
tr.set(key, "user data".getBytes());
return null;
});