foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • quiz
Java
  • Understand ACID properties and transaction management
  • Implement proper commit and rollback handling
  • Use batch processing for efficient bulk operations

Transactions and Batch Processing

The Art of Data Integrity

When working with databases, you often need to execute multiple operations that must either all succeed or all fail together. Imagine transferring money between bank accounts - you can't debit one account without crediting another. Transactions ensure this "all or nothing" behavior, while batch processing lets you execute multiple statements efficiently.

Understanding Transactions

A transaction is a sequence of database operations treated as a single logical unit. It follows the ACID principles:

┌─────────────────────────────────────────────────────────────────┐
│                    ACID Properties                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   A - Atomicity:     All operations succeed, or none do        │
│                      (All or nothing)                           │
│                                                                 │
│   C - Consistency:   Database moves from one valid state        │
│                      to another valid state                     │
│                                                                 │
│   I - Isolation:     Concurrent transactions don't              │
│                      interfere with each other                  │
│                                                                 │
│   D - Durability:    Once committed, changes persist            │
│                      even after system failures                 │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Transaction Lifecycle

A typical transaction follows this flow:

┌─────────────────────────────────────────────────────────────────┐
│                     Transaction Flow                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   1. Disable auto-commit                                        │
│      └── conn.setAutoCommit(false)                             │
│                                                                 │
│   2. Execute operations                                         │
│      ├── INSERT...                                             │
│      ├── UPDATE...                                             │
│      └── DELETE...                                             │
│                                                                 │
│   3a. Success: Commit all changes                               │
│       └── conn.commit()                                        │
│                                                                 │
│   3b. Failure: Rollback all changes                            │
│       └── conn.rollback()                                      │
│                                                                 │
│   4. Restore auto-commit                                        │
│      └── conn.setAutoCommit(true)                              │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Auto-Commit Mode

By default, JDBC connections operate in auto-commit mode - each statement is immediately committed to the database. For transactions, you need to disable this:

// Default behavior - each statement commits immediately
Connection conn = DriverManager.getConnection(url, user, password);
// conn.getAutoCommit() returns true

// For transactions, disable auto-commit
conn.setAutoCommit(false);  // Now changes are not committed automatically

Basic Transaction Pattern

Here's the fundamental pattern for transaction handling:

Connection conn = null;

try {
    conn = DriverManager.getConnection(url, user, password);
    conn.setAutoCommit(false);  // Start transaction
    
    // Execute multiple operations
    // Operation 1
    PreparedStatement pstmt1 = conn.prepareStatement(
        "UPDATE accounts SET balance = balance - ? WHERE id = ?");
    pstmt1.setDouble(1, 100.00);
    pstmt1.setInt(2, fromAccountId);
    pstmt1.executeUpdate();
    
    // Operation 2
    PreparedStatement pstmt2 = conn.prepareStatement(
        "UPDATE accounts SET balance = balance + ? WHERE id = ?");
    pstmt2.setDouble(1, 100.00);
    pstmt2.setInt(2, toAccountId);
    pstmt2.executeUpdate();
    
    conn.commit();  // All operations succeeded - commit
    System.out.println("Transfer successful!");
    
} catch (SQLException e) {
    if (conn != null) {
        try {
            conn.rollback();  // Something failed - rollback all changes
            System.out.println("Transaction rolled back!");
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }
    e.printStackTrace();
} finally {
    if (conn != null) {
        try {
            conn.setAutoCommit(true);  // Restore default behavior
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Real-World Example: Bank Transfer

Let's see a complete, production-ready example:

public class BankService {
    
    public boolean transferMoney(int fromAccount, int toAccount, 
            double amount) throws SQLException {
        
        String debitSql = "UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?";
        String creditSql = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
        String logSql = "INSERT INTO transactions (from_account, to_account, amount, timestamp) VALUES (?, ?, ?, ?)";
        
        try (Connection conn = dataSource.getConnection()) {
            conn.setAutoCommit(false);
            
            try {
                // Step 1: Debit the source account
                try (PreparedStatement debitStmt = conn.prepareStatement(debitSql)) {
                    debitStmt.setDouble(1, amount);
                    debitStmt.setInt(2, fromAccount);
                    debitStmt.setDouble(3, amount);  // Check sufficient balance
                    
                    int rows = debitStmt.executeUpdate();
                    if (rows == 0) {
                        throw new SQLException("Insufficient funds or account not found");
                    }
                }
                
                // Step 2: Credit the destination account
                try (PreparedStatement creditStmt = conn.prepareStatement(creditSql)) {
                    creditStmt.setDouble(1, amount);
                    creditStmt.setInt(2, toAccount);
                    
                    int rows = creditStmt.executeUpdate();
                    if (rows == 0) {
                        throw new SQLException("Destination account not found");
                    }
                }
                
                // Step 3: Log the transaction
                try (PreparedStatement logStmt = conn.prepareStatement(logSql)) {
                    logStmt.setInt(1, fromAccount);
                    logStmt.setInt(2, toAccount);
                    logStmt.setDouble(3, amount);
                    logStmt.setTimestamp(4, Timestamp.valueOf(LocalDateTime.now()));
                    logStmt.executeUpdate();
                }
                
                // All steps succeeded - commit
                conn.commit();
                return true;
                
            } catch (SQLException e) {
                conn.rollback();  // Undo all changes
                throw e;  // Re-throw for caller to handle
            }
        }
    }
}

Savepoints: Partial Rollbacks

Savepoints allow you to rollback part of a transaction while keeping earlier changes:

Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);

Savepoint savepoint1 = null;
Savepoint savepoint2 = null;

try {
    // Step 1: Create order
    executeInsert(conn, "INSERT INTO orders ...");
    savepoint1 = conn.setSavepoint("orderCreated");
    
    // Step 2: Add order items
    executeInsert(conn, "INSERT INTO order_items ...");
    savepoint2 = conn.setSavepoint("itemsAdded");
    
    // Step 3: Update inventory (might fail)
    try {
        executeUpdate(conn, "UPDATE inventory SET stock = stock - 1 ...");
    } catch (SQLException e) {
        // Inventory update failed - rollback to savepoint2
        // Order and items are preserved
        conn.rollback(savepoint2);
        System.out.println("Inventory update failed, but order is saved");
    }
    
    conn.commit();
    
} catch (SQLException e) {
    conn.rollback();  // Complete rollback
    throw e;
} finally {
    conn.setAutoCommit(true);
}

Transaction Isolation Levels

Isolation levels control how transactions interact with each other:

Level Dirty Reads Non-Repeatable Reads Phantom Reads
READ_UNCOMMITTED ✅ Possible ✅ Possible ✅ Possible
READ_COMMITTED ❌ Prevented ✅ Possible ✅ Possible
REPEATABLE_READ ❌ Prevented ❌ Prevented ✅ Possible
SERIALIZABLE ❌ Prevented ❌ Prevented ❌ Prevented

Setting Isolation Level

// Check current isolation level
int level = conn.getTransactionIsolation();

// Set isolation level (before starting transaction)
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
conn.setAutoCommit(false);

// Execute transaction...

Understanding the Problems

Dirty Read: Reading uncommitted changes from another transaction
Non-Repeatable Read: Same query returns different results within a transaction
Phantom Read: New rows appear between two identical queries

Batch Processing

When you need to execute many similar statements, batch processing significantly improves performance by sending multiple statements to the database in one round-trip:

Without Batch:                    With Batch:
┌──────────────┐                  ┌──────────────┐
│   App        │                  │   App        │
├──────────────┤                  ├──────────────┤
│ INSERT 1 ──────► DB             │ INSERT 1     │
│ INSERT 2 ──────► DB             │ INSERT 2     │ ──► DB (single trip)
│ INSERT 3 ──────► DB             │ INSERT 3     │
│ INSERT 4 ──────► DB             │ INSERT 4     │
└──────────────┘                  └──────────────┘
   4 round-trips                     1 round-trip

Basic Batch Operations

Using Statement for Different Queries

try (Connection conn = dataSource.getConnection();
     Statement stmt = conn.createStatement()) {
    
    conn.setAutoCommit(false);
    
    // Add different SQL statements to batch
    stmt.addBatch("INSERT INTO users (name) VALUES ('Alice')");
    stmt.addBatch("INSERT INTO users (name) VALUES ('Bob')");
    stmt.addBatch("UPDATE products SET price = price * 1.1 WHERE category = 'Electronics'");
    stmt.addBatch("DELETE FROM logs WHERE created_at < '2024-01-01'");
    
    // Execute all at once
    int[] results = stmt.executeBatch();
    
    conn.commit();
    
    // Check results
    for (int i = 0; i < results.length; i++) {
        System.out.println("Statement " + (i + 1) + " affected " + results[i] + " rows");
    }
}

Using PreparedStatement for Same Query

More common - inserting many rows with the same structure:

String sql = "INSERT INTO employees (name, department, salary) VALUES (?, ?, ?)";

try (Connection conn = dataSource.getConnection();
     PreparedStatement pstmt = conn.prepareStatement(sql)) {
    
    conn.setAutoCommit(false);
    
    List<Employee> employees = getEmployeesToInsert();
    
    for (Employee emp : employees) {
        pstmt.setString(1, emp.getName());
        pstmt.setString(2, emp.getDepartment());
        pstmt.setDouble(3, emp.getSalary());
        pstmt.addBatch();  // Add to batch
    }
    
    int[] results = pstmt.executeBatch();
    conn.commit();
    
    int totalInserted = Arrays.stream(results).sum();
    System.out.println("Inserted " + totalInserted + " employees");
}

Batch Processing with Chunking

For very large datasets, process in chunks to manage memory:

public void insertLargeDataset(List<Product> products) throws SQLException {
    String sql = "INSERT INTO products (name, price, category) VALUES (?, ?, ?)";
    int batchSize = 1000;  // Process 1000 at a time
    
    try (Connection conn = dataSource.getConnection();
         PreparedStatement pstmt = conn.prepareStatement(sql)) {
        
        conn.setAutoCommit(false);
        int count = 0;
        
        for (Product product : products) {
            pstmt.setString(1, product.getName());
            pstmt.setDouble(2, product.getPrice());
            pstmt.setString(3, product.getCategory());
            pstmt.addBatch();
            
            count++;
            
            // Execute batch every 1000 records
            if (count % batchSize == 0) {
                pstmt.executeBatch();
                pstmt.clearBatch();  // Clear for next chunk
                System.out.println("Processed " + count + " records...");
            }
        }
        
        // Execute remaining records
        if (count % batchSize != 0) {
            pstmt.executeBatch();
        }
        
        conn.commit();
        System.out.println("Total: " + count + " products inserted");
    }
}

Combining Transactions and Batches

Transactions and batch processing work great together:

public void importOrderWithItems(Order order, List<OrderItem> items) 
        throws SQLException {
    
    String orderSql = "INSERT INTO orders (customer_id, total, status) VALUES (?, ?, ?)";
    String itemSql = "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)";
    
    try (Connection conn = dataSource.getConnection()) {
        conn.setAutoCommit(false);
        
        try {
            // Insert order and get generated ID
            long orderId;
            try (PreparedStatement orderStmt = conn.prepareStatement(
                    orderSql, Statement.RETURN_GENERATED_KEYS)) {
                orderStmt.setLong(1, order.getCustomerId());
                orderStmt.setBigDecimal(2, order.getTotal());
                orderStmt.setString(3, "PENDING");
                orderStmt.executeUpdate();
                
                try (ResultSet keys = orderStmt.getGeneratedKeys()) {
                    keys.next();
                    orderId = keys.getLong(1);
                }
            }
            
            // Batch insert all items
            try (PreparedStatement itemStmt = conn.prepareStatement(itemSql)) {
                for (OrderItem item : items) {
                    itemStmt.setLong(1, orderId);
                    itemStmt.setLong(2, item.getProductId());
                    itemStmt.setInt(3, item.getQuantity());
                    itemStmt.setBigDecimal(4, item.getPrice());
                    itemStmt.addBatch();
                }
                itemStmt.executeBatch();
            }
            
            conn.commit();
            
        } catch (SQLException e) {
            conn.rollback();
            throw e;
        }
    }
}

Best Practices Summary

For Transactions:

  • Always disable auto-commit explicitly
  • Use try-catch-finally or try-with-resources
  • Always rollback in case of exceptions
  • Choose appropriate isolation level
  • Keep transactions short to reduce locking

For Batch Processing:

  • Use PreparedStatement for repeated operations
  • Process large datasets in chunks
  • Clear the batch after execution
  • Monitor memory usage for very large batches
  • Combine with transactions for data integrity

Transactions ensure all-or-nothing execution with ACID properties. Use setAutoCommit(false), commit(), rollback(). Savepoints enable partial rollback with setSavepoint(). Control concurrent access with isolation levels. Batch processing improves performance for bulk operations—use addBatch(), executeBatch(). Always handle both commit and rollback scenarios. Combine transactions and batches for reliable, efficient data operations.

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service