🗄️ 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
NVLyerine 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 MATCHEDblokları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 UNKNOWNya da “recompile” seçenekleri. - NOLOCK (dirty read) risklidir; raporlamada
READ COMMITTED SNAPSHOTtercih 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 BYzorunlu.
☕ 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:
@EmbeddedIdtercih;@IdClasszor 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
| Strateji | Kullanım | Batch Insert | Not |
|---|---|---|---|
| IDENTITY | SQL Server | ❌ | Generated keys → batch kırılır |
| SEQUENCE | Oracle / SQL Server (2012+) | ✅ | allocationSize = sequence increment |
| TABLE | Evrensel | ⚠️ | 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 COMMITTEDyaygın; uzun transaction → UNDO baskısı. - SQL Server: varsayılan
READ COMMITTED;SNAPSHOTaçarak okuma kilitlerini azalt. - Spring:
@Transactional(propagation=REQUIRED)tipiktir; audit/log içinREQUIRES_NEW.
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public void placeOrder(...) { /* ... */ }
🛡️ Optimistic & Pessimistic Lock
- Optimistic:
@Versionalanı; ç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
SqlResultSetMappingya 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 UNKNOWNveya 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.