← Back to list

Why Transactions Matter in Modern Applications

When you start working with databases or backend systems, one concept keeps coming up again and again: transactions. Before jumping into…

CodeCraft by Ramcharan · 2026-02-02 05:24 · 0 claps · 5.1 min read
#transactions #spring-transaction #rollbacks
Open on Medium ↗
Wiki topics: 🌐 · Web Development

Why Transactions Matter in Modern Applications

When you start working with databases or backend systems, one concept keeps coming up again and again: transactions. Before jumping into frameworks or annotations, let’s understand the idea in the simplest possible way.

What Is a Transaction?

At its core, A transaction is a set of actions that must either succeed together or fail together.

There is no partial success. Either everything happens, or nothing happens.

A Simple Non-Technical Example: ATM Withdrawal

Check balance → Deduct money → Dispense cash → Update account

Now think about this scenario: Money is deducted from your account. But the ATM fails to dispense cash

That’s unacceptable. So if step 3 fails, step 2 must be undone.

This entire sequence is treated as one transaction.

Why Do We Even Need Transactions?

Transactions exist to prevent systems from reaching inconsistent or broken states. Without them, one operation may succeed while another fails — like an order being saved without a payment — whereas transactions ensure that if any step fails, everything is rolled back and the system remains correct.

Transaction States (Very Important)

A transaction has only a few possible states:

  1. Begin — the transaction starts
  2. Commit — everything succeeded, changes are saved permanently
  3. Rollback — something failed, all changes are undone

Transactions in Database Terms

DECLARE
    -- You can declare variables if needed
    v_order_id    orders.order_id%TYPE := 101;
    v_payment_id  payments.payment_id%TYPE := 201;
BEGIN
    -- Start the transaction (implicit in PL/SQL block)

    -- Insert into orders table
    INSERT INTO orders (order_id, customer_id, order_date, total_amount)
    VALUES (v_order_id, 1, SYSDATE, 500);

    -- Insert into payments table
    INSERT INTO payments (payment_id, order_id, payment_date, amount)
    VALUES (v_payment_id, v_order_id, SYSDATE, 500);

    -- Commit the transaction if everything is fine
    COMMIT;

EXCEPTION
    WHEN OTHERS THEN
        -- If any error occurs, rollback the transaction
        ROLLBACK;
        DBMS_OUTPUT.PUT_LINE('Transaction failed: ' || SQLERRM);
END;
/

ACID Properties of Transactions

ACID stands for: Atomicity, Consistency, Isolation, and Durability.

  1. Atomicity: Either the whole transaction happens, or nothing happens.
  2. Consistency: Transactions always leave the database in a valid state.
  3. Isolation: Each transaction behaves as if it’s the only one in the system.
  4. Durability: Once a transaction is committed, the changes cannot be lost, even if the system crashes.

How Spring Transactions Work

  • Spring does not actually create or manage transactions itself in your method.
  • Instead, it uses AOP (Aspect-Oriented Programming) to “wrap” your method in transactional behavior.
  • Think of AOP like a wrapper or interceptor: it adds extra logic before and after your method runs.

Transaction Manager

Spring delegates the actual transaction work to a transaction manager. They handle opening connections or sessions, starting Transactions, committing or rolling back, and releasing the resources.

  1. JDBC → DataSourceTransactionManager
  2. JPA/Hibernate → JpaTransactionManager
  3. Hibernate native → HibernateTransactionManager
@Transactional
public void placeOrder() {
    // do stuff
}

Spring does these things under the hood:

When you annotate a method with @Transactional, Spring:

  1. Creates a proxy for the bean.

2. Proxy Intercepts Call

  • Spring AOP intercepts the call to placeOrder().
  • Decides to start a transaction because of @Transactional.

3. Transaction Started

  • PlatformTransactionManager.getTransaction() is called.
  • Checks propagation rules.
  • Opens a connection (JDBC) or session (JPA/Hibernate).

4. Method Execution

  • Calls orderRepository.save(order) → executed inside transaction.
  • Calls paymentService.processPayment(order).

5. Outcome

  • No exception: commit() called → transaction committed.
  • RuntimeException / Error: rollback() called → transaction rolled back.
  • Checked exception: By default, the transaction is not rolled back unless rollbackFor is set.

6. Cleanup

  • Connection/session is released.
  • Proxy returns control to the caller.

There are two main types of proxies:

  • JDK Dynamic Proxy → used if the bean implements an interface.
  • CGLIB Proxy → used if the bean is a class without an interface.

Important:

  • Only public methods called from outside the bean go through the proxy.
  • Internal method calls won’t trigger a transaction if called from within the same class.

Transaction Propagation

Transaction propagation tells Spring what to do when a transactional method is called from another transaction method(should it reuse the existing transactions, create a new transaction, or run without transactions). Spring provides 7 propagation behaviours.

  1. REQUIRED(default): Use the existing transaction if there is one; otherwise, create a new transaction.
  2. REQUIRES_NEW: Always create a new transaction, suspend the existing one if there is one.
@Service
public class OuterService {

    @Autowired
    private InnerService innerService;

    @Transactional
    public void outer() {
        // Step 1: do something
        innerService.inner();
        // Step 3: do something else
    }
}

@Service
public class InnerService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void inner() {
        // inner operation
    }
}
  1. MANDATORY: Must run inside an existing transaction; throws an exception if there is none.
@Service
public class InnerService {

    @Transactional(propagation = Propagation.MANDATORY)
    public void inner() {
        System.out.println("Doing something inside a transaction");
    }
}

@Service
public class OuterService {

    @Autowired
    private InnerService innerService;

    @Transactional
    public void outer() {
        innerService.inner();  // ✅ Works fine
    }

    public void test() {
        innerService.inner();  // ❌ Throws exception
    }
}
  1. SUPPORTS: Use existing transaction if there is one; otherwise, run non-transactionally.

  2. NOT_SUPPORTED: Always run non-transactionally; suspend any existing transaction.

  3. NEVER: Must run outside a transaction; throws an exception if a transaction exists.

  4. NESTED: Run in a nested transaction if a transaction exists; otherwise, behaves like REQUIRED. Nested transactions can rollback independently without affecting the parent.

Isolation Level in Transactions

Isolation level determines how and when changes made by one transaction become visible to other transactions.

It’s about data consistency and concurrency control.

Types of Data Anomalies

  1. Dirty Read: Transaction A reads data that Transaction B has changed but not yet saved. If Transaction B rolls back, A has read something that never really existed.
  2. Non-Repeatable Read: Transaction A reads the same row twice, but Transaction B changes it in between. So, Transaction A gets different results in the same transaction.
  3. Phantom Read: Transaction A reads a set of rows that match a condition. Transaction B inserts new rows that also match that condition. Transaction A reads again → new “phantom” rows appear.

Rollback Rules

  • By default: Rollback only on unchecked exceptions (RuntimeException, Error).
  • Checked exceptions do not trigger rollback unless specified:
@Transactional(rollbackFor = {Exception.class, SQLException.class})
public void doSomething() throws Exception { ... }

You can also prevent rollback for certain exceptions:

@Transactional(noRollbackFor = {CustomRuntimeException.class})

Transaction in Testing

In integration testing, you often insert data into the database, run business logic, and assert results. Without transactions, each test leaves data behind, polluting the database and making it hard to reset the state between tests. Spring solves this by wrapping each test in a transaction that, by default, is rolled back at the end of the test, ensuring a clean database every time.

  1. **@Transactional**
  • Each test runs in a separate transaction.
  • The transaction is rolled back after the test by default.
  • No manual cleanup needed.
  • Works with @SpringBootTest or @DataJpaTest.

2. @Rollback Annotation

  • transaction commits at the end of the test.
  • Useful for debugging or populating test data.
  • By default, @Transactional in tests → rollback = true.

3. @TestExecutionListeners and Transaction Management

Spring test framework uses TransactionalTestExecutionListener:

  • Intercepts before and after the test method
  • Starts transaction before test
  • Rolls back after test

Important Notes

  1. Propagation Matters
  • Default @Transactional uses Propagation.REQUIRED.
  • Service method transactions are nested inside the test transaction.
  • If the service usesREQUIRES_NEW, it will commit independently, even if the test rolls back.
  1. Read-Only Transactions
  • Optimizes DB operations for read-only tests.
  • Can prevent accidental writes.
@Transactional(readOnly = true)

3. Integration vs Unit Tests

  • @Transactional Tests are integration tests, not pure unit tests.
  • They require Spring context and database.

Simple Example:

@SpringBootTest
@Transactional
class OrderServiceIntegrationTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderRepository orderRepository;

    @Test
    void testSuccessfulOrder() {
        Order order = new Order("Laptop", 1);
        orderService.placeOrder(order);

        // Test DB state inside transaction
        assertEquals(1, orderRepository.count());
    }

    @Test
    void testOrderRollbackOnException() {
        Order order = new Order("Laptop", 1);

        assertThrows(RuntimeException.class, () -> {
            orderService.placeOrderWithFailure(order);
        });

        // DB should remain clean after rollback
        assertEquals(0, orderRepository.count());
    }
}

메타데이터
post_id
41e6b84fcaf4
slug
why-transactions-matter-in-modern-applications-41e6b84fcaf4
url
https://medium.com/@yramcharanteja/why-transactions-matter-in-modern-applications-41e6b84fcaf4
canonical_url
https://medium.com/@yramcharanteja/why-transactions-matter-in-modern-applications-41e6b84fcaf4
author_url
https://medium.com/@yramcharanteja
status
ok
fetched_at
2026-08-07 21:47:05