Tutorials Logic, IN +91 8092939553 info@tutorialslogic.com
FAQs Support
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Interview Questions Website Development
Compiler Tutorials

Spring Transactions

@Transactional Annotation

Spring's @Transactional annotation manages database transactions declaratively. When applied to a method or class, Spring wraps the execution in a transaction — committing on success and rolling back on unchecked exceptions (RuntimeException and Error) by default.

Add @EnableTransactionManagement to your configuration class (Spring Boot enables it automatically).

@Transactional with Propagation and Isolation
package com.example.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final InventoryService inventoryService;
    private final AuditService auditService;

    public OrderService(OrderRepository orderRepository,
                        InventoryService inventoryService,
                        AuditService auditService) {
        this.orderRepository  = orderRepository;
        this.inventoryService = inventoryService;
        this.auditService     = auditService;
    }

    // Default: REQUIRED propagation, DEFAULT isolation, rollback on RuntimeException
    @Transactional
    public Order placeOrder(Long userId, Long productId, int qty) {
        // All operations share the same transaction
        inventoryService.deductStock(productId, qty); // participates in this transaction
        Order order = orderRepository.save(new Order(userId, productId, qty));
        auditService.logOrder(order); // also in same transaction
        return order;
        // Commits here if no exception
    }

    // Read-only: hint to DB to optimize (no dirty checking, no flush)
    @Transactional(readOnly = true)
    public List<Order> getOrdersByUser(Long userId) {
        return orderRepository.findByUserId(userId);
    }

    // Rollback on checked exceptions too
    @Transactional(rollbackFor = Exception.class)
    public void processPayment(Long orderId) throws Exception {
        // Rolls back even if a checked Exception is thrown
    }

    // Never rollback on IllegalArgumentException
    @Transactional(noRollbackFor = IllegalArgumentException.class)
    public void updateOrder(Long orderId, String status) {
        // IllegalArgumentException won't trigger rollback
    }

    // SERIALIZABLE isolation: highest level, prevents phantom reads
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public void criticalOperation() {
        // Fully isolated from other transactions
    }

    // REQUIRES_NEW: always starts a new transaction, suspends the current one
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAuditAlways(String message) {
        // This runs in its own transaction — commits even if outer tx rolls back
        auditService.save(message);
    }
}

Propagation Types

PropagationBehavior
REQUIRED (default)Join existing transaction; create new one if none exists
REQUIRES_NEWAlways create a new transaction; suspend existing one
SUPPORTSJoin existing transaction; run non-transactionally if none
NOT_SUPPORTEDRun non-transactionally; suspend existing transaction
MANDATORYMust run within an existing transaction; throw exception if none
NEVERMust NOT run within a transaction; throw exception if one exists
NESTEDRun within a nested transaction (savepoint); rollback to savepoint on failure

Isolation Levels

Isolation LevelDirty ReadNon-Repeatable ReadPhantom Read
READ_UNCOMMITTEDPossiblePossiblePossible
READ_COMMITTEDPreventedPossiblePossible
REPEATABLE_READPreventedPreventedPossible
SERIALIZABLEPreventedPreventedPrevented
Programmatic Transactions with TransactionTemplate
package com.example.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

@Service
public class ProgrammaticTxService {

    private final TransactionTemplate transactionTemplate;
    private final OrderRepository orderRepository;

    public ProgrammaticTxService(PlatformTransactionManager txManager,
                                  OrderRepository orderRepository) {
        // TransactionTemplate wraps PlatformTransactionManager
        this.transactionTemplate = new TransactionTemplate(txManager);
        this.orderRepository     = orderRepository;
    }

    public Order createOrderProgrammatically(Order order) {
        // Configure transaction settings
        transactionTemplate.setIsolationLevel(
                TransactionTemplate.ISOLATION_READ_COMMITTED);
        transactionTemplate.setTimeout(30); // seconds

        // execute() returns a value; executeWithoutResult() for void
        return transactionTemplate.execute(status -> {
            try {
                Order saved = orderRepository.save(order);
                // ... more operations
                return saved;
            } catch (Exception e) {
                // Manually mark for rollback
                status.setRollbackOnly();
                throw new RuntimeException("Order creation failed", e);
            }
        });
    }
}
Common @Transactional Pitfalls
@Service
public class TransactionPitfalls {

    // PITFALL 1: Self-invocation bypasses the proxy — @Transactional has no effect
    public void outerMethod() {
        this.innerMethod(); // Calls directly, NOT through Spring proxy — NO transaction!
    }

    @Transactional
    public void innerMethod() {
        // This transaction is IGNORED when called from outerMethod() above
    }

    // FIX: Inject self or use a separate bean
    @Autowired
    private TransactionPitfalls self;

    public void outerMethodFixed() {
        self.innerMethod(); // Goes through proxy — transaction works!
    }

    // PITFALL 2: @Transactional on private methods — Spring proxy can't intercept
    @Transactional
    private void privateMethod() {
        // NO transaction — Spring AOP only works on public methods
    }

    // PITFALL 3: Catching RuntimeException prevents rollback
    @Transactional
    public void catchingException() {
        try {
            // ... DB operation
            throw new RuntimeException("error");
        } catch (RuntimeException e) {
            // Swallowing the exception — transaction COMMITS instead of rolling back!
            System.out.println("Caught: " + e.getMessage());
        }
    }

    // FIX: Re-throw or mark rollback manually
    @Transactional
    public void catchAndRethrow(TransactionStatus status) {
        try {
            throw new RuntimeException("error");
        } catch (RuntimeException e) {
            // Option 1: re-throw
            throw e;
            // Option 2: mark rollback only (if you need to handle the exception)
            // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }
}

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.