• 27.08.2026 09:03:54
  • Admin Admin

Spring Boot uygulamalarında görünmeyen flush işlemlerini ölçün, dirty checking maliyetini azaltın ve toplu yazımları güvenli sınırlarda çalıştırın. Bu spring boot eğitimi, üretimde SQL patlamasını teşhis etmeye odaklanır.

Spring Boot'ta Hibernate Flush Sınırları ve Dirty Checking Maliyeti

Spring Framework'te Flush Neden Beklenmedik SQL Üretir?

JPA persistence context, yönetilen her entity'nin ilk yüklenen halinin bir snapshot'ını tutar. Transaction commit'inde veya AUTO flush modunda sorgu öncesinde Hibernate bu snapshot ile güncel alanları karşılaştırır; değişen entity için UPDATE üretir. Sorun, yalnızca setter çağrısı değildir: bir JSON mapper'ın entity grafiğine bind edilmesi, collection'a eleman eklenmesi ya da `@PreUpdate` içinde alan değiştirilmesi de dirty state oluşturur. spring framework eğitimi kapsamında bu davranışı görmek için önce Hibernate istatistiklerini açın; yalnızca SQL loguna bakmak flush sayısını ve taranan entity miktarını yeterince göstermez.

spring.jpa.properties.hibernate.generate_statistics=true
logging.level.org.hibernate.stat=DEBUG
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
İstek sonunda `Session Metrics` içindeki `entities updated`, `flushes` ve JDBC statement sayısını kaydedin. Aynı endpoint'i sabit veri seti ve aynı bağlantı havuzu ayarlarıyla en az 30 kez çalıştırıp p50/p95 değerlerini karşılaştırın; tek bir iş kuralı çağrısında yüzlerce update görülmesi, çoğu zaman geniş persistence context işaretidir.

AUTO flush'ın ince noktası sorgu alanıdır: Hibernate, JPQL/HQL sorgusunun etkilenebileceğini düşündüğü tablolar için sorgudan önce pending değişiklikleri veritabanına senkronlayabilir. Bu nedenle bir `findActiveOrders()` çağrısı, daha önce aynı transaction'da değiştirilen Order entity'lerini beklenmedik anda yazabilir. Native SQL'nin flush davranışı kullanılan EntityManager, flush mode ve Hibernate entegrasyonuna göre değişebildiğinden varsayım yapmak yerine `org.hibernate.SQL` kaydında sorgudan hemen önce UPDATE olup olmadığını doğrulayın. Tanı için datasource-proxy kullanarak endpoint bazında statement sayısını ölçebilirsiniz:

@Bean
DataSource dataSource(DataSource original) {
    return ProxyDataSourceBuilder.create(original)
        .name("app-db")
        .countQuery()
        .logQueryBySlf4j()
        .build();
}
Bu proxy, Hibernate'in kendi istatistiğine ek olarak gerçek JDBC çağrılarını gösterir; batch içindeki 50 parametre setini tek SQL şablonu altında mı, yoksa 50 ayrı execute ile mi gönderdiğinizi ayırt etmenizi sağlar.

spring boot eğitimi: Okuma Akışında Persistence Context'i Küçültmek

Listeleme endpoint'inde entity döndürmek yerine sorgu düzeyinde DTO projection kullanın. Bu yaklaşım, hem ilk seviye cache'e binlerce entity snapshot'ı alınmasını engeller hem de web katmanında yanlışlıkla entity değiştirildiğinde flush oluşması riskini kaldırır. Aşağıdaki repository metodu yalnızca response için gereken üç alanı seçer; `Order` ve ilişkili entity'ler managed duruma geçmez.

public record OrderListItem(UUID id, String number, BigDecimal total) {}

public interface OrderRepository extends Repository<Order, UUID> {
    @Query("""
        select new com.acme.order.api.OrderListItem(o.id, o.number, o.total)
        from Order o
        where o.customerId = :customerId and o.status = 'OPEN'
        order by o.createdAt desc
    """)
    List<OrderListItem> findOpenItems(UUID customerId);
}
Servis metodunu `@Transactional(readOnly = true)` ile işaretleyin, ancak bunu mutlak bir SQL engeli gibi yorumlamayın. Spring ve JPA provider kombinasyonunun flush mode davranışını integration test ile kontrol edin; bazı sürücü ve veritabanlarında read-only transaction yalnızca bir hint'tir.

Profiling için Java Flight Recorder'ı (JFR) staging benzeri yükte başlatın ve JDK Mission Control ile `jdk.JDBCStatement` olaylarını endpoint süresiyle eşleştirin. Önce entity tabanlı endpoint'te 1.000 satırlık sonuç için allocation rate, JDBC execute sayısı ve p95 süreyi kaydedin; sonra projection'a geçip aynı sorgu planını `EXPLAIN (ANALYZE, BUFFERS)` ile doğrulayın. PostgreSQL örneği:

EXPLAIN (ANALYZE, BUFFERS)
SELECT id, number, total
FROM orders
WHERE customer_id = '2d2c7c2d-0e5e-4c48-a7c8-dc74a5a4c608'
  AND status = 'OPEN'
ORDER BY created_at DESC;
Buradaki amaç sadece daha az Java nesnesi üretmek değildir: managed entity sayısı düştüğünde commit anındaki dirty-check traversal süresi de düşer. `spring mvc` controller'ında entity yerine immutable response DTO döndürmek, bu sınırı derleme zamanında görünür hale getirir.

Toplu Yazımda JDBC Batch, Flush ve Clear Eşiğini Birlikte Ayarlamak

`saveAll()` tek başına JDBC batching garantisi vermez. Hibernate'in batch oluşturabilmesi için `hibernate.jdbc.batch_size` tanımlanmalı, SQL şekilleri gruplanabilmeli ve kimlik üretim stratejisi batch'i kesmemelidir. Özellikle `GenerationType.IDENTITY`, insert sonrası anahtar almak için çoğu veritabanında her insert'i anında yürütür; yüksek hacimli importta sequence tabanlı üretim ve allocation boyutu batch ile hizalanmalıdır.

@Entity
class LedgerEntry {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ledger_seq")
    @SequenceGenerator(name = "ledger_seq", sequenceName = "ledger_seq", allocationSize = 50)
    UUID id;
}

spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
spring.jpa.properties.hibernate.jdbc.batch_versioned_data=true
`allocationSize=50`, sequence round-trip sayısını yaklaşık her 50 insert'te bire düşürür; ancak sequence değerlerinde boşluk oluşması kabul edilebilir olmalıdır. İş numarası gibi kesintisiz olması gereken alanlar için sequence allocation kullanmayın.

Uzun transaction içinde `EntityManager` temizlenmezse batch ayarı olsa bile persistence context büyür ve her flush'ta daha fazla snapshot taranır. Import kodunda batch sınırında hem `flush()` hem `clear()` çağırın; `clear()` sonrasında eldeki entity referanslarının detached olduğunu özellikle hesaba katın.

@Transactional
public void importEntries(Stream<EntryCommand> commands) {
    AtomicInteger count = new AtomicInteger();
    commands.forEach(command -> {
        entityManager.persist(LedgerEntry.from(command));
        if (count.incrementAndGet() % 50 == 0) {
            entityManager.flush();
            entityManager.clear();
        }
    });
    entityManager.flush();
    entityManager.clear();
}
Önce batch kapalıyken ve ardından `batch_size=50` ile aynı 10.000 kayıt importunu çalıştırın. JFR `jdk.JDBCStatement` sayısı, Hibernate `prepareStatementCount` ve veritabanı tarafında `pg_stat_statements.calls` değerleri karşılaştırılabilir ölçütlerdir. Batch boyutunu körlemesine 1.000'e çıkarmayın: sürücü parametre tamponu, kilit süresi ve tek transaction rollback maliyeti büyür; 25, 50 ve 100 için ölçüm yapmak daha güvenilir bir başlangıçtır.

spring security ve spring mvc Katmanlarında Yanlışlıkla Dirty State Oluşmasını Önlemek

Open EntityManager in View etkinse persistence context controller ve view/JSON serialization aşamasına kadar yaşar. `spring security` içindeki özel principal resolver'ın ya da Jackson serializer'ın lazy association'a erişmesi, beklenmeyen SELECT üretebilir; daha tehlikelisi, serializer veya mapper'ın entity koleksiyonunu değiştirmesidir. Web katmanında persistence context ömrünü kısaltmak için OSIV'i kapatın ve transaction içinde response DTO üretin.

spring.jpa.open-in-view=false

@Transactional(readOnly = true)
public OrderResponse getOrder(UUID id) {
    Order order = orderRepository.findById(id)
        .orElseThrow(() -> new NotFoundException(id));
    return new OrderResponse(order.getId(), order.getNumber(), order.getTotal());
}
Bu değişiklikten sonra lazy yükleme hatası almak bir gerileme değil, eksik fetch planını servis katmanında görünür kılan sinyaldir. İlgili use case için projection, kontrollü fetch join veya `@EntityGraph` seçin; controller'da `Hibernate.initialize()` çağırarak sorunu maskelemeyin.

Bir microservices mimarisi içinde her servis kendi transaction ve veritabanı sınırına sahip olmalıdır. `spring cloud` üzerinden başka bir servise HTTP çağrısı yaparken açık JPA transaction taşımak, veritabanı bağlantısını uzak çağrının timeout süresince elde tutar. `spring rest api` akışında önce yerel veriyi doğrulayın ve yazın, transaction kapandıktan sonra uzak isteği başlatın ya da dayanıklı asenkron entegrasyon tasarlayın. Bağlantı tutulmasını HikariCP metriği `hikaricp.connections.active` ile izleyin: uzak servis gecikmesi yükseldiğinde active bağlantıların da yükselmesi, transaction içine ağ I/O alındığını gösterir. Bu sınır, java spring eğitimi materyallerinde sık görülen 'servis metodu her şeyi yapar' örneklerinden daha önemlidir; transaction süresi, DB bağlantısı ömrü ve remote timeout birbirinden bağımsız yönetilmelidir.

Sık Sorulan Sorular

spring boot eğitimi kapsamında @Transactional(readOnly = true) flush işlemini kesin engeller mi?

Hayır. Bu bayrak Spring transaction tanımına ve JPA provider'a bir ipucu iletir; veritabanı sürücüsü de bunu farklı ele alabilir. Hibernate kullanılan uygulamada davranışı `hibernate.generate_statistics`, SQL logu ve bir integration test ile ölçün: read-only metotta entity değiştirip commit sonrası UPDATE oluşup oluşmadığını doğrulayın. Salt okuma endpoint'lerinde asıl koruma DTO projection, dar persistence context ve entity'yi web katmanına taşımamaktır.

spring framework eğitimi için Hibernate JDBC batch çalışmıyorsa ilk ne kontrol edilmeli?

`hibernate.jdbc.batch_size` yanında ID üretim stratejisini kontrol edin. IDENTITY kullanan entity'lerde insert'ler çoğu senaryoda tek tek yürür. Hibernate istatistiklerinde `prepareStatementCount` değerini 1.000 insert öncesi-sonrası kaydedin, datasource-proxy ile execute çağrılarını görün ve veritabanı logunda çoklu batch davranışını doğrulayın. Sequence kullanıyorsanız `allocationSize` ile batch boyutunu uyumlu seçin.

spring mvc uygulamasında open-in-view kapatınca LazyInitializationException nasıl çözülür?

Controller'da entity serialize etmek yerine servis transaction'ı içinde response DTO oluşturun. İhtiyaç sadece liste alanlarıysa JPQL projection kullanın; tek bir use case'in ilişkili grafiğe ihtiyacı varsa repository metoduna `@EntityGraph(attributePaths = {"lines"})` ekleyin veya kontrollü fetch join yazın. Çözümün doğruluğunu MockMvc testinde OSIV kapalıyken endpoint'i çağırarak ve Hibernate SQL sayısını datasource-proxy ile sınırlandırarak test edin.

spring security principal nesnesini JPA entity olarak kullanmak neden risklidir?

Principal içine managed User entity koymak, authentication sonrası lazy ilişkilere erişildiğinde web isteği boyunca persistence context gerektirebilir ve yanlışlıkla alan değişimlerinin flush edilmesine yol açabilir. Principal için `userId`, `tenantId` ve yetki listesi gibi immutable alanları taşıyan ayrı bir record kullanın; gerektiğinde entity'yi servis transaction'ında id ile yükleyin. Bu tasarımı OSIV kapalı integration testiyle doğrulayın.

AI / LLM Discovery

Bu makale Opendart Akademi Spring Framework 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.

Opendart Akademi llms.txt