Transactions and Write Management
DB plugin provides a robust transaction system for managing write operations. Transactions ensure data consistency, enable batch operations, and provide validation before any data reaches the database.
Transaction Lifecycle
Basic Transaction Pattern
beginWrites / commitWrites / endWrites
1Database db = Database.Static.getDefault();23db.beginWrites();4try {5// Make changes6article1.save();7article2.save();8author.save();910// Commit changes11db.commitWrites();1213} finally {14// Always clean up15db.endWrites();16}
What Happens
- beginWrites() - Starts a transaction, increments depth counter
- save() - Buffers the write, doesn't touch the database yet
- commitWrites() - Validates all buffered writes, then writes them atomically
- endWrites() - Decrements depth counter, cleans up resources
Why Use Transactions
1// Without transaction - NOT atomic2article.save(); // Might succeed3author.save(); // Might fail4// Now you have inconsistent data!56// With transaction - atomic7db.beginWrites();8try {9article.save();10author.save();11db.commitWrites(); // Both succeed or both fail12} finally {13db.endWrites();14}
Nested Transactions
DB plugin supports nested transactions using a depth counter:
1Database db = Database.Static.getDefault();23db.beginWrites(); // Depth 14try {5article.save();67db.beginWrites(); // Depth 2 (nested)8try {9author.save();10db.commitWrites(); // Commits only at depth 111} finally {12db.endWrites(); // Back to depth 113}1415db.commitWrites(); // Actually commits everything16} finally {17db.endWrites(); // Depth 018}
How Nesting Works
- beginWrites() increments a counter
- endWrites() decrements it
- commitWrites() only writes when counter reaches 0
- Allows method composition without worrying about transaction state
1public void saveArticleWithAuthor(Article article, Author author) {2// This method doesn't know if it's called within a transaction3db.beginWrites();4try {5article.save();6author.save();7db.commitWrites();8} finally {9db.endWrites();10}11}1213// Called standalone - commits immediately14saveArticleWithAuthor(article, author);1516// Called within transaction - commits with outer transaction17db.beginWrites();18try {19saveArticleWithAuthor(article1, author1);20saveArticleWithAuthor(article2, author2);21db.commitWrites(); // All 4 objects committed together22} finally {23db.endWrites();24}
Commit Strategies
commitWrites - Immediate Consistency
Commits changes immediately with strong consistency guarantees:
1db.beginWrites();2try {3article.save();4db.commitWrites();5// Data is immediately visible to other transactions6} finally {7db.endWrites();8}
Use when:
- You need immediate read-after-write consistency
- Other processes depend on this data immediately
- Working with critical data (financial, inventory, etc.)
commitWritesEventually - Eventual Consistency
Commits changes for eventual consistency, allowing for optimization:
1db.beginWrites();2try {3article.save();4db.commitWritesEventually();5// Data will be visible soon, but maybe not immediately6} finally {7db.endWrites();8}
Use when:
- Eventual consistency is acceptable
- Performance is critical
- Working with caching or replication systems
- Bulk imports or batch operations
The database implementation may:
- Batch writes together
- Delay replication
- Optimize index updates
- Trade immediate consistency for throughput
Isolated Writes
Create an isolated write session that doesn't interact with the main transaction:
1// Main transaction2db.beginWrites();3try {4article.save();56// Isolated session7db.beginIsolatedWrites();8try {9// This saves independently10auditLog.save();11db.commitWrites();12} finally {13db.endWrites();14}1516// Main transaction continues17db.commitWrites();18} finally {19db.endWrites();20}
Use cases:
- Audit logging that should succeed even if main transaction fails
- Separate concerns (e.g., metrics, analytics)
- Writing to different databases
Write Buffering and Validation
The Validation Phase
All validation happens before any database writes:
1db.beginWrites();2try {3// These are buffered4article1.save();5article2.save();6article3.save();78// This triggers validation of ALL three9db.commitWrites();1011// If any validation fails, NONE are written12} catch (ValidationException e) {13// Handle validation errors for all objects14Map<Object, Map<ObjectField, List<Throwable>>> errors = e.getAllErrors();15} finally {16db.endWrites();17}
Validation Order
- Annotation validation -
@Required,@Maximum, etc. - beforeSave() hooks - Custom pre-save logic
- onValidate() hooks - Custom validation logic
- afterValidate() hooks - Post-validation logic
- Database write - Only if all validation passes
- afterSave() hooks - Post-save logic
Example: Custom Validation
1public class Article extends Record {23@Required4private String title;56@Required7private String content;89@Override10protected void beforeSave() {11// Normalize data before validation12if (title != null) {13title = title.trim();14}15}1617@Override18protected void onValidate() {19// Custom validation logic20if (content != null && content.length() < 100) {21getState().addError(22getState().getField("content"),23new IllegalArgumentException("Content must be at least 100 characters")24);25}2627if (title != null && title.toLowerCase().contains("spam")) {28getState().addError(29getState().getField("title"),30new IllegalArgumentException("Title contains forbidden words")31);32}33}3435@Override36protected void afterSave() {37// Post-save logic (cache invalidation, notifications, etc.)38System.out.println("Article saved: " + getLabel());39}40}
Error Handling
Handling ValidationException
1db.beginWrites();2try {3article.save();4db.commitWrites();56} catch (ValidationException e) {7// Get all validation errors8Map<Object, Map<ObjectField, List<Throwable>>> allErrors = e.getAllErrors();910for (Map.Entry<Object, Map<ObjectField, List<Throwable>>> entry : allErrors.entrySet()) {11Object object = entry.getKey();12Map<ObjectField, List<Throwable>> fieldErrors = entry.getValue();1314System.out.println("Errors in " + object.getClass().getSimpleName() + ":");1516for (Map.Entry<ObjectField, List<Throwable>> fieldEntry : fieldErrors.entrySet()) {17String fieldName = fieldEntry.getKey().getInternalName();18for (Throwable error : fieldEntry.getValue()) {19System.out.println(" " + fieldName + ": " + error.getMessage());20}21}22}2324} catch (DatabaseException e) {25// Handle database errors (connection, constraint violations, etc.)26logger.error("Database error", e);27throw e;2829} finally {30db.endWrites();31}
Handling Database Errors
1db.beginWrites();2try {3article.save();4db.commitWrites();56} catch (DatabaseException e) {7// Check for specific error types8if (e.getCause() instanceof SQLException) {9SQLException sqlEx = (SQLException) e.getCause();1011// Unique constraint violation12if (sqlEx.getErrorCode() == 1062) { // MySQL duplicate entry13throw new DuplicateException("Article already exists", e);14}1516// Deadlock17if (sqlEx.getErrorCode() == 1213) { // MySQL deadlock18// Retry logic19return retryOperation();20}21}2223throw e;2425} finally {26db.endWrites();27}
Automatic Retry
DB plugin automatically retries certain recoverable errors:
1// This is handled automatically by AbstractDatabase2db.beginWrites();3try {4article.save();5db.commitWrites(); // Will retry on transient errors6} finally {7db.endWrites();8}
Automatically retried errors:
- Connection timeouts
- Deadlocks
- Transient network failures
- Lock wait timeouts
Uses exponential backoff with configurable max attempts.
Batch Operations
Efficient Batch Writes
1public void importArticles(List<ArticleData> dataList) {2Database db = Database.Static.getDefault();34db.beginWrites();5try {6for (ArticleData data : dataList) {7Article article = new Article();8article.setTitle(data.getTitle());9article.setContent(data.getContent());10article.setAuthor(data.getAuthor());11article.save(); // Buffered, not written yet12}1314// All articles validated and written in one batch15db.commitWrites();1617System.out.println("Imported " + dataList.size() + " articles");1819} catch (ValidationException e) {20System.err.println("Import failed due to validation errors");21// None of the articles are saved22throw e;2324} finally {25db.endWrites();26}27}
Batch with Progress Tracking
1public void importWithProgress(List<ArticleData> dataList) {2Database db = Database.Static.getDefault();3int batchSize = 100;4int processed = 0;56for (int i = 0; i < dataList.size(); i += batchSize) {7int end = Math.min(i + batchSize, dataList.size());8List<ArticleData> batch = dataList.subList(i, end);910db.beginWrites();11try {12for (ArticleData data : batch) {13Article article = new Article();14// ... populate article15article.save();16}1718db.commitWrites();19processed += batch.size();20System.out.println("Processed " + processed + " / " + dataList.size());2122} catch (Exception e) {23System.err.println("Batch failed at index " + i);24throw e;2526} finally {27db.endWrites();28}29}30}
Parallel Batch Processing
1public void parallelImport(List<ArticleData> dataList) throws InterruptedException {2int numThreads = Runtime.getRuntime().availableProcessors();3ExecutorService executor = Executors.newFixedThreadPool(numThreads);4int batchSize = 100;56List<Future<?>> futures = new ArrayList<>();78for (int i = 0; i < dataList.size(); i += batchSize) {9int start = i;10int end = Math.min(i + batchSize, dataList.size());1112Future<?> future = executor.submit(() -> {13List<ArticleData> batch = dataList.subList(start, end);14Database db = Database.Static.getDefault();1516db.beginWrites();17try {18for (ArticleData data : batch) {19Article article = new Article();20// ... populate article21article.save();22}23db.commitWrites();24} finally {25db.endWrites();26}27});2829futures.add(future);30}3132// Wait for all batches to complete33for (Future<?> future : futures) {34try {35future.get();36} catch (ExecutionException e) {37System.err.println("Batch failed: " + e.getCause().getMessage());38}39}4041executor.shutdown();42}
Distributed Locks
For unique constraint enforcement across distributed systems:
1public class Article extends Record {23@Indexed4@Unique5private String slug;67@Override8protected void beforeSave() {9// DB plugin automatically acquires a distributed lock10// for unique fields during save11}12}
Transaction Patterns
Unit of Work Pattern
1public class ArticleService {23public void publishArticle(UUID articleId) {4Database db = Database.Static.getDefault();56db.beginWrites();7try {8Article article = Query.from(Article.class)9.where("_id = ?", articleId)10.first();1112if (article == null) {13throw new IllegalArgumentException("Article not found");14}1516// Make multiple related changes17article.setPublishDate(db.now());18article.setStatus("published");19article.save();2021// Update author stats22Author author = article.getAuthor();23author.incrementPublishedCount();24author.save();2526// Create activity log27ActivityLog log = new ActivityLog();28log.setType("article_published");29log.setArticle(article);30log.setTimestamp(db.now());31log.save();3233// All changes committed together34db.commitWrites();3536} finally {37db.endWrites();38}39}40}
Saga Pattern (Compensating Transactions)
1public class OrderService {23public void placeOrder(Order order) {4Database db = Database.Static.getDefault();5boolean inventoryReserved = false;6boolean paymentProcessed = false;78try {9db.beginWrites();1011// Step 1: Reserve inventory12reserveInventory(order);13inventoryReserved = true;1415// Step 2: Process payment16processPayment(order);17paymentProcessed = true;1819// Step 3: Create order20order.setStatus("confirmed");21order.save();2223db.commitWrites();2425} catch (Exception e) {26// Compensating actions27if (paymentProcessed) {28refundPayment(order);29}30if (inventoryReserved) {31releaseInventory(order);32}33throw e;3435} finally {36db.endWrites();37}38}39}
Optimistic Locking
1public class Article extends Record {23@Indexed4private int version;56public void updateWithOptimisticLock(String newTitle) {7Database db = Database.Static.getDefault();89db.beginWrites();10try {11// Load current version12Article current = Query.from(Article.class)13.where("_id = ?", this.getId())14.first();1516if (current.getVersion() != this.version) {17throw new ConcurrentModificationException(18"Article was modified by another transaction"19);20}2122// Update with new version23this.setTitle(newTitle);24this.setVersion(this.version + 1);25this.save();2627db.commitWrites();2829} finally {30db.endWrites();31}32}33}
Performance Optimization
Batch vs. Individual Commits
1// Slow - many small transactions2for (Article article : articles) {3db.beginWrites();4try {5article.save();6db.commitWrites();7} finally {8db.endWrites();9}10}1112// Fast - one large transaction13db.beginWrites();14try {15for (Article article : articles) {16article.save();17}18db.commitWrites();19} finally {20db.endWrites();21}
Eventual Consistency for Bulk Operations
1// Use eventual consistency for better performance2db.beginWrites();3try {4for (Article article : articles) {5article.save();6}7db.commitWritesEventually(); // Faster than commitWrites()8} finally {9db.endWrites();10}
Skip Validation for Trusted Data
1// Import from trusted source - skip validation2db.beginWrites();3try {4for (ArticleData data : trustedData) {5Article article = convertToArticle(data);6db.saveUnsafely(article.getState()); // No validation7}8db.commitWrites();9} finally {10db.endWrites();11}
Best Practices
1. Always Use try-finally
1// Good2db.beginWrites();3try {4article.save();5db.commitWrites();6} finally {7db.endWrites();8}910// Bad - endWrites() might not be called11db.beginWrites();12article.save();13db.commitWrites();14db.endWrites();
2. Keep Transactions Short
1// Good - short transaction2db.beginWrites();3try {4article.save();5db.commitWrites();6} finally {7db.endWrites();8}910// Bad - long transaction holding locks11db.beginWrites();12try {13article.save();14sendEmail(article); // External I/O15updateSearchIndex(article); // Slow operation16db.commitWrites();17} finally {18db.endWrites();19}
3. Use Appropriate Commit Strategy
1// Critical data - immediate consistency2db.beginWrites();3try {4payment.save();5db.commitWrites(); // Must be immediately visible6} finally {7db.endWrites();8}910// Non-critical data - eventual consistency11db.beginWrites();12try {13viewCount.save();14db.commitWritesEventually(); // Performance > consistency15} finally {16db.endWrites();17}
4. Don't Catch and Ignore Exceptions
1// Bad - hides errors2db.beginWrites();3try {4article.save();5db.commitWrites();6} catch (Exception e) {7// Ignored!8} finally {9db.endWrites();10}1112// Good - handle or propagate13db.beginWrites();14try {15article.save();16db.commitWrites();17} catch (ValidationException e) {18logger.error("Validation failed: {}", e.getMessage());19throw e;20} finally {21db.endWrites();22}