Skip to main content

Database operations

DB plugin provides two layers for interacting with your data: the Query API for building and executing queries, and the Database interface for lower-level operations like index management, batch reads, and utilities. For most use cases, prefer the Query API.

Practical examples

Search with filters

1
Query<Article> q = Query.from(Article.class);
2
3
// Full-text search
4
if (query != null && !query.isEmpty()) {
5
q.where("_any matches ?", query);
6
}
7
8
// Filter by author
9
if (author != null) {
10
q.and("author = ?", author);
11
}
12
13
// Date range
14
if (startDate != null) {
15
q.and("publishDate >= ?", startDate);
16
}
17
if (endDate != null) {
18
q.and("publishDate <= ?", endDate);
19
}
20
21
// Pagination
22
int offset = (page - 1) * pageSize;
23
return q.sortDescending("publishDate")
24
.select(offset, pageSize);

Dynamic query building

1
Query<Article> query = Query.from(Article.class);
2
3
for (Map.Entry<String, Object> entry : filters.entrySet()) {
4
String field = entry.getKey();
5
Object value = entry.getValue();
6
7
query.and(field + " = ?", value);
8
}
9
10
List<Article> results = query.selectAll();
1
// Find articles with overlapping tags, same author, or similar title
2
PaginatedResult<Article> related = Query.from(Article.class)
3
.where("_id != ?", article.getId()) // Exclude current article
4
.and("tags = ?", article.getTags()) // Matching tags
5
.or("author = ?", article.getAuthor()) // Same author
6
.sortDescending("publishDate")
7
.select(0, limit);
1
Query<Article> baseQuery = Query.from(Article.class)
2
.where("_any matches ?", searchQuery);
3
4
// Get articles
5
PaginatedResult<Article> articles = baseQuery
6
.sortRelevant(100, "_any matches ?", searchQuery)
7
.select(0, 20);
8
9
// Get author facets
10
Map<String, Long> authorCounts = new HashMap<>();
11
List<Grouping<Article>> authorGroups = baseQuery
12
.groupBy("author");
13
for (Grouping<Article> group : authorGroups) {
14
Author author = (Author) group.getKeys().get(0);
15
authorCounts.put(author.getName(), group.getCount());
16
}
17
18
// Get tag facets
19
Map<String, Long> tagCounts = new HashMap<>();
20
List<Grouping<Article>> tagGroups = baseQuery
21
.groupBy("tags");
22
for (Grouping<Article> group : tagGroups) {
23
String tag = (String) group.getKeys().get(0);
24
tagCounts.put(tag, group.getCount());
25
}

Soft delete pattern

1
public class SoftDeleteArticle extends Record {
2
3
private String title;
4
5
@Indexed(visibility = true)
6
private Date deletedDate;
7
8
public boolean isDeleted() {
9
return deletedDate != null;
10
}
11
12
public void softDelete() {
13
this.deletedDate = new Date(Database.Static.getDefault().now());
14
this.save();
15
}
16
17
public void restore() {
18
this.deletedDate = null;
19
this.save();
20
}
21
22
public String getTitle() {
23
return title;
24
}
25
26
public void setTitle(String title) {
27
this.title = title;
28
}
29
30
public Date getDeletedDate() {
31
return deletedDate;
32
}
33
34
public void setDeletedDate(Date deletedDate) {
35
this.deletedDate = deletedDate;
36
}
37
}

When querying, records with populated visibility-indexed fields are automatically hidden:

1
// Query hides articles with populated visibility indexes
2
List<SoftDeleteArticle> activeArticles = Query.from(SoftDeleteArticle.class)
3
.selectAll();

Cascading delete

1
public class CascadingDeleteAuthor extends Record {
2
3
private String name;
4
5
@Override
6
protected void beforeDelete() {
7
Database db = Database.Static.getDefault();
8
9
// Delete all articles by this author
10
db.deleteByQuery(
11
Query.from(Article.class)
12
.where("author = ?", this)
13
);
14
}
15
16
public String getName() {
17
return name;
18
}
19
20
public void setName(String name) {
21
this.name = name;
22
}
23
}

Cache invalidation with notifiers

1
public class CacheInvalidatorExample {
2
3
private final Cache<UUID, Article> cache;
4
5
public CacheInvalidatorExample(Database db, Cache<UUID, Article> cache) {
6
this.cache = cache;
7
8
// Invalidate cache on updates
9
db.addUpdateNotifier(new UpdateNotifier<Article>() {
10
11
@Override
12
public void onUpdate(Article article) {
13
cache.remove(article.getId());
14
}
15
});
16
17
// Invalidate cache on deletes
18
db.addDeleteNotifier(new DeleteNotifier<Article>() {
19
20
@Override
21
public void onDelete(Article article) {
22
cache.remove(article.getId());
23
}
24
});
25
}
26
27
public Article get(UUID id) {
28
return cache.get(
29
id, key -> {
30
return Database.Static.getDefault().readFirst(
31
Query.from(Article.class).where("_id = ?", key)
32
);
33
});
34
}
35
36
// Simple cache interface for example purposes
37
public interface Cache<K, V> {
38
39
V get(K key, java.util.function.Function<K, V> loader);
40
41
void remove(K key);
42
}
43
}

Query API

The Query class provides a fluent API for building and executing database queries. Inspired by Apple's Cocoa Predicates and LINQ, it offers a clean, readable syntax for expressing complex query logic.

Query construction

1
// Query a specific type
2
Query<Article> query = Query.from(Article.class);
3
4
// Query all types
5
Query<Object> allQuery = Query.fromAll();
6
7
// Query by ObjectType
8
ObjectType articleType = ObjectType.getInstance(Article.class);
9
Query<Object> typeQuery = Query.fromType(articleType);
10
11
// Query by type group (multiple related types)
12
Query<Object> contentQuery = Query.fromGroup("content");

To query against a specific database:

1
Database searchDb = Database.Static.getInstance("search");
2
3
Query<Article> query = Query.from(Article.class)
4
.using(searchDb);

Predicates and filtering

where - initial predicate

Set the initial query predicate:

1
// Simple equality
2
Query.from(Article.class)
3
.where("title = ?", "Getting Started");
4
5
// Comparison
6
Query.from(Article.class)
7
.where("publishDate > ?", cutoffDate);
8
9
// Multiple parameters
10
Query.from(Article.class)
11
.where("title = ? and author = ?", "Getting Started", author);

and - add AND conditions

Chain additional conditions with AND:

1
Query.from(Article.class)
2
.where("publishDate > ?", cutoffDate)
3
.and("author/name = ?", "John Doe")
4
.and("tags = ?", "java");

or - add OR conditions

Chain conditions with OR:

1
Query.from(Article.class)
2
.where("author/name = ?", "John Doe")
3
.or("author/name = ?", "Jane Smith");
4
5
// Complex combinations
6
// (publishDate > cutoff) OR (featured = true AND author = john)
7
Query.from(Article.class)
8
.where("publishDate > ?", cutoffDate)
9
.or("featured = ? and author = ?", true, john);

not - negate conditions

1
// Articles NOT by John Doe
2
Query.from(Article.class)
3
.where("author/name != ?", "John Doe");
4
5
// Or using not()
6
Query.from(Article.class)
7
.not("author/name = ?", "John Doe");

having - filter without affecting ranking

Use having to apply predicates that filter results without affecting ranking in full-text search databases (like Solr). Unlike and or or, which can influence the relevance score, having applies the predicate purely as a filter:

1
// Search with full-text ranking, but filter by category without affecting relevance scores
2
// Unlike and() or or(), having() applies the predicate purely as a filter
3
Query.from(Article.class)
4
.where("_any matches ?", "database performance") // Affects ranking
5
.having("category = ?", "technology"); // Filters only, no ranking impact

Predicate operators

Equality operators

1
// Equals
2
Query.from(Article.class)
3
.where("status = ?", "published");
4
5
// Not equals
6
Query.from(Article.class)
7
.where("status != ?", "draft");
8
9
// Null/missing check
10
Query.from(Article.class)
11
.where("deletedDate = missing");
12
13
Query.from(Article.class)
14
.where("deletedDate != missing");

Comparison operators

1
// Greater than
2
Query.from(Article.class)
3
.where("publishDate > ?", date);
4
5
// Greater than or equal
6
Query.from(Article.class)
7
.where("publishDate >= ?", date);
8
9
// Less than
10
Query.from(Article.class)
11
.where("publishDate < ?", date);
12
13
// Less than or equal
14
Query.from(Article.class)
15
.where("publishDate <= ?", date);

String operators

1
// Starts with
2
Query.from(Article.class)
3
.where("title ^= ?", "Getting");
4
5
// Contains
6
Query.from(Article.class)
7
.where("content contains ?", "database");

Collection operators

1
// Contains any of the values
2
Query.from(Article.class)
3
.where("tags = ?", Arrays.asList("java", "database"));
4
5
// Contains all values
6
Query.from(Article.class)
7
.where("tags = ?", tag1)
8
.and("tags = ?", tag2); // Both tags must be present

Range queries

1
// Between dates
2
Query.from(Article.class)
3
.where("publishDate > ? and publishDate < ?", startDate, endDate);
1
// Search across all indexed fields
2
Query.from(Article.class)
3
.where("_any matches ?", "search terms");
4
5
// Search specific field
6
Query.from(Article.class)
7
.where("content matches ?", "search terms");

Special field keys

1
// _id - Object ID
2
Query.from(Article.class)
3
.where("_id = ?", articleId);
4
5
// Multiple IDs
6
Query.from(Article.class)
7
.where("_id = ?", Arrays.asList(id1, id2, id3));
8
9
// _type - Object Type
10
Query.from(Article.class)
11
.where("_type = ?", Article.class);
12
13
// _any - Search All Fields
14
Query.from(Article.class)
15
.where("_any matches ?", "search query");
16
17
// _label - Display Label
18
Query.from(Article.class)
19
.where("_label matches ?", "Article");

Path-based field access

Query nested fields using path notation:

1
// Author's name
2
Query.from(Article.class)
3
.where("author/name = ?", "John Doe");
4
5
// Nested objects
6
Query.from(Article.class)
7
.where("author/company/name = ?", "Acme Corp");
8
9
// Collection items
10
Query.from(Article.class)
11
.where("comments/author/name = ?", "Jane Smith");
12
13
// Map values
14
Query.from(Article.class)
15
.where("metadata/category = ?", "technology");

Sorting

sortAscending

1
Query.from(Article.class)
2
.sortAscending("title");
3
4
// Multiple fields
5
Query.from(Article.class)
6
.sortAscending("author/name")
7
.sortAscending("publishDate");

sortDescending

1
Query.from(Article.class)
2
.sortDescending("publishDate");

Sort by relevance

Sort by relevance to a predicate (requires search-capable database):

1
Query.from(Article.class)
2
.where("_any matches ?", "java database")
3
.sortRelevant(100, "_any matches ?", "java database");

Sort by newest/oldest

1
// Newest first
2
Query.from(Article.class)
3
.sortNewest(100, "publishDate");
4
5
// Oldest first
6
Query.from(Article.class)
7
.sortOldest(100, "publishDate");

Multiple sort criteria

1
Query.from(Article.class)
2
.sortDescending("featured") // Featured articles first
3
.sortDescending("publishDate") // Then by date
4
.sortAscending("title"); // Then by title

Query execution

selectAll

Execute query and return all results:

1
List<Article> articles = Query.from(Article.class)
2
.where("publishDate > ?", cutoffDate)
3
.selectAll();

select (pagination)

Execute with offset and limit:

1
// First page (items 0-9)
2
PaginatedResult<Article> page1 = Query.from(Article.class)
3
.sortDescending("publishDate")
4
.select(0, 10);
5
6
// Second page (items 10-19)
7
PaginatedResult<Article> page2 = Query.from(Article.class)
8
.sortDescending("publishDate")
9
.select(10, 10);
10
11
// Calculate offset from page number
12
int page = 3;
13
int pageSize = 10;
14
int offset = (page - 1) * pageSize;
15
Query<Article> query = Query.from(Article.class);
16
PaginatedResult<Article> pageN = query.select(offset, pageSize);

first

Get the first result (or null):

1
Article article = Query.from(Article.class)
2
.where("slug = ?", "getting-started")
3
.first();
4
5
if (article == null) {
6
// Not found
7
}

findFirst (Optional)

Get the first result as an Optional:

1
Optional<Article> optional = Query.from(Article.class)
2
.where("slug = ?", "getting-started")
3
.findFirst();
4
5
optional.ifPresent(article -> {
6
System.out.println("Found: " + article.getTitle());
7
});
8
9
// Or with default
10
Article article = Query.from(Article.class)
11
.where("slug = ?", "getting-started")
12
.findFirst()
13
.orElse(defaultArticle);

count

Count matching objects:

1
long total = Query.from(Article.class).count();
2
3
long published = Query.from(Article.class)
4
.where("publishDate != missing")
5
.count();

iterable

Get a streaming iterable for large result sets:

1
Query<Article> query = Query.from(Article.class)
2
.where("publishDate > ?", cutoffDate);
3
4
for (Article article : query.iterable(100)) { // Fetch size 100
5
// Process each article
6
// Memory efficient for large datasets
7
}

deleteAll

Delete all matching objects:

1
// Delete old draft articles
2
Query.from(Article.class)
3
.where("status = ? and createdDate < ?", "draft", cutoffDate)
4
.deleteAll();

Grouping and aggregation

groupBy

Group results by one or more fields:

1
// Group by author
2
List<Grouping<Article>> byAuthor = Query.from(Article.class)
3
.groupBy("author"); // Returns List<Grouping<Article>>
4
5
for (Grouping<Article> group : byAuthor) {
6
Author author = (Author) group.getKeys().get(0);
7
long count = group.getCount();
8
System.out.println(author.getName() + ": " + count + " articles");
9
}
1
// Group by multiple fields
2
List<Grouping<Article>> groups = Query.from(Article.class)
3
.groupBy("author", "publishYear");
4
5
for (Grouping<Article> group : groups) {
6
Author author = (Author) group.getKeys().get(0);
7
Integer year = (Integer) group.getKeys().get(1);
8
long count = group.getCount();
9
System.out.println(author.getName() + " in " + year + ": " + count);
10
}

Filtering grouped results

1
// Authors with more than 5 articles
2
List<Grouping<Article>> prolificAuthors = Query.from(Article.class)
3
.groupBy("author")
4
.stream()
5
.filter(group -> group.getCount() > 5)
6
.collect(Collectors.toList());

Aggregation functions

1
// Get count for each group
2
for (Grouping<Article> group : groups) {
3
long count = group.getCount();
4
// Use count...
5
}
6
7
// Access first item in group
8
for (Grouping<Article> group : groups) {
9
Article firstArticle = group.createItemsQuery().first();
10
// Use firstArticle...
11
}
12
13
// Get all items in group
14
for (Grouping<Article> group : groups) {
15
List<Article> articles = group.createItemsQuery().selectAll();
16
// Process all articles in this group...
17
}

Query modifiers

timeout

Set query timeout in seconds:

1
Query.from(Article.class)
2
.timeout(30.0) // 30 second timeout
3
.selectAll();

noCache

Bypass query result cache:

1
Query.from(Article.class)
2
.noCache()
3
.selectAll();

master

Execute on master/primary database (force read from write source):

1
// Ensure we read from master, not replica
2
Query.from(Article.class)
3
.master()
4
.where("_id = ?", articleId)
5
.first();

Useful when:

  • You just wrote data and need to read it immediately
  • Avoiding replication lag
  • Critical reads that must be up-to-date

resolveToReferenceOnly

Load only reference information, not full objects:

1
List<Article> refs = Query.from(Article.class)
2
.resolveToReferenceOnly()
3
.selectAll();
4
5
// refs contains lightweight reference objects
6
// Good for getting IDs, types, but not full data

resolveInvisible

Include records marked as invisible:

1
Query.from(Article.class)
2
.resolveInvisible()
3
.selectAll();

option

Set database-specific options:

1
Query.from(Article.class)
2
.option("hint", "USE INDEX (idx_author)") // SQL hint
3
.option("readPreference", "secondary") // MongoDB option
4
.selectAll();

comment

Add an informational comment to the query:

1
Query.from(Article.class)
2
.comment("Dashboard recent articles widget")
3
.selectAll();

Useful for:

  • Debugging slow queries
  • Monitoring and logging
  • Tracking query origins in database logs

Subqueries

Use queries as predicate values for powerful filtering:

1
// Find articles by authors in California
2
List<Article> articles = Query.from(Article.class)
3
.where(
4
"author = ?",
5
Query.from(Author.class)
6
.where("state = ?", "CA")
7
)
8
.selectAll();
9
10
// Find articles NOT by specific authors
11
List<Article> filtered = Query.from(Article.class)
12
.where(
13
"author != ?",
14
Query.from(Author.class)
15
.where("blacklisted = ?", true)
16
)
17
.selectAll();
1
// Complex subquery
2
Query<Article> articles = Query.from(Article.class)
3
.where(
4
"author = ?",
5
Query.from(Author.class)
6
.where(
7
"company = ?",
8
Query.from(Company.class)
9
.where("name = ?", "Acme Corp")
10
)
11
);

Write operations

save

Saves an object with full validation:

1
Article article = new Article();
2
article.setTitle("New Article");
3
article.setAuthor(author);
4
article.setPublishDate(new Date());
5
6
try {
7
db.save(article.getState());
8
// or simply: article.save();
9
} catch (ValidationException e) {
10
// Handle validation errors
11
for (ObjectField field : e.getStates()
12
.stream()
13
.map(State::getErrorFields)
14
.flatMap(Collection::stream)
15
.collect(Collectors.toList())) {
16
System.out.println("Error in " + field.getInternalName() + ": "
17
+ field.getState().getFieldErrors(field));
18
}
19
}

The save process:

  1. Validates all fields (annotations + custom validation)
  2. Fires beforeSave() hooks
  3. Writes to database
  4. Updates indexes
  5. Fires afterSave() hooks

saveUnsafely

Saves without validation (use with caution):

1
// Skip validation for performance-critical paths
2
db.saveUnsafely(article.getState());

When to use:

  • Importing trusted data
  • System-level operations
  • Performance-critical batch operations where you've pre-validated

When NOT to use:

  • User input
  • External data sources
  • When data integrity is critical

delete

Deletes an object:

1
db.delete(article.getState());
2
// or: article.delete();

The delete process:

  1. Fires beforeDelete() hooks
  2. Removes from database
  3. Updates indexes
  4. Fires afterDelete() hooks

deleteByQuery

Deletes all objects matching a query:

1
// Delete old articles
2
Date cutoff = DateUtils.addMonths(new Date(), -12);
3
db.deleteByQuery(
4
Query.from(Article.class)
5
.where("publishDate < ?", cutoff)
6
);
warning

This bypasses lifecycle hooks for performance. If you need hooks to fire, load and delete objects individually:

1
List<Article> articles = Query.from(Article.class)
2
.where("publishDate < ?", cutoff)
3
.selectAll();
4
5
db.beginWrites();
6
try {
7
for (Article article : articles) {
8
article.delete(); // Fires hooks
9
}
10
db.commitWrites();
11
} finally {
12
db.endWrites();
13
}

Database interface

The Database interface provides lower-level methods for operations not covered by the Query API. For read operations, prefer Query unless you need direct database access.

Getting a database instance

Default database

1
Database db = Database.Static.getDefault();

Named database

1
Database searchDb = Database.Static.getInstance("search");

All databases

1
List<Database> allDatabases = Database.Static.getAll();

By type

1
List<SqlDatabase> sqlDatabases = Database.Static.getByClass(SqlDatabase.class);

Temporary override

Temporarily override the default database for the current thread:

1
Database specialDb = Database.Static.getInstance("special");
2
3
Database.Static.overrideDefault(specialDb);
4
try {
5
// All operations in this block use specialDb
6
article.save();
7
Query.from(Article.class).selectAll();
8
} finally {
9
Database.Static.restoreDefault();
10
}

Read methods

note

For most read operations, use the Query API instead. The Database read methods are useful when you need to work with raw Query objects or implement custom database logic.

readAll

Reads all objects matching a query:

1
Query<Article> query = Query.from(Article.class)
2
.where("publishDate > ?", cutoffDate);
3
4
List<Article> articles = db.readAll(query);

readFirst

Reads the first object matching a query:

1
Article article = db.readFirst(
2
Query.from(Article.class)
3
.where("title = ?", "Getting Started")
4
);
5
6
if (article == null) {
7
// No matching article found
8
}

readCount

Returns the count of objects matching a query:

1
long totalArticles = db.readCount(
2
Query.from(Article.class)
3
);
4
5
long publishedCount = db.readCount(
6
Query.from(Article.class)
7
.where("publishDate != missing")
8
);

readPartial

Reads a subset of results with offset and limit (pagination):

1
// Page 1: items 0-9
2
PaginatedResult<Article> page1 = db.readPartial(
3
Query.from(Article.class).sortDescending("publishDate"),
4
0, // offset
5
10 // limit
6
);
7
8
// Page 2: items 10-19
9
PaginatedResult<Article> page2 = db.readPartial(
10
Query.from(Article.class).sortDescending("publishDate"),
11
10, // offset
12
10 // limit
13
);

readIterable

Returns an iterable for streaming large result sets without loading everything into memory:

1
Query<Article> query = Query.from(Article.class);
2
Iterable<Article> articles = db.readIterable(query, 100); // Fetch size
3
4
for (Article article : articles) {
5
// Process each article
6
System.out.println(article.getTitle());
7
8
// Memory is managed efficiently - old items can be garbage collected
9
}

This is useful for:

  • Processing large datasets
  • Export operations
  • Batch processing
  • Reducing memory footprint

readAllGrouped

Reads grouped results with aggregations:

1
// Group articles by author
2
List<Grouping<Article>> groupings = db.readAllGrouped(
3
Query.from(Article.class),
4
"author"
5
);
6
7
for (Grouping<Article> grouping : groupings) {
8
Author author = (Author) grouping.getKeys().get(0);
9
long count = grouping.getCount();
10
System.out.println(author.getName() + ": " + count + " articles");
11
}
1
// Group by multiple fields
2
List<Grouping<Article>> groupings = db.readAllGrouped(
3
Query.from(Article.class),
4
"author",
5
"publishDate.year" // Group by year
6
);
7
8
for (Grouping<Article> grouping : groupings) {
9
Author author = (Author) grouping.getKeys().get(0);
10
Integer year = (Integer) grouping.getKeys().get(1);
11
long count = grouping.getCount();
12
System.out.println(author.getName() + " in " + year + ": " + count);
13
}

readPartialGrouped

Paginated version of readAllGrouped:

1
PaginatedResult<Grouping<Article>> page = db.readPartialGrouped(
2
Query.from(Article.class),
3
0, // offset
4
10, // limit
5
"author"
6
);

readLastUpdate

Returns the most recent update time for objects matching a query:

1
Date lastUpdate = db.readLastUpdate(
2
Query.from(Article.class)
3
);
4
5
if (lastUpdate != null) {
6
System.out.println("Last article update: " + lastUpdate);
7
}

Useful for:

  • Cache invalidation
  • Change detection
  • Synchronization checks

Index operations

index

Updates indexes for a specific object:

1
// Re-index after making changes that affect queries
2
article.setTitle("Updated Title");
3
db.index(article.getState());

Typically you don't need to call this directly as save() handles indexing. Use it when:

  • Manually updating index data
  • Recovering from index corruption
  • Implementing custom indexing strategies

indexAll

Re-indexes all objects for a specific index:

1
ObjectType articleType = ObjectType.getInstance(Article.class);
2
ObjectIndex titleIndex = articleType.getIndex("title");
3
4
// Re-index all articles for the title index
5
db.indexAll(titleIndex);

Use cases:

  • Adding a new index to existing data
  • Recovering from index corruption
  • Changing index definitions

recalculate

Recalculates specific indexes for an object:

1
ObjectType type = ObjectType.getInstance(Article.class);
2
ObjectIndex authorIndex = type.getIndex("author");
3
ObjectIndex dateIndex = type.getIndex("publishDate");
4
5
// Recalculate only these indexes
6
db.recalculate(article.getState(), authorIndex, dateIndex);

More efficient than full re-indexing when you know exactly which indexes need updates.

Batch operations

Batch reads

Read multiple objects by ID efficiently:

1
List<UUID> articleIds = Arrays.asList(id1, id2, id3, id4, id5);
2
3
List<Article> articles = Query.from(Article.class)
4
.where("_id = ?", articleIds)
5
.selectAll();

Batch writes

Write multiple objects in a single transaction:

1
db.beginWrites();
2
try {
3
for (Article article : articles) {
4
db.save(article.getState());
5
}
6
db.commitWrites(); // All saved atomically
7
} finally {
8
db.endWrites();
9
}

Benefits:

  • Atomic (all succeed or all fail)
  • Better performance
  • Validation happens before any writes
  • Single database roundtrip

Database utilities

ping

Check if the database is available:

1
// Ping replica/read connection
2
try {
3
db.ping(false);
4
5
// Ping primary/write connection
6
db.ping(true);
7
} catch (Exception e) {
8
System.out.println("Ping failed: " + e.getMessage());
9
throw new RuntimeException(e);
10
}

Use in:

  • Health checks
  • Monitoring
  • Graceful degradation logic

now

Get the database server's current time:

1
long dbTime = db.now();
2
3
// Example: Set timestamp from database
4
article.setPublishDate(new Date(db.now()));
5
article.save();

Why use this instead of new Date()?

  • Consistent timestamps across distributed systems
  • Avoids clock skew between app servers
  • Some databases have special time functions

getName / setName

1
String name = db.getName();
2
db.setName("newName");

Database names are used to:

  • Identify databases in multi-database configurations
  • Route queries to specific databases
  • Configure database-specific settings

Environment (deprecated)

note

getEnvironment() and setEnvironment() are deprecated. Use DatabaseEnvironment.getCurrent() and DatabaseEnvironment.override() instead.

Notifiers (update listeners)

Register callbacks for object changes:

Update notifiers

1
// Listen for article updates
2
db.addUpdateNotifier(new UpdateNotifier<Article>() {
3
4
@Override
5
public void onUpdate(Article article) {
6
cacheService.invalidate(article.getId());
7
}
8
});

Delete notifiers

1
// Listen for article deletions
2
db.addDeleteNotifier(new DeleteNotifier<Article>() {
3
4
@Override
5
public void onDelete(Article article) {
6
// Handle deletion
7
cacheService.remove(article.getId());
8
searchIndex.remove(article.getId());
9
}
10
});

Removing notifiers

1
UpdateNotifier<Article> notifier = new UpdateNotifier<Article>() {
2
3
@Override
4
public void onUpdate(Article article) {
5
// Handle updates
6
}
7
};
8
db.addUpdateNotifier(notifier);
9
10
// Later, remove it
11
db.removeUpdateNotifier(notifier);

Check notifier support

1
if (db.canCallNotifiers()) {
2
// This database supports notifiers
3
db.addUpdateNotifier(notifier);
4
} else {
5
// Use polling or other mechanisms
6
}
note

Not all database implementations support notifiers. AbstractSqlDatabase typically does, but some specialized databases may not.

Performance tips

Index your query fields

1
// Ensure queried fields are indexed
2
// @Indexed
3
// private String title;
4
//
5
// @Indexed
6
// private Date publishDate;
7
//
8
// @Indexed
9
// private Author author;

Use batch operations

1
// Good - single transaction
2
db.beginWrites();
3
for (Article article : articles) {
4
db.save(article.getState());
5
}
6
db.commitWrites();
7
8
// Bad - multiple transactions
9
for (Article article : articles) {
10
db.save(article.getState());
11
}

Use pagination

1
// Good - loads 10 items
2
query.select(0, 10);
3
4
// Bad - loads everything
5
query.selectAll();

Use count() instead of loading all

1
// Good
2
long count = query.count();
3
4
// Bad
5
long badCount = query.selectAll().size();

Use readIterable for large datasets

1
// Good - memory efficient
2
for (Article article : db.readIterable(query, 100)) {
3
process(article);
4
}
5
6
// Bad - loads everything into memory
7
List<Article> all = db.readAll(query);
8
for (Article article : all) {
9
process(article);
10
}

Use specific predicates

1
// Good - uses index efficiently
2
Query.from(Article.class)
3
.where("publishDate > ?", cutoffDate);
4
5
// Less efficient - may scan more rows
6
Query.from(Article.class)
7
.where("publishDate != missing");

Bulk import

1
Database db = Database.Static.getDefault();
2
db.beginWrites();
3
4
try {
5
for (ArticleData articleData : data) {
6
Article article = new Article();
7
article.setTitle(articleData.getTitle());
8
article.setContent(articleData.getContent());
9
// ... set other fields
10
11
db.save(article.getState());
12
}
13
14
db.commitWrites();
15
System.out.println("Imported " + data.size() + " articles");
16
17
} catch (Exception e) {
18
// Transaction will be rolled back
19
System.err.println("Import failed: " + e.getMessage());
20
throw e;
21
} finally {
22
db.endWrites();
23
}

Streaming export

1
Database db = Database.Static.getDefault();
2
CSVWriter csv = new CSVWriter(writer);
3
4
// Write header
5
csv.writeNext(new String[] { "ID", "Title", "Author", "Date" });
6
7
// Stream results
8
Query<Article> query = Query.from(Article.class)
9
.sortDescending("publishDate");
10
11
for (Article article : db.readIterable(query, 100)) {
12
csv.writeNext(new String[] {
13
article.getId().toString(),
14
article.getTitle(),
15
article.getAuthor().getName(),
16
article.getPublishDate().toString()
17
});
18
}
19
20
csv.close();

Common pitfalls

Forgetting to index

1
// This will fail if 'title' is not indexed
2
// Query.from(Article.class)
3
// .where("title = ?", "Getting Started")
4
// .selectAll();
5
// ERROR: Field 'title' is not indexed

Loading too much data

1
// Avoid loading thousands of objects at once
2
// List<Article> all = Query.from(Article.class).selectAll();
3
// Use pagination or iterable() instead

Inefficient filtering

1
// Bad - loads everything then filters in memory
2
List<Article> all = Query.from(Article.class).selectAll();
3
List<Article> filtered = all.stream()
4
.filter(a -> a.getPublishDate().after(cutoffDate))
5
.collect(Collectors.toList());
6
7
// Good - filters at database level
8
List<Article> goodFiltered = Query.from(Article.class)
9
.where("publishDate > ?", cutoffDate)
10
.selectAll();

Not handling null results

1
// Bad - can throw NullPointerException
2
Article article = Query.from(Article.class)
3
.where("_id = ?", id)
4
.first();
5
// String title = article.getTitle(); // NPE if article is null
6
7
// Good - check for null
8
Article safeArticle = Query.from(Article.class)
9
.where("_id = ?", id)
10
.first();
11
if (safeArticle != null) {
12
String title = safeArticle.getTitle();
13
}
14
15
// Or use Optional
16
String optionalTitle = Query.from(Article.class)
17
.where("_id = ?", id)
18
.findFirst()
19
.map(Article::getTitle)
20
.orElse("Untitled");

Error handling

ValidationException

1
try {
2
article.save();
3
} catch (ValidationException e) {
4
for (State state : e.getStates()) {
5
for (ObjectField field : state.getErrorFields()) {
6
String fieldName = field.getInternalName();
7
for (Throwable error : state.getFieldErrors(field)) {
8
System.err.println("Validation error in " + fieldName + ": " + error.getMessage());
9
}
10
}
11
}
12
}