# Questions entretien Spring Boot : propagation des transactions expliquée > Maîtrisez la propagation des transactions Spring Boot : REQUIRED, REQUIRES_NEW, NESTED et plus. 12 questions d'entretien avec exemples de code et pièges courants. - Published: 2026-03-18 - Updated: 2026-03-31 - Author: SharpSkill - Tags: spring boot, transactions, propagation, java, entretien technique - Reading time: 14 min --- La propagation des transactions représente un concept fondamental dans Spring Boot, régulièrement évalué lors des entretiens techniques. Comprendre comment les transactions interagissent entre méthodes annotées `@Transactional` permet d'éviter des bugs subtils en production et de concevoir des architectures robustes. > **Conseil de préparation** > > Les recruteurs testent la capacité à choisir le bon niveau de propagation selon le contexte métier. Savoir expliquer pourquoi REQUIRES_NEW plutôt que REQUIRED dans un cas précis fait la différence. ## Fondamentaux de la propagation transactionnelle ### Question 1 : Qu'est-ce que la propagation des transactions dans Spring ? La propagation définit le comportement d'une méthode transactionnelle lorsqu'elle est appelée dans le contexte d'une transaction existante. Elle répond à la question : "Que se passe-t-il si une méthode `@Transactional` en appelle une autre également annotée ?" ```java // OrderService.java // Démonstration du concept de propagation @Service public class OrderService { private final PaymentService paymentService; private final OrderRepository orderRepository; // Transaction parent - démarre une nouvelle transaction @Transactional public void createOrder(OrderRequest request) { // Sauvegarde la commande dans la transaction courante Order order = orderRepository.save(new Order(request)); // Appel d'une autre méthode @Transactional // La propagation détermine : même transaction ou nouvelle ? paymentService.processPayment(order.getId(), request.getAmount()); } } @Service public class PaymentService { // Propagation par défaut : REQUIRED // Rejoint la transaction existante de createOrder() @Transactional public void processPayment(Long orderId, BigDecimal amount) { // S'exécute dans la MÊME transaction que createOrder() // Si cette méthode échoue, la commande est également rollback } } ``` Spring propose sept niveaux de propagation, chacun adapté à des besoins métier spécifiques. Le choix impacte directement la cohérence des données et les performances. ### Question 2 : Décrivez le comportement de REQUIRED (propagation par défaut) REQUIRED est la propagation par défaut. Si une transaction existe, la méthode la rejoint. Sinon, une nouvelle transaction est créée. C'est le comportement le plus courant et le plus intuitif. ```java // UserService.java // REQUIRED : comportement par défaut @Service public class UserService { private final UserRepository userRepository; private final AuditService auditService; @Transactional(propagation = Propagation.REQUIRED) public void updateUser(Long userId, UserUpdateRequest request) { // Démarre une transaction si aucune n'existe User user = userRepository.findById(userId).orElseThrow(); user.setEmail(request.getEmail()); userRepository.save(user); // L'audit rejoint la même transaction auditService.logUpdate(userId, "EMAIL_CHANGED"); } } @Service public class AuditService { private final AuditLogRepository auditLogRepository; @Transactional(propagation = Propagation.REQUIRED) public void logUpdate(Long userId, String action) { // Rejoint la transaction de updateUser() // Commit ou rollback ensemble auditLogRepository.save(new AuditLog(userId, action, Instant.now())); } } ``` Le diagramme ci-dessous illustre le flux transactionnel : ```text updateUser() démarre TX-1 ├── save(user) → TX-1 └── logUpdate() → rejoint TX-1 (REQUIRED) └── save(audit) → TX-1 Si logUpdate() échoue → TX-1 rollback → user ET audit annulés ``` ### Question 3 : Quand utiliser REQUIRES_NEW au lieu de REQUIRED ? REQUIRES_NEW suspend la transaction existante et crée une nouvelle transaction indépendante. Utile quand une opération doit être commitée indépendamment du résultat de la transaction parent. ```java // PaymentService.java // REQUIRES_NEW : transaction indépendante @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); // L'audit DOIT être persisté même si le paiement échoue ensuite auditService.logPaymentAttempt(orderId, amount); // Simulation d'une erreur après l'audit if (amount.compareTo(BigDecimal.ZERO) < 0) { throw new InvalidAmountException("Montant négatif"); } } } @Service public class PaymentAuditService { private final PaymentAuditRepository auditRepository; // REQUIRES_NEW : commit indépendant de la transaction parent @Transactional(propagation = Propagation.REQUIRES_NEW) public void logPaymentAttempt(Long orderId, BigDecimal amount) { // Nouvelle transaction TX-2 créée // TX-1 (processPayment) est suspendue auditRepository.save(new PaymentAudit(orderId, amount, "ATTEMPTED")); // TX-2 commit ici, indépendamment de TX-1 } } ``` Le flux transactionnel avec REQUIRES_NEW : ```text processPayment() démarre TX-1 ├── save(payment) → TX-1 ├── logPaymentAttempt() → TX-1 SUSPENDUE │ └── démarre TX-2 → nouvelle transaction │ └── save(audit) → TX-2 │ └── TX-2 COMMIT → audit persisté │ └── TX-1 REPREND └── throw InvalidAmountException └── TX-1 ROLLBACK → payment annulé, mais audit conservé ``` > **Attention aux deadlocks** > > REQUIRES_NEW peut causer des deadlocks si la nouvelle transaction accède aux mêmes ressources verrouillées par la transaction suspendue. Éviter d'utiliser REQUIRES_NEW pour modifier les mêmes tables que la transaction parent. ### Question 4 : Expliquez la propagation NESTED et sa différence avec REQUIRES_NEW NESTED crée un savepoint dans la transaction courante. Si la méthode échoue, seules les modifications depuis le savepoint sont annulées, pas toute la transaction parent. ```java // BatchProcessingService.java // NESTED : savepoint dans la transaction parent @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 { // Chaque item est traité avec un savepoint itemProcessor.processItem(item); result.addSuccess(item.getId()); } catch (ProcessingException e) { // Rollback uniquement cet item, pas tout le batch result.addFailure(item.getId(), e.getMessage()); } } return result; // Commit des items réussis } } @Service public class ItemProcessor { private final ItemRepository itemRepository; // NESTED : crée un savepoint, rollback partiel possible @Transactional(propagation = Propagation.NESTED) public void processItem(Item item) { item.setStatus("PROCESSING"); itemRepository.save(item); // Validation métier if (!isValid(item)) { throw new ProcessingException("Item invalide"); // Rollback jusqu'au savepoint → cet item uniquement } item.setStatus("COMPLETED"); itemRepository.save(item); } } ``` Comparaison NESTED vs REQUIRES_NEW : ```text NESTED : ├── Utilise un savepoint dans TX parent ├── Si échec → rollback au savepoint ├── Si TX parent rollback → NESTED aussi rollback └── Plus performant (pas de nouvelle connexion) REQUIRES_NEW : ├── Crée une transaction complètement indépendante ├── Si échec → rollback TX enfant uniquement ├── Si TX parent rollback → TX enfant DÉJÀ COMMITÉE └── Nouvelle connexion nécessaire ``` > **Support NESTED** > > La propagation NESTED nécessite un support JDBC de savepoints. La plupart des bases modernes (PostgreSQL, MySQL, Oracle) le supportent. Vérifier la compatibilité avant utilisation. ## Propagations avancées ### Question 5 : Quand utiliser SUPPORTS et NOT_SUPPORTED ? SUPPORTS exécute dans la transaction existante si présente, sinon sans transaction. NOT_SUPPORTED suspend toute transaction existante et exécute sans transaction. ```java // ReportingService.java // SUPPORTS : transaction optionnelle @Service public class ReportingService { private final ReportRepository reportRepository; // SUPPORTS : fonctionne avec ou sans transaction // Utile pour les lectures qui n'ont pas besoin de garanties transactionnelles @Transactional(propagation = Propagation.SUPPORTS) public Report generateReport(Long reportId) { // Si appelé depuis une méthode @Transactional → utilise sa TX // Si appelé directement → pas de transaction (lecture seule OK) return reportRepository.generateComplexReport(reportId); } } @Service public class ExternalNotificationService { private final ExternalApiClient apiClient; // NOT_SUPPORTED : jamais dans une transaction // Évite de bloquer la TX pendant un appel externe lent @Transactional(propagation = Propagation.NOT_SUPPORTED) public void sendExternalNotification(String message) { // TX parente suspendue pendant l'appel apiClient.send(message); // Appel HTTP potentiellement lent // TX parente reprend après } } ``` ```java // OrderService.java // Exemple d'utilisation combinée @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); // Génération du rapport dans la même TX (SUPPORTS) Report report = reportingService.generateReport(orderId); // Notification externe HORS transaction (NOT_SUPPORTED) // Évite timeout de la TX si l'API externe est lente notificationService.sendExternalNotification( "Order " + orderId + " completed" ); } } ``` ### Question 6 : Expliquez MANDATORY et NEVER MANDATORY exige une transaction existante et lance une exception sinon. NEVER exige l'absence de transaction et lance une exception si une existe. ```java // AuditService.java // MANDATORY : doit être appelé depuis une transaction @Service public class AuditService { private final AuditRepository auditRepository; // MANDATORY : refuse de s'exécuter sans transaction existante // Garantit que l'audit est toujours atomique avec l'opération auditée @Transactional(propagation = Propagation.MANDATORY) public void logCriticalAction(String action, Long entityId) { // Si appelé sans transaction → IllegalTransactionStateException auditRepository.save(new AuditLog(action, entityId, Instant.now())); } } // CacheService.java // NEVER : ne doit jamais être dans une transaction @Service public class CacheService { private final CacheManager cacheManager; // NEVER : le cache ne doit pas participer aux transactions // Évite les incohérences entre cache et BDD après rollback @Transactional(propagation = Propagation.NEVER) public void invalidateCache(String cacheKey) { // Si appelé depuis une @Transactional → IllegalTransactionStateException cacheManager.getCache("entities").evict(cacheKey); } } ``` ```java // SecurityService.java // Utilisation correcte de MANDATORY @Service public class SecurityService { private final AuditService auditService; @Transactional public void changeUserPassword(Long userId, String newPassword) { // Opération sensible... updatePassword(userId, newPassword); // L'audit DOIT être dans la même transaction // MANDATORY garantit cette contrainte architecturale auditService.logCriticalAction("PASSWORD_CHANGE", userId); } // ERREUR : appel direct sans transaction public void badUsage() { // Lance IllegalTransactionStateException car pas de TX auditService.logCriticalAction("TEST", 1L); } } ``` ## Pièges courants en entretien ### Question 7 : Pourquoi @Transactional ne fonctionne pas sur les appels internes ? L'un des pièges les plus fréquents. Les appels de méthode internes (self-invocation) contournent le proxy Spring, désactivant la gestion transactionnelle. ```java // BrokenService.java // ERREUR CLASSIQUE : self-invocation @Service public class BrokenService { private final ItemRepository itemRepository; public void processItems(List itemIds) { for (Long id : itemIds) { // PIÈGE : appel interne → contourne le proxy // @Transactional de processItem() est IGNORÉ processItem(id); } } @Transactional public void processItem(Long itemId) { // Cette transaction n'est JAMAIS créée lors d'un appel interne Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } ``` Solutions pour éviter ce piège : ```java // Solution 1 : Injection de self @Service public class FixedServiceWithSelfInjection { private final ItemRepository itemRepository; @Lazy @Autowired private FixedServiceWithSelfInjection self; // Auto-injection public void processItems(List itemIds) { for (Long id : itemIds) { // Appel via le proxy → @Transactional fonctionne self.processItem(id); } } @Transactional public void processItem(Long itemId) { // Transaction correctement créée Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } // Solution 2 : Séparer en deux services (recommandé) @Service public class ItemOrchestrator { private final ItemProcessor processor; public void processItems(List itemIds) { for (Long id : itemIds) { // Appel sur un autre bean → proxy fonctionne processor.processItem(id); } } } @Service public class ItemProcessor { private final ItemRepository itemRepository; @Transactional public void processItem(Long itemId) { // Transaction correctement gérée Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } ``` ### Question 8 : Comment gérer les exceptions et le rollback ? Par défaut, Spring rollback uniquement sur les RuntimeException et Error. Les checked exceptions ne déclenchent PAS de rollback automatique. ```java // TransactionRollbackDemo.java // Comportement du rollback selon le type d'exception @Service public class TransactionRollbackDemo { private final OrderRepository orderRepository; // RuntimeException → ROLLBACK automatique @Transactional public void methodWithRuntimeException() { orderRepository.save(new Order()); throw new RuntimeException("Erreur"); // ROLLBACK } // Checked Exception → PAS de rollback par défaut @Transactional public void methodWithCheckedException() throws IOException { orderRepository.save(new Order()); throw new IOException("Erreur fichier"); // COMMIT quand même ! } // Forcer le rollback sur checked exception @Transactional(rollbackFor = IOException.class) public void methodWithRollbackFor() throws IOException { orderRepository.save(new Order()); throw new IOException("Erreur"); // ROLLBACK grâce à rollbackFor } // Exclure une RuntimeException du rollback @Transactional(noRollbackFor = BusinessException.class) public void methodWithNoRollbackFor() { orderRepository.save(new Order()); throw new BusinessException("Warning"); // COMMIT malgré l'exception } } ``` Configuration recommandée pour les cas métier : ```java // BaseTransactionalService.java // Configuration transactionnelle cohérente @Service public abstract class BaseTransactionalService { // Rollback sur toutes les exceptions (checked et 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) { // Ne rollback PAS - on veut garder la trace de la tentative throw new InsufficientFundsException("Solde insuffisant"); } return new PaymentResult(payment.getId(), "SUCCESS"); } } ``` ### Question 9 : Comment fonctionne l'isolation transactionnelle avec la propagation ? L'isolation et la propagation sont complémentaires. L'isolation détermine la visibilité des données entre transactions concurrentes. ```java // IsolationDemo.java // Combinaison isolation + propagation @Service public class IsolationDemo { private final AccountRepository accountRepository; // READ_COMMITTED : voit les données commitées par d'autres transactions @Transactional( isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED ) public BigDecimal getAccountBalance(Long accountId) { // Peut voir des valeurs différentes si relu pendant la transaction return accountRepository.findById(accountId) .map(Account::getBalance) .orElse(BigDecimal.ZERO); } // REPEATABLE_READ : garantit la même lecture pendant la transaction @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(); // Même si une autre transaction modifie 'from' entre-temps, // on voit toujours la valeur initiale (snapshot) from.debit(amount); to.credit(amount); accountRepository.save(from); accountRepository.save(to); } // SERIALIZABLE : isolation maximale, pas de concurrence @Transactional( isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRES_NEW ) public void criticalOperation(Long accountId) { // Bloque toute autre transaction sur ces données // Utiliser avec parcimonie - impact performance Account account = accountRepository.findById(accountId).orElseThrow(); account.performCriticalUpdate(); accountRepository.save(account); } } ``` Tableau récapitulatif des niveaux d'isolation : ```text | Isolation | Dirty Read | Non-Repeatable | Phantom | |------------------|------------|----------------|---------| | READ_UNCOMMITTED | Possible | Possible | Possible| | READ_COMMITTED | Non | Possible | Possible| | REPEATABLE_READ | Non | Non | Possible| | SERIALIZABLE | Non | Non | Non | ``` > **Impact sur les performances** > > Plus l'isolation est stricte, plus les performances peuvent être impactées par les verrous. SERIALIZABLE peut causer des contentions importantes en environnement de production à fort trafic. ## Patterns avancés ### Question 10 : Comment implémenter le pattern "transaction outbox" ? Le pattern outbox garantit la cohérence entre les modifications en base et l'envoi de messages/événements, même en cas de panne. ```java // OutboxService.java // Pattern Transactional Outbox @Service public class OutboxService { private final OutboxRepository outboxRepository; // Sauvegarde l'événement dans la même transaction que l'entité métier @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) { // Création de la commande Order order = new Order(request); orderRepository.save(order); // Événement outbox dans la MÊME transaction (MANDATORY) // Si commit réussit → les deux sont persistés // Si rollback → aucun n'est persisté outboxService.saveEvent( "ORDER", order.getId(), "ORDER_CREATED", toJson(new OrderCreatedEvent(order)) ); return order; } } // OutboxPublisher.java // Processus séparé qui publie les événements @Service public class OutboxPublisher { private final OutboxRepository outboxRepository; private final MessageBroker messageBroker; // Transaction indépendante pour chaque publication @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); } } } ``` ### Question 11 : Comment tester les différentes propagations ? Les tests de propagation nécessitent une attention particulière pour vérifier le comportement transactionnel attendu. ```java // TransactionPropagationTest.java // Tests des comportements de propagation @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 - payment dans la même transaction (REQUIRED) paymentService.processPayment(order.getId(), BigDecimal.TEN); // Then - les deux sont visibles avant 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 dans REQUIRES_NEW orderId = orderService.createOrderWithAudit(new OrderRequest()); throw new RuntimeException("Simulated failure after audit"); } catch (RuntimeException e) { // Transaction principale rollback } // Then - l'audit (REQUIRES_NEW) est quand même commitée assertThat(findAuditLog(orderId)).isNotNull(); // Mais la commande est rollback assertThat(findOrder(orderId)).isNull(); } @Test void mandatory_shouldThrowWithoutTransaction() { // Given - aucune transaction active // When/Then - doit lancer une exception assertThatThrownBy(() -> auditService.logCriticalAction("TEST", 1L)) .isInstanceOf(IllegalTransactionStateException.class) .hasMessageContaining("No existing transaction"); } } ``` ```java // PropagationIntegrationTest.java // Test d'intégration avec rollback et commit réels @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 - traitement batch avec NESTED BatchResult result = orderService.processBatchWithNested( List.of(validItem(), invalidItem(), validItem()) ); // Then - seuls les items valides sont persistés assertThat(result.getSuccessCount()).isEqualTo(2); assertThat(result.getFailureCount()).isEqualTo(1); assertThat(orderRepository.findAll().size()).isEqualTo(initialCount + 2); } } ``` ### Question 12 : Quelles sont les bonnes pratiques de configuration transactionnelle ? Une configuration transactionnelle cohérente au niveau du projet évite les surprises et facilite la maintenance. ```java // TransactionConfig.java // Configuration centralisée des transactions @Configuration @EnableTransactionManagement public class TransactionConfig { // Timeout par défaut pour toutes les transactions @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager tm = new DataSourceTransactionManager(dataSource); tm.setDefaultTimeout(30); // 30 secondes max par transaction return tm; } } // BaseService.java // Annotations par défaut pour les services @Service @Transactional( readOnly = true, // Lecture seule par défaut rollbackFor = Exception.class // Rollback sur toute exception ) public abstract class BaseService { // Méthodes de lecture héritent de readOnly = true } // OrderService.java // Service avec configuration cohérente @Service public class OrderService extends BaseService { private final OrderRepository orderRepository; // Hérite de readOnly = true public Order findById(Long id) { return orderRepository.findById(id).orElseThrow(); } // Override pour les écritures @Transactional(readOnly = false) public Order createOrder(OrderRequest request) { return orderRepository.save(new Order(request)); } // Configuration explicite pour les cas critiques @Transactional( readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE, timeout = 10 ) public void criticalOperation(Long orderId) { // Opération avec isolation maximale et timeout court } } ``` Checklist des bonnes pratiques : ```text Configuration transactionnelle - Checklist ✅ readOnly = true par défaut, override explicite pour les écritures ✅ rollbackFor = Exception.class pour inclure les checked exceptions ✅ Timeout approprié selon le type d'opération ✅ Éviter les appels internes (@Transactional ignoré) ✅ REQUIRES_NEW uniquement si commit indépendant nécessaire ✅ MANDATORY pour garantir le contexte transactionnel ✅ Tests explicites des comportements de rollback ✅ Monitoring des transactions longues ✅ Documentation des choix de propagation non-standard ``` ## Conclusion La propagation des transactions est un concept fondamental évalué dans les entretiens Spring Boot. Les points essentiels à retenir : **Propagations courantes :** - ✅ REQUIRED (défaut) : rejoint ou crée une transaction - ✅ REQUIRES_NEW : transaction indépendante, commit séparé - ✅ NESTED : savepoint pour rollback partiel - ✅ MANDATORY : exige une transaction existante **Pièges à éviter :** - ✅ Self-invocation : contourne le proxy, @Transactional ignoré - ✅ Checked exceptions : pas de rollback par défaut - ✅ REQUIRES_NEW sur mêmes données : risque de deadlock - ✅ Timeout absent : transactions bloquées indéfiniment **Bonnes pratiques :** - ✅ readOnly = true par défaut - ✅ rollbackFor = Exception.class systématique - ✅ Séparer les services pour éviter self-invocation - ✅ Tester explicitement les comportements de rollback La maîtrise de la propagation transactionnelle démontre une compréhension approfondie de Spring et de la gestion des données. Ces concepts sont incontournables pour concevoir des applications robustes et passer les entretiens techniques avec succès. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/spring-boot/spring-boot-transaction-propagation-interview