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 - atomic7Database db = Database.Static.getDefault();8db.beginWrites();9try {10article.save();11author.save();12db.commitWrites(); // Both succeed or both fail13} finally {14db.endWrites();15}
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
1Database db = Database.Static.getDefault();23// Called standalone - commits immediately4saveArticleWithAuthor(article1, author1);56// Called within transaction - commits with outer transaction7db.beginWrites();8try {9saveArticleWithAuthor(article1, author1);10saveArticleWithAuthor(article2, author2);11db.commitWrites(); // All 4 objects committed together12} finally {13db.endWrites();14}
Commit Strategies
commitWrites - Immediate Consistency
Commits changes immediately with strong consistency guarantees:
1Database db = Database.Static.getDefault();23db.beginWrites();4try {5article.save();6db.commitWrites();7// Data is immediately visible to other transactions8} finally {9db.endWrites();10}
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:
1Database db = Database.Static.getDefault();23db.beginWrites();4try {5article.save();6db.commitWritesEventually();7// Data will be visible soon, but maybe not immediately8} finally {9db.endWrites();10}
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:
1Database db = Database.Static.getDefault();23// Main transaction4db.beginWrites();5try {6article.save();78// Isolated session9db.beginIsolatedWrites();10try {11// This saves independently12auditLog.save();13db.commitWrites();14} finally {15db.endWrites();16}1718// Main transaction continues19db.commitWrites();20} finally {21db.endWrites();22}
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:
1Database db = Database.Static.getDefault();23db.beginWrites();4try {5// These are buffered6article1.save();7article2.save();8article3.save();910// This triggers validation of ALL three11db.commitWrites();1213// If any validation fails, NONE are written14} catch (ValidationException e) {15// Handle validation errors for all objects16List<State> statesWithErrors = e.getStates();17} finally {18db.endWrites();19}
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 ArticleWithValidation 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}4041// Getters and setters42public String getTitle() {43return title;44}4546public void setTitle(String title) {47this.title = title;48}4950public String getContent() {51return content;52}5354public void setContent(String content) {55this.content = content;56}57}
Error Handling
Handling ValidationException
1Database db = Database.Static.getDefault();23db.beginWrites();4try {5article.save();6db.commitWrites();78} catch (ValidationException e) {9// Get all validation errors10for (State state : e.getStates()) {11System.out.println("Errors in " + state.getType().getInternalName() + ":");1213for (ObjectField field : state.getErrorFields()) {14String fieldName = field.getInternalName();15for (String error : state.getErrors(field)) {16System.out.println(" " + fieldName + ": " + error);17}18}19}2021} catch (DatabaseException e) {22// Handle database errors (connection, constraint violations, etc.)23System.err.println("Database error: " + e.getMessage());24throw e;2526} finally {27db.endWrites();28}
Handling Database Errors
1Database db = Database.Static.getDefault();23db.beginWrites();4try {5article.save();6db.commitWrites();78} catch (DatabaseException e) {9// Check for specific error types10if (e.getCause() instanceof SQLException) {11SQLException sqlEx = (SQLException) e.getCause();1213// Unique constraint violation14if (sqlEx.getErrorCode() == 1062) { // MySQL duplicate entry15throw new RuntimeException("Article already exists", e);16}1718// Deadlock19if (sqlEx.getErrorCode() == 1213) { // MySQL deadlock20// Retry logic21// return retryOperation();22}23}2425throw e;2627} finally {28db.endWrites();29}
Automatic Retry
DB plugin automatically retries certain recoverable errors:
1// This is handled automatically by AbstractDatabase2Database db = Database.Static.getDefault();34db.beginWrites();5try {6article.save();7db.commitWrites(); // Will retry on transient errors8} finally {9db.endWrites();10}
Automatically retried errors:
- Connection timeouts
- Deadlocks
- Transient network failures
- Lock wait timeouts
Uses exponential backoff with configurable max attempts.
Batch Operations
Efficient Batch Writes
1Database db = Database.Static.getDefault();23db.beginWrites();4try {5for (ArticleData data : dataList) {6Article article = new Article();7article.setTitle(data.getTitle());8article.setContent(data.getContent());9article.setAuthor(data.getAuthor());10article.save(); // Buffered, not written yet11}1213// All articles validated and written in one batch14db.commitWrites();1516System.out.println("Imported " + dataList.size() + " articles");1718} catch (ValidationException e) {19System.err.println("Import failed due to validation errors");20// None of the articles are saved21throw e;2223} finally {24db.endWrites();25}
Batch with Progress Tracking
1Database db = Database.Static.getDefault();2int batchSize = 100;3int processed = 0;45for (int i = 0; i < dataList.size(); i += batchSize) {6int end = Math.min(i + batchSize, dataList.size());7List<ArticleData> batch = dataList.subList(i, end);89db.beginWrites();10try {11for (ArticleData data : batch) {12Article article = new Article();13// ... populate article14article.save();15}1617db.commitWrites();18processed += batch.size();19System.out.println("Processed " + processed + " / " + dataList.size());2021} catch (Exception e) {22System.err.println("Batch failed at index " + i);23throw e;2425} finally {26db.endWrites();27}28}
Parallel Batch Processing
1int numThreads = Runtime.getRuntime().availableProcessors();2ExecutorService executor = Executors.newFixedThreadPool(numThreads);3int batchSize = 100;45List<Future<?>> futures = new ArrayList<>();67for (int i = 0; i < dataList.size(); i += batchSize) {8int start = i;9int end = Math.min(i + batchSize, dataList.size());1011Future<?> future = executor.submit(() -> {12List<ArticleData> batch = dataList.subList(start, end);13Database db = Database.Static.getDefault();1415db.beginWrites();16try {17for (ArticleData data : batch) {18Article article = new Article();19// ... populate article20article.save();21}22db.commitWrites();23} finally {24db.endWrites();25}26});2728futures.add(future);29}3031// Wait for all batches to complete32for (Future<?> future : futures) {33try {34future.get();35} catch (ExecutionException e) {36System.err.println("Batch failed: " + e.getCause().getMessage());37}38}3940executor.shutdown();
Distributed Locks
For unique constraint enforcement across distributed systems:
1import com.psddev.dari.db.Record;23public class Article extends Record {45@Indexed(unique = true)6private String slug;78@Override9protected void beforeSave() {10// DB plugin automatically acquires a distributed lock11// for unique fields during save12}13}
Transaction Patterns
Unit of Work Pattern
1Database db = Database.Static.getDefault();23db.beginWrites();4try {5Article article = Query.from(Article.class)6.where("_id = ?", articleId)7.first();89if (article == null) {10throw new IllegalArgumentException("Article not found");11}1213// Make multiple related changes14article.setPublishDate(new Date(db.now()));15article.setTitle("My Article");16article.save();1718// Update author stats19Author author = article.getAuthor();20author.incrementPublishedCount();21author.save();2223// Create activity log24ActivityLog log = new ActivityLog();25log.setType("article_published");26log.setArticle(article);27log.setTimestamp(new Date(db.now()));28log.save();2930// All changes committed together31db.commitWrites();3233} finally {34db.endWrites();35}
Saga Pattern (Compensating Transactions)
1Database db = Database.Static.getDefault();2boolean inventoryReserved = false;3boolean paymentProcessed = false;45try {6db.beginWrites();78// Step 1: Reserve inventory9reserveInventory(order);10inventoryReserved = true;1112// Step 2: Process payment13processPayment(order);14paymentProcessed = true;1516// Step 3: Create order17order.setStatus("confirmed");18order.save();1920db.commitWrites();2122} catch (Exception e) {23// Compensating actions24if (paymentProcessed) {25refundPayment(order);26}27if (inventoryReserved) {28releaseInventory(order);29}30throw e;3132} finally {33db.endWrites();34}
Optimistic Locking
1Database db = Database.Static.getDefault();23db.beginWrites();4try {5// Load current version6VersionedArticle current = Query.from(VersionedArticle.class)7.where("_id = ?", article.getId())8.first();910if (current.getVersion() != article.getVersion()) {11throw new ConcurrentModificationException(12"Article was modified by another transaction"13);14}1516// Update with new version17article.setTitle("New Title");18article.setVersion(article.getVersion() + 1);19article.save();2021db.commitWrites();2223} finally {24db.endWrites();25}
Performance Optimization
Batch vs. Individual Commits
1Database db = Database.Static.getDefault();23// Slow - many small transactions4for (Article article : articles) {5db.beginWrites();6try {7article.save();8db.commitWrites();9} finally {10db.endWrites();11}12}1314// Fast - one large transaction15db.beginWrites();16try {17for (Article article : articles) {18article.save();19}20db.commitWrites();21} finally {22db.endWrites();23}
Eventual Consistency for Bulk Operations
1Database db = Database.Static.getDefault();23// Use eventual consistency for better performance4db.beginWrites();5try {6for (Article article : articles) {7article.save();8}9db.commitWritesEventually(); // Faster than commitWrites()10} finally {11db.endWrites();12}
Skip Validation for Trusted Data
1Database db = Database.Static.getDefault();23// Import from trusted source - skip validation4db.beginWrites();5try {6for (ArticleData data : trustedData) {7Article article = convertToArticle(data);8db.saveUnsafely(article.getState()); // No validation9}10db.commitWrites();11} finally {12db.endWrites();13}
Best Practices
1. Always Use try-finally
1Database db = Database.Static.getDefault();23// Good4db.beginWrites();5try {6article.save();7db.commitWrites();8} finally {9db.endWrites();10}1112// Bad - endWrites() might not be called13db.beginWrites();14article.save();15db.commitWrites();16db.endWrites();
2. Keep Transactions Short
1Database db = Database.Static.getDefault();23// Good - short transaction4db.beginWrites();5try {6article.save();7db.commitWrites();8} finally {9db.endWrites();10}1112// Bad - long transaction holding locks13db.beginWrites();14try {15article.save();16sendEmail(article); // External I/O17updateSearchIndex(article); // Slow operation18db.commitWrites();19} finally {20db.endWrites();21}
3. Use Appropriate Commit Strategy
1Database db = Database.Static.getDefault();23// Critical data - immediate consistency4db.beginWrites();5try {6payment.save();7db.commitWrites(); // Must be immediately visible8} finally {9db.endWrites();10}1112// Non-critical data - eventual consistency13db.beginWrites();14try {15viewCount.save();16db.commitWritesEventually(); // Performance > consistency17} finally {18db.endWrites();19}
4. Don't Catch and Ignore Exceptions
1Database db = Database.Static.getDefault();23// Bad - hides errors4db.beginWrites();5try {6article.save();7db.commitWrites();8} catch (Exception e) {9// Ignored!10} finally {11db.endWrites();12}1314// Good - handle or propagate15db.beginWrites();16try {17article.save();18db.commitWrites();19} catch (ValidationException e) {20System.err.println("Validation failed: " + e.getMessage());21throw e;22} finally {23db.endWrites();24}