Spring Data JPA ve Hibernate ORM kullanan servislerde flush sırasında oluşan dirty checking yükünü ölçün, persistence context boyutunu sınırlayın ve uzun transaction'ları somut profil araçlarıyla yeniden tasarlayın.
Spring Data JPA'da Hibernate Flush Maliyeti ve Transaction Tasarımı
Spring Data JPA ve Hibernate ORM flush maliyetini görünür kılmak
Hibernate ORM, managed durumundaki her entity için snapshot tutar. Transaction commit olurken veya AUTO flush gerektiren bir sorgudan önce bu snapshot'ları güncel alanlarla karşılaştırır. Bir istek 20.000 entity okuduktan sonra yalnızca bir kaydı değiştiriyorsa, asıl maliyet tek UPDATE değil persistence context içindeki binlerce adayın dirty checking taramasıdır. İlk teşhis için staging ortamında Hibernate istatistiklerini açın; bu ayar sürekli yüksek trafikli üretimde açık kalırsa sayaç güncellemeleri ek maliyet üretir.
# application-staging.properties
spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.stat=DEBUG
logging.level.org.hibernate.SQL=DEBUG
# JDBC sorgu sayisi ve bind degerleri icin
datasource-proxy.logging=SLF4Jİstatistiklerde flush sayısını, entity update sayısını ve transaction başına sorgu sayısını aynı istek kimliğiyle eşleyin. JDBC katmanında net sorgu sayımı için datasource-proxy kullanın; yalnızca SQL loguna bakmak, batch içindeki ifadeleri ve tekrar eden select'leri ayırt etmeyi zorlaştırır. Örneğin bir GET isteğinde update yokken flush sayısı artıyorsa, endpoint beklenmeden yazılabilir persistence context ile çalışıyordur.
Java backend geliştirme için persistence context boyutunu küçültmek
Aşağıdaki anti-pattern, listeyi managed entity olarak yükler ve her satır için mutasyon olasılığını Hibernate'e bırakır. Özellikle Open Session in View açıkken controller veya serializer katmanında tetiklenen lazy ilişkiler, transaction sınırından sonra bile persistence context'i gereksiz büyütebilir. Önce OSIV'i kapatın, ardından okuma senaryosunda entity yerine projection döndürün.
# application.properties
spring.jpa.open-in-view=false
public interface OrderSummary {
Long getId();
BigDecimal getTotal();
Instant getCreatedAt();
}
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("""
select o.id as id, o.total as total, o.createdAt as createdAt
from Order o
where o.customerId = :customerId
order by o.createdAt desc
""")
List<OrderSummary> findSummaries(UUID customerId);
}
@Service
@RequiredArgsConstructor
class OrderQueryService {
private final OrderRepository repository;
@Transactional(readOnly = true)
List<OrderSummary> list(UUID customerId) {
return repository.findSummaries(customerId);
}
}Bu değişiklikte mekanizma önemlidir: interface projection ile Hibernate'in her satır için Order entity snapshot'ı oluşturması engellenir; sonuç doğrudan seçilen kolonlardan üretilir. @Transactional(readOnly = true) tek başına mimari bir garanti değildir. Spring Framework, kullanılan JPA provider ve transaction manager'a göre flush modunu veya JDBC connection'ın read-only ipucunu değiştirir; yine de bu metoda managed entity alıp alanını değiştirirseniz davranışı entegrasyon testiyle doğrulayın. PostgreSQL'de sorgunun gerçekten index kullandığını görmek için EXPLAIN (ANALYZE, BUFFERS) çalıştırın; projection, kötü bir sıralama planını tek başına düzeltmez.
Java microservices içinde batch yazma ve kontrollü flush
İçe aktarma veya olay tüketicisi gibi binlerce kaydı yazan bir Java microservices bileşeninde transaction'ın sonuna kadar entity biriktirmeyin. Her flush SQL'i veritabanına gönderir, clear ise persistence context'i boşaltarak Java heap'te snapshot tutulmasını keser. JDBC batch için Hibernate ayarını ve döngü içindeki flush-clear periyodunu birlikte kullanın; yalnızca saveAll çağırmak batch garantisi vermez.
spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
@Service
@RequiredArgsConstructor
class ImportService {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void importRows(List<ImportRow> rows) {
int batchSize = 50;
for (int i = 0; i < rows.size(); i++) {
entityManager.persist(Order.from(rows.get(i)));
if ((i + 1) % batchSize == 0) {
entityManager.flush();
entityManager.clear();
}
}
entityManager.flush();
entityManager.clear();
}
}Kritik edge case: IDENTITY tabanlı identifier üretimi birçok veritabanı sürücüsünde insert'i hemen çalıştırmayı gerektirdiğinden JDBC batching'i sınırlayabilir. Batch beklenirken loglarda tek tek INSERT görüyorsanız entity identifier stratejisini, sürücü davranışını ve Hibernate batch istatistiklerini kontrol edin. Bu optimizasyonda önce-sonra karşılaştırmasına en az üç sayı koyun: 10.000 satırda toplam SQL statement sayısı, process RSS veya heap tepe değeri ve p95 import süresi. Batch size değerini 50 ile başlayıp 25, 100 ve 200 için ayrı ölçün; daha büyük batch her zaman daha düşük gecikme vermez, sürücü buffer'ı ve lock beklemesi artabilir.
Spring Boot eğitimi kapsamında JFR ile önce-sonra kanıtı üretmek
Flush düzeltmesini bir benchmark iddiasına dönüştürmek için aynı veri hacmi, aynı veritabanı ve aynı istek dağılımıyla iki sürüm çalıştırın. Entegre Spring Boot uygulamasında yalnızca JMH kullanmak yanıltıcıdır; connection pool, gerçek SQL planı ve transaction interceptor maliyetleri JMH metodunun dışında kalır. Bunun yerine Testcontainers ile sabit PostgreSQL imajı başlatın, k6 ile 60 saniyelik yük uygulayın ve JVM Flight Recorder kaydı alın.
# Uygulama PID'si icin 60 saniyelik profil kaydi
jcmd $PID JFR.start name=flush settings=profile duration=60s filename=flush-before.jfr
# Aynı senaryoyu k6 ile calistirin
k6 run --vus 40 --duration 60s order-list.js
# Projection ve OSIV degisikliginden sonra ayni komutlari tekrarlayin
jcmd $PID JFR.start name=flush settings=profile duration=60s filename=flush-after.jfrJDK Mission Control'da iki kayıtta Allocation in new TLAB, Garbage Collections, Socket Read ve Execution Sample görünümlerini karşılaştırın. datasource-proxy raporundan istek başına SQL sayısını, k6 sonucundan p50-p95-p99 sürelerini alın. Sadece ortalama süre düşüşünü kabul kriteri yapmayın: projection sonrası SQL sayısı aynı kalıp allocation düşebilir; batch sonrası SQL sayısı düşüp lock contention nedeniyle p99 yükselebilir. Bu ayrım, java eğitimi ve java programlama eğitimi materyallerinde sık atlanan, üretimde karar kalitesini belirleyen ayrıntıdır.
Spring AI, Spring MCP ve Model Context Protocol araçlarında kısa transaction
Spring AI veya Spring MCP ile bir model context protocol aracı yazarken LLM çağrısını açık database transaction içinde tutmayın. Model yanıtı ağda saniyelerce bekleyebilir; bu sırada connection pool'dan bir bağlantı işgal edilir ve lock süresi uzar. Veriyi kısa read transaction ile DTO olarak alın, uzak çağrıyı transaction dışında yapın, ardından optimistic locking ile kısa bir yazma transaction'ı açın.
@Service
@RequiredArgsConstructor
class RecommendationToolService {
private final TransactionTemplate transactionTemplate;
private final OrderRepository orders;
private final AiClient aiClient;
public RecommendationResult recommend(long orderId) {
OrderSnapshot snapshot = transactionTemplate.execute(status ->
orders.findSnapshotById(orderId).orElseThrow()
);
String advice = aiClient.generate(snapshot.toPrompt());
return transactionTemplate.execute(status -> {
Order order = orders.findById(orderId).orElseThrow();
order.applyAdvice(advice); // @Version alanı stale yazmayı engeller
return new RecommendationResult(order.getId(), order.getVersion());
});
}
}Bu sınırlandırma spring ai entegrasyonunda connection havuzunun model gecikmesine bağlanmasını engeller. spring mcp tool handler'ına JPA entity döndürmek de sakıncalıdır: lazy association serileştirme sırasında ek sorgu üretebilir ve entity alanları model bağlamına gereğinden fazla taşınabilir. Java fullstack eğitimi veya bir java kursu içinde bu örneği UI tarafında version alanını taşıyan bir update isteğiyle tamamlayın; 409 benzeri optimistic lock hatasını kullanıcıya yeniden yükleme akışı olarak göstermek, sessiz son-yazan-kazanır davranışından daha güvenlidir.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Java Spring Boot ReactJS FullStack Eğitimi (Yıldız Teknik Üniversitesi SEM)
Sık Sorulan Sorular
Spring Data JPA'da @Transactional(readOnly = true) flush işlemini kesin olarak engeller mi?
Hayır. Etki, Spring Framework transaction manager'ı ve JPA provider entegrasyonuna bağlıdır. Hibernate Session üzerinde FlushMode.MANUAL davranışını ve JDBC read-only ipucunu sağlayabilen yapılandırmalar vardır, ancak bunu varsaymak yerine bir entegrasyon testinde entity değiştirip commit sonrası SQL logunu doğrulayın. Salt okuma endpoint'lerinde projection kullanmak, managed entity snapshot'ını baştan üretmediği için daha deterministik bir kazançtır.
Hibernate ORM batch insert neden spring boot eğitimindeki örneğe rağmen tek tek çalışıyor?
Önce hibernate.jdbc.batch_size ayarını ve datasource-proxy ile gerçek executeBatch çağrılarını kontrol edin. IDENTITY identifier stratejisi, insert sonrası anahtarı hemen alma ihtiyacı nedeniyle batching'i sınırlayabilir. Ayrıca farklı entity tiplerinin karışması batch'i böler; hibernate.order_inserts=true bu nedenle önemlidir. 50 kayıtlık testte statement sayısını ölçmeden yalnızca log satırlarına göre karar vermeyin.
Java backend geliştirme projelerinde flush performansı JFR ile nasıl ölçülür?
Aynı Testcontainers veritabanı verisi ve aynı k6 senaryosuyla önce ve sonra 60 saniyelik JFR kaydı alın. JDK Mission Control'da TLAB allocation, GC pause ve execution sample verilerini karşılaştırın; ayrıca datasource-proxy ile istek başına SQL sayısını, k6 ile p95 ve p99 gecikmesini kaydedin. Heap düşerken p99 artıyorsa batch boyutu veya veritabanı lock beklemesi ayrı incelenmelidir.
Spring AI ve model context protocol araçlarında JPA transaction sınırı nerede olmalı?
Model veya MCP istemcisine yapılan ağ çağrısından önce yalnızca gerekli alanları DTO olarak kısa bir transaction içinde okuyun. Uzak yanıt geldikten sonra yazma gerekiyorsa yeni ve kısa bir transaction açın. Entity üzerinde @Version kullanarak iki çağrı arasındaki değişikliği optimistic lock hatasıyla yakalayın; LLM çağrısı boyunca connection veya row lock taşımayın.
AI / LLM Discovery
Bu makale Opendart Akademi Java eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


