public class TransactionInJava {

   
/*

        Propagation Level   Description    Use Case

        REQUIRED (default)  Joins existing transaction or creates new one
            Most common scenario - standard service methods
        REQUIRES_NEW    Suspends current transaction, creates new independent one
            Logging, auditing, email sending
        MANDATORY   Requires existing transaction, throws exception if none
            Security-critical operations
        NESTED  Creates savepoint within existing transaction
            Partial rollbacks, batch processing
        SUPPORTS    Joins transaction if exists, runs non-transactional if not
            Read-only queries
        NOT_SUPPORTED   Suspends transaction, runs non-transactional
            Operations that shouldn't be transactional
        NEVER   Throws exception if transaction exists
            Non-transactional operations that can't risk being in a transaction

     */

}

@SpringBootApplication
@EnableTransactionManagement 
// Usually enabled by default in Spring Boot
class Application {
   
public static void main(String[] args) {
       
SpringApplication.run(Application.class, args);
    }
}

@Service
@Slf4j
public class
OrderService {

   
@Autowired
    private
OrderRepository orderRepository;

   
@Autowired
    private
AuditService auditService;

   
@Autowired
    private
InventoryService inventoryService;

   
// Default: REQUIRED propagation
   
@Transactional
    public
Order createOrder(OrderRequest request) {
       
// Saves within current transaction
       
Order order = orderRepository.save(Order.builder()
                .
productId(request.getProductId())
                .
quantity(request.getQuantity())
                .
status("PENDING")
                .
build());

       
// Deduct inventory - participates in same transaction
       
inventoryService.deductStock(request.getProductId(), request.getQuantity());

       
// This runs in a NEW, independent transaction
       
auditService.logOrderCreation(order.getId());

       
// If anything fails here, order AND inventory update are rolled back
        // But the audit log remains (thanks to REQUIRES_NEW)

       
return order;
    }

   
// REQUIRES_NEW - Always creates independent transaction
   
@Transactional(propagation = Propagation.REQUIRES_NEW)
   
public void updateInventory(Long productId, int quantity) {
       
// Runs in separate transaction
        // Committed independently even if outer transaction fails
       
inventoryRepository.updateStock(productId, quantity);
    }

   
// MANDATORY - Must be called within existing transaction
   
@Transactional(propagation = Propagation.MANDATORY)
   
public void validateOrder(Long orderId) {
       
// If called without transaction, throws exception
       
orderRepository.validateOrder(orderId);
    }

   
// NESTED - Creates savepoint within existing transaction
   
@Transactional(propagation = Propagation.NESTED)
   
public void applyDiscount(Long orderId, double discountPercent) {
       
// Can rollback only this operation without affecting outer transaction
       
orderRepository.applyDiscount(orderId, discountPercent);
    }

   
// SUPPORTS - Transaction optional
   
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
   
public Order getOrder(Long id) {
       
// Reads data - transaction not required
       
return orderRepository.findById(id).orElse(null);
    }

   
// NOT_SUPPORTED - Suspends any current transaction
   
@Transactional(propagation = Propagation.NOT_SUPPORTED)
   
public void sendConfirmationEmail(Order order) {
       
// Email sending shouldn't be part of database transaction
       
emailService.send(order);
    }
}

@Service
@Slf4j
class
AuditService {

   
@Autowired
    private
AuditLogRepository auditRepository;

   
// Always runs in its own transaction
   
@Transactional(propagation = Propagation.REQUIRES_NEW)
   
public void logOrderCreation(Long orderId) {
       
try {
           
auditRepository.save(AuditLog.builder()
                    .
entityType("ORDER")
                    .
entityId(orderId)
                    .
action("CREATED")
                    .
timestamp(LocalDateTime.now())
                    .
build());
        }
catch (Exception e) {
           
// Log failure but don't propagate - audit failure shouldn't break main flow
           
log.error("Failed to save audit log for order: {}", orderId, e);
        }
    }
}

@Service
class
BatchProcessor {

   
// Each item runs in its own transaction
   
@Transactional(propagation = Propagation.REQUIRES_NEW)
   
public void processItemWithIsolation(Item item) {
       
// If this fails, other items still commit
       
processItem(item);
    }

   
// All items in single transaction with savepoints
   
@Transactional(propagation = Propagation.NESTED)
   
public void processItemWithSavepoint(Item item) {
       
// If this fails, only this item rolls back
        // But changes from previous successful items are preserved
       
processItem(item);
    }
}

@Service
class
UserService {

   
@Transactional
    public void
methodA() {
       
methodB(); // Won't start new transaction!
   
}

   
@Transactional(propagation = Propagation.REQUIRES_NEW)
   
public void methodB() {
       
// This won't run in a new transaction when called from methodA
   
}

   
// Good practice example
   
@Transactional(readOnly = true)
   
public List<Product> searchProducts(String keyword) {
       
return productRepository.search(keyword);
    }
}

 

 

 

Read-write and read-only transaction routing with Spring