• 26.08.2026 19:51:21
  • Admin Admin

Spring Boot'ta Hibernate ORM JDBC batch ayarlarını ölçerek doğrulama, ID stratejisi seçimi ve bulk DML sonrası persistence context tutarlılığı için uygulanabilir bir java backend geliştirme rehberi.

Spring Boot'ta Hibernate ORM ile JDBC Batch ve Bulk DML Tasarımı

Java backend geliştirme için batch temelini ölçerek kurun

JDBC batch ayarı eklemeden önce aynı veri setiyle bir referans ölçümü alın. 10.000 OrderLine insert içeren, tek transaction'lı bir yük senaryosunda Hibernate Statistics'ten entity insert ve prepared statement sayılarını; uygulama ölçerinden toplam süreyi kaydedin. Spring Framework transaction proxy'si metot çağrısını transaction içine alır, fakat tek başına JDBC sürücüsüne batch göndermez. Bu ayrımı görmek için istatistikleri yalnızca test profiline açın:

spring:
  jpa:
    properties:
      hibernate.generate_statistics: true
logging:
  level:
    org.hibernate.stat: DEBUG
10.000 insert için 10.000 prepare statement veya 10.000 ayrı execute görüyorsanız batch oluşmuyordur. Hedef, `hibernate.jdbc.batch_size=50` sonrasında sürücüye yaklaşık 200 batch gönderilmesidir; bu sayı sürücünün SQL yeniden yazma davranışına göre farklı SQL paketlerine dönüşebilir.

Sadece uygulama süreölçeri yeterli değildir: aynı test sırasında Java Flight Recorder kaydı alın ve Java Mission Control ile allocation hot spot'larını, socket write sayısını ve GC duraklamalarını karşılaştırın. Ölçümü en az üç kez, aynı JVM heap boyutu ve aynı PostgreSQL/MySQL örneğiyle çalıştırın. Örnek komut:

jcmd $PID JFR.start name=jpa-batch settings=profile   duration=120s filename=/tmp/jpa-batch-before.jfr
Önce-sonra karşılaştırmasında sadece ortalama süreyi değil, `prepareStatementCount / entityInsertCount` oranını, p95 süreyi ve JFR'daki `byte[]` ile `Object[]` allocation miktarını kaydedin. Çok büyük batch boyutları sürücü tamponlarını ve Hibernate'in action queue'sunu büyüttüğü için 50, 100 ve 250 değerlerini ayrı deneyin; 250'nin daha az round-trip üretmesi, daha düşük p95 verdiği anlamına gelmez.

Hibernate ORM JDBC batch ayarları ve ID üretim tuzağı

Hibernate ORM tarafında batch'in oluşması için batch boyutu, sıralama ve sürücü ayarı birlikte değerlendirilmelidir. PostgreSQL JDBC sürücüsünde `reWriteBatchedInserts=true`, uyumlu insert dizilerini çok satırlı protokol mesajlarına dönüştürebilir. Hibernate'in insert sıralaması ise farklı entity tiplerinin action queue içinde gruplanmasını sağlar:

spring:
  datasource:
    url: jdbc:postgresql://db.internal:5432/app?reWriteBatchedInserts=true
  jpa:
    properties:
      hibernate.jdbc.batch_size: 50
      hibernate.order_inserts: true
      hibernate.order_updates: true
      hibernate.jdbc.batch_versioned_data: true
`hibernate.order_inserts` açıkken Hibernate, kodun `Order`, `AuditRecord`, `Order` şeklindeki persist sırasını yeniden düzenleyebilir. Bu nedenle insert trigger'ları veya sıra bağımlı denetim tabloları varsa önce entegrasyon testiyle doğrulayın. MySQL Connector/J kullanılan bir sistemde karşılık gelen sürücü parametresi genellikle `rewriteBatchedStatements=true` olur; iki sürücünün URL parametreleri birbirinin yerine geçmez.

`GenerationType.IDENTITY` çoğu veritabanında insert sonrasında üretilen anahtarı hemen istemeyi gerektirdiğinden Hibernate'in insert batching imkanını fiilen sınırlar. Sequence destekleyen veritabanlarında anahtarları önceden ayırabilen bir strateji kullanın:

@Entity
class OrderLine {
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "order_line_seq")
  @SequenceGenerator(
      name = "order_line_seq",
      sequenceName = "order_line_seq",
      allocationSize = 50)
  private Long id;
}
`allocationSize=50` yalnızca Java anotasyonu değildir. Şema migration'ında sequence increment ve Hibernate optimizer davranışını üretilen SQL üzerinden inceleyin. Birden fazla uygulama sürümü aynı sequence'i kullanırken allocation ayarını migration yapmadan değiştirmek, boş ID aralıklarından daha ciddi olarak yanlış yapılandırılmış pooled optimizer senaryolarında çakışma riski doğurabilir. Bu nedenle migration sonrası iki node ile eşzamanlı insert testi çalıştırın.

Spring Data JPA ile persistence context boyutunu sınırlama

Spring Data JPA'daki `saveAll()` çağrısı tek başına batch garantisi vermez. `IDENTITY` kimlik üretimi, her iterasyonda `flush()` çağrısı veya farklı entity tiplerinin karışması batch'i dağıtır. Büyük importlarda `EntityManager` ile sabit boyutlu flush-clear döngüsü kurun:

@Transactional
public void importLines(List<LineCommand> commands) {
  int batchSize = 50;
  for (int i = 0; i < commands.size(); i++) {
    LineCommand c = commands.get(i);
    entityManager.persist(new OrderLine(c.orderId(), c.sku(), c.quantity()));

    if ((i + 1) % batchSize == 0) {
      entityManager.flush();
      entityManager.clear();
    }
  }
  entityManager.flush();
  entityManager.clear();
}
`clear()` bir optimizasyon etiketi değil, birinci seviye cache'teki tüm managed entity'leri detach eden işlemdir. 500.000 satırlık importta bunu yapmazsanız Hibernate her entity snapshot'ını transaction sonuna kadar tutar ve heap basıncı JFR'da büyür.

Flush-clear sınırı içindeki bir entity'nin başka bir managed parent'a nesne referansı varsa, clear sonrası detached parent nedeniyle `PersistentObjectException` veya beklenmeyen cascade davranışı görebilirsiniz. Bu yüzden import komutlarında ilişki nesnesi yerine scalar foreign key taşıyın veya her chunk'ta parent'ı `getReference()` ile tekrar bağlayın. Örneğin `new OrderLine(c.orderId(), ...)` yaklaşımı, `Order` nesnesini bellekte tutmadan ilişkiyi kurar. Bu yaklaşım java microservices ortamında Kafka tüketicisinin 50.000 kayıtlık bir teslimini işlerken de transaction heap'ini sınırlı tutar.

Bir spring boot eğitimi laboratuvarında bu kodu `batch_size=1`, 50 ve 100 ile karşılaştırın. Her koşulda Hibernate Statistics, JFR ve veritabanı tarafında `pg_stat_statements` veya MySQL Performance Schema verisini toplayın. Java programlama eğitimi ve java kursu materyallerinde sık görülen hata, yalnızca uygulama logundaki toplam süreye bakıp veritabanına kaç execute gönderildiğini ölçmemektir.

Bulk DML'de optimistic locking ve cache geçersizleştirme

JPQL bulk update, yüklenen entity'leri dolaşmaz; doğrudan SQL `UPDATE` üretir. Sonuç olarak `@PreUpdate`, dirty checking ve entity başına optimistic locking akışı otomatik çalışmaz. Stok veya durum geçişi için version koşulunu sorguya açıkça koyun ve etkilenen satır sayısını zorunlu kontrol edin:

public interface OrderRepository extends JpaRepository<Order, Long> {
  @Modifying(flushAutomatically = true, clearAutomatically = true)
  @Query("""
      update Order o
         set o.status = :next,
             o.version = o.version + 1,
             o.updatedAt = CURRENT_TIMESTAMP
       where o.id = :id
         and o.version = :expectedVersion
         and o.status = :current
      """)
  int transition(
      @Param("id") UUID id,
      @Param("expectedVersion") long expectedVersion,
      @Param("current") OrderStatus current,
      @Param("next") OrderStatus next);
}
Servis katmanında sonuç `0` ise bunu başarı saymayın: kayıt silinmiş, durum değişmiş veya başka bir transaction version'ı artırmış olabilir. API'ye 409 Conflict döndürmek ya da tüketici mesajını retry politikasına almak domain kararına bağlıdır; ikisini aynı hata gibi ele almak veri kaybına yol açar.

`flushAutomatically=true`, bulk sorgusundan önce aynı persistence context'te birikmiş değişikliklerin SQL'e yazılmasını sağlar; `clearAutomatically=true` ise bulk SQL'den sonra elde kalan managed entity'lerin eski alanları göstermesini önler. Ancak native SQL, veritabanı trigger'ı veya uygulama dışındaki bir job ikinci seviye cache'i güncellemez. Hibernate L2 cache kullanıyorsanız ilgili entity bölgesini işlem sonrası açıkça temizleyin:

entityManager.getEntityManagerFactory()
    .getCache()
    .evict(Order.class, orderId);
Toplu milyonlarca kayıt güncellemesinde tek tek eviction yerine region eviction maliyetini ölçün. Ayrıca Envers gibi entity event'ine dayalı audit mekanizmalarının JPQL bulk DML ile satır bazında revision üretmeyeceğini kabul edin; audit gerekiyorsa ayrı append-only audit insert'i veya veritabanı trigger'ı tasarlayın.

Spring AI, Spring MCP ve model context protocol kullanan import akışları

Spring AI veya Spring MCP ile model context protocol üzerinden bir LLM'e import başlatma yetkisi veriliyorsa, modelin doğrudan entity alanı veya JPQL üretmesine izin vermeyin. Araç çıktısını sınırlandırılmış bir komuta çevirin: en fazla 10.000 satır, izinli tenant kimliği, sabit batch boyutu ve dry-run zorunluluğu gibi kontrolleri Java kodunda uygulayın. Örneğin dry-run aşamasında `SELECT count(*) FROM staging_order_line WHERE tenant_id = ?` ile beklenen satır sayısını alın, gerçek importta aynı `tenant_id` için batch metodu çağırın ve `import_id` ile idempotency tablosuna sonuç yazın. Böylece modelin ürettiği serbest metin yerine denetlenebilir parametreler transaction sınırına girer.

Bu konu bir java eğitimi içinde JDBC sürücü davranışını, bir java fullstack eğitimi içinde ise kullanıcıya import ilerlemesini doğru göstermeyi bağlar. İstemciye yüzde ilerleme vermek için `processedRows / acceptedRows` değerini batch sonunda Redis'e yazın; transaction rollback olursa ilerleme olayını yayımlamayın. Operasyonel tarafta Prometheus için `import_batch_duration_seconds`, `import_rows_total` ve `import_conflicts_total` sayaçlarını `tenant` gibi yüksek cardinality'li etiketler olmadan yayınlayın. Böylece spring framework tabanlı servis, batch küçüldüğünde mi yoksa veritabanı lock beklemesi arttığında mı yavaşladığını metrikten ayırt edebilir.

Sık Sorulan Sorular

Spring Boot'ta Hibernate ORM JDBC batch neden çalışmıyor?

Önce `hibernate.generate_statistics=true` ile prepared statement sayısını ve entity insert sayısını karşılaştırın. Yaygın nedenler `GenerationType.IDENTITY`, her kayıtta `flush()`, batch boyutunun tanımlanmaması ve PostgreSQL'de `reWriteBatchedInserts=true` gibi sürücü ayarlarının eksik olmasıdır. 10.000 insert için statement sayısı 10.000'e yakınsa batch oluşmamıştır.

Spring Data JPA saveAll batch insert yapar mı?

`saveAll()` entity'leri aynı transaction içinde persist edebilir, ancak JDBC batch garantisi vermez. Hibernate batch ayarı, kimlik üretim stratejisi ve flush davranışı belirleyicidir. Büyük listelerde 50-100 kayıt aralığında `EntityManager.flush()` ve `clear()` kullanın; ardından Hibernate Statistics ile batch etkisini doğrulayın.

Java microservices içinde bulk update optimistic locking nasıl yapılır?

JPQL bulk update sorgusuna `where id = :id and version = :expectedVersion` koşulunu ekleyin, `version = version + 1` atamasını açıkça yapın ve repository'nin döndürdüğü satır sayısını kontrol edin. Sonuç 0 ise mesajı başarıyla işlenmiş saymayın. `clearAutomatically=true` ile aynı transaction içindeki eski managed entity durumlarını temizleyin.

Spring AI ve Spring MCP ile import aracı batch işlemi başlatabilir mi?

Başlatabilir, ancak model context protocol aracının yalnızca doğrulanmış komut parametreleri üretmesi gerekir. Tenant, maksimum satır sayısı, dry-run ve idempotency key gibi sınırlar Java servisinde zorunlu olmalıdır. Model tarafından üretilmiş JPQL veya native SQL'i doğrudan çalıştırmak yerine sabit repository ve servis metotlarını çağırı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.

Opendart Akademi llms.txt