Skip to main content
Version: 1.1.0

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
List<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
List<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
List<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 (Map.Entry<ObjectField, List<Throwable>> entry : e.getErrors().entrySet()) {
12
System.out.println("Error in " + entry.getKey().getInternalName());
13
}
14
}

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
);
7
8
// Returns the number of deleted objects
9
long deletedCount = db.deleteByQuery(query);
10
System.out.println("Deleted " + deletedCount + " articles");

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
boolean isAvailable = db.ping(false);
3
4
// Ping primary/write connection
5
boolean primaryAvailable = db.ping(true);
6
7
if (!isAvailable) {
8
// Handle database unavailability
9
throw new ServiceUnavailableException("Database is down");
10
}

Use in:

  • Health checks
  • Monitoring
  • Graceful degradation logic

now

Get the database server's current time:

1
Date dbTime = db.now();

Why use this instead of new Date()?

  • Consistent timestamps across distributed systems
  • Avoids clock skew between app servers
  • Some databases have special time functions
1
// Example: Set timestamp from database
2
article.setPublishDate(db.now());
3
article.save();

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
@Override
4
public void onUpdate(UpdateNotification<Article> notification) {
5
List<Article> updated = notification.getObjects();
6
System.out.println("Articles updated: " + updated.size());
7
8
for (Article article : updated) {
9
// Handle update - invalidate cache, send notifications, etc.
10
cacheService.invalidate(article.getId());
11
}
12
}
13
});

Delete Notifiers

1
// Listen for article deletions
2
db.addDeleteNotifier(new DeleteNotifier<Article>() {
3
@Override
4
public void onDelete(DeleteNotification<Article> notification) {
5
List<Article> deleted = notification.getObjects();
6
7
for (Article article : deleted) {
8
// Handle deletion
9
cacheService.remove(article.getId());
10
searchIndex.remove(article.getId());
11
}
12
}
13
});

Removing Notifiers

1
UpdateNotifier<Article> notifier = new UpdateNotifier<Article>() { ... };
2
db.addUpdateNotifier(notifier);
3
4
// Later, remove it
5
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
public void importArticles(List<ArticleData> data) {
2
Database db = Database.Static.getDefault();
3
db.beginWrites();
4
5
try {
6
for (ArticleData articleData : data) {
7
Article article = new Article();
8
article.setTitle(articleData.getTitle());
9
article.setContent(articleData.getContent());
10
// ... set other fields
11
12
db.save(article.getState());
13
}
14
15
db.commitWrites();
16
System.out.println("Imported " + data.size() + " articles");
17
18
} catch (Exception e) {
19
// Transaction will be rolled back
20
System.err.println("Import failed: " + e.getMessage());
21
throw e;
22
} finally {
23
db.endWrites();
24
}
25
}

Soft Delete Pattern

1
public class Article extends Record {
2
3
@Indexed(visibility = true)
4
private Date deletedDate;
5
6
public boolean isDeleted() {
7
return deletedDate != null;
8
}
9
10
public void softDelete() {
11
this.deletedDate = Database.Static.getDefault().now();
12
this.save();
13
}
14
15
public void restore() {
16
this.deletedDate = null;
17
this.save();
18
}
19
}
20
21
// Query hides articles with populated visibility indexes
22
List<Article> activeArticles = Query.from(Article.class)
23
.selectAll();

Streaming Export

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

Cascading Delete

1
public class Author extends Record {
2
3
@Override
4
protected void beforeDelete() {
5
Database db = Database.Static.getDefault();
6
7
// Delete all articles by this author
8
db.deleteByQuery(
9
Query.from(Article.class)
10
.where("author = ?", this)
11
);
12
}
13
}

Cache Invalidation with Notifiers

1
public class CacheInvalidator {
2
3
private final Cache<UUID, Article> cache;
4
5
public CacheInvalidator(Database db, Cache<UUID, Article> cache) {
6
this.cache = cache;
7
8
// Invalidate cache on updates
9
db.addUpdateNotifier(new UpdateNotifier<Article>() {
10
@Override
11
public void onUpdate(UpdateNotification<Article> notification) {
12
for (Article article : notification.getObjects()) {
13
cache.remove(article.getId());
14
}
15
}
16
});
17
18
// Invalidate cache on deletes
19
db.addDeleteNotifier(new DeleteNotifier<Article>() {
20
@Override
21
public void onDelete(DeleteNotification<Article> notification) {
22
for (Article article : notification.getObjects()) {
23
cache.remove(article.getId());
24
}
25
}
26
});
27
}
28
29
public Article get(UUID id) {
30
return cache.get(id, key -> {
31
return Database.Static.getDefault().readFirst(
32
Query.from(Article.class).where("_id = ?", key)
33
);
34
});
35
}
36
}

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 count = db.readAll(query).size();

Use Specific Queries

1
// Good - only loads needed objects
2
List<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> recent = all.stream()
13
.filter(a -> a.getPublishDate().after(cutoff))
14
.sorted(...)
15
.limit(10)
16
.collect(Collectors.toList());

Error Handling

ValidationException

1
try {
2
article.save();
3
} catch (ValidationException e) {
4
Map<ObjectField, List<Throwable>> errors = e.getErrors();
5
for (Map.Entry<ObjectField, List<Throwable>> entry : errors.entrySet()) {
6
String fieldName = entry.getKey().getInternalName();
7
for (Throwable error : entry.getValue()) {
8
logger.error("Validation error in {}: {}", fieldName, error.getMessage());
9
}
10
}
11
}