Spring Boot Sollicitatiegesprek: Transactiepropagatie
Beheers Spring Boot transactiepropagatie: REQUIRED, REQUIRES_NEW, NESTED en meer. 12 sollicitatievragen met code en veelgemaakte valkuilen.

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.
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?"
// 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.
// 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:
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 geannuleerdVraag 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.
// 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:
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 behoudenREQUIRES_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.
// NESTED: savepoint binnen de oudertransactie
@Service
public class BatchProcessingService {
private final ItemRepository itemRepository;
private final ItemProcessor itemProcessor;
@Transactional
public BatchResult processBatch(List<Item> 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:
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 verbindingNESTED-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.
// 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
}
}// 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.
// 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);
}
}// 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);
}
}Klaar om je Spring Boot gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
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.
// KLASSIEKE FOUT: self-invocation
@Service
public class BrokenService {
private final ItemRepository itemRepository;
public void processItems(List<Long> 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:
// Oplossing 1: Self-injectie
@Service
public class FixedServiceWithSelfInjection {
private final ItemRepository itemRepository;
@Lazy
@Autowired
private FixedServiceWithSelfInjection self; // Self-injectie
public void processItems(List<Long> 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<Long> 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.
// 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:
// 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.
// 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:
| 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 |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.
// 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<OutboxEvent> 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.
// 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");
}
}// 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.
// 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:
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 propagatiekeuzesKlaar om je Spring Boot gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
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.
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Tags
Delen
Gerelateerde artikelen

30 Spring Boot Interviewvragen: Volledige Gids voor Java-ontwikkelaars
Bereid je voor op je Spring Boot-interviews met deze 30 essentiële vragen over auto-configuratie, starters, Spring Data JPA, security en testing.

Spring Modulith: Modulaire Monoliet-architectuur Uitgelegd
Leer Spring Modulith om modulaire monolieten in Java te bouwen. Architectuur, modules, async events en testen met Spring Boot 3 voorbeelden.

Spring Batch 5 Interview: Partitioning, Chunks en Fault Tolerance
Slaag voor Spring Batch 5 interviews: 15 essentiële vragen over partitioning, chunk-verwerking en fault tolerance met Java 21 codevoorbeelden.