# Spring Boot Interview: Transaktions-Propagation erklärt > Beherrsche die Spring Boot Transaktions-Propagation: REQUIRED, REQUIRES_NEW, NESTED und mehr. 12 Interview-Fragen mit Code und typischen Fallstricken. - Published: 2026-03-18 - Updated: 2026-05-01 - Author: SharpSkill - Tags: spring boot, transactions, propagation, java, interview - Reading time: 14 min --- Die Transaktions-Propagation stellt ein grundlegendes Konzept in Spring Boot dar und wird regelmäßig in technischen Interviews abgefragt. Das Verständnis, wie Transaktionen zwischen `@Transactional`-annotierten Methoden interagieren, hilft, subtile Bugs in der Produktion zu vermeiden und robuste Architekturen zu entwerfen. > **Vorbereitungshinweis** > > Interviewer prüfen die Fähigkeit, das richtige Propagationslevel basierend auf dem Geschäftskontext zu wählen. Erklären zu können, warum REQUIRES_NEW statt REQUIRED in einem konkreten Fall, macht den Unterschied. ## Grundlagen der Transaktions-Propagation ### Frage 1: Was ist Transaktions-Propagation in Spring? Propagation definiert das Verhalten einer transaktionalen Methode, wenn sie im Kontext einer bestehenden Transaktion aufgerufen wird. Sie beantwortet die Frage: „Was passiert, wenn eine `@Transactional`-Methode eine andere ebenfalls annotierte Methode aufruft?" ```java // OrderService.java // Demonstration des Propagationskonzepts @Service public class OrderService { private final PaymentService paymentService; private final OrderRepository orderRepository; // Eltern-Transaktion - startet eine neue Transaktion @Transactional public void createOrder(OrderRequest request) { // Speichert die Bestellung in der aktuellen Transaktion Order order = orderRepository.save(new Order(request)); // Aufruf einer anderen @Transactional-Methode // Propagation entscheidet: gleiche Transaktion oder neue? paymentService.processPayment(order.getId(), request.getAmount()); } } @Service public class PaymentService { // Standard-Propagation: REQUIRED // Tritt der bestehenden Transaktion von createOrder() bei @Transactional public void processPayment(Long orderId, BigDecimal amount) { // Wird in der GLEICHEN Transaktion wie createOrder() ausgeführt // Schlägt diese Methode fehl, wird auch die Bestellung zurückgerollt } } ``` Spring bietet sieben Propagationslevel, jedes für spezifische Geschäftsanforderungen geeignet. Die Wahl wirkt sich direkt auf Datenkonsistenz und Performance aus. ### Frage 2: Beschreibe das Verhalten von REQUIRED (Standard-Propagation) REQUIRED ist die Standard-Propagation. Existiert eine Transaktion, tritt die Methode dieser bei. Andernfalls wird eine neue erstellt. Das ist das häufigste und intuitivste Verhalten. ```java // UserService.java // REQUIRED: Standardverhalten @Service public class UserService { private final UserRepository userRepository; private final AuditService auditService; @Transactional(propagation = Propagation.REQUIRED) public void updateUser(Long userId, UserUpdateRequest request) { // Startet eine Transaktion, falls keine existiert User user = userRepository.findById(userId).orElseThrow(); user.setEmail(request.getEmail()); userRepository.save(user); // Audit tritt der gleichen Transaktion bei auditService.logUpdate(userId, "EMAIL_CHANGED"); } } @Service public class AuditService { private final AuditLogRepository auditLogRepository; @Transactional(propagation = Propagation.REQUIRED) public void logUpdate(Long userId, String action) { // Tritt der Transaktion von updateUser() bei // Commit oder Rollback gemeinsam auditLogRepository.save(new AuditLog(userId, action, Instant.now())); } } ``` Das folgende Diagramm veranschaulicht den transaktionalen Ablauf: ```text updateUser() startet TX-1 ├── save(user) → TX-1 └── logUpdate() → tritt TX-1 bei (REQUIRED) └── save(audit) → TX-1 Wenn logUpdate() fehlschlägt → Rollback TX-1 → user UND audit verworfen ``` ### Frage 3: Wann sollte man REQUIRES_NEW statt REQUIRED verwenden? REQUIRES_NEW suspendiert die bestehende Transaktion und erstellt eine neue, unabhängige Transaktion. Nützlich, wenn eine Operation unabhängig vom Ergebnis der Eltern-Transaktion committet werden muss. ```java // PaymentService.java // REQUIRES_NEW: unabhängige Transaktion @Service public class PaymentService { private final PaymentRepository paymentRepository; private final PaymentAuditService auditService; @Transactional public void processPayment(Long orderId, BigDecimal amount) { Payment payment = new Payment(orderId, amount); paymentRepository.save(payment); // Audit MUSS persistiert werden, auch wenn die Zahlung später fehlschlägt auditService.logPaymentAttempt(orderId, amount); // Simuliert einen Fehler nach dem Audit if (amount.compareTo(BigDecimal.ZERO) < 0) { throw new InvalidAmountException("Negative amount"); } } } @Service public class PaymentAuditService { private final PaymentAuditRepository auditRepository; // REQUIRES_NEW: committet unabhängig von der Eltern-Transaktion @Transactional(propagation = Propagation.REQUIRES_NEW) public void logPaymentAttempt(Long orderId, BigDecimal amount) { // Neue Transaktion TX-2 erstellt // TX-1 (processPayment) ist suspendiert auditRepository.save(new PaymentAudit(orderId, amount, "ATTEMPTED")); // TX-2 committet hier, unabhängig von TX-1 } } ``` Der transaktionale Ablauf mit REQUIRES_NEW: ```text processPayment() startet TX-1 ├── save(payment) → TX-1 ├── logPaymentAttempt() → TX-1 SUSPENDIERT │ └── startet TX-2 → neue Transaktion │ └── save(audit) → TX-2 │ └── COMMIT TX-2 → audit persistiert │ └── TX-1 SETZT FORT └── throw InvalidAmountException └── ROLLBACK TX-1 → Zahlung verworfen, aber audit erhalten ``` > **Vorsicht vor Deadlocks** > > REQUIRES_NEW kann Deadlocks verursachen, wenn die neue Transaktion auf dieselben Ressourcen zugreift, die von der suspendierten Transaktion gesperrt sind. Vermeide REQUIRES_NEW, um dieselben Tabellen wie die Eltern-Transaktion zu modifizieren. ### Frage 4: Erkläre die NESTED-Propagation und den Unterschied zu REQUIRES_NEW NESTED erstellt einen Savepoint innerhalb der aktuellen Transaktion. Schlägt die Methode fehl, werden nur die Änderungen seit dem Savepoint zurückgerollt, nicht die gesamte Eltern-Transaktion. ```java // BatchProcessingService.java // NESTED: Savepoint innerhalb der Eltern-Transaktion @Service public class BatchProcessingService { private final ItemRepository itemRepository; private final ItemProcessor itemProcessor; @Transactional public BatchResult processBatch(List items) { BatchResult result = new BatchResult(); for (Item item : items) { try { // Jedes Item wird mit einem Savepoint verarbeitet itemProcessor.processItem(item); result.addSuccess(item.getId()); } catch (ProcessingException e) { // Rollback nur dieses Items, nicht des gesamten Batches result.addFailure(item.getId(), e.getMessage()); } } return result; // Commit erfolgreicher Items } } @Service public class ItemProcessor { private final ItemRepository itemRepository; // NESTED: erstellt einen Savepoint, partielles Rollback möglich @Transactional(propagation = Propagation.NESTED) public void processItem(Item item) { item.setStatus("PROCESSING"); itemRepository.save(item); // Geschäftsvalidierung if (!isValid(item)) { throw new ProcessingException("Invalid item"); // Rollback zum Savepoint → nur dieses Item } item.setStatus("COMPLETED"); itemRepository.save(item); } } ``` Vergleich von NESTED vs. REQUIRES_NEW: ```text NESTED: ├── Verwendet einen Savepoint innerhalb der Eltern-TX ├── Bei Fehler → Rollback zum Savepoint ├── Wenn Eltern-TX zurückgerollt wird → NESTED ebenfalls └── Performanter (keine neue Verbindung) REQUIRES_NEW: ├── Erstellt eine völlig unabhängige Transaktion ├── Bei Fehler → Rollback nur der Kind-TX ├── Wenn Eltern-TX zurückgerollt wird → Kind-TX BEREITS COMMITTET └── Erfordert eine neue Verbindung ``` > **NESTED-Unterstützung** > > NESTED-Propagation erfordert JDBC-Savepoint-Unterstützung. Die meisten modernen Datenbanken (PostgreSQL, MySQL, Oracle) unterstützen dies. Kompatibilität vor der Verwendung prüfen. ## Erweiterte Propagationstypen ### Frage 5: Wann sollte man SUPPORTS und NOT_SUPPORTED verwenden? SUPPORTS führt innerhalb der bestehenden Transaktion aus, falls vorhanden, sonst ohne Transaktion. NOT_SUPPORTED suspendiert jede bestehende Transaktion und führt ohne Transaktion aus. ```java // ReportingService.java // SUPPORTS: optionale Transaktion @Service public class ReportingService { private final ReportRepository reportRepository; // SUPPORTS: funktioniert mit oder ohne Transaktion // Nützlich für Lesezugriffe, die keine transaktionalen Garantien brauchen @Transactional(propagation = Propagation.SUPPORTS) public Report generateReport(Long reportId) { // Bei Aufruf aus einer @Transactional-Methode → nutzt deren TX // Bei direktem Aufruf → keine Transaktion (read-only OK) return reportRepository.generateComplexReport(reportId); } } @Service public class ExternalNotificationService { private final ExternalApiClient apiClient; // NOT_SUPPORTED: niemals in einer Transaktion // Verhindert TX-Blockade während eines langsamen externen Aufrufs @Transactional(propagation = Propagation.NOT_SUPPORTED) public void sendExternalNotification(String message) { // Eltern-TX während des Aufrufs suspendiert apiClient.send(message); // Potenziell langsamer HTTP-Aufruf // Eltern-TX wird danach fortgesetzt } } ``` ```java // OrderService.java // Beispiel für kombinierte Verwendung @Service public class OrderService { private final OrderRepository orderRepository; private final ReportingService reportingService; private final ExternalNotificationService notificationService; @Transactional public void completeOrder(Long orderId) { Order order = orderRepository.findById(orderId).orElseThrow(); order.setStatus("COMPLETED"); orderRepository.save(order); // Report-Generierung in der gleichen TX (SUPPORTS) Report report = reportingService.generateReport(orderId); // Externe Benachrichtigung AUSSERHALB der Transaktion (NOT_SUPPORTED) // Verhindert TX-Timeout, falls externe API langsam ist notificationService.sendExternalNotification( "Order " + orderId + " completed" ); } } ``` ### Frage 6: Erkläre MANDATORY und NEVER MANDATORY erfordert eine bestehende Transaktion und wirft sonst eine Exception. NEVER erfordert das Fehlen einer Transaktion und wirft eine Exception, falls eine existiert. ```java // AuditService.java // MANDATORY: muss aus einer Transaktion heraus aufgerufen werden @Service public class AuditService { private final AuditRepository auditRepository; // MANDATORY: weigert sich, ohne bestehende Transaktion zu laufen // Garantiert, dass das Audit immer atomar mit der auditierten Operation ist @Transactional(propagation = Propagation.MANDATORY) public void logCriticalAction(String action, Long entityId) { // Aufruf ohne Transaktion → IllegalTransactionStateException auditRepository.save(new AuditLog(action, entityId, Instant.now())); } } // CacheService.java // NEVER: darf niemals in einer Transaktion sein @Service public class CacheService { private final CacheManager cacheManager; // NEVER: Cache sollte nicht an Transaktionen teilnehmen // Verhindert Inkonsistenzen zwischen Cache und DB nach Rollback @Transactional(propagation = Propagation.NEVER) public void invalidateCache(String cacheKey) { // Aufruf aus @Transactional → IllegalTransactionStateException cacheManager.getCache("entities").evict(cacheKey); } } ``` ```java // SecurityService.java // Korrekte Verwendung von MANDATORY @Service public class SecurityService { private final AuditService auditService; @Transactional public void changeUserPassword(Long userId, String newPassword) { // Sensible Operation... updatePassword(userId, newPassword); // Audit MUSS in derselben Transaktion sein // MANDATORY erzwingt diese Architekturvorgabe auditService.logCriticalAction("PASSWORD_CHANGE", userId); } // FEHLER: direkter Aufruf ohne Transaktion public void badUsage() { // Wirft IllegalTransactionStateException, da keine TX vorhanden auditService.logCriticalAction("TEST", 1L); } } ``` ## Häufige Interview-Fallstricke ### Frage 7: Warum funktioniert @Transactional bei internen Aufrufen nicht? Einer der häufigsten Fallstricke. Interne Methodenaufrufe (Self-Invocation) umgehen den Spring-Proxy und deaktivieren das Transaktionsmanagement. ```java // BrokenService.java // KLASSISCHER FEHLER: Self-Invocation @Service public class BrokenService { private final ItemRepository itemRepository; public void processItems(List itemIds) { for (Long id : itemIds) { // FALLE: interner Aufruf → umgeht den Proxy // @Transactional auf processItem() wird IGNORIERT processItem(id); } } @Transactional public void processItem(Long itemId) { // Diese Transaktion wird bei internen Aufrufen NIE erstellt Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } ``` Lösungen, um diese Falle zu vermeiden: ```java // Lösung 1: Selbstinjektion @Service public class FixedServiceWithSelfInjection { private final ItemRepository itemRepository; @Lazy @Autowired private FixedServiceWithSelfInjection self; // Selbstinjektion public void processItems(List itemIds) { for (Long id : itemIds) { // Aufruf über Proxy → @Transactional funktioniert self.processItem(id); } } @Transactional public void processItem(Long itemId) { // Transaktion korrekt erstellt Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } // Lösung 2: Aufteilung in zwei Services (empfohlen) @Service public class ItemOrchestrator { private final ItemProcessor processor; public void processItems(List itemIds) { for (Long id : itemIds) { // Aufruf an anderes Bean → Proxy funktioniert processor.processItem(id); } } } @Service public class ItemProcessor { private final ItemRepository itemRepository; @Transactional public void processItem(Long itemId) { // Transaktion korrekt verwaltet Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } ``` ### Frage 8: Wie behandelt man Exceptions und Rollback? Standardmäßig führt Spring nur bei RuntimeException und Error ein Rollback durch. Checked Exceptions lösen KEIN automatisches Rollback aus. ```java // TransactionRollbackDemo.java // Rollback-Verhalten je nach Exception-Typ @Service public class TransactionRollbackDemo { private final OrderRepository orderRepository; // RuntimeException → automatisches ROLLBACK @Transactional public void methodWithRuntimeException() { orderRepository.save(new Order()); throw new RuntimeException("Error"); // ROLLBACK } // Checked Exception → standardmäßig KEIN Rollback @Transactional public void methodWithCheckedException() throws IOException { orderRepository.save(new Order()); throw new IOException("File error"); // COMMITTET trotzdem! } // Rollback bei Checked Exception erzwingen @Transactional(rollbackFor = IOException.class) public void methodWithRollbackFor() throws IOException { orderRepository.save(new Order()); throw new IOException("Error"); // ROLLBACK dank rollbackFor } // Eine RuntimeException vom Rollback ausschließen @Transactional(noRollbackFor = BusinessException.class) public void methodWithNoRollbackFor() { orderRepository.save(new Order()); throw new BusinessException("Warning"); // COMMITTET trotz Exception } } ``` Empfohlene Konfiguration für Geschäftsfälle: ```java // BaseTransactionalService.java // Konsistente transaktionale Konfiguration @Service public abstract class BaseTransactionalService { // Rollback bei allen Exceptions (checked und unchecked) @Transactional(rollbackFor = Exception.class) protected void executeInTransaction(Runnable operation) { operation.run(); } } // PaymentService.java @Service public class PaymentService extends BaseTransactionalService { private final PaymentRepository paymentRepository; @Transactional( rollbackFor = Exception.class, noRollbackFor = InsufficientFundsException.class ) public PaymentResult processPayment(PaymentRequest request) { Payment payment = new Payment(request); paymentRepository.save(payment); if (request.getAmount().compareTo(getBalance()) > 0) { // KEIN Rollback - der Versuch soll als Datensatz erhalten bleiben throw new InsufficientFundsException("Insufficient balance"); } return new PaymentResult(payment.getId(), "SUCCESS"); } } ``` ### Frage 9: Wie funktioniert Transaktionsisolation mit Propagation? Isolation und Propagation ergänzen sich. Isolation bestimmt die Datensichtbarkeit zwischen nebenläufigen Transaktionen. ```java // IsolationDemo.java // Kombination Isolation + Propagation @Service public class IsolationDemo { private final AccountRepository accountRepository; // READ_COMMITTED: sieht von anderen Transaktionen committete Daten @Transactional( isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED ) public BigDecimal getAccountBalance(Long accountId) { // Kann unterschiedliche Werte sehen, wenn während der TX erneut gelesen return accountRepository.findById(accountId) .map(Account::getBalance) .orElse(BigDecimal.ZERO); } // REPEATABLE_READ: garantiert dieselbe Lesung während der Transaktion @Transactional( isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED ) public void transferWithConsistentRead(Long fromId, Long toId, BigDecimal amount) { Account from = accountRepository.findById(fromId).orElseThrow(); Account to = accountRepository.findById(toId).orElseThrow(); // Selbst wenn eine andere Transaktion 'from' zwischenzeitlich ändert, // sehen wir immer den initialen Wert (Snapshot) from.debit(amount); to.credit(amount); accountRepository.save(from); accountRepository.save(to); } // SERIALIZABLE: maximale Isolation, keine Nebenläufigkeit @Transactional( isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRES_NEW ) public void criticalOperation(Long accountId) { // Blockiert jede andere Transaktion auf diesen Daten // Sparsam einsetzen - Performance-Auswirkung Account account = accountRepository.findById(accountId).orElseThrow(); account.performCriticalUpdate(); accountRepository.save(account); } } ``` Übersichtstabelle der Isolationslevel: ```text | Isolation | Dirty Read | Non-Repeatable | Phantom | |------------------|------------|----------------|---------| | READ_UNCOMMITTED | Möglich | Möglich | Möglich | | READ_COMMITTED | Nein | Möglich | Möglich | | REPEATABLE_READ | Nein | Nein | Möglich | | SERIALIZABLE | Nein | Nein | Nein | ``` > **Performance-Auswirkung** > > Je strenger die Isolation, desto stärker wird die Performance durch Locks beeinflusst. SERIALIZABLE kann in stark frequentierten Produktionsumgebungen zu erheblichen Konflikten führen. ## Erweiterte Patterns ### Frage 10: Wie implementiert man das „Transactional Outbox"-Pattern? Das Outbox-Pattern garantiert Konsistenz zwischen Datenbankänderungen und dem Versenden von Nachrichten/Events, selbst im Fehlerfall. ```java // OutboxService.java // Transactional Outbox Pattern @Service public class OutboxService { private final OutboxRepository outboxRepository; // Speichert das Event in derselben Transaktion wie die Geschäftsentität @Transactional(propagation = Propagation.MANDATORY) public void saveEvent(String aggregateType, Long aggregateId, String eventType, String payload) { OutboxEvent event = OutboxEvent.builder() .aggregateType(aggregateType) .aggregateId(aggregateId) .eventType(eventType) .payload(payload) .status("PENDING") .createdAt(Instant.now()) .build(); outboxRepository.save(event); } } @Service public class OrderService { private final OrderRepository orderRepository; private final OutboxService outboxService; @Transactional public Order createOrder(OrderRequest request) { // Bestellung erstellen Order order = new Order(request); orderRepository.save(order); // Outbox-Event in DERSELBEN Transaktion (MANDATORY) // Bei erfolgreichem Commit → beide werden persistiert // Bei Rollback → keines wird persistiert outboxService.saveEvent( "ORDER", order.getId(), "ORDER_CREATED", toJson(new OrderCreatedEvent(order)) ); return order; } } // OutboxPublisher.java // Separater Prozess, der Events veröffentlicht @Service public class OutboxPublisher { private final OutboxRepository outboxRepository; private final MessageBroker messageBroker; // Unabhängige Transaktion für jede Veröffentlichung @Scheduled(fixedDelay = 1000) @Transactional(propagation = Propagation.REQUIRES_NEW) public void publishPendingEvents() { List events = outboxRepository .findByStatusOrderByCreatedAt("PENDING"); for (OutboxEvent event : events) { try { messageBroker.publish(event.getEventType(), event.getPayload()); event.setStatus("PUBLISHED"); event.setPublishedAt(Instant.now()); } catch (Exception e) { event.setStatus("FAILED"); event.setError(e.getMessage()); } outboxRepository.save(event); } } } ``` ### Frage 11: Wie testet man verschiedene Propagationsverhalten? Propagationstests erfordern besondere Aufmerksamkeit, um das erwartete transaktionale Verhalten zu verifizieren. ```java // TransactionPropagationTest.java // Testen von Propagationsverhalten @SpringBootTest @Transactional class TransactionPropagationTest { @Autowired private OrderService orderService; @Autowired private PaymentService paymentService; @Autowired private TestEntityManager entityManager; @Test void required_shouldShareTransaction() { // Given Order order = orderService.createOrder(new OrderRequest()); // When - Zahlung in derselben Transaktion (REQUIRED) paymentService.processPayment(order.getId(), BigDecimal.TEN); // Then - beide vor dem Commit sichtbar entityManager.flush(); assertThat(entityManager.find(Order.class, order.getId())).isNotNull(); assertThat(entityManager.find(Payment.class, order.getId())).isNotNull(); } @Test void requiresNew_shouldCommitIndependently() { // Given Long orderId = null; try { // When - Audit in REQUIRES_NEW orderId = orderService.createOrderWithAudit(new OrderRequest()); throw new RuntimeException("Simulated failure after audit"); } catch (RuntimeException e) { // Haupttransaktion wird zurückgerollt } // Then - Audit (REQUIRES_NEW) ist trotzdem committet assertThat(findAuditLog(orderId)).isNotNull(); // Aber die Bestellung wird zurückgerollt assertThat(findOrder(orderId)).isNull(); } @Test void mandatory_shouldThrowWithoutTransaction() { // Given - keine aktive Transaktion // When/Then - muss eine Exception werfen assertThatThrownBy(() -> auditService.logCriticalAction("TEST", 1L)) .isInstanceOf(IllegalTransactionStateException.class) .hasMessageContaining("No existing transaction"); } } ``` ```java // PropagationIntegrationTest.java // Integrationstest mit echtem Rollback und Commit @SpringBootTest class PropagationIntegrationTest { @Autowired private OrderService orderService; @Autowired private OrderRepository orderRepository; @Autowired private AuditLogRepository auditLogRepository; @Test void nested_shouldRollbackOnlyNestedOnFailure() { // Given int initialCount = orderRepository.findAll().size(); // When - Batch-Verarbeitung mit NESTED BatchResult result = orderService.processBatchWithNested( List.of(validItem(), invalidItem(), validItem()) ); // Then - nur gültige Items werden persistiert assertThat(result.getSuccessCount()).isEqualTo(2); assertThat(result.getFailureCount()).isEqualTo(1); assertThat(orderRepository.findAll().size()).isEqualTo(initialCount + 2); } } ``` ### Frage 12: Was sind die Best Practices für die Transaktionskonfiguration? Eine konsistente transaktionale Konfiguration auf Projektebene vermeidet Überraschungen und erleichtert die Wartung. ```java // TransactionConfig.java // Zentralisierte Transaktionskonfiguration @Configuration @EnableTransactionManagement public class TransactionConfig { // Standard-Timeout für alle Transaktionen @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager tm = new DataSourceTransactionManager(dataSource); tm.setDefaultTimeout(30); // 30 Sekunden max pro Transaktion return tm; } } // BaseService.java // Standardannotationen für Services @Service @Transactional( readOnly = true, // Standardmäßig nur lesend rollbackFor = Exception.class // Rollback bei jeder Exception ) public abstract class BaseService { // Lesemethoden erben readOnly = true } // OrderService.java // Service mit konsistenter Konfiguration @Service public class OrderService extends BaseService { private final OrderRepository orderRepository; // Erbt readOnly = true public Order findById(Long id) { return orderRepository.findById(id).orElseThrow(); } // Override für Schreibzugriffe @Transactional(readOnly = false) public Order createOrder(OrderRequest request) { return orderRepository.save(new Order(request)); } // Explizite Konfiguration für kritische Fälle @Transactional( readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE, timeout = 10 ) public void criticalOperation(Long orderId) { // Operation mit maximaler Isolation und kurzem Timeout } } ``` Best-Practices-Checkliste: ```text Transaktionskonfiguration - Checkliste ✅ readOnly = true standardmäßig, expliziter Override für Schreibzugriffe ✅ rollbackFor = Exception.class um Checked Exceptions einzuschließen ✅ Passender Timeout je nach Operationstyp ✅ Interne Aufrufe vermeiden (@Transactional ignoriert) ✅ REQUIRES_NEW nur, wenn unabhängiger Commit notwendig ist ✅ MANDATORY um transaktionalen Kontext zu garantieren ✅ Explizite Tests für Rollback-Verhalten ✅ Lange laufende Transaktionen überwachen ✅ Nicht-standardmäßige Propagationsentscheidungen dokumentieren ``` ## Fazit Transaktions-Propagation ist ein grundlegendes Konzept, das in Spring Boot Interviews abgefragt wird. Schlüsselpunkte zum Merken: **Häufige Propagationstypen:** - ✅ REQUIRED (Standard): tritt einer Transaktion bei oder erstellt eine - ✅ REQUIRES_NEW: unabhängige Transaktion, separater Commit - ✅ NESTED: Savepoint für partielles Rollback - ✅ MANDATORY: erfordert eine bestehende Transaktion **Zu vermeidende Fallstricke:** - ✅ Self-Invocation: umgeht Proxy, @Transactional ignoriert - ✅ Checked Exceptions: standardmäßig kein Rollback - ✅ REQUIRES_NEW auf denselben Daten: Deadlock-Risiko - ✅ Fehlender Timeout: Transaktionen unbegrenzt blockiert **Best Practices:** - ✅ readOnly = true standardmäßig - ✅ rollbackFor = Exception.class systematisch - ✅ Services trennen, um Self-Invocation zu vermeiden - ✅ Rollback-Verhalten explizit testen Die Beherrschung der Transaktions-Propagation zeigt ein tiefes Verständnis von Spring und Datenmanagement. Diese Konzepte sind entscheidend, um robuste Anwendungen zu entwerfen und technische Interviews erfolgreich zu bestehen. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/de/blog/spring-boot/spring-boot-transaction-propagation-interview