# Spring Boot Mülakatı: İşlem Yayılımı Açıklandı > Spring Boot işlem yayılımına hakim olun: REQUIRED, REQUIRES_NEW, NESTED ve daha fazlası. Kod örnekleri ve yaygın tuzaklarla 12 mülakat sorusu. - Published: 2026-03-18 - Updated: 2026-05-01 - Author: SharpSkill - Tags: spring boot, transactions, propagation, java, interview - Reading time: 14 min --- İşlem yayılımı, teknik mülakatlarda düzenli olarak değerlendirilen Spring Boot'un temel bir kavramıdır. `@Transactional` ile işaretlenmiş metotlar arasında işlemlerin nasıl etkileşime girdiğini anlamak, üretimde meydana gelebilecek ince hataları önlemeye ve sağlam mimariler tasarlamaya yardımcı olur. > **Hazırlık Tavsiyesi** > > Mülakat yapanlar, iş bağlamına göre doğru yayılım seviyesini seçme yeteneğini test eder. Belirli bir durumda neden REQUIRED yerine REQUIRES_NEW kullanıldığını açıklayabilmek fark yaratır. ## İşlem yayılımının temelleri ### Soru 1: Spring'de işlem yayılımı nedir? Yayılım, mevcut bir işlem bağlamında çağrıldığında işlemsel bir metodun davranışını tanımlar. Şu soruyu yanıtlar: "Bir `@Transactional` metot, aynı zamanda işaretlenmiş başka bir metodu çağırdığında ne olur?" ```java // OrderService.java // Yayılım kavramının gösterimi @Service public class OrderService { private final PaymentService paymentService; private final OrderRepository orderRepository; // Üst işlem - yeni bir işlem başlatır @Transactional public void createOrder(OrderRequest request) { // Siparişi mevcut işlemde kaydeder Order order = orderRepository.save(new Order(request)); // Başka bir @Transactional metoda çağrı // Yayılım belirler: aynı işlem mi yeni işlem mi? paymentService.processPayment(order.getId(), request.getAmount()); } } @Service public class PaymentService { // Varsayılan yayılım: REQUIRED // createOrder() içindeki mevcut işleme katılır @Transactional public void processPayment(Long orderId, BigDecimal amount) { // createOrder() ile AYNI işlemde çalışır // Bu metot başarısız olursa, sipariş de geri alınır } } ``` Spring, her biri belirli iş ihtiyaçlarına uygun yedi yayılım seviyesi sunar. Seçim, veri tutarlılığını ve performansı doğrudan etkiler. ### Soru 2: REQUIRED'in (varsayılan yayılım) davranışını açıklayın REQUIRED varsayılan yayılımdır. Bir işlem varsa, metot ona katılır. Aksi takdirde yeni bir işlem oluşturulur. Bu, en yaygın ve sezgisel davranıştır. ```java // UserService.java // REQUIRED: varsayılan davranış @Service public class UserService { private final UserRepository userRepository; private final AuditService auditService; @Transactional(propagation = Propagation.REQUIRED) public void updateUser(Long userId, UserUpdateRequest request) { // Hiçbir işlem yoksa yeni bir işlem başlatır User user = userRepository.findById(userId).orElseThrow(); user.setEmail(request.getEmail()); userRepository.save(user); // Audit aynı işleme katılır auditService.logUpdate(userId, "EMAIL_CHANGED"); } } @Service public class AuditService { private final AuditLogRepository auditLogRepository; @Transactional(propagation = Propagation.REQUIRED) public void logUpdate(Long userId, String action) { // updateUser() işlemine katılır // Birlikte commit veya rollback auditLogRepository.save(new AuditLog(userId, action, Instant.now())); } } ``` Aşağıdaki diyagram işlemsel akışı göstermektedir: ```text updateUser() TX-1'i başlatır ├── save(user) → TX-1 └── logUpdate() → TX-1'e katılır (REQUIRED) └── save(audit) → TX-1 logUpdate() başarısız olursa → TX-1 geri alınır → user VE audit iptal edilir ``` ### Soru 3: REQUIRED yerine ne zaman REQUIRES_NEW kullanılmalı? REQUIRES_NEW, mevcut işlemi askıya alır ve yeni bağımsız bir işlem oluşturur. Bir işlemin üst işlemin sonucundan bağımsız olarak commit edilmesi gerektiğinde kullanışlıdır. ```java // PaymentService.java // REQUIRES_NEW: bağımsız işlem @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); // Ödeme daha sonra başarısız olsa bile audit MUTLAKA kaydedilmeli auditService.logPaymentAttempt(orderId, amount); // Audit'ten sonra hata simülasyonu if (amount.compareTo(BigDecimal.ZERO) < 0) { throw new InvalidAmountException("Negative amount"); } } } @Service public class PaymentAuditService { private final PaymentAuditRepository auditRepository; // REQUIRES_NEW: üst işlemden bağımsız commit eder @Transactional(propagation = Propagation.REQUIRES_NEW) public void logPaymentAttempt(Long orderId, BigDecimal amount) { // Yeni TX-2 işlemi oluşturuldu // TX-1 (processPayment) askıya alınmıştır auditRepository.save(new PaymentAudit(orderId, amount, "ATTEMPTED")); // TX-2 burada commit eder, TX-1'den bağımsız olarak } } ``` REQUIRES_NEW ile işlemsel akış: ```text processPayment() TX-1'i başlatır ├── save(payment) → TX-1 ├── logPaymentAttempt() → TX-1 ASKIYA ALINIR │ └── TX-2'yi başlatır → yeni işlem │ └── save(audit) → TX-2 │ └── COMMIT TX-2 → audit kaydedildi │ └── TX-1 DEVAM EDER └── throw InvalidAmountException └── ROLLBACK TX-1 → ödeme iptal, ama audit korundu ``` > **Deadlock'lara dikkat** > > REQUIRES_NEW, yeni işlem askıya alınmış işlem tarafından kilitlenen aynı kaynaklara erişirse deadlock'lara neden olabilir. Üst işlemle aynı tabloları değiştirmek için REQUIRES_NEW kullanmaktan kaçının. ### Soru 4: NESTED yayılımını açıklayın ve REQUIRES_NEW'den nasıl farklı olduğunu belirtin NESTED, mevcut işlem içinde bir savepoint oluşturur. Metot başarısız olursa, yalnızca savepoint'ten sonraki değişiklikler geri alınır, tüm üst işlem değil. ```java // BatchProcessingService.java // NESTED: üst işlem içinde savepoint @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 { // Her item bir savepoint ile işlenir itemProcessor.processItem(item); result.addSuccess(item.getId()); } catch (ProcessingException e) { // Tüm batch değil, sadece bu item rollback result.addFailure(item.getId(), e.getMessage()); } } return result; // Başarılı item'ların commit'i } } @Service public class ItemProcessor { private final ItemRepository itemRepository; // NESTED: savepoint oluşturur, kısmi rollback mümkün @Transactional(propagation = Propagation.NESTED) public void processItem(Item item) { item.setStatus("PROCESSING"); itemRepository.save(item); // İş doğrulaması if (!isValid(item)) { throw new ProcessingException("Invalid item"); // Savepoint'e rollback → sadece bu item } item.setStatus("COMPLETED"); itemRepository.save(item); } } ``` NESTED ve REQUIRES_NEW karşılaştırması: ```text NESTED: ├── Üst TX içinde bir savepoint kullanır ├── Hata durumunda → savepoint'e rollback ├── Üst TX rollback yaparsa → NESTED de rollback olur └── Daha performanslı (yeni bağlantı yok) REQUIRES_NEW: ├── Tamamen bağımsız bir işlem oluşturur ├── Hata durumunda → sadece alt TX rollback ├── Üst TX rollback yaparsa → alt TX ZATEN COMMIT EDİLMİŞ └── Yeni bir bağlantı gerektirir ``` > **NESTED Desteği** > > NESTED yayılımı JDBC savepoint desteği gerektirir. Çoğu modern veritabanı (PostgreSQL, MySQL, Oracle) bunu destekler. Kullanmadan önce uyumluluğu doğrulayın. ## Gelişmiş yayılım türleri ### Soru 5: SUPPORTS ve NOT_SUPPORTED ne zaman kullanılır? SUPPORTS, varsa mevcut işlem içinde, yoksa işlemsiz çalışır. NOT_SUPPORTED, mevcut işlemi askıya alır ve işlemsiz çalışır. ```java // ReportingService.java // SUPPORTS: opsiyonel işlem @Service public class ReportingService { private final ReportRepository reportRepository; // SUPPORTS: işlemli veya işlemsiz çalışır // İşlemsel garantiler gerektirmeyen okumalar için kullanışlı @Transactional(propagation = Propagation.SUPPORTS) public Report generateReport(Long reportId) { // @Transactional metot tarafından çağrılırsa → onun TX'ini kullanır // Doğrudan çağrılırsa → işlem yok (sadece okuma OK) return reportRepository.generateComplexReport(reportId); } } @Service public class ExternalNotificationService { private final ExternalApiClient apiClient; // NOT_SUPPORTED: asla bir işlem içinde değil // Yavaş bir dış çağrı sırasında TX'i bloklamaktan kaçınır @Transactional(propagation = Propagation.NOT_SUPPORTED) public void sendExternalNotification(String message) { // Çağrı sırasında üst TX askıya alınır apiClient.send(message); // Potansiyel yavaş HTTP çağrısı // Üst TX sonrasında devam eder } } ``` ```java // OrderService.java // Birleşik kullanım örneği @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); // Aynı TX içinde rapor oluşturma (SUPPORTS) Report report = reportingService.generateReport(orderId); // İşlem DIŞINDA dış bildirim (NOT_SUPPORTED) // Dış API yavaşsa TX timeout'unu önler notificationService.sendExternalNotification( "Order " + orderId + " completed" ); } } ``` ### Soru 6: MANDATORY ve NEVER'i açıklayın MANDATORY mevcut bir işlem gerektirir ve aksi takdirde bir istisna fırlatır. NEVER işlem olmamasını gerektirir ve varsa bir istisna fırlatır. ```java // AuditService.java // MANDATORY: bir işlem içinden çağrılmalı @Service public class AuditService { private final AuditRepository auditRepository; // MANDATORY: mevcut işlem olmadan çalışmayı reddeder // Audit'in audit edilen işlemle her zaman atomik olmasını garanti eder @Transactional(propagation = Propagation.MANDATORY) public void logCriticalAction(String action, Long entityId) { // İşlem olmadan çağrılırsa → IllegalTransactionStateException auditRepository.save(new AuditLog(action, entityId, Instant.now())); } } // CacheService.java // NEVER: asla bir işlem içinde olmamalı @Service public class CacheService { private final CacheManager cacheManager; // NEVER: cache işlemlere katılmamalı // Rollback sonrası cache ile DB arasında tutarsızlıkları önler @Transactional(propagation = Propagation.NEVER) public void invalidateCache(String cacheKey) { // @Transactional'dan çağrılırsa → IllegalTransactionStateException cacheManager.getCache("entities").evict(cacheKey); } } ``` ```java // SecurityService.java // MANDATORY'nin doğru kullanımı @Service public class SecurityService { private final AuditService auditService; @Transactional public void changeUserPassword(Long userId, String newPassword) { // Hassas işlem... updatePassword(userId, newPassword); // Audit aynı işlemde OLMALI // MANDATORY bu mimari kısıtlamayı zorunlu kılar auditService.logCriticalAction("PASSWORD_CHANGE", userId); } // HATA: işlem olmadan doğrudan çağrı public void badUsage() { // TX olmadığı için IllegalTransactionStateException fırlatır auditService.logCriticalAction("TEST", 1L); } } ``` ## Yaygın mülakat tuzakları ### Soru 7: Neden @Transactional iç çağrılarda çalışmaz? En yaygın tuzaklardan biridir. İç metot çağrıları (self-invocation) Spring proxy'sini atlayarak işlem yönetimini devre dışı bırakır. ```java // BrokenService.java // KLASİK HATA: self-invocation @Service public class BrokenService { private final ItemRepository itemRepository; public void processItems(List itemIds) { for (Long id : itemIds) { // TUZAK: iç çağrı → proxy'yi atlar // processItem() üzerindeki @Transactional GÖZ ARDI EDİLİR processItem(id); } } @Transactional public void processItem(Long itemId) { // Bu işlem iç çağrılarda ASLA oluşturulmaz Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } ``` Bu tuzaktan kaçınmak için çözümler: ```java // Çözüm 1: Self-injection @Service public class FixedServiceWithSelfInjection { private final ItemRepository itemRepository; @Lazy @Autowired private FixedServiceWithSelfInjection self; // Self-injection public void processItems(List itemIds) { for (Long id : itemIds) { // Proxy üzerinden çağrı → @Transactional çalışır self.processItem(id); } } @Transactional public void processItem(Long itemId) { // İşlem doğru şekilde oluşturuldu Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } // Çözüm 2: İki servise ayır (önerilen) @Service public class ItemOrchestrator { private final ItemProcessor processor; public void processItems(List itemIds) { for (Long id : itemIds) { // Başka bean'e çağrı → proxy çalışır processor.processItem(id); } } } @Service public class ItemProcessor { private final ItemRepository itemRepository; @Transactional public void processItem(Long itemId) { // İşlem doğru şekilde yönetiliyor Item item = itemRepository.findById(itemId).orElseThrow(); item.setStatus("PROCESSED"); itemRepository.save(item); } } ``` ### Soru 8: İstisnalar ve rollback nasıl ele alınır? Varsayılan olarak Spring yalnızca RuntimeException ve Error'da rollback yapar. Checked istisnalar otomatik rollback'i TETİKLEMEZ. ```java // TransactionRollbackDemo.java // İstisna türüne göre rollback davranışı @Service public class TransactionRollbackDemo { private final OrderRepository orderRepository; // RuntimeException → otomatik ROLLBACK @Transactional public void methodWithRuntimeException() { orderRepository.save(new Order()); throw new RuntimeException("Error"); // ROLLBACK } // Checked Exception → varsayılan olarak rollback YOK @Transactional public void methodWithCheckedException() throws IOException { orderRepository.save(new Order()); throw new IOException("File error"); // Yine de COMMIT eder! } // Checked exception'da rollback'i zorla @Transactional(rollbackFor = IOException.class) public void methodWithRollbackFor() throws IOException { orderRepository.save(new Order()); throw new IOException("Error"); // rollbackFor sayesinde ROLLBACK } // Bir RuntimeException'ı rollback'ten hariç tut @Transactional(noRollbackFor = BusinessException.class) public void methodWithNoRollbackFor() { orderRepository.save(new Order()); throw new BusinessException("Warning"); // İstisnaya rağmen COMMIT } } ``` İş durumları için önerilen yapılandırma: ```java // BaseTransactionalService.java // Tutarlı işlemsel yapılandırma @Service public abstract class BaseTransactionalService { // Tüm istisnalarda rollback (checked ve 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) { // Rollback YAPMAZ - deneme kaydını korumak istiyoruz throw new InsufficientFundsException("Insufficient balance"); } return new PaymentResult(payment.getId(), "SUCCESS"); } } ``` ### Soru 9: İşlem izolasyonu yayılımla nasıl çalışır? İzolasyon ve yayılım birbirini tamamlar. İzolasyon, eşzamanlı işlemler arasındaki veri görünürlüğünü belirler. ```java // IsolationDemo.java // İzolasyon + yayılım kombinasyonu @Service public class IsolationDemo { private final AccountRepository accountRepository; // READ_COMMITTED: diğer işlemlerce commit edilen verileri görür @Transactional( isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED ) public BigDecimal getAccountBalance(Long accountId) { // İşlem sırasında yeniden okunursa farklı değerler görebilir return accountRepository.findById(accountId) .map(Account::getBalance) .orElse(BigDecimal.ZERO); } // REPEATABLE_READ: işlem sırasında aynı okumayı garanti eder @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(); // Başka bir işlem 'from'u bu sırada değiştirse bile, // her zaman ilk değeri görürüz (snapshot) from.debit(amount); to.credit(amount); accountRepository.save(from); accountRepository.save(to); } // SERIALIZABLE: maksimum izolasyon, eşzamanlılık yok @Transactional( isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRES_NEW ) public void criticalOperation(Long accountId) { // Bu veriler üzerindeki diğer tüm işlemleri bloklar // Tutumlu kullanın - performans etkisi Account account = accountRepository.findById(accountId).orElseThrow(); account.performCriticalUpdate(); accountRepository.save(account); } } ``` İzolasyon seviyeleri özet tablosu: ```text | İzolasyon | Dirty Read | Non-Repeatable | Phantom | |------------------|------------|----------------|---------| | READ_UNCOMMITTED | Mümkün | Mümkün | Mümkün | | READ_COMMITTED | Hayır | Mümkün | Mümkün | | REPEATABLE_READ | Hayır | Hayır | Mümkün | | SERIALIZABLE | Hayır | Hayır | Hayır | ``` > **Performans etkisi** > > İzolasyon ne kadar sıkı olursa, performans kilitlerden o kadar etkilenir. SERIALIZABLE, yüksek trafikli üretim ortamlarında önemli rekabete neden olabilir. ## Gelişmiş desenler ### Soru 10: "Transactional outbox" deseni nasıl uygulanır? Outbox deseni, hata durumunda bile veritabanı değişiklikleri ile mesaj/olay gönderimi arasında tutarlılığı garanti eder. ```java // OutboxService.java // Transactional Outbox Deseni @Service public class OutboxService { private final OutboxRepository outboxRepository; // Olayı iş varlığı ile aynı işlemde kaydeder @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) { // Siparişi oluştur Order order = new Order(request); orderRepository.save(order); // AYNI işlemde outbox olayı (MANDATORY) // Commit başarılı olursa → her ikisi de kaydedilir // Rollback olursa → hiçbiri kaydedilmez outboxService.saveEvent( "ORDER", order.getId(), "ORDER_CREATED", toJson(new OrderCreatedEvent(order)) ); return order; } } // OutboxPublisher.java // Olayları yayınlayan ayrı süreç @Service public class OutboxPublisher { private final OutboxRepository outboxRepository; private final MessageBroker messageBroker; // Her yayın için bağımsız işlem @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); } } } ``` ### Soru 11: Farklı yayılım davranışları nasıl test edilir? Yayılım testleri, beklenen işlemsel davranışı doğrulamak için özel dikkat gerektirir. ```java // TransactionPropagationTest.java // Yayılım davranışlarını test etme @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 - aynı işlemde ödeme (REQUIRED) paymentService.processPayment(order.getId(), BigDecimal.TEN); // Then - ikisi de commit'ten önce görünür 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 - REQUIRES_NEW içinde audit orderId = orderService.createOrderWithAudit(new OrderRequest()); throw new RuntimeException("Simulated failure after audit"); } catch (RuntimeException e) { // Ana işlem rollback yapar } // Then - audit (REQUIRES_NEW) hâlâ commit edilmiş assertThat(findAuditLog(orderId)).isNotNull(); // Ama sipariş geri alındı assertThat(findOrder(orderId)).isNull(); } @Test void mandatory_shouldThrowWithoutTransaction() { // Given - aktif işlem yok // When/Then - bir istisna fırlatmalı assertThatThrownBy(() -> auditService.logCriticalAction("TEST", 1L)) .isInstanceOf(IllegalTransactionStateException.class) .hasMessageContaining("No existing transaction"); } } ``` ```java // PropagationIntegrationTest.java // Gerçek rollback ve commit ile entegrasyon testi @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 - NESTED ile batch işleme BatchResult result = orderService.processBatchWithNested( List.of(validItem(), invalidItem(), validItem()) ); // Then - sadece geçerli item'lar kaydedilir assertThat(result.getSuccessCount()).isEqualTo(2); assertThat(result.getFailureCount()).isEqualTo(1); assertThat(orderRepository.findAll().size()).isEqualTo(initialCount + 2); } } ``` ### Soru 12: İşlem yapılandırması için en iyi uygulamalar nelerdir? Proje düzeyinde tutarlı bir işlemsel yapılandırma sürprizleri önler ve bakımı kolaylaştırır. ```java // TransactionConfig.java // Merkezi işlem yapılandırması @Configuration @EnableTransactionManagement public class TransactionConfig { // Tüm işlemler için varsayılan timeout @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { DataSourceTransactionManager tm = new DataSourceTransactionManager(dataSource); tm.setDefaultTimeout(30); // İşlem başına maksimum 30 saniye return tm; } } // BaseService.java // Servisler için varsayılan anotasyonlar @Service @Transactional( readOnly = true, // Varsayılan olarak salt okunur rollbackFor = Exception.class // Herhangi bir istisnada rollback ) public abstract class BaseService { // Okuma metotları readOnly = true'yu devralır } // OrderService.java // Tutarlı yapılandırmaya sahip servis @Service public class OrderService extends BaseService { private final OrderRepository orderRepository; // readOnly = true'yu devralır public Order findById(Long id) { return orderRepository.findById(id).orElseThrow(); } // Yazma için override @Transactional(readOnly = false) public Order createOrder(OrderRequest request) { return orderRepository.save(new Order(request)); } // Kritik durumlar için açık yapılandırma @Transactional( readOnly = false, propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE, timeout = 10 ) public void criticalOperation(Long orderId) { // Maksimum izolasyon ve kısa timeout ile işlem } } ``` En iyi uygulamalar kontrol listesi: ```text İşlem Yapılandırması - Kontrol Listesi ✅ readOnly = true varsayılan, yazma için açık override ✅ Checked istisnaları dahil etmek için rollbackFor = Exception.class ✅ İşlem türüne göre uygun timeout ✅ İç çağrılardan kaçının (@Transactional göz ardı edilir) ✅ REQUIRES_NEW yalnızca bağımsız commit gerektiğinde ✅ İşlemsel bağlamı garanti etmek için MANDATORY ✅ Rollback davranışları için açık testler ✅ Uzun süren işlemleri izleyin ✅ Standart olmayan yayılım seçimlerini belgeleyin ``` ## Sonuç İşlem yayılımı, Spring Boot mülakatlarında değerlendirilen temel bir kavramdır. Hatırlanması gereken kilit noktalar: **Yaygın yayılım türleri:** - ✅ REQUIRED (varsayılan): bir işleme katılır veya oluşturur - ✅ REQUIRES_NEW: bağımsız işlem, ayrı commit - ✅ NESTED: kısmi rollback için savepoint - ✅ MANDATORY: mevcut bir işlem gerektirir **Kaçınılması gereken tuzaklar:** - ✅ Self-invocation: proxy'yi atlar, @Transactional göz ardı edilir - ✅ Checked istisnalar: varsayılan olarak rollback yok - ✅ Aynı verilerde REQUIRES_NEW: deadlock riski - ✅ Eksik timeout: süresiz olarak bloklanmış işlemler **En iyi uygulamalar:** - ✅ readOnly = true varsayılan olarak - ✅ rollbackFor = Exception.class sistematik olarak - ✅ Self-invocation'dan kaçınmak için ayrı servisler - ✅ Rollback davranışlarını açıkça test edin İşlem yayılımına hakim olmak, Spring ve veri yönetimi konusunda derin bir anlayışı gösterir. Bu kavramlar, sağlam uygulamalar tasarlamak ve teknik mülakatları başarıyla geçmek için elzemdir. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/tr/blog/spring-boot/spring-boot-transaction-propagation-interview