• 4.09.2026 09:01:18
  • Admin Admin

Spring Data JPA ile yapılan bulk update işlemlerinin Hibernate ORM persistence context'ini neden bayat bıraktığını, sürüm kontrolünü nasıl koruyacağınızı ve sorguyu ölçerek güvenle iyileştireceğinizi inceleyin.

Spring Data JPA'da Bulk Update Sonrası Tutarlı Veri Okuma

Spring Data JPA bulk update neden bayat nesne üretir?

JPQL ile çalıştırılan UPDATE ve DELETE, Hibernate ORM'ün birinci seviye önbelleğindeki yönetilen entity nesnelerini dolaşmaz; SQL doğrudan veritabanına gider. Bu nedenle aynı transaction içinde daha önce yüklenen Order nesnesinin status alanı eski kalırken satır veritabanında değişmiş olabilir. Ayrıca entity callback'leri, dirty checking ve @Version artışı bulk sorguda otomatik uygulanmaz. Java backend geliştirme kodunda bunu görünür kılmak için aynı EntityManager üzerinde önce entity yükleyip sonra bulk sorgu çalıştıran bir entegrasyon testi yazın.

@Test
@Transactional
void bulk_update_managed_entityyi_bayat_birakir() {
    Order order = orderRepository.saveAndFlush(new Order(Status.NEW));
    Order managed = entityManager.find(Order.class, order.getId());

    entityManager.createQuery("update Order o set o.status = :status where o.id = :id")
        .setParameter("status", Status.PAID)
        .setParameter("id", order.getId())
        .executeUpdate();

    assertThat(managed.getStatus()).isEqualTo(Status.NEW);

    entityManager.clear();
    assertThat(entityManager.find(Order.class, order.getId()).getStatus())
        .isEqualTo(Status.PAID);
}

Bu testte managed nesnesinin eski değeri göstermesi bir ORM hatası değildir: EntityManager identity map içinde aynı primary key için ikinci bir Java nesnesi üretmez. Kritik edge case şudur: Bayat managed nesnesinde sonradan başka bir alan değiştirip flush ederseniz, dinamik update kullanılmayan bir mapping'de eski status tekrar yazılabilir. Bu riski H2 yerine Testcontainers PostgreSQL ile doğrulayın; gerçek sürücü, transaction izolasyonu ve SQL üretimi üretim ortamına daha yakın davranır.

Spring Framework transaction sınırında güvenli durum geçişi

Spring Framework transaction'ında bulk mutation için en güvenli temel desen, koşullu güncelleme, elle artırılmış sürüm ve etkilenen satır sayısının kontrolüdür. Aşağıdaki sorgu hem beklenen durumdan geçişi hem optimistic locking'i tek SQL ifadesine taşır. updated == 0 sonucu, kaydın bulunamadığını, başka bir isteğin önce geçtiğini veya istemcinin eski sürüm gönderdiğini ayırt etmek için ikinci bir salt-okuma sorgusu ile sınıflandırılmalıdır.

public interface OrderRepository extends JpaRepository<Order, UUID> {

    @Modifying(flushAutomatically = true, clearAutomatically = true)
    @Query("""
        update Order o
           set o.status = :next,
               o.version = o.version + 1,
               o.paidAt = :paidAt
         where o.id = :id
           and o.status = :expected
           and o.version = :version
        """)
    int transition(
        UUID id, Status expected, Status next,
        long version, Instant paidAt);
}

@Service
@RequiredArgsConstructor
class PaymentService {
    private final OrderRepository orders;

    @Transactional
    void markPaid(UUID id, long version) {
        int updated = orders.transition(
            id, Status.NEW, Status.PAID, version, Instant.now());
        if (updated != 1) {
            throw new OptimisticLockingFailureException("order transition rejected");
        }
    }
}

flushAutomatically = true, çağrı öncesinde aynı persistence context'te birikmiş değişikliklerin bulk SQL tarafından yanlış sırada ezilmesini önler. clearAutomatically = true ise sorgudan sonra context'i temizler; bunun bedeli, çağıran kodun daha önce tuttuğu tüm managed entity referanslarının detached hale gelmesidir. Bu sebeple bulk update sonrasında entity nesnesini kullanmaya devam eden servislerde yalnızca entityManager.refresh(entity) seçeneğini düşünün veya servis sınırını command odaklı tutun. Bir java programlama eğitimi laboratuvarında özellikle self-invocation hatasını test edin: Aynı sınıftaki this.markPaid(...) çağrısı Spring'in @Transactional proxy'sinden geçmez.

Hibernate ORM ölçümü: sorgu sayısı, süre ve plan karşılaştırması

Bulk update optimizasyonuna başlamadan önce iki karşılaştırılabilir senaryo kaydedin: entity'leri tek tek yükleyip değiştiren sürüm ve tek JPQL update sürümü. Test profilinde Hibernate istatistiklerini açarak JDBC statement sayısını görün; warm-up sonrası en az 20 tekrarın medyanını ve p95 değerini ayrı raporlayın. Tek satırlık güncellemede beklenen fark yalnız süre değil, entity-başına SELECT ve UPDATE sayısının bir bulk UPDATE'e düşmesidir.

# application-test.properties
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

SQL zamanını uygulama logundan tahmin etmek yerine p6spy ile gerçek JDBC çağrısını yakalayın ve Prometheus'ta endpoint bazında timer tutun. Örneğin orders.transition.bulk için Micrometer Timer ile süre, ayrı bir Counter ile updated == 0 sayısını kaydedin. Önce-sonra değerlendirmesinde yalnız ortalamayı kullanmayın: indeks eksikse bulk UPDATE tek statement olmasına rağmen çok sayıda satırı taradığı için p99 süresi artabilir.

Timer.Sample sample = Timer.start(meterRegistry);
try {
    int updated = orders.transition(id, expected, next, version, Instant.now());
    meterRegistry.counter("orders.transition.rows",
        "result", updated == 1 ? "updated" : "rejected").increment();
} finally {
    sample.stop(meterRegistry.timer("orders.transition.bulk"));
}

Bu ölçüm yaklaşımı spring boot eğitimi içinde sık atlanan bir ayrıntıyı açığa çıkarır: Hibernate'in statement sayısı düşükken veritabanı kilit bekleme süresi yüksek olabilir. PostgreSQL tarafında pg_stat_activity.wait_event_type ve pg_locks ile aynı zaman penceresini kontrol edin; uygulama timer'ı yükselip SQL execute süresi sabit görünüyorsa havuzda bağlantı bekleme veya lock contention olasılığı vardır.

Java microservices için indeks, batch ve kilit etkisi

Durum geçişi sorgusundaki eşitlik filtreleri id, status ve version üzerindedir. Primary key zaten seçiciyse ek bileşik indeks çoğu tabloda gereksizdir; buna karşılık bekleyen işlerin toplu kapatılması gibi where status = 'NEW' and created_at < ? sorguları için kısmi indeks anlamlı olabilir. PostgreSQL'de planı gerçek parametrelerle ölçün ve test transaction'ı içinde geri alın.

CREATE INDEX CONCURRENTLY idx_orders_new_created_at
    ON orders (created_at)
    WHERE status = 'NEW';

BEGIN;
EXPLAIN (ANALYZE, BUFFERS)
UPDATE orders
   SET status = 'EXPIRED', version = version + 1
 WHERE status = 'NEW'
   AND created_at < TIMESTAMPTZ '2026-01-01T00:00:00Z';
ROLLBACK;

İndeks öncesi ve sonrası planda Seq Scan, okunan buffer sayısı, güncellenen satır sayısı ve toplam execution time değerlerini aynı veri hacminde karşılaştırın. EXPLAIN ANALYZE gerçekten UPDATE çalıştırdığı için üretim veritabanında rastgele çalıştırılmamalıdır; rollback veri değişikliğini geri alsa da lock, WAL ve I/O maliyeti oluşturur. Büyük bir tabloyu milyonlarca satırla tek transaction'da güncellemek uzun süreli row lock ve replication lag üretebilir.

Java microservices ortamında süresi dolan işleri paralel worker'lara dağıtmak için önce küçük bir aday kümesini FOR UPDATE SKIP LOCKED ile seçin, ardından bu kimlikleri batch halinde güncelleyin. PostgreSQL native query'sinde 500-2000 satır aralığını başlangıç noktası alın; doğru değer lock süresi, WAL üretimi ve bağlantı havuzu kapasitesiyle ölçülür. JPA'da çok büyük IN (:ids) listeleri sürücü parametre sınırına yaklaşabilir, bu yüzden batch boyutunu sabit ve gözlemlenebilir tutun.

Spring AI, Spring MCP ve model context protocol araçlarında mutation sınırı

Spring AI kullanan bir destek asistanı veya Spring MCP ile yayınlanan bir araç, doğrudan serbest SQL ya da genel amaçlı updateOrder aracı açmamalıdır. Model context protocol üzerinden yalnız orderId, expectedStatus ve version alanlarını kabul eden, yukarıdaki koşullu transition metoduna bağlanan bir command tanımlayın. Araç çağrısında kullanıcı kimliği, istenen geçiş ve dönen satır sayısını audit tablosuna yazın; modelin aynı aracı retry etmesi durumunda sürüm koşulu ikinci mutation'ı reddeder.

POST /internal/orders/transition
{
  "orderId": "8cfc6f71-3d83-4a13-919a-6809d4197a1e",
  "expectedStatus": "NEW",
  "nextStatus": "PAID",
  "version": 7
}

HTTP/1.1 409 Conflict
{
  "code": "ORDER_VERSION_CONFLICT",
  "retryable": false
}

Bir java eğitimi veya java kursu içeriğinde bu örneği yalnız repository API'si olarak bırakmayın: java fullstack eğitimi katılımcısı arayüzden If-Match veya version alanı göndermeli, backend 409 yanıtını görünür bir yeniden yükleme akışına bağlamalıdır. Spring AI ya da Spring MCP katmanı için ayrıca allow-list, kullanıcı bazlı yetki kontrolü ve çağrı başına timeout uygulayın; LLM çıktısının enum dışı bir durum adı üretmesi controller seviyesinde bean validation ile 400'e dönmelidir. Bu sınırlar, spring framework, hibernate orm ve spring data jpa bilgisini üretim komut güvenliğiyle birleştirir.

Sık Sorulan Sorular

Spring Data JPA bulk update sonrasında entity neden eski değer gösteriyor?

JPQL bulk update persistence context'i güncellemez. Repository metodunda clearAutomatically kullanın veya entityManager.clear() çağırıp entity'yi yeniden yükleyin. Aynı transaction içinde eski managed nesneyi tekrar flush etmeyin; aksi halde mapping ayarınıza bağlı olarak eski kolon değerleri yeniden yazılabilir.

Hibernate ORM bulk update ile @Version nasıl korunur?

Bulk sorguya hem mevcut sürüm koşulunu hem de version = version + 1 ifadesini ekleyin. executeUpdate() sonucunun tam olarak 1 olmasını zorunlu tutun. Hibernate, entity lifecycle üzerinden geçmeyen bulk SQL için @Version alanını kendiliğinden artırmaz.

Spring Boot eğitimi için bulk update performansı nasıl ölçülür?

Test profilinde hibernate.generate_statistics açın, p6spy ile JDBC sürelerini kaydedin ve Micrometer Timer ile endpoint p95 değerini toplayın. Entity-döngüsü ve tek UPDATE sürümünü aynı Testcontainers PostgreSQL veri setinde en az 20 tekrar çalıştırın; statement sayısı, BUFFERS çıktısı ve p95'i birlikte karşılaştırın.

Spring AI ve model context protocol ile veri güncelleme aracı güvenli nasıl tasarlanır?

Aracı genel SQL yerine version, expectedStatus ve nextStatus alanlarıyla sınırlı bir command endpoint'ine bağlayın. Sunucuda enum doğrulaması, yetki kontrolü, audit kaydı ve rows-updated kontrolü yapın. 0 satır güncellendiğinde modeli otomatik retry yerine 409 çatışma sonucu ile durdurun.

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