Spring Boot JPA INNER JOIN ve JOIN FETCH (@OneToMany Lazy) ile Hibernate Performans Optimizasyonu
Spring Boot JPA INNER JOIN ve JOIN FETCH (@OneToMany Lazy) ile Hibernate Performans Optimizasyonu
Bu rehberde Spring Boot JPA INNER JOIN, JOIN FETCH ve @OneToMany Lazy ilişkisinde N+1 problemi nasıl önlenir adım adım gösteriyoruz.
@OneToMany Lazy ilişkisi nedir?
@OneToMany ilişkiler varsayılan olarak Lazy yüktür. Yani üst varlık (ör. Sektor) yüklendiğinde koleksiyon (firmalar) ayrı bir zamanda, ihtiyaç duyulduğunda ek SELECT ile getirilir. Bu da özellikle listelemelerde N+1 problemini tetikleyebilir.
Örnek Entity: Sektor ve Firma
Sektor Entity
@Entity
@Table(name = "sektorler")
@Getter
@Setter
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class Sektor implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "adi", length = 50)
private String adi;
@Column(name = "detay", length = 50)
private String detay;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "sektor", orphanRemoval = true)
private List<Firma> firmalar = new ArrayList<>();
}
Firma Entity
@Entity
@Table(name = "firma", schema = "s_eminonu")
@Getter
@Setter
public class Firma implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "adi", length = 50)
private String adi;
@Column(name = "email", length = 50)
private String email;
@Column(name = "firma_detay", length = 500)
private String detay;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "sektor_id")
private Sektor sektor;
}
INNER JOIN ve JOIN FETCH farkı
INNER JOIN, SQL birleştirmesini ifade eder. JOIN FETCH ise JPA/Hibernate seviyesinde ilişkiyi tek sorguda hydrate ederek Lazy yüklemeyi sorgu bazında geçersiz kılar. Bu sayede N+1 ortadan kalkar ve Hibernate performansı belirgin artar.
| Özellik | INNER JOIN | JOIN FETCH |
|---|---|---|
| Amacı | Tabloları birleştirir | İlişkili varlıkları tek sorguda yükler |
| N+1 önleme | Garantilemez | Evet |
| JPA etkisi | SQL düzeyinde | JPA düzeyinde fetch stratejisi |
| Use-case | Ham JOIN gereksinimi | Listeleme/raporlama, read-only |
Service & Repository ile JOIN FETCH
SektorlerService
@Service
@RequiredArgsConstructor
public class SektorlerService {
@Transactional
public List<Sektor> getAllSektorWithFirma() {
return sektorlerRepository.fetchSektorWithFirma();
}
}
SektorlerRepository
@Transactional(readOnly = true)
public interface SektorlerRepository extends JpaRepository<Sektor, Long> {
@Query("SELECT s FROM Sektor s JOIN FETCH s.firmalar ORDER BY s.id DESC")
List<Sektor> fetchSektorWithFirma();
}
İpucu: Çoklu koleksiyonlarda birden fazla JOIN FETCH kullanmak cartesian product oluşturabilir. Gerekirse DTO projection veya @BatchSize ile alternatif yaklaşım düşünün.
Hibernate’in oluşturduğu SQL
SELECT
sektor0_.id AS id1_9_0_,
firmalar1_.id AS id1_1_1_,
sektor0_.adi AS adi2_9_0_,
sektor0_.detay AS detay3_9_0_,
firmalar1_.adi AS adi2_1_1_,
firmalar1_.email AS email6_1_1_,
firmalar1_.firma_detay AS firma_de4_1_1_,
firmalar1_.sektor_id AS sektor_12_1_1_
FROM sektor sektor0_
INNER JOIN firma firmalar1_ ON sektor0_.id = firmalar1_.sektor_id
ORDER BY sektor0_.id DESC;
JOIN FETCH kullanmanın avantajları
- Performans artışı: İlişkili veriler tek SELECT ile yüklenir.
- N+1 problemi önlenir: Her kayıt için ek sorgular kalkar.
- Daha az veritabanı trafiği: Network ve IO maliyeti azalır.
- Daha hızlı listeleme: Özellikle büyük veri kümelerinde gözle görülür hızlanma.
JOIN FETCH kullanırken dikkat
- Sayfalama (Pagination): Koleksiyon
fetch joinilePageablebirlikte sorun çıkarabilir. Alternatif: two-step query veya DTO projection. - Birden çok koleksiyon: Aynı sorguda birden fazla koleksiyonu
fetch joinetmeyin. - Filtreleme: Gerekli alanları seçin; ağır kolonu gereksiz taşımayın.
- Önbellek: İmkan varsa 2. seviye cache ve
@BatchSizeile birleştirin.
SSS: Spring Boot JPA INNER JOIN & JOIN FETCH
JOIN FETCH performansı her zaman daha mı iyidir?
Kullanım senaryosuna bağlıdır. Okuma ağırlıklı ve ilişkili veriye gerçekten ihtiyaç duyulan listelerde evet; ancak sayfalama veya çok geniş kollekksiyonlarda dikkatli olmak gerekir.
@OneToMany Lazy yerine EAGER kullansam olur mu?
Genel en iyi uygulama Lazy'dir. EAGER, beklenmedik ek yük ve döngüsel yüklemelere neden olabilir.
N+1 problemini başka nasıl azaltabilirim?
DTO projection, @BatchSize, EntityGraph, ikinci seviye cache ve iyi tasarlanmış repository sınırlarıyla.
İleri okuma