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.

İş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.
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?"
// 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.
// 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:
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 edilirSoru 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.
// 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ış:
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 korunduREQUIRES_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.
// NESTED: üst işlem içinde savepoint
@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 {
// 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ı:
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ı gerektirirNESTED 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.
// 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
}
}// 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.
// 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);
}
}// 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);
}
}Spring Boot mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
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.
// KLASİK HATA: self-invocation
@Service
public class BrokenService {
private final ItemRepository itemRepository;
public void processItems(List<Long> 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:
// Çözüm 1: Self-injection
@Service
public class FixedServiceWithSelfInjection {
private final ItemRepository itemRepository;
@Lazy
@Autowired
private FixedServiceWithSelfInjection self; // Self-injection
public void processItems(List<Long> 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<Long> 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.
// İ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:
// 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.
// İ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:
| İ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 |İ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.
// 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<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);
}
}
}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.
// 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");
}
}// 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.
// 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:
İş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 belgeleyinSpring Boot mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
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.
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Etiketler
Paylaş
İlgili makaleler

30 Spring Boot Mülakat Sorusu: Java Geliştiricileri için Eksiksiz Kılavuz
Auto-configuration, starter'lar, Spring Data JPA, güvenlik ve test konularını kapsayan 30 temel soruyla Spring Boot mülakatlarına hazırlanın.

Spring Modulith: Modüler Monolit Mimarisi Açıklaması
Java'da modüler monolitler oluşturmak için Spring Modulith öğrenin. Mimari, modüller, asenkron eventler ve Spring Boot 3 örnekleriyle test.

Spring Batch 5 Mülakat: Partitioning, Chunk ve Hata Toleransı
Spring Batch 5 mülakatlarında ustalaşın: partitioning, chunk işleme ve hata toleransı üzerine 15 temel soru, Java 21 kod örnekleriyle.