# Spring Boot Sollicitatiegesprek: Transactiepropagatie > Beheers Spring Boot transactiepropagatie: REQUIRED, REQUIRES_NEW, NESTED en meer. 12 sollicitatievragen met code en veelgemaakte valkuilen. - Published: 2026-03-18 - Updated: 2026-05-01 - Author: SharpSkill - Tags: spring boot, transactions, propagation, java, interview - Reading time: 14 min --- Transactiepropagatie vormt een fundamenteel concept in Spring Boot, dat regelmatig wordt geëvalueerd tijdens technische sollicitatiegesprekken. Begrijpen hoe transacties op elkaar inwerken tussen `@Transactional`-geannoteerde methoden helpt om subtiele productiebugs te vermijden en robuuste architecturen te ontwerpen. > **Voorbereidingsadvies** > > Interviewers testen het vermogen om het juiste propagatieniveau te kiezen op basis van de bedrijfscontext. Kunnen uitleggen waarom REQUIRES_NEW in plaats van REQUIRED in een specifiek geval maakt het verschil. ## Fundamenten van transactiepropagatie ### Vraag 1: Wat is transactiepropagatie in Spring? Propagatie definieert het gedrag van een transactionele methode wanneer deze wordt aangeroepen binnen de context van een bestaande transactie. Het beantwoordt de vraag: "Wat gebeurt er wanneer een `@Transactional`-methode een andere geannoteerde methode aanroept?" ```java // OrderService.java // Demonstratie van het propagatieconcept @Service public class OrderService { private final PaymentService paymentService; private final OrderRepository orderRepository; // Oudertransactie - start een nieuwe transactie @Transactional public void createOrder(OrderRequest request) { // Slaat de bestelling op in de huidige transactie Order order = orderRepository.save(new Order(request)); // Aanroep van een andere @Transactional-methode // Propagatie bepaalt: dezelfde transactie of nieuwe? paymentService.processPayment(order.getId(), request.getAmount()); } } @Service public class PaymentService { // Standaard propagatie: REQUIRED // Sluit zich aan bij de bestaande transactie van createOrder() @Transactional public void processPayment(Long orderId, BigDecimal amount) { // Voert uit in DEZELFDE transactie als createOrder() // Als deze methode faalt, wordt ook de bestelling teruggedraaid } } ``` Spring biedt zeven propagatieniveaus, elk geschikt voor specifieke bedrijfsbehoeften. De keuze heeft directe impact op data-consistentie en performance. ### Vraag 2: Beschrijf het gedrag van REQUIRED (standaard propagatie) REQUIRED is de standaard propagatie. Als er een transactie bestaat, sluit de methode zich daarbij aan. Anders wordt een nieuwe gemaakt. Dit is het meest voorkomende en intuïtieve gedrag. ```java // UserService.java // REQUIRED: standaardgedrag @Service public class UserService { private final UserRepository userRepository; private final AuditService auditService; @Transactional(propagation = Propagation.REQUIRED) public void updateUser(Long userId, UserUpdateRequest request) { // Start een transactie als er geen bestaat User user = userRepository.findById(userId).orElseThrow(); user.setEmail(request.getEmail()); userRepository.save(user); // De audit sluit zich aan bij dezelfde transactie auditService.logUpdate(userId, "EMAIL_CHANGED"); } } @Service public class AuditService { private final AuditLogRepository auditLogRepository; @Transactional(propagation = Propagation.REQUIRED) public void logUpdate(Long userId, String action) { // Sluit zich aan bij de transactie van updateUser() // Commit of rollback samen auditLogRepository.save(new AuditLog(userId, action, Instant.now())); } } ``` Het onderstaande diagram illustreert de transactionele flow: ```text updateUser() start TX-1 ├── save(user) → TX-1 └── logUpdate() → sluit aan bij TX-1 (REQUIRED) └── save(audit) → TX-1 Als logUpdate() faalt → rollback TX-1 → user EN audit geannuleerd ``` ### Vraag 3: Wanneer REQUIRES_NEW gebruiken in plaats van REQUIRED? REQUIRES_NEW pauzeert de bestaande transactie en maakt een nieuwe onafhankelijke transactie. Nuttig wanneer een operatie gecommit moet worden ongeacht het resultaat van de oudertransactie. ```java // PaymentService.java // REQUIRES_NEW: onafhankelijke transactie @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 MOET worden bewaard zelfs als de betaling later faalt auditService.logPaymentAttempt(orderId, amount); // Simulatie van een fout na de audit if (amount.compareTo(BigDecimal.ZERO) < 0) { throw new InvalidAmountException("Negative amount"); } } } @Service public class PaymentAuditService { private final PaymentAuditRepository auditRepository; // REQUIRES_NEW: commit onafhankelijk van de oudertransactie @Transactional(propagation = Propagation.REQUIRES_NEW) public void logPaymentAttempt(Long orderId, BigDecimal amount) { // Nieuwe transactie TX-2 gemaakt // TX-1 (processPayment) is gepauzeerd auditRepository.save(new PaymentAudit(orderId, amount, "ATTEMPTED")); // TX-2 commit hier, onafhankelijk van TX-1 } } ``` De transactionele flow met REQUIRES_NEW: ```text processPayment() start TX-1 ├── save(payment) → TX-1 ├── logPaymentAttempt() → TX-1 GEPAUZEERD │ └── start TX-2 → nieuwe transactie │ └── save(audit) → TX-2 │ └── COMMIT TX-2 → audit bewaard │ └── TX-1 HERVAT └── throw InvalidAmountException └── ROLLBACK TX-1 → betaling geannuleerd, maar audit behouden ``` > **Pas op voor deadlocks** > > REQUIRES_NEW kan deadlocks veroorzaken als de nieuwe transactie toegang krijgt tot dezelfde bronnen die zijn vergrendeld door de gepauzeerde transactie. Vermijd REQUIRES_NEW om dezelfde tabellen als de oudertransactie te wijzigen. ### Vraag 4: Leg NESTED-propagatie uit en hoe verschilt deze van REQUIRES_NEW NESTED maakt een savepoint binnen de huidige transactie. Als de methode faalt, worden alleen de wijzigingen sinds het savepoint teruggedraaid, niet de hele oudertransactie. ```java // BatchProcessingService.java // NESTED: savepoint binnen de oudertransactie @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 { // Elk item wordt verwerkt met een savepoint itemProcessor.processItem(item); result.addSuccess(item.getId()); } catch (ProcessingException e) { // Rollback alleen van dit item, niet van de hele batch result.addFailure(item.getId(), e.getMessage()); } } return result; // Commit van succesvolle items } } @Service public class ItemProcessor { private final ItemRepository itemRepository; // NESTED: maakt een savepoint, gedeeltelijke rollback mogelijk @Transactional(propagation = Propagation.NESTED) public void processItem(Item item) { item.setStatus("PROCESSING"); itemRepository.save(item); // Bedrijfsvalidatie if (!isValid(item)) { throw new ProcessingException("Invalid item"); // Rollback naar savepoint → alleen dit item } item.setStatus("COMPLETED"); itemRepository.save(item); } } ``` Vergelijking NESTED vs REQUIRES_NEW: ```text NESTED: ├── Gebruikt een savepoint binnen ouder-TX ├── Bij fout → rollback naar savepoint ├── Als ouder-TX rollback → NESTED ook rollback └── Performanter (geen nieuwe verbinding) REQUIRES_NEW: ├── Maakt een volledig onafhankelijke transactie ├── Bij fout → rollback alleen van kind-TX ├── Als ouder-TX rollback → kind-TX is AL GECOMMIT └── Vereist een nieuwe verbinding ``` > **NESTED-ondersteuning** > > NESTED-propagatie vereist JDBC-savepoint-ondersteuning. De meeste moderne databases (PostgreSQL, MySQL, Oracle) ondersteunen dit. Verifieer compatibiliteit voor gebruik. ## Geavanceerde propagatietypen ### Vraag 5: Wanneer SUPPORTS en NOT_SUPPORTED gebruiken? SUPPORTS voert uit binnen de bestaande transactie indien aanwezig, anders zonder transactie. NOT_SUPPORTED pauzeert elke bestaande transactie en voert uit zonder transactie. ```java // ReportingService.java // SUPPORTS: optionele transactie @Service public class ReportingService { private final ReportRepository reportRepository; // SUPPORTS: werkt met of zonder transactie // Nuttig voor leesoperaties die geen transactionele garanties nodig hebben @Transactional(propagation = Propagation.SUPPORTS) public Report generateReport(Long reportId) { // Indien aangeroepen vanuit @Transactional-methode → gebruikt zijn TX // Indien direct aangeroepen → geen transactie (lezen OK) return reportRepository.generateComplexReport(reportId); } } @Service public class ExternalNotificationService { private final ExternalApiClient apiClient; // NOT_SUPPORTED: nooit binnen een transactie // Voorkomt blokkeren van TX tijdens een trage externe aanroep @Transactional(propagation = Propagation.NOT_SUPPORTED) public void sendExternalNotification(String message) { // Ouder-TX gepauzeerd tijdens de aanroep apiClient.send(message); // Mogelijk trage HTTP-aanroep // Ouder-TX hervat erna } } ``` ```java // OrderService.java // Voorbeeld van gecombineerd gebruik @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); // Rapportgeneratie in dezelfde TX (SUPPORTS) Report report = reportingService.generateReport(orderId); // Externe notificatie BUITEN transactie (NOT_SUPPORTED) // Voorkomt TX-timeout als externe API traag is notificationService.sendExternalNotification( "Order " + orderId + " completed" ); } } ``` ### Vraag 6: Leg MANDATORY en NEVER uit MANDATORY vereist een bestaande transactie en gooit anders een exception. NEVER vereist de afwezigheid van een transactie en gooit een exception als er een bestaat. ```java // AuditService.java // MANDATORY: moet vanuit een transactie worden aangeroepen @Service public class AuditService { private final AuditRepository auditRepository; // MANDATORY: weigert uit te voeren zonder bestaande transactie // Garandeert dat audit altijd atomair is met de geauditeerde operatie @Transactional(propagation = Propagation.MANDATORY) public void logCriticalAction(String action, Long entityId) { // Indien aangeroepen zonder transactie → IllegalTransactionStateException auditRepository.save(new AuditLog(action, entityId, Instant.now())); } } // CacheService.java // NEVER: mag nooit binnen een transactie zijn @Service public class CacheService { private final CacheManager cacheManager; // NEVER: cache mag niet deelnemen aan transacties // Voorkomt inconsistenties tussen cache en DB na rollback @Transactional(propagation = Propagation.NEVER) public void invalidateCache(String cacheKey) { // Indien aangeroepen vanuit @Transactional → IllegalTransactionStateException cacheManager.getCache("entities").evict(cacheKey); } } ``` ```java // SecurityService.java // Correct gebruik van MANDATORY @Service public class SecurityService { private final AuditService auditService; @Transactional public void changeUserPassword(Long userId, String newPassword) { // Gevoelige operatie... updatePassword(userId, newPassword); // Audit MOET in dezelfde transactie zijn // MANDATORY dwingt deze architecturale beperking af auditService.logCriticalAction("PASSWORD_CHANGE", userId); } // FOUT: directe aanroep zonder transactie public void badUsage() { // Gooit IllegalTransactionStateException omdat er geen TX is auditService.logCriticalAction("TEST", 1L); } } ``` ## Veelvoorkomende sollicitatievalkuilen ### Vraag 7: Waarom werkt @Transactional niet bij interne aanroepen? Een van de meest voorkomende valkuilen. Interne methode-aanroepen (self-invocation) omzeilen de Spring-proxy, waardoor transactiebeheer wordt uitgeschakeld. ```java // BrokenService.java // KLASSIEKE FOUT: self-invocation @Service public class BrokenService { private final ItemRepository itemRepository; public void processItems(List itemIds) { for (Long id : itemIds) { // VALKUIL: interne aanroep → omzeilt de proxy // @Transactional op processItem() wordt GENEGEERD processItem(id); } } @Transactional public void processItem(Long itemId) { // Deze transactie wordt NOOIT gemaakt bij interne aanroepen Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } ``` Oplossingen om deze valkuil te vermijden: ```java // Oplossing 1: Self-injectie @Service public class FixedServiceWithSelfInjection { private final ItemRepository itemRepository; @Lazy @Autowired private FixedServiceWithSelfInjection self; // Self-injectie public void processItems(List itemIds) { for (Long id : itemIds) { // Aanroep via proxy → @Transactional werkt self.processItem(id); } } @Transactional public void processItem(Long itemId) { // Transactie correct gemaakt Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } // Oplossing 2: Splitsen in twee services (aanbevolen) @Service public class ItemOrchestrator { private final ItemProcessor processor; public void processItems(List itemIds) { for (Long id : itemIds) { // Aanroep naar andere bean → proxy werkt processor.processItem(id); } } } @Service public class ItemProcessor { private final ItemRepository itemRepository; @Transactional public void processItem(Long itemId) { // Transactie correct beheerd Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } ``` ### Vraag 8: Hoe omgaan met exceptions en rollback? Standaard doet Spring alleen rollback bij RuntimeException en Error. Checked exceptions activeren GEEN automatische rollback. ```java // TransactionRollbackDemo.java // Rollback-gedrag op basis van exception-type @Service public class TransactionRollbackDemo { private final OrderRepository orderRepository; // RuntimeException → automatische ROLLBACK @Transactional public void methodWithRuntimeException() { orderRepository.save(new Order()); throw new RuntimeException("Error"); // ROLLBACK } // Checked Exception → GEEN rollback standaard @Transactional public void methodWithCheckedException() throws IOException { orderRepository.save(new Order()); throw new IOException("File error"); // COMMIT toch! } // Forceer rollback bij checked exception @Transactional(rollbackFor = IOException.class) public void methodWithRollbackFor() throws IOException { orderRepository.save(new Order()); throw new IOException("Error"); // ROLLBACK dankzij rollbackFor } // Sluit een RuntimeException uit van rollback @Transactional(noRollbackFor = BusinessException.class) public void methodWithNoRollbackFor() { orderRepository.save(new Order()); throw new BusinessException("Warning"); // COMMIT ondanks exception } } ``` Aanbevolen configuratie voor bedrijfsgevallen: ```java // BaseTransactionalService.java // Consistente transactionele configuratie @Service public abstract class BaseTransactionalService { // Rollback bij alle exceptions (checked en 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) { // GEEN rollback - we willen het pogingsrecord behouden throw new InsufficientFundsException("Insufficient balance"); } return new PaymentResult(payment.getId(), "SUCCESS"); } } ``` ### Vraag 9: Hoe werkt transactie-isolatie met propagatie? Isolatie en propagatie zijn complementair. Isolatie bepaalt de zichtbaarheid van data tussen gelijktijdige transacties. ```java // IsolationDemo.java // Combinatie isolatie + propagatie @Service public class IsolationDemo { private final AccountRepository accountRepository; // READ_COMMITTED: ziet door andere transacties gecommitte data @Transactional( isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED ) public BigDecimal getAccountBalance(Long accountId) { // Kan verschillende waarden zien als opnieuw gelezen tijdens transactie return accountRepository.findById(accountId) .map(Account::getBalance) .orElse(BigDecimal.ZERO); } // REPEATABLE_READ: garandeert dezelfde lezing tijdens transactie @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(); // Zelfs als een andere transactie 'from' tussentijds wijzigt, // zien we altijd de initiële waarde (snapshot) from.debit(amount); to.credit(amount); accountRepository.save(from); accountRepository.save(to); } // SERIALIZABLE: maximale isolatie, geen concurrency @Transactional( isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRES_NEW ) public void criticalOperation(Long accountId) { // Blokkeert elke andere transactie op deze data // Spaarzaam gebruiken - performance-impact Account account = accountRepository.findById(accountId).orElseThrow(); account.performCriticalUpdate(); accountRepository.save(account); } } ``` Overzichtstabel van isolatieniveaus: ```text | Isolatie | Dirty Read | Non-Repeatable | Phantom | |------------------|------------|----------------|---------| | READ_UNCOMMITTED | Mogelijk | Mogelijk | Mogelijk| | READ_COMMITTED | Nee | Mogelijk | Mogelijk| | REPEATABLE_READ | Nee | Nee | Mogelijk| | SERIALIZABLE | Nee | Nee | Nee | ``` > **Performance-impact** > > Hoe strikter de isolatie, hoe meer de performance kan worden beïnvloed door locks. SERIALIZABLE kan significante contentie veroorzaken in productie met hoog verkeer. ## Geavanceerde patronen ### Vraag 10: Hoe het "transactional outbox"-patroon implementeren? Het outbox-patroon garandeert consistentie tussen databasewijzigingen en het verzenden van berichten/events, zelfs bij falen. ```java // OutboxService.java // Transactional Outbox Pattern @Service public class OutboxService { private final OutboxRepository outboxRepository; // Slaat het event op in dezelfde transactie als de bedrijfsentiteit @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) { // De bestelling maken Order order = new Order(request); orderRepository.save(order); // Outbox-event in DEZELFDE transactie (MANDATORY) // Als commit slaagt → beide worden bewaard // Als rollback → geen wordt bewaard outboxService.saveEvent( "ORDER", order.getId(), "ORDER_CREATED", toJson(new OrderCreatedEvent(order)) ); return order; } } // OutboxPublisher.java // Apart proces dat events publiceert @Service public class OutboxPublisher { private final OutboxRepository outboxRepository; private final MessageBroker messageBroker; // Onafhankelijke transactie voor elke publicatie @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); } } } ``` ### Vraag 11: Hoe verschillende propagatiegedragingen testen? Propagatietests vereisen speciale aandacht om het verwachte transactionele gedrag te verifiëren. ```java // TransactionPropagationTest.java // Testen van propagatiegedragingen @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 - betaling in dezelfde transactie (REQUIRED) paymentService.processPayment(order.getId(), BigDecimal.TEN); // Then - beide zichtbaar voor commit 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) { // Hoofdtransactie doet rollback } // Then - audit (REQUIRES_NEW) is nog steeds gecommit assertThat(findAuditLog(orderId)).isNotNull(); // Maar de bestelling is teruggedraaid assertThat(findOrder(orderId)).isNull(); } @Test void mandatory_shouldThrowWithoutTransaction() { // Given - geen actieve transactie // When/Then - moet een exception gooien assertThatThrownBy(() -> auditService.logCriticalAction("TEST", 1L)) .isInstanceOf(IllegalTransactionStateException.class) .hasMessageContaining("No existing transaction"); } } ``` ```java // PropagationIntegrationTest.java // Integratietest met echte rollback en 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-verwerking met NESTED BatchResult result = orderService.processBatchWithNested( List.of(validItem(), invalidItem(), validItem()) ); // Then - alleen geldige items worden bewaard assertThat(result.getSuccessCount()).isEqualTo(2); assertThat(result.getFailureCount()).isEqualTo(1); assertThat(orderRepository.findAll().size()).isEqualTo(initialCount + 2); } } ``` ### Vraag 12: Wat zijn de best practices voor transactieconfiguratie? Een consistente transactionele configuratie op projectniveau voorkomt verrassingen en vereenvoudigt onderhoud. ```java // TransactionConfig.java // Gecentraliseerde transactieconfiguratie @Configuration @EnableTransactionManagement public class TransactionConfig { // Standaard timeout voor alle transacties @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager tm = new DataSourceTransactionManager(dataSource); tm.setDefaultTimeout(30); // 30 seconden max per transactie return tm; } } // BaseService.java // Standaard annotaties voor services @Service @Transactional( readOnly = true, // Alleen-lezen standaard rollbackFor = Exception.class // Rollback bij elke exception ) public abstract class BaseService { // Leesmethoden erven readOnly = true } // OrderService.java // Service met consistente configuratie @Service public class OrderService extends BaseService { private final OrderRepository orderRepository; // Erft readOnly = true public Order findById(Long id) { return orderRepository.findById(id).orElseThrow(); } // Override voor schrijfoperaties @Transactional(readOnly = false) public Order createOrder(OrderRequest request) { return orderRepository.save(new Order(request)); } // Expliciete configuratie voor kritieke gevallen @Transactional( readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE, timeout = 10 ) public void criticalOperation(Long orderId) { // Operatie met maximale isolatie en korte timeout } } ``` Best practices checklist: ```text Transactieconfiguratie - Checklist ✅ readOnly = true standaard, expliciete override voor schrijfoperaties ✅ rollbackFor = Exception.class om checked exceptions op te nemen ✅ Passende timeout op basis van operatietype ✅ Vermijd interne aanroepen (@Transactional genegeerd) ✅ REQUIRES_NEW alleen wanneer onafhankelijke commit nodig is ✅ MANDATORY om transactionele context te garanderen ✅ Expliciete tests voor rollback-gedragingen ✅ Monitor langlopende transacties ✅ Documenteer niet-standaard propagatiekeuzes ``` ## Conclusie Transactiepropagatie is een fundamenteel concept dat wordt geëvalueerd in Spring Boot sollicitatiegesprekken. Belangrijke punten om te onthouden: **Veelvoorkomende propagatietypen:** - ✅ REQUIRED (standaard): sluit aan bij of maakt een transactie - ✅ REQUIRES_NEW: onafhankelijke transactie, aparte commit - ✅ NESTED: savepoint voor gedeeltelijke rollback - ✅ MANDATORY: vereist een bestaande transactie **Te vermijden valkuilen:** - ✅ Self-invocation: omzeilt proxy, @Transactional genegeerd - ✅ Checked exceptions: geen rollback standaard - ✅ REQUIRES_NEW op dezelfde data: deadlock-risico - ✅ Ontbrekende timeout: transacties oneindig geblokkeerd **Best practices:** - ✅ readOnly = true standaard - ✅ rollbackFor = Exception.class systematisch - ✅ Aparte services om self-invocation te vermijden - ✅ Test rollback-gedragingen expliciet Transactiepropagatie beheersen toont diepgaand begrip van Spring en datamanagement. Deze concepten zijn essentieel om robuuste applicaties te ontwerpen en technische sollicitatiegesprekken succesvol te doorstaan. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/nl/blog/spring-boot/spring-boot-transaction-propagation-interview