|
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);
}
}
|