Transactions
Transactions in QuantaDB ensure that multiple database operations are executed atomically - either all operations succeed, or none of them do. This is crucial for maintaining data consistency in your application.
Basic Transaction Usage
Transactions are executed using the transaction
method on the storage instance. Here's a basic example:
// Start a transaction
await db.storage.transaction((txn) async {
// Create multiple users atomically
final user1 = User(
id: 'txn:1',
name: 'Transaction User 1',
email: 'txn1@example.com',
isActive: true,
lastLogin: DateTime.now(),
);
await txn.put('user:txn:1', user1);
final user2 = User(
id: 'txn:2',
name: 'Transaction User 2',
email: 'txn2@example.com',
isActive: true,
lastLogin: DateTime.now(),
);
await txn.put('user:txn:2', user2);
// If any operation fails, the entire transaction is rolled back
});
print('Transaction completed successfully');
Transaction Properties
1. Atomicity
All operations within a transaction are treated as a single unit. If any operation fails, the entire transaction is rolled back:
try {
await db.storage.transaction((txn) async {
// First operation succeeds
await txn.put('key1', 'value1');
// Second operation fails
throw Exception('Operation failed');
// This operation never executes
await txn.put('key2', 'value2');
});
} catch (e) {
print('Transaction failed: $e');
// Verify that no changes were made
final value1 = await db.get('key1');
final value2 = await db.get('key2');
print('key1: $value1, key2: $value2'); // Both should be null
}
2. Isolation
Transactions are isolated from each other. Changes made in one transaction are not visible to other transactions until the transaction is committed:
// Transaction 1
await db.storage.transaction((txn1) async {
await txn1.put('shared:key', 'value1');
// Start Transaction 2 in parallel
db.storage.transaction((txn2) async {
// This will not see the changes from txn1
final value = await txn2.get('shared:key');
print('Transaction 2 sees: $value'); // Will be null or previous value
});
// Transaction 1's changes are not yet visible
});
3. Consistency
Transactions maintain database consistency by ensuring all operations either complete successfully or are rolled back:
await db.storage.transaction((txn) async {
// Update related records atomically
final user = User(
id: 'user:1',
name: 'John Doe',
email: 'john@example.com',
isActive: true,
lastLogin: DateTime.now(),
);
// Update user record
await txn.put('user:1', user);
// Update user's settings
await txn.put('settings:user:1', {
'theme': 'dark',
'notifications': true,
});
// Update user's profile
await txn.put('profile:user:1', {
'bio': 'Software Developer',
'location': 'New York',
});
// All updates succeed or none do
});
Best Practices
-
Keep Transactions Short
// Good: Short, focused transaction
await db.storage.transaction((txn) async {
await txn.put('key1', 'value1');
await txn.put('key2', 'value2');
});
// Avoid: Long-running transactions
await db.storage.transaction((txn) async {
// Don't include complex business logic or external API calls
await txn.put('key1', 'value1');
await someExternalApiCall(); // Bad practice
await txn.put('key2', 'value2');
}); -
Error Handling
try {
await db.storage.transaction((txn) async {
// Transaction operations
});
} catch (e) {
// Handle transaction failure
print('Transaction failed: $e');
// Implement retry logic or fallback behavior
} -
Verify Transaction Results
await db.storage.transaction((txn) async {
// Perform operations
});
// Verify the results after transaction completion
final user1 = await db.get<User>('user:txn:1');
final user2 = await db.get<User>('user:txn:2');
print('Transaction users: ${user1 != null && user2 != null}');
Common Use Cases
-
Batch Updates
await db.storage.transaction((txn) async {
for (var i = 0; i < 100; i++) {
await txn.put('batch:$i', 'value$i');
}
}); -
Related Data Updates
await db.storage.transaction((txn) async {
// Update user and their related data atomically
await txn.put('user:1', user);
await txn.put('user:1:settings', settings);
await txn.put('user:1:preferences', preferences);
}); -
Data Migration
await db.storage.transaction((txn) async {
// Migrate data to new schema
final oldData = await txn.get('old:key');
if (oldData != null) {
final newData = migrateToNewSchema(oldData);
await txn.put('new:key', newData);
await txn.delete('old:key');
}
});