Məzmuna keçLogo

Command Palette

Search for a command to run...

ORM — Kod və Verilənlər Bazasının Dostluğu

Dərc olundu
14 apr 2025
ORM — Kod və Verilənlər Bazasının Dostluğu

ORM Dəryasına Kəllə Vuraq - Kod və Verilənlər Bazasının Dostluğu (və Bəzən Düşmənçiliyi :)

Bu gün proqramlaşdırmanın bəlkə də ən çox müzakirə olunan, bəzən sevilən, bəzən isə "əşşi, SQL yazardım da" dedirdən mövzularından birinə - ORM (Object-Relational Mapping)-ə baş vururuq. Hər bir təcrübəli developerin yolu mütləq bir yerdə ORM ilə kəsişib. Bəzilərimiz onunla sıx dost olub, bəzilərimiz isə məsafəli münasibət saxlayır. Gəlin görək, bu ORM nə olan şeydir, niyə yaranıb və bizə nə kimi faydaları (və ya başağrıları) var.

Bu yazıda məqsədimiz ORM-in nə olduğunu, fəlsəfəsini, arxa planını və işləmə mexanizmini ətraflı şəkildə, amma hələlik JPA kimi spesifik spesifikasiyalara çox dərinə getmədən izah etməkdir. Hədəfimiz ORM konsepsiyasını tam qavramaqdır. Spesifik nümunələr lazım olduqda isə Java, Spring/Spring Boot ekosisteminə müraciət edəcəyik.

ORM-in Arxaplanı: "İmpedans Uyğunsuzluğu" Problemi

Hər şey o məşhur problemdən başladı: Object-Relational Impedance Mismatch. Gəlin etiraf edək, obyekt-yönümlü dünyanın (OOP) paradiqmaları ilə relyasiyalı verilənlər bazalarının (RDBMS) strukturu arasında fundamental fərqlər var.

  • Obyekt Dünyası: Obyektlər, classlar, inheritance (miras), polymorphism (çoxformalılıq), composition (birləşmə), mürəkkəb əlaqələr... Hər şey obyektlər və onların davranışları ətrafında fırlanır.
  • Relyasiyalı Dünya: Cədvəllər (tables), sətirlər (rows), sütunlar (columns), primary keylər, foreign keylər... Hər şey strukturlaşdırılmış data və onlar arasındakı əlaqələr (relations) üzərində qurulub.

Bu iki fərqli dünyanı bir-birinə bağlamaq həmişə bir başağrısı olub:

  • Java-dakı List<Order> obyektini databazada necə saxlayacaqsan? Ayrı orders cədvəli? Bəs Product obyektinin içindəki Category obyekti?
  • Verilənlər bazasından çəkdiyin sətirləri (rows) tək-tək Java obyektlərinə necə çevirəcəksən? Hər dəfə ResultSetdən getXXX() metodları ilə datanı çəkib obyektin setXXX() metodlarına ötürmək? 🤔 Bəli, əvvəllər belə edirdik... və bu, çoxlu boilerplate kod demək idi. Hər yeni cədvəl və ya sütun üçün eyni darıxdırıcı mapləmə kodunu təkrar-təkrar yazmaq... Karyerasının ilk illərini xatırlayanlar yəqin ki, indi yüngülcə gülümsəyir.
  • Databazadakı VARCHAR tipi Java-da Stringə, NUMBER tipi Integer və ya BigDecimalə necə map olunmalıdır? Tiplər arasındakı bu uyğunsuzluqlar da əlavə məntiq tələb edirdi.

Bax, ORM məhz bu "impedans uyğunsuzluğu" problemini həll etmək, obyekt dünyası ilə relyasiyalı dünya arasında bir körpü yaratmaq üçün ortaya çıxmış bir texnika və konseptdir. Məqsəd: developerləri darıxdırıcı, təkrarlanan SQL və mapləmə kodlarından xilas edib, diqqətlərini biznes məntiqinə yönəltmələrini təmin etmək.

ORM Nədir? Texniki Pərdəarxası

ORM, sadə dildə desək, proqramdakı obyektlərinizi (məsələn, Java classlarınızı) verilənlər bazasındakı cədvəllərə (tables) və əksinə avtomatik olaraq mapləyən (xəritələyən) bir texnikadır. Bu proses arxa planda baş verir və developer bir çox halda birbaşa SQL yazmaq məcburiyyətində qalmır.

⚙️ ORM-in Əsas Komponentləri və Konseptləri:

  1. Mapping Metadata: ORM-ə obyektlərinlə cədvəllər arasında əlaqəni necə quracağını demək lazımdır. Bu məlumatlar adətən aşağıdakı yollarla təmin edilir:
    • Annotations: Java dünyasında ən populyar üsuldur. Class və field-lərin üzərinə @Entity, @Table, @Id, @Column, @OneToMany kimi annotasiyalar əlavə edərək mapləməni birbaşa kodun içində təyin edirsən. (JPA spesifikasiyası bu annotasiyaları standartlaşdırıb, amma fərqli ORM framework-lərinin öz spesifik annotasiyaları da ola bilər).
    • XML Konfiqurasiya Faylları: Mapləmə məlumatları ayrı XML fayllarında saxlanılır. Bu, bəzən kodu mapləmə detallarından təmiz saxlamaq üçün üstünlük təşkil edir, amma idarəetməsi daha çətin ola bilir.
  2. Session / EntityManager / Context: ORM alətləri adətən bir "session" və ya "context" obyekti üzərindən işləyir (Java dünyasında JPA bunu EntityManager adlandırır). Bu obyekt verilənlər bazası ilə proqram arasında gedən bütün əməliyyatları idarə edir. Bir növ iş vahididir (Unit of Work pattern). Məsələn:
    • Obyektləri saxlamaq (save/persist).
    • Obyektləri tapmaq (find/get).
    • Obyektləri silmək (delete/remove).
    • Sorğular icra etmək.
  3. Identity Map: Bu, ORM-in vacib bir optimallaşdırma mexanizmidir. Eyni session/context daxilində eyni primary keyə malik bir cədvəl sətiri üçün yalnız bir obyekt instansiyasının yaradılmasını təmin edir. Yəni, eyni ID ilə obyekti dəfələrlə sorğulasanız belə, ORM hər dəfə yeni obyekt yaratmaq əvəzinə, artıq yaddaşda olan eyni instansı qaytarır. Bu, həm performansı artırır, həm də data konsistentliyini qoruyur.
  4. Lazy Loading vs. Eager Loading: Obyektlər arasında əlaqələr olduqda (məsələn, Order obyekti Customer obyektinə bağlıdırsa), bağlı obyektləri nə zaman yükləmək lazımdır?
    • Eager Loading (Təcili Yükləmə): Əsas obyekt yüklənən kimi bütün bağlı obyektlər də avtomatik yüklənir. Sadədir, amma gərəksiz yerə çoxlu data çəkilməsinə və performans problemlərinə səbəb ola bilər (xüsusilə dərin və ya çoxlu əlaqələrdə).
    • Lazy Loading (Tənbəl Yükləmə): Əsas obyekt yüklənərkən bağlı obyektlər yüklənmir. Onlara ilk dəfə müraciət edildikdə (məsələn, order.getCustomer().getName()) ORM arxa planda əlavə bir sorğu göndərərək həmin obyekti yükləyir. Bu, başlanğıcda daha sürətlidir və yaddaşdan qənaət edir, amma diqqətli istifadə olunmazsa, məşhur **"N+1 Select Problemi"**nə yol aça bilər (hər bir əsas obyekt üçün bağlı obyekti çəkmək məqsədilə əlavə bir sorğu göndərilməsi).
  5. Caching (Keşləmə): Performansı artırmaq üçün ORM-lər adətən keşləmə mexanizmləri təqdim edir:
    • First-Level Cache (Birinci Səviyyə Keş): Bu, Session/EntityManager səviyyəsindəki keşdir və defolt olaraq aktivdir (Identity Map ilə əlaqəlidir). Eyni session daxilində eyni obyektə təkrar müraciətlərdə databazaya getmədən keşdən qaytarılır. Session bağlandıqda bu keş təmizlənir.
    • Second-Level Cache (İkinci Səviyyə Keş): Bu, daha qlobal bir keşdir və fərqli session-lar arasında paylaşıla bilər. Aktivləşdirilməsi və konfiqurasiyası əlavə iş tələb edir. Çox oxunan, amma az dəyişən datalar üçün faydalıdır.
  6. Query Language (Sorğu Dili): ORM-lər adətən SQL-ə bənzər, amma obyektlər və onların property-ləri üzərində işləyən xüsusi sorğu dilləri təqdim edir (məsələn, Hibernate-də HQL, JPA standartında JPQL). Bu, developerin SQL sintaksisindən çox, obyekt modelinə fokuslanmasına imkan verir. Həmçinin, proqramatik şəkildə sorğular yaratmaq üçün Criteria API kimi interfeyslər də mövcuddur. Əlbəttə, əksər ORM-lər lazım olduqda birbaşa Native SQL işlətməyə də imkan verir.

ORM İş Prinsipi: Real Case Ssenarisi

Gəlin çox sadə bir nümunə üzərindən ORM-in arxa planda nələr etdiyinə baxaq. Təsəvvür edək ki, bir Product classımız və ona uyğun products cədvəlimiz var.

entities/Product.java
// Konseptual Entity Class (JPA annotasiyaları ilə)
@Entity
@Table(name = "products")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private Long id;

    @Column(name = "product_name", nullable = false) 
    private String name;

    private double price;

    // Getters and Setters...
}

İndi isə əsas əməliyyatlara baxaq (kodlar sadələşdirilmiş konseptual nümunələrdir):

Obyektin Saxlanması (Saving)

➡️ 1. Obyektin Saxlanması (Saving):

repositories/ProductRepositoryUsage.java
// Tutaq ki, bir Spring Data JPA Repository istifadə edirik
Product newProduct = new Product();
newProduct.setName("Super Gadget");
newProduct.setPrice(99.99);

productRepository.save(newProduct); 
  • Arxa planda nə baş verir?
    1. productRepository.save(newProduct) çağırılır.

    2. ORM (məsələn, Hibernate) bu yeni Product obyektini görür.

    3. Obyektin vəziyyətini (state) analiz edir.

    4. Mapping metadata-ya əsasən uyğun SQL INSERT sorğusunu generasiya edir:

      sql/insert-products.sql
      INSERT INTO products (product_name, price) VALUES (?, ?)
    5. Parametrləri ('Super Gadget', 99.99) sorğuya bağlayır.

    6. Aktiv tranzaksiya daxilində bu SQL sorğusunu verilənlər bazasına göndərir.

    7. Əgər ID avtomatik generasiya olunursa, bazadan qayıdan ID-ni newProduct obyektinə set edir.

Obyektin Əldə Edilməsi (Fetching)

➡️ 2. Obyektin Əldə Edilməsi (Fetching):

repositories/ProductRepositoryFind.java
// ID-si 1 olan məhsulu tapmaq istəyirik
Optional<Product> productOpt = productRepository.findById(1L); 
  • Arxa planda nə baş verir?

    1. productRepository.findById(1L) çağırılır.
    2. ORM əvvəlcə First-Level Cachedə (Session/EntityManager keşində) bu ID ilə obyektin olub-olmadığını yoxlayır.
    3. Əgər keşdə yoxdursa: a. Uyğun SQL SELECT sorğusunu generasiya edir:
    sql/select-product.sql
    SELECT id, product_name, price FROM products WHERE id = ?

    b. Parametri (1L) sorğuya bağlayır. c. Sorğunu verilənlər bazasına göndərir. d. Nəticəni (ResultSet) alır. e. ResultSetdəki datanı Product obyektinə mapləyir. f. Yaranan Product obyektini First-Level Cache-ə əlavə edir. 4. Əgər keşdə varsa, birbaşa keşdəki obyekti qaytarır (bazaya getmir). 5. Nəticəni (Product obyektini Optional içində) qaytarır.

Obyektin Yenilənməsi (Updating)

➡️ 3. Obyektin Yenilənməsi (Updating):

repositories/ProductRepositoryUpdate.java
// Əvvəlcə obyekti tapırıq
Optional<Product> productOpt = productRepository.findById(1L);
if (productOpt.isPresent()) {
    Product productToUpdate = productOpt.get();
    productToUpdate.setPrice(109.99); // Obyektin vəziyyətini dəyişirik

    // Spring Data JPA-da update üçün də save istifadə olunur
    productRepository.save(productToUpdate); 
}
  • Arxa planda nə baş verir (adətən tranzaksiya sonunda və ya session flush olunduqda):

    1. ORM bu productToUpdate obyektinin Session/Context tərəfindən idarə olunduğunu bilir.
    2. ORM obyektin ilkin vəziyyəti ilə cari vəziyyətini müqayisə edir (Dirty Checking).
    3. Dəyişiklik aşkar etdikdə (price dəyişib): a. Uyğun SQL UPDATE sorğusunu generasiya edir:
    sql/update-product.sql
    sql UPDATE products SET price = ? WHERE id = ?

    b. Parametrləri (109.99, 1L) sorğuya bağlayır. c. Aktiv tranzaksiya commit olunduqda (və ya session flush edildikdə) sorğunu bazaya göndərir.

Gördüyünüz kimi, ORM bizi birbaşa SQL yazmaqdan və nəticələri əl ilə mapləməkdən xilas edir. Amma bu rahatlığın bir BƏDƏLİ də var...

ORM vs. Specification: Mühüm Bir Fərq

Texniki müzakirələrdə bəzən qarışıqlıq yaranır: ORM bir spesifikasiyadırmı? Xeyr.

  • Specification (Spesifikasiya): Bir texnologiyanın və ya interfeysin necə işləməli olduğunu təyin edən qaydalar toplusu, bir müqavilədir. O, konkret implementasiya təqdim etmir, sadəcə standartları müəyyən edir.
    • Nümunələr: Java Servlet API, JDBC API, JPA (Java Persistence API). Bunlar spesifikasiyalardır. Məsələn, JPA deyir ki, @Entity annotasiyası olmalıdır, EntityManager interfeysi bu metodları təmin etməlidir və s.
  • ORM (Object-Relational Mapping): Bu, bir texnika, konsept və ya dizayn patternidir. Obyektlərlə relyasiyalı databazalar arasındakı problemi həll etmək üçün bir yanaşmadır.
  • ORM Implementasiyası (Framework/Library): ORM konseptini həyata keçirən konkret alətlərdir. Bu alətlər bəzən müəyyən bir spesifikasiyanı (məsələn, JPA-nı) implementasiya edə bilər.
    • Nümunələr: Hibernate (Java, JPA implementasiyası), EclipseLink (Java, JPA implementasiyası), MyBatis (Java, daha çox SQL Mapper olsa da, ORM xüsusiyyətləri var), SQLAlchemy (Python), Entity Framework Core (.NET), Dapper (.NET, Micro-ORM).

Niyə Qarışdırılır? Java dünyasında Hibernate o qədər populyarlaşıb və JPA standartı ilə o qədər sıx bağlıdır ki, bəzən insanlar "JPA" dedikdə əslində Hibernate-i və ya ümumilikdə ORM konseptini nəzərdə tuturlar. Amma texniki olaraq fərqlidirlər: JPA spesifikasiyadır, Hibernate isə (digərləri kimi) həmin spesifikasiyanı implementasiya edən və ORM texnikasını tətbiq edən bir framework-dür.

➡️ Bu mövzuya – spesifikasiyalar və onların implementasiyaları arasındakı fərqlərə – gələcək yazılarımızda daha dərindən toxunacağıq. Xüsusilə JPA və onun implementasiyaları haqqında ayrıca danışacağıq.

ORM-in Üstünlükləri və Çatışmazlıqları: Balans Nöqtəsi

Hər texnologiya kimi, ORM-in də öz güclü və zəif tərəfləri var. "Gümüş güllə" deyil, sadəcə bir alətdir.

📈 Üstünlüklər:

  • Məhsuldarlıq və Sürət: Ən böyük üstünlüyü budur. CRUD (Create, Read, Update, Delete) əməliyyatları üçün SQL yazmaq və mapləmə etmək vaxtını kəskin şəkildə azaldır. Developerlər daha çox biznes məntiqinə fokuslana bilir.
  • Verilənlər Bazası Müstəqilliyi (Database Independence): Nəzəri olaraq, ORM istifadə etməklə fərqli verilənlər bazalarına (məsələn, PostgreSQL-dən MySQL-ə) keçid etmək daha asan olmalıdır. ORM aradakı sintaksis fərqlərini abstraksiya edir (amma praktikada bu hər zaman tam problemsiz olmur).
  • Obyekt-Yönümlü Yanaşma: Kodunuz daha obyekt-yönümlü qalır. Databaza strukturu ilə deyil, domen obyektlərinizlə işləyirsiniz.
  • Daxili Funksionallıq: Keşləmə, tənbəl yükləmə (lazy loading), tranzaksiya idarəetməsi, optimist kilidləmə (optimistic locking) kimi bir çox mürəkkəb funksionallığı "qutudan çıxan" şəkildə təqdim edir.
  • Daha Yaxşı Maintainability (Saxlanılabilirlik): Verilənlərə giriş məntiqi (data access logic) daha mərkəzləşmiş və standartlaşdırılmış olur. Cədvəl strukturu dəyişdikdə, adətən yalnız mapləməni (annotasiya və ya XML) yeniləmək kifayət edir.

📉 Çatışmazlıqlar:

  • Performans Overhead-i: ORM bir abstraksiya qatıdır və bu qat özü ilə müəyyən bir əlavə yük (overhead) gətirir. Əl ilə optimallaşdırılmış təmiz SQL sorğuları demək olar ki, həmişə ORM-in generasiya etdiyi sorğulardan daha sürətli işləyəcək.
  • Mürəkkəblik və Öyrənmə Əyrisi: ORM alətləri özləri də mürəkkəb sistemlərdir. Onların necə işlədiyini, konfiqurasiyasını, optimallaşdırma yollarını (lazy/eager loading, caching, N+1 problemi həlli) dərindən başa düşmək vaxt və təcrübə tələb edir.
  • "Leaky Abstraction" (Sızdıran Abstraksiya): ORM SQL-i sizdən gizlətməyə çalışsa da, bəzən performans problemlərini həll etmək və ya mürəkkəb sorğuları optimallaşdırmaq üçün ORM-in arxa planda hansı SQL-i generasiya etdiyini bilmək və ona təsir etmək məcburiyyətində qalırsınız. Abstraksiya "sızdırır".
  • Mürəkkəb Sorğular: Çoxlu JOINlar, qruplaşdırmalar (GROUP BY), alt sorğular (subqueries) və ya databazanın spesifik funksiyalarını (məsələn, pəncərə funksiyaları - window functions) istifadə edən çox mürəkkəb hesabat (reporting) sorğularını ORM-in öz sorğu dili ilə yazmaq bəzən çətin və ya qeyri-effektiv olur. Belə hallarda birbaşa SQL yazmaq daha məqsədəuyğundur.
  • "N+1 Select Problemi": Lazy loading düzgün konfiqurasiya edilmədikdə və ya diqqətsiz istifadə olunduqda asanlıqla yaranan klassik performans problemidir.

ORM – Gümüş Güllə Yoxsa Faydalı Alət?

Beləliklə, ORM nədir? Sehrli bir çubuq? Əlbəttə ki, yox. O, sadəcə müəyyən problemləri həll etmək üçün yaradılmış güclü bir alətdir.

ORM-dən nə zaman istifadə etməli?

  • Əgər proyektiniz əsasən CRUD əməliyyatları üzərində qurulubsa.
  • Obyekt-yönümlü dizayn və domen modeliniz vacibdirsə (Domain-Driven Design).
  • Development sürətini artırmaq prioritetdirsə.
  • Komandanızın ORM təcrübəsi varsa və ya öyrənməyə hazırdırsa.

Nə zaman ehtiyatlı olmalı və ya alternativləri düşünməli?

  • Performansın ən kritik olduğu hissələrdə.
  • Çox mürəkkəb, spesifik SQL tələb edən hesabatlar və ya analitik sorğular yazdıqda.
  • Böyük həcmli data ilə kütləvi əməliyyatlar (bulk operations) apardıqda (baxmayaraq ki, bəzi ORM-lər bunun üçün də optimallaşdırmalar təqdim edir).
  • ORM-in mürəkkəbliyini idarə etmək üçün resursunuz (vaxt, bilik) məhduddursa (bəlkə də daha yüngül bir "Micro-ORM" və ya hətta təmiz JDBC/SQL daha uyğun ola bilər).

Ən əsası, ORM-i kor-koranə istifadə etmək deyil, onun necə işlədiyini başa düşməkdir. Abstraksiyanın arxasında nə baş verdiyini anlamaq, potensial problemləri qabaqcadan görməyə və onları effektiv şəkildə həll etməyə imkan verir.


Unutmayın, ən yaxşı alət belə, onu düzgün istifadə etməyi bacarmayanda faydasızdır. ORM də istisna deyil. Onu ağıllı şəkildə tətbiq etdikdə, o, sizin ən yaxın dostlarınızdan birinə çevrilə bilər. Yanlış istifadə etdikdə isə... Gecələrinizi debug etməklə keçirə bilərsiniz. 😉

Thanks for reading.