Java backend geliştirme ekiplerinde Hibernate ORM L2 cache'i yalnızca açmak yetmez. Spring Boot üzerinde hit oranını ölçmeyi, bulk güncellemelerde bayat veriyi engellemeyi ve çoklu pod tutarlılığını test etmeyi öğrenin.
Spring Boot'ta Hibernate ORM L2 Cache Tutarlılığı ve Ölçümü
Hibernate ORM L2 cache: Doğru varlığı ve eşzamanlılık stratejisini seçmek
Hibernate ORM ikinci seviye cache'i SessionFactory kapsamındadır; aynı JVM içindeki farklı EntityManager işlemleri arasında çalışır. İlk adaylar, çok okunan, nadir güncellenen ve kimlik ile getirilen referans verilerdir: ülke, para birimi, katalog kategorisi gibi. Sipariş, bakiye veya stok gibi yüksek yazma oranlı aggregate'lerde cache put, lock ve invalidation maliyeti çoğu zaman veritabanı sorgusundan pahalıya gelir. Karar vermeden önce PostgreSQL tarafında pg_stat_statements ile en sık çalışan select ... where id = ? sorgularını çıkarın; aday bir entity için dakikadaki okuma/yazma oranı en az 20:1 değilse L2 cache'i varsayılan seçenek kabul etmeyin.
Bir entity'nin READ_WRITE stratejisini kullanabilmesi için sürüm alanı gerekir. Hibernate, yönetilen entity güncellemesinde cache entry'sini soft-lock ile korur ve commit sonrasında yeni durumu yayınlar. Aşağıdaki yapılandırma, JCache uyumlu bir sağlayıcı ile yalnızca Product entity'sini cache'ler; tüm entity'lere körlemesine @Cache eklemekten kaçının.
@Entity
@Cacheable
@org.hibernate.annotations.Cache(
usage = CacheConcurrencyStrategy.READ_WRITE,
region = "catalog.product"
)
public class Product {
@Id
private UUID id;
@Version
private long version;
private String sku;
private BigDecimal listPrice;
}
# application.yml
spring:
jpa:
properties:
hibernate.cache.use_second_level_cache: true
hibernate.cache.region.factory_class: org.hibernate.cache.jcache.internal.JCacheRegionFactory
hibernate.javax.cache.missing_cache_strategy: fail
hibernate.generate_statistics: trueBuradaki missing_cache_strategy: fail özellikle değerlidir: yanlış yazılmış region adı nedeniyle sağlayıcının sınırsız veya beklenmedik varsayılan cache oluşturmasını test ve üretim ortamında erken görünür hale getirir.Spring Data JPA tarafında findById, persistence context'te nesne yoksa L2 cache'e ulaşabilir; ancak interface projection, native SQL, DTO constructor query ve çoğu join fetch senaryosu entity cache hit'i üretmez. Bu nedenle repository metodunun adı değil, ürettiği SQL ve hydration biçimi önemlidir. logging.level.org.hibernate.SQL=DEBUG ile kısa süreli lokal inceleme yapın, üretimde ise SQL loglamak yerine Hibernate statistics sayacını kullanın.
Spring Boot'ta cache hit oranını profil etmek ve önce-sonra karşılaştırmak
Bir cache değişikliğini p95 gecikme düşüşü ve veritabanı sorgu sayısı birlikte değişmeden başarılı saymayın. Ölçüm için aynı veri kümesiyle iki Gatling koşusu çalıştırın: ilkinde L2 kapalı, ikincisinde yalnızca hedef entity cache'li olsun. Her koşudan önce uygulama pod'unu yeniden başlatın, 2 dakikalık warm-up sonrasındaki 10 dakikalık pencereyi raporlayın. PostgreSQL için pg_stat_statements_reset(), Hibernate için Statistics.clear() çağrısı ölçüm penceresini temizler. p95 düşerken veritabanı CPU'su ve secondLevelCachePutCount artıyorsa, cache churn yazma yükünü başka bir katmana taşıyor olabilir.
Hibernate statistics'i entegrasyon testine koymak, cache regresyonunu görünür bir sözleşmeye dönüştürür. Testte iki ayrı transaction kullanılması kritiktir: Aynı transaction içindeki ikinci okuma L1 cache hit'idir ve L2 cache'i kanıtlamaz.
@Test
void ikinci_transaction_productu_L2_cacheten_okur() {
SessionFactory sessionFactory = emf.unwrap(SessionFactory.class);
Statistics statistics = sessionFactory.getStatistics();
statistics.clear();
transactionTemplate.execute(status -> {
productRepository.findById(productId).orElseThrow();
return null;
});
transactionTemplate.execute(status -> {
productRepository.findById(productId).orElseThrow();
return null;
});
assertThat(statistics.getSecondLevelCachePutCount()).isGreaterThan(0);
assertThat(statistics.getSecondLevelCacheHitCount()).isGreaterThan(0);
}Bu test, provider'ın gerçekten bağlı olup olmadığını da doğrular. Sadece @Cacheable anotasyonunun derlenmesi, cache provider'ın uygulamada etkin olduğu anlamına gelmez.Canlı sistemde Spring Boot Actuator ile önce curl -s http://localhost:8080/actuator/metrics çağrısından Hibernate sayaçlarının gerçek adlarını keşfedin; Micrometer adları kullanılan binder sürümüne göre noktalı isim ve tag kombinasyonlarıyla yayınlanabilir. Ardından Prometheus'ta hit, miss ve put değerlerini ayrı izleyin. Hedef sadece yüksek hit oranı değildir: örneğin katalog endpoint'inde hit oranı yüzde 85 iken cache put sayısı istek sayısına yakınsa, TTL, region boyutu veya sık güncelleme nedeniyle entry'ler sürekli yeniden yazılıyordur.
Spring Data JPA bulk DML sonrasında bayat cache entry'lerini önlemek
En tehlikeli edge case, Spring Data JPA ile çalışan JPQL bulk update veya native SQL'dir. Bu sorgular entity'leri persistence context'e yüklemeden doğrudan veritabanına gider; dolayısıyla @Version kontrolünü, entity listener'larını ve normal Hibernate L2 invalidation akışını atlayabilir. @Modifying(clearAutomatically = true) yalnızca çağrıyı yapan EntityManager'ın L1 cache'ini temizler, diğer pod'lardaki L2 entry'lerini temizlemez.
Bulk güncelleme kaçınılmazsa, commit'ten sonra ilgili entity region'ını programatik olarak boşaltın. Eviction'ın afterCommit içinde yapılması önemlidir: transaction rollback olursa geçerli cache verisini gereksiz yere silmezsiniz.
@Transactional
public void applyCampaignDiscount(UUID campaignId, BigDecimal rate) {
int changed = productRepository.applyCampaignDiscount(campaignId, rate);
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
sessionFactory.getCache().evictEntityData(Product.class);
}
}
);
log.info("campaign={}, changedProducts={}", campaignId, changed);
}
@Modifying
@Query("""
update Product p
set p.listPrice = p.listPrice * (1 - :rate)
where p.campaignId = :campaignId
""")
int applyCampaignDiscount(UUID campaignId, BigDecimal rate);Bu yaklaşım region çapında invalidation yapar; milyonlarca ürün içeren bir region için pahalıdır. Böyle bir tabloda entity bazlı getReferenceById güncellemeleri, ürün kimliklerini event olarak yayınlama veya kampanya fiyatını ayrı bir read modelde tutma daha iyi bir maliyet profili verir.Query cache'i L2 entity cache ile karıştırmayın. Query cache çoğunlukla sonuç entity'lerini değil, sonuç kimliklerini ve query-space timestamp'lerini tutar. Yüksek kardinaliteli filtreler için setHint("org.hibernate.cacheable", true) eklemek, her parametre kombinasyonu için yeni key üreterek region'ı doldurabilir. Sadece sınırlı parametre uzayı olan, örneğin aktif ülke listesi gibi sorguları cache'leyin ve provider metriklerinde query cache miss ile eviction eğrisini ayrı inceleyin.
Java microservices ortamında L2 cache topolojisi ve tenant izolasyonu
Birden fazla Spring Boot pod'u varsa Caffeine gibi yerel bir L2 sağlayıcı her pod'da farklı kopya üretir. Bu, ürün adı gibi birkaç saniye bayat kalabilen veri için kabul edilebilir olabilir; fiyat, yetki veya abonelik durumu için değildir. Java microservices mimarisinde tutarlılık gereksinimi yüksekse Infinispan ya da Redis tabanlı, invalidation yayınlayabilen bir sağlayıcı değerlendirin. Seçimi doğrulamak için iki pod başlatın: Pod A'dan entity okuyup cache'i ısıtın, Pod B'den yönetilen entity update yapın, sonra Pod A'nın SQL veya cache hit davranışını OpenTelemetry trace yerine doğrudan Hibernate statistics ve veritabanı audit kaydıyla karşılaştırın.
Multi-tenant sistemlerde cache key'in tenant bilgisini taşıması zorunludur. Hibernate'in discriminator filtresi tek başına yeterli değildir; yanlış tenant resolver, aynı entity kimliğinin başka müşteriye cache'ten dönmesine yol açabilir. Hibernate multi-tenancy kullanıyorsanız resolver'ı request scope yerine güvenilir security context'ten üretin ve her transaction başlangıcında doğrulayın.
@Component
public final class TenantResolver
implements CurrentTenantIdentifierResolver<String> {
@Override
public String resolveCurrentTenantIdentifier() {
return SecurityContextHolder.getContext().getAuthentication()
.getAuthorities().stream()
.filter(a -> a.getAuthority().startsWith("TENANT_"))
.findFirst()
.map(a -> a.getAuthority().substring("TENANT_".length()))
.orElseThrow(() -> new AccessDeniedException("Tenant missing"));
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}Testte aynı productId ile iki tenant oluşturup iki ayrı transaction'da okuma yapın; yalnızca HTTP seviyesinde 403 kontrolü yapmak cache anahtarı ayrımını kanıtlamaz.Spring AI veya Spring MCP kullanan servislerde model aracına sunulan müşteri verisi, model context protocol çağrısının sonucu diye global entity cache'e konulmamalıdır. Araç çağrısı sonucu kullanıcı, tenant, yetki kapsamı ve prompt bağlamına bağlıdır. Spring MCP tool sonucu için kısa ömürlü cache gerekiyorsa key'e en az tenantId:userId:toolName:normalizedArgumentsHash:policyVersion ekleyin; Hibernate ORM L2 cache'ini yalnızca domain entity'leri için bırakın.
Java eğitimi rotasında cache sözleşmesini test edilebilir hale getirmek
İyi bir java eğitimi veya java programlama eğitimi, L2 cache'i sadece anotasyon olarak öğretmemelidir. Uygulanabilir laboratuvar şudur: Testcontainers ile PostgreSQL ve seçilen JCache provider'ı ayağa kaldırın, iki transaction testinde L2 hit sayacını doğrulayın, ardından bulk DML çalıştırıp eski fiyatın dönmediğini ispatlayın. Bu egzersiz, bir java kursu içeriğinde persistence context ile SessionFactory cache sınırını gözle görünür hale getirir.
Spring framework ve spring boot eğitimi kapsamında Actuator, Micrometer, transaction synchronization ve repository sınırları aynı örnekte ele alınmalıdır. Spring data jpa ile yazılan repository metodunun projection döndürmesi halinde entity cache'e uğramadığını gösteren test ekleyin. Hibernate orm odaklı bu testler, java backend geliştirme ekiplerinin cache kararını tahmin yerine sayaç, SQL planı ve regresyon testiyle vermesini sağlar.
Bir java fullstack eğitimi modülünde istemcinin ETag ile tuttuğu HTTP cache ile sunucudaki L2 cache'i ayrı katmanlar olarak modelleyin. Örneğin ürün güncellemesinde API'nin yeni version değerini dönmesi, istemcinin If-None-Match isteğini doğrulaması ve sunucunun L2 entry'sini güncellemesi üç ayrı sözleşmedir. Bunlardan yalnızca birini test etmek, uçtan uca fiyat güncelliğini garanti etmez.
İ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 Boot'ta Hibernate ORM L2 cache hit oranı nasıl ölçülür?
Önce hibernate.generate_statistics=true ayarını etkinleştirin. Aynı entity'yi iki farklı transaction içinde okuyup SessionFactory Statistics üzerinden getSecondLevelCacheHitCount, getSecondLevelCacheMissCount ve getSecondLevelCachePutCount değerlerini kaydedin. Yük testinde bu sayaçları PostgreSQL pg_stat_statements sorgu sayısı ve endpoint p95 değeriyle aynı zaman penceresinde karşılaştırın.
Spring Data JPA bulk update neden L2 cache'i bayat bırakır?
JPQL bulk update ve native SQL, entity'yi yüklemeden SQL çalıştırır. Bu nedenle normal dirty checking, @Version akışı ve entity düzeyindeki cache güncellemesi devreye girmez. clearAutomatically yalnızca çağrıyı yapan persistence context'i temizler. Commit sonrası SessionFactory.getCache().evictEntityData(Entity.class) çağrısı yapın veya bulk işlemi entity bazlı güncelleme ve event tabanlı invalidation ile değiştirin.
Java microservices uygulamasında Caffeine ile Hibernate L2 cache kullanılır mı?
Kullanılır, ancak her pod kendi Caffeine cache'ini tuttuğu için pod'lar arası invalidation yoktur. Nadir değişen katalog verisinde kısa TTL ile kabul edilebilir. Yetki, fiyat veya tenant'a bağlı veri için paylaşımlı invalidation sağlayan bir provider kullanın ya da cache'i devre dışı bırakın. Kararı iki podlu bir entegrasyon testinde update sonrası eski değerin dönüp dönmediğini ölçerek verin.
Spring AI ve Spring MCP sonuçları Hibernate ORM cache'ine yazılmalı mı?
Hayır. Spring AI ve model context protocol araç sonuçları prompt, kullanıcı yetkisi, tenant ve politika sürümüne bağlı olabilir; entity cache region'ı bu bağlamı anahtarına eklemez. Gerekirse tool sonucu için ayrı bir cache kullanın ve key'i tenantId, userId, tool adı, normalize argüman hash'i ve policyVersion ile üretin.
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.


