🗄️ SQL & Hibernate — Uygulamalı Rehber

Bu bölümde SQL/PLSQL ve Hibernate/JPA odaklı ileri seviye pratikler, performans optimizasyonları, “trick”ler ve üretim ortamı için kritik nüanslar yer alır.

🔎 Genel Bakış

  • RDBMS farkındalığı: Oracle ve SQL Server davranışları, hint’ler, identity/sequence farkları.
  • JPA vs Hibernate: JPA API “standart”, Hibernate implementasyon ve ek özellikler.
  • Performans: N+1 analizi, fetch join limitleri, batch/bulk işlemler, cache stratejileri.
  • Güvenilirlik: Transaction izolasyon, lock’lar, optimistic/pessimistic senaryolar.

🧰 SQL Temelleri (Derin)

📐 Şema Tasarımı & İndeksleme

  • Normalizasyon (3NF) → okuma ağır ise denormalizasyon düşün.
  • Composite index: Eşitlik sütunları en başta, aralık koşulları sonda. (WHERE a = ? AND b BETWEEN ? AND ? → index (a, b))
  • Covering index: SELECT alanlarını index’e dahil et (include) → tablo erişimini azalt.
  • Cardinality/Selectivity: Düşük seçicilikte (ör. boolean) index faydasız olabilir.
-- Composite & covering index (SQL Server)
CREATE NONCLUSTERED INDEX IX_Order_User_Date
ON dbo.[Order](UserId, OrderDate)
INCLUDE (TotalAmount);

🪄 Sorgu Yazım “Trick”leri

  • Seek-pagination (OFFSET yerine): Büyük sayfalarda hızlı ilerleme.
  • Window functions: Raporlamada ROW_NUMBER(), SUM() OVER().
  • MERGE yerine “UPSERT”i dikkatle kullan: bazı motorlarda trigger/lock tuzakları var.
  • COALESCE (ANSI) – Oracle’da NVL yerine taşınabilirlik için tercih et.
-- Seek Pagination (id > last_seen_id)
SELECT id, name, created_at
FROM users
WHERE (created_at, id) > (:lastCreatedAt, :lastId) -- (Oracle: use tuple workaround)
ORDER BY created_at, id
FETCH NEXT 50 ROWS ONLY;
-- Window örneği
SELECT user_id,
       SUM(amount) OVER (PARTITION BY user_id ORDER BY txn_date
                         ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
FROM payments;

🏛️ Oracle & PL/SQL

🎯 Önemli Noktalar

  • Sequence + SEQUENCE stratejisi JPA’da batch insert için idealdir.
  • Read Consistency: MVCC; SELECT genelde kilitlemez, ancak uzun süren transactionlar ORA-01555 (snapshot too old) üretebilir.
  • Analytic functions çok güçlü; LISTAGG, RATIO_TO_REPORT, LAG/LEAD.
  • Partitioning (büyük tablolar): range/hash/list – bakım ve performans avantajı.
-- Oracle: Sequence & trigger (eğer JPA'da SEQUENCE kullanmıyorsan)
CREATE SEQUENCE seq_user START WITH 1 INCREMENT BY 50 NOCACHE;

🧩 PL/SQL Prosedür Çağırma (JPA)

@NamedStoredProcedureQuery(
  name = "User.syncStatus",
  procedureName = "PKG_USER.SYNC_STATUS",
  parameters = {
    @StoredProcedureParameter(mode = ParameterMode.IN,  name = "P_USER_ID", type = Long.class),
    @StoredProcedureParameter(mode = ParameterMode.OUT, name = "P_RESULT",  type = Integer.class)
})
@Entity class User { @Id Long id; /* ... */ }

// Kullanım:
StoredProcedureQuery q = em.createNamedStoredProcedureQuery("User.syncStatus");
q.setParameter("P_USER_ID", 42L);
q.execute();
Integer result = (Integer) q.getOutputParameterValue("P_RESULT");

🔧 Oracle Trick’leri

  • sequence.increment_by ile @SequenceGenerator(allocationSize) uyumlu olmalı → aksi halde id “gap” & re-fetch olur.
  • MERGE + WHEN MATCHED/NOT MATCHED bloklarında unique ihlalleri için uygun indeksleme şart.
  • DATE vs TIMESTAMP tiplerini doğru seç; saat bilgisini kaybetme.
  • Optimizer hintleri (/*+ INDEX(t idx) */) geçici çözüm; önce planı düzelt.

🧭 SQL Server (T-SQL)

🎯 Önemli Noktalar

  • IDENTITY yaygındır, fakat Hibernate batch insert’i engeller; SEQUENCE (SQL Server 2012+) ile batch mümkün.
  • Parameter Sniffing: plan kötüleşirse OPTIMIZE FOR UNKNOWN ya da “recompile” seçenekleri.
  • NOLOCK (dirty read) risklidir; raporlamada READ COMMITTED SNAPSHOT tercih edilebilir.
  • Filtered Index ve INCLUDE ile covering indeksi esnek kur.
-- SEQUENCE & JPA için uygun
CREATE SEQUENCE dbo.user_seq START WITH 1 INCREMENT BY 50;

-- Param sniffing azaltma
SELECT *
FROM dbo.Orders
WHERE CustomerId = @cid
OPTION (OPTIMIZE FOR UNKNOWN);

🔧 SQL Server Trick’leri

  • TEMP TABLE vs TABLE VARIABLE: büyük veride temp table istatistik avantajlıdır.
  • MERGE bilinen bug’lara sahip olabilir; ayrı UPDATE/INSERT çoğu zaman daha güvenli.
  • ROW_NUMBER() ile deterministik pagination ORDER BY zorunlu.

☕ JPA Temelleri

🧱 Mapping & İlişkiler

  • @ManyToOne default EAGER (JPA); Hibernate LAZY yap → @ManyToOne(fetch = LAZY).
  • @OneToMany her zaman LAZY; büyük koleksiyonlarda Set → Hash maliyeti; List + index gerektiğinde @OrderColumn.
  • ManyToMany yerine genelde ara Join Entity (ek alanlar/audit) kullan.
  • Embeddable ile adres, para gibi value objectleri modelle.
  • Composite Key: @EmbeddedId tercih; @IdClass zor senaryolarda.
@Entity class Order {
  @Id @GeneratedValue Long id;

  @ManyToOne(fetch = FetchType.LAZY) // JPA default EAGER -> değiştir!
  private User user;

  @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
  private List<OrderLine> lines = new ArrayList<>();
}

🔄 Flush/Dirty Checking

  • FlushModeType.COMMIT ile gereksiz flush’tan kaçın (raporlama/okuma ağırlıklı akış).
  • Dirty checking: managed entity alanı değiştiğinde otomatik UPDATE.
  • Toplu güncellemelerde bulk JPQL kullan (ancak 1st level cache senkron değil).
em.setFlushMode(FlushModeType.COMMIT);

int updated = em.createQuery(
  "update User u set u.status = :s where u.lastLogin < :d")
  .setParameter("s", Status.INACTIVE)
  .setParameter("d", LocalDate.now().minusYears(1))
  .executeUpdate(); // Bulk sonrası cache senkronla: clear/refresh

🐍 Hibernate İncileri

🆔 ID Stratejileri

StratejiKullanımBatch InsertNot
IDENTITYSQL ServerGenerated keys → batch kırılır
SEQUENCEOracle / SQL Server (2012+)allocationSize = sequence increment
TABLEEvrensel⚠️Kilit & performans maliyeti
@SequenceGenerator(name="user_seq", sequenceName="USER_SEQ", allocationSize=50)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq")
private Long id;

📦 Batch & Order

  • hibernate.jdbc.batch_size=50, order_inserts=true, order_updates=true.
  • Collection insert/update’lerde @BatchSize veya global default_batch_fetch_size.
// application.properties
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.default_batch_fetch_size=50

🧠 2nd Level Cache & Query Cache

  • Okuma ağırlıklı @ManyToOne hedeflerini READ_ONLY cache’le (referans veriler).
  • Koleksiyon cache’i yazma yoğun ise kaçın; invalidation maliyeti yüksek.
  • Query cache, parametre & region disiplin ister; genelde “rapor cache” ile sınırlı tut.
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
@Entity class Country { @Id String code; String name; }

⚡ Performans & N+1

🔍 N+1 Teşhisi & Çözümleri

  • Hibernate Statistics / SQL log ile sorgu sayısını izle.
  • JOIN FETCH ile ilişkileri tek sorguda getir (ama pagination’da dikkat!).
  • @EntityGraph (JPA) ile dinamik fetch planları.
  • Batch fetch@BatchSize(size=50) çok sayıda LAZY ilişki için.
// Fetch join (pagination ile birlikte duplicate riskine dikkat)
@Query("select o from Order o join fetch o.user where o.status = :s")
List<Order> findWithUser(@Param("s") Status s);

// EntityGraph
@EntityGraph(attributePaths = {"user", "lines"})
List<Order> findByStatus(Status s);

🚧 Fetch Join + Pagination Uyarısı

JOIN FETCH ile Pageable kullanırsan duplicate satırlar & yanlış total riski doğar. Çözüm: id’leri sayfalayıp, ikinci sorguda IN (:ids) ile fetch join; veya EntityGraph + batch fetch.

🔐 Transaction & Locking

🧾 İzolasyon & Spring Propagation

  • Oracle: MVCC, READ COMMITTED yaygın; uzun transaction → UNDO baskısı.
  • SQL Server: varsayılan READ COMMITTED; SNAPSHOT açarak okuma kilitlerini azalt.
  • Spring: @Transactional(propagation=REQUIRED) tipiktir; audit/log için REQUIRES_NEW.
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public void placeOrder(...) { /* ... */ }

🛡️ Optimistic & Pessimistic Lock

  • Optimistic: @Version alanı; çakışma az ise idealdir.
  • Pessimistic: LockModeType.PESSIMISTIC_WRITE; sıcak satırda yarış çoksa düşün.
@Version
private Long version;

// Pessimistic
em.find(Order.class, id, LockModeType.PESSIMISTIC_WRITE);

📑 Paging & Projections

🎯 DTO Projection (Ağır Entity’yi Taşımadan)

// JPQL DTO
@Query("select new com.acme.dto.OrderView(o.id, u.username, o.total) " +
       "from Order o join o.user u where o.status = :s")
Page<OrderView> findViews(@Param("s") Status s, Pageable pageable);

// Spring Data Interface Projection
interface OrderView { Long getId(); String getUserUsername(); BigDecimal getTotal(); }
Page<OrderView> findByStatus(Status s, Pageable p);
  • Interface projection hızlıdır; gereksiz kolonlar taşınmaz.
  • Native query → DTO için SqlResultSetMapping ya da Spring’in constructor binding yaklaşımı.

🧭 Seek Pagination

OFFSET/FETCH büyük sayfalarda pahalıdır. Seek (keyset) yaklaşımıyla WHERE (created_at,id) > (?,?) şeklinde ilerle.

✅ Checklist & Trickler

SQL

  • Index sırası: eşitlik → aralık. Covering için include (SQL Server) / ek kolonlar.
  • Plan analizi: EXPLAIN / Actual Execution Plan bakmadan optimize etme.
  • Rapor/analitik: window fonksiyonlarını kullan, alt sorgu yükünü azalt.

Hibernate

  • IDENTITY yerine SEQUENCE → batch insert kazanımı.
  • N+1 → EntityGraph / batch fetch / dikkatli fetch join.
  • FlushMode.COMMIT ile gereksiz flush’ı engelle.
  • 2nd level cache: READ_ONLY referans verileri; yazma yoğun koleksiyonları cache’leme.

Oracle

  • allocationSize == sequence increment kuralına uy.
  • Analytic functions & partitioning’den çekinme.

SQL Server

  • Param sniffing → OPTIMIZE FOR UNKNOWN veya yeniden derleme seçenekleri.
  • NOLOCK’u dikkatli kullan (dirty read!).

❓ Mülakat Notları (Kısa Cevaplı)

1) N+1 nedir ve nasıl çözülür?

Ana sorgudan sonra her satır için ek sorgular çalışması. JOIN FETCH, @EntityGraph, batch fetch ile çözülür; pagination’da iki-aşamalı strateji.

2) Oracle’da sequence ile JPA nasıl hizalanır?

@SequenceGenerator(allocationSize = N) ile Oracle INCREMENT BY N eşit olmalı.

3) SQL Server'da IDENTITY neden batch’i bozar?

Her insert sonrası anahtar alımı gerekir; sürücü “returning keys” süreci batch’i parçalar. SEQUENCE ile çözüm.

4) Optimistic vs Pessimistic lock?

Optimistic: @Version ile çakışma anında hata. Pessimistic: satırı kilitle, yarış çoksa tercih.

5) DTO projection neden iyi?

Daha az veri, daha az hydration; controller/response için net kontrat.