Skip to main content

Database Operations

The Database interface provides a comprehensive set of methods for interacting with your data store. This guide covers all read and write operations, index management, and database utilities.

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 Operations

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

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
}

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.

Practical Examples

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
}

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();

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();

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
}

Performance Considerations

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 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 readCount Instead of Loading All

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

Use Specific Queries

1
// Good - only loads needed objects
2
PaginatedResult<Article> recent = db.readPartial(
3
Query.from(Article.class)
4
.where("publishDate > ?", cutoff)
5
.sortDescending("publishDate"),
6
0,
7
10
8
);
9
10
// Bad - loads everything then filters
11
List<Article> all = db.readAll(Query.from(Article.class));
12
List<Article> recentBad = all.stream()
13
.filter(a -> a.getPublishDate().after(cutoff))
14
.sorted((a1, a2) -> a2.getPublishDate().compareTo(a1.getPublishDate()))
15
.limit(10)
16
.collect(Collectors.toList());

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
}