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
1Database db = Database.Static.getDefault();
Named Database
1Database searchDb = Database.Static.getInstance("search");
All Databases
1List<Database> allDatabases = Database.Static.getAll();
By Type
1List<SqlDatabase> sqlDatabases = Database.Static.getByClass(SqlDatabase.class);
Temporary Override
Temporarily override the default database for the current thread:
1Database specialDb = Database.Static.getInstance("special");23Database.Static.overrideDefault(specialDb);4try {5// All operations in this block use specialDb6article.save();7Query.from(Article.class).selectAll();8} finally {9Database.Static.restoreDefault();10}
Read Operations
readAll
Reads all objects matching a query:
1Query<Article> query = Query.from(Article.class)2.where("publishDate > ?", cutoffDate);34List<Article> articles = db.readAll(query);
readFirst
Reads the first object matching a query:
1Article article = db.readFirst(2Query.from(Article.class)3.where("title = ?", "Getting Started")4);56if (article == null) {7// No matching article found8}
readCount
Returns the count of objects matching a query:
1long totalArticles = db.readCount(2Query.from(Article.class)3);45long publishedCount = db.readCount(6Query.from(Article.class)7.where("publishDate != missing")8);
readPartial
Reads a subset of results with offset and limit (pagination):
1// Page 1: items 0-92List<Article> page1 = db.readPartial(3Query.from(Article.class).sortDescending("publishDate"),40, // offset510 // limit6);78// Page 2: items 10-199List<Article> page2 = db.readPartial(10Query.from(Article.class).sortDescending("publishDate"),1110, // offset1210 // limit13);
readIterable
Returns an iterable for streaming large result sets without loading everything into memory:
1Query<Article> query = Query.from(Article.class);2Iterable<Article> articles = db.readIterable(query, 100); // Fetch size34for (Article article : articles) {5// Process each article6System.out.println(article.getTitle());78// Memory is managed efficiently - old items can be garbage collected9}
This is useful for:
- Processing large datasets
- Export operations
- Batch processing
- Reducing memory footprint
readAllGrouped
Reads grouped results with aggregations:
1// Group articles by author2List<Grouping<Article>> groupings = db.readAllGrouped(3Query.from(Article.class),4"author"5);67for (Grouping<Article> grouping : groupings) {8Author author = (Author) grouping.getKeys().get(0);9long count = grouping.getCount();10System.out.println(author.getName() + ": " + count + " articles");11}
1// Group by multiple fields2List<Grouping<Article>> groupings = db.readAllGrouped(3Query.from(Article.class),4"author",5"publishDate.year" // Group by year6);78for (Grouping<Article> grouping : groupings) {9Author author = (Author) grouping.getKeys().get(0);10Integer year = (Integer) grouping.getKeys().get(1);11long count = grouping.getCount();12System.out.println(author.getName() + " in " + year + ": " + count);13}
readPartialGrouped
Paginated version of readAllGrouped:
1List<Grouping<Article>> page = db.readPartialGrouped(2Query.from(Article.class),30, // offset410, // limit5"author"6);
readLastUpdate
Returns the most recent update time for objects matching a query:
1Date lastUpdate = db.readLastUpdate(2Query.from(Article.class)3);45if (lastUpdate != null) {6System.out.println("Last article update: " + lastUpdate);7}
Useful for:
- Cache invalidation
- Change detection
- Synchronization checks
Write Operations
save
Saves an object with full validation:
1Article article = new Article();2article.setTitle("New Article");3article.setAuthor(author);4article.setPublishDate(new Date());56try {7db.save(article.getState());8// or simply: article.save();9} catch (ValidationException e) {10// Handle validation errors11for (Map.Entry<ObjectField, List<Throwable>> entry : e.getErrors().entrySet()) {12System.out.println("Error in " + entry.getKey().getInternalName());13}14}
The save process:
- Validates all fields (annotations + custom validation)
- Fires
beforeSave()hooks - Writes to database
- Updates indexes
- Fires
afterSave()hooks
saveUnsafely
Saves without validation (use with caution):
1// Skip validation for performance-critical paths2db.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:
1db.delete(article.getState());2// or: article.delete();
The delete process:
- Fires
beforeDelete()hooks - Removes from database
- Updates indexes
- Fires
afterDelete()hooks
deleteByQuery
Deletes all objects matching a query:
1// Delete old articles2Date cutoff = DateUtils.addMonths(new Date(), -12);3db.deleteByQuery(4Query.from(Article.class)5.where("publishDate < ?", cutoff)6);78// Returns the number of deleted objects9long deletedCount = db.deleteByQuery(query);10System.out.println("Deleted " + deletedCount + " articles");
Warning: This bypasses lifecycle hooks for performance. If you need hooks to fire, load and delete objects individually:
1List<Article> articles = Query.from(Article.class)2.where("publishDate < ?", cutoff)3.selectAll();45db.beginWrites();6try {7for (Article article : articles) {8article.delete(); // Fires hooks9}10db.commitWrites();11} finally {12db.endWrites();13}
Index Operations
index
Updates indexes for a specific object:
1// Re-index after making changes that affect queries2article.setTitle("Updated Title");3db.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:
1ObjectType articleType = ObjectType.getInstance(Article.class);2ObjectIndex titleIndex = articleType.getIndex("title");34// Re-index all articles for the title index5db.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:
1ObjectType type = ObjectType.getInstance(Article.class);2ObjectIndex authorIndex = type.getIndex("author");3ObjectIndex dateIndex = type.getIndex("publishDate");45// Recalculate only these indexes6db.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:
1List<UUID> articleIds = Arrays.asList(id1, id2, id3, id4, id5);23List<Article> articles = Query.from(Article.class)4.where("_id = ?", articleIds)5.selectAll();
Batch Writes
Write multiple objects in a single transaction:
1db.beginWrites();2try {3for (Article article : articles) {4db.save(article.getState());5}6db.commitWrites(); // All saved atomically7} finally {8db.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 connection2boolean isAvailable = db.ping(false);34// Ping primary/write connection5boolean primaryAvailable = db.ping(true);67if (!isAvailable) {8// Handle database unavailability9throw new ServiceUnavailableException("Database is down");10}
Use in:
- Health checks
- Monitoring
- Graceful degradation logic
now
Get the database server's current time:
1Date 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 database2article.setPublishDate(db.now());3article.save();
getName / setName
1String name = db.getName();2db.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 updates2db.addUpdateNotifier(new UpdateNotifier<Article>() {3@Override4public void onUpdate(UpdateNotification<Article> notification) {5List<Article> updated = notification.getObjects();6System.out.println("Articles updated: " + updated.size());78for (Article article : updated) {9// Handle update - invalidate cache, send notifications, etc.10cacheService.invalidate(article.getId());11}12}13});
Delete Notifiers
1// Listen for article deletions2db.addDeleteNotifier(new DeleteNotifier<Article>() {3@Override4public void onDelete(DeleteNotification<Article> notification) {5List<Article> deleted = notification.getObjects();67for (Article article : deleted) {8// Handle deletion9cacheService.remove(article.getId());10searchIndex.remove(article.getId());11}12}13});
Removing Notifiers
1UpdateNotifier<Article> notifier = new UpdateNotifier<Article>() { ... };2db.addUpdateNotifier(notifier);34// Later, remove it5db.removeUpdateNotifier(notifier);
Check Notifier Support
1if (db.canCallNotifiers()) {2// This database supports notifiers3db.addUpdateNotifier(notifier);4} else {5// Use polling or other mechanisms6}
Note: Not all database implementations support notifiers. AbstractSqlDatabase typically does, but some specialized databases may not.
Practical Examples
Bulk Import
1public void importArticles(List<ArticleData> data) {2Database db = Database.Static.getDefault();3db.beginWrites();45try {6for (ArticleData articleData : data) {7Article article = new Article();8article.setTitle(articleData.getTitle());9article.setContent(articleData.getContent());10// ... set other fields1112db.save(article.getState());13}1415db.commitWrites();16System.out.println("Imported " + data.size() + " articles");1718} catch (Exception e) {19// Transaction will be rolled back20System.err.println("Import failed: " + e.getMessage());21throw e;22} finally {23db.endWrites();24}25}
Soft Delete Pattern
1public class Article extends Record {23@Indexed(visibility = true)4private Date deletedDate;56public boolean isDeleted() {7return deletedDate != null;8}910public void softDelete() {11this.deletedDate = Database.Static.getDefault().now();12this.save();13}1415public void restore() {16this.deletedDate = null;17this.save();18}19}2021// Query hides articles with populated visibility indexes22List<Article> activeArticles = Query.from(Article.class)23.selectAll();
Streaming Export
1public void exportToCSV(Writer writer) throws IOException {2Database db = Database.Static.getDefault();3CSVWriter csv = new CSVWriter(writer);45// Write header6csv.writeNext(new String[]{"ID", "Title", "Author", "Date"});78// Stream results9Query<Article> query = Query.from(Article.class)10.sortDescending("publishDate");1112for (Article article : db.readIterable(query, 100)) {13csv.writeNext(new String[]{14article.getId().toString(),15article.getTitle(),16article.getAuthor().getName(),17article.getPublishDate().toString()18});19}2021csv.close();22}
Cascading Delete
1public class Author extends Record {23@Override4protected void beforeDelete() {5Database db = Database.Static.getDefault();67// Delete all articles by this author8db.deleteByQuery(9Query.from(Article.class)10.where("author = ?", this)11);12}13}
Cache Invalidation with Notifiers
1public class CacheInvalidator {23private final Cache<UUID, Article> cache;45public CacheInvalidator(Database db, Cache<UUID, Article> cache) {6this.cache = cache;78// Invalidate cache on updates9db.addUpdateNotifier(new UpdateNotifier<Article>() {10@Override11public void onUpdate(UpdateNotification<Article> notification) {12for (Article article : notification.getObjects()) {13cache.remove(article.getId());14}15}16});1718// Invalidate cache on deletes19db.addDeleteNotifier(new DeleteNotifier<Article>() {20@Override21public void onDelete(DeleteNotification<Article> notification) {22for (Article article : notification.getObjects()) {23cache.remove(article.getId());24}25}26});27}2829public Article get(UUID id) {30return cache.get(id, key -> {31return Database.Static.getDefault().readFirst(32Query.from(Article.class).where("_id = ?", key)33);34});35}36}
Performance Considerations
Use Batch Operations
1// Good - single transaction2db.beginWrites();3for (Article article : articles) {4db.save(article.getState());5}6db.commitWrites();78// Bad - multiple transactions9for (Article article : articles) {10db.save(article.getState());11}
Use readIterable for Large Datasets
1// Good - memory efficient2for (Article article : db.readIterable(query, 100)) {3process(article);4}56// Bad - loads everything into memory7List<Article> all = db.readAll(query);8for (Article article : all) {9process(article);10}
Use readCount Instead of Loading All
1// Good2long count = db.readCount(query);34// Bad5long count = db.readAll(query).size();
Use Specific Queries
1// Good - only loads needed objects2List<Article> recent = db.readPartial(3Query.from(Article.class)4.where("publishDate > ?", cutoff)5.sortDescending("publishDate"),60,7108);910// Bad - loads everything then filters11List<Article> all = db.readAll(Query.from(Article.class));12List<Article> recent = all.stream()13.filter(a -> a.getPublishDate().after(cutoff))14.sorted(...)15.limit(10)16.collect(Collectors.toList());
Error Handling
ValidationException
1try {2article.save();3} catch (ValidationException e) {4Map<ObjectField, List<Throwable>> errors = e.getErrors();5for (Map.Entry<ObjectField, List<Throwable>> entry : errors.entrySet()) {6String fieldName = entry.getKey().getInternalName();7for (Throwable error : entry.getValue()) {8logger.error("Validation error in {}: {}", fieldName, error.getMessage());9}10}11}