Tutorials Logic, IN info@tutorialslogic.com
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Website Development
Practice
Quiz Challenge Interview Questions Certification Practice
Tools
Online Compiler JSON Formatter Regex Tester CSS Unit Converter Color Picker
Compiler Tools

Spring Transactions @Transactional, Propagation, Isolation: Tutorial, Examples, FAQs & Interview Tips

@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.