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.

ÖzellikINNER JOINJOIN FETCH
AmacıTabloları birleştirirİlişkili varlıkları tek sorguda yükler
N+1 önlemeGarantilemezEvet
JPA etkisiSQL düzeyindeJPA düzeyinde fetch stratejisi
Use-caseHam JOIN gereksinimiListeleme/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 join ile Pageable birlikte sorun çıkarabilir. Alternatif: two-step query veya DTO projection.
  • Birden çok koleksiyon: Aynı sorguda birden fazla koleksiyonu fetch join etmeyin.
  • Filtreleme: Gerekli alanları seçin; ağır kolonu gereksiz taşımayın.
  • Önbellek: İmkan varsa 2. seviye cache ve @BatchSize ile 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

Spring Boot JPA JOIN FETCH ile @OneToMany Lazy ilişkide tek sorgu akış diyagramı
JOIN FETCH ile tek sorguda ilişkiyi yükleme akışı.

Beğendiysen bir çay ısmarlayabilirsin ☕

Bana çay ısmarla

Spring Boot ile ilgili yorumlar

Yorum Paylaş

EMail Zorunlu alanlar * *