Skip to main content
Version: 3.0.x

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(2.0, "_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(2.0, "_any matches ?", "java database");

The weight argument scales how much the predicate contributes to each result's relevance score, mirroring Lucene's (predicate)^weight boost. A weight of 1.0 leaves the predicate's natural score unchanged; larger values amplify it. The boost is additive — non-matching documents are still returned, just with lower scores.

Negative weight values boost non-matching documents by a flat constant |weight| (not BM25-scaled like the positive case). Use this when you want to deprioritize matches rather than rank by their strength.

Sort by newest/oldest​

1
// Favor recent articles. A weight of 1.0 gives a one-year half-life.
2
Query.from(Article.class)
3
.sortNewest(1.0, "publishDate");
4
5
// Favor older articles. The two operators are mathematically mirrored.
6
Query.from(Article.class)
7
.sortOldest(1.0, "publishDate");

Both sortNewest and sortOldest are boosts on the relevance score, not hard chronological sorts. The weight argument controls the half-life as 1 year / weight:

weightHalf-life
1.01 year
2.06 months
12.0~1 month
52.0~1 week

sortNewest multiplies scores by a curve in (0, 1] — fresh items pass through at 1.0, very old items approach 0. sortOldest is the multiplicative inverse — fresh items pass through at 1.0, older items grow unbounded with age. The two operators are mathematically mirrored for items with a value at the key: sortNewest(w) × sortOldest(w) = 1 at every age.

Items missing a value at the key are deprioritized in both operators — they receive a multiplier of 0 and rank at the bottom regardless of sort direction.

Because both are boosts, a strong relevance match can still outrank a freshness-favored item. For deterministic chronological ordering, use sortDescending or sortAscending on a date field instead.

Sort by distance​

Sort by proximity to a geographic point (requires search-capable database):

1
Location office = new Location(40.7484, -73.9857);
2
3
// Nearest articles first
4
Query.from(Article.class)
5
.sortClosest("location", office);
6
7
// Farthest articles first
8
Query.from(Article.class)
9
.sortFarthest("location", office);

sortClosest and sortFarthest take a Location, whose x is latitude and y is longitude, both in degrees. Location normalizes coordinates outside their valid ranges instead of letting the database reject them: latitude clamps to -90 through 90, longitude wraps around -180 through 180, and a coordinate that isn't a number becomes 0.

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
}