Zkratka proObject-relational mappingTakéObject-relational mapping, O/R mapping, O-R mappingPokročilý

Definice

ORM je vrstva softwaru, která mapuje objekty v aplikaci na tabulky a vztahy v relační databázi. Umožňuje pracovat s daty přes třídy, entity a metody místo ručního skládání SQL pro každý dotaz, ale zároveň přidává abstrakci, jejíž chování, výkon a transakce musí vývojář chápat.

Kategorie: Softwarový vývojAktualizováno

Nezaměňujte: Zkratka ORM může mimo softwarový vývoj znamenat také online reputation management, zde jde o object-relational mapping mezi objektovým kódem a relační databází.

Proč ORM vzniklo mezi objekty a tabulkami

Objektový kód obvykle pracuje s třídami, vlastnostmi a odkazy mezi instancemi. Relační databáze ukládá data do tabulek, sloupců, řádků a vazeb přes cizí klíč. ORM vyplňuje mezeru mezi těmito dvěma modely: vývojář popíše entity aplikace a knihovna za něj připraví část SQL, načte výsledky a převede je zpět do objektů.

Objektově-relační mapování šetří hlavně opakovanou práci. Bez ORM se v mnoha projektech ručně píše kód pro vkládání, aktualizace, mapování sloupců na atributy, převod typů a kontrolu změn. ORM tuto rutinu soustředí do jedné vrstvy, takže aplikační kód může častěji vyjadřovat záměr: najdi zákazníka, ulož objednávku, načti související položky.

Co ORM překládá do SQL a zpět

ORM typicky mapuje třídu nebo entitu na tabulku, vlastnost na sloupec a vztah mezi objekty na vazbu mezi tabulkami. Knihovna zároveň řeší životní cyklus entit: nové objekty vloží, změněné objekty aktualizuje a odstraněné objekty smaže. Některé ORM používají vzor Active Record, kde objekt obsahuje i metody pro uložení. Jiné oddělují entity od repozitářů nebo databázového kontextu.

Důležitou částí je dotazování. Vývojář často zapíše filtr v jazyce aplikace a ORM jej převede na SQL dotaz. Výsledek není magický překlad libovolného kódu. ORM podporuje jen konstrukce, které dokáže bezpečně vyjádřit v databázi, případně část práce provede až v paměti aplikace. Právě hranice mezi databází a aplikací rozhoduje o výkonu.

Kde ORM zvyšuje cenu abstrakce

ORM může skrýt náklady, které jsou v SQL vidět okamžitě. Typickým problémem je N+1 dotaz: aplikace načte seznam záznamů a potom pro každý z nich samostatně dočítá související data. Další riziko vzniká u složitých reportů, hromadných aktualizací a dotazů závislých na konkrétních vlastnostech databáze, například indexech, transakcích nebo zamykání.

Dobře používané ORM neznamená odmítnutí SQL. Seniorní praxe často kombinuje ORM pro běžné operace a ručně psané SQL pro dotazy, kde je potřeba přesně řídit plán, agregace nebo objem přenášených dat. ORM je produktivní nástroj, ne náhrada za znalost relačního modelu.

Dva příklady z praxe

Filtr aktivních zákazníků v administraci

E-shop potřebuje v administraci vypsat aktivní zákazníky z Česka. Vývojář zapíše dotaz nad entitami a ORM připraví odpovídající SELECT, včetně mapování výsledků na objekty. Kód je čitelný pro tým, ale stále je vhodné zkontrolovat vygenerované SQL při pomalé stránce.

var customers = await db.Customers
    .Where(c => c.IsActive && c.Country == "CZ")
    .OrderBy(c => c.Name)
    .ToListAsync();

Dočítání zákazníka u každé objednávky

Dashboard načte poslední objednávky a u každé zobrazí jméno zákazníka. Při lazy loadingu může ORM poslat jeden dotaz pro objednávky a potom další dotaz pro každého zákazníka. Explicitní načtení vztahu obvykle sníží počet dotazů a odstraní špičku latence.

var orders = await db.Orders
    .Include(o => o.Customer)
    .OrderByDescending(o => o.CreatedAt)
    .Take(50)
    .ToListAsync();

Příklady z praxe

  1. Odhalení N+1 dotazu ve výpisu objednávek

    Vývojář interního skladového systému hlásil, že přehled 200 objednávek se načítá téměř osm sekund. Profiler databáze ukázal 201 dotazů: jeden na seznam objednávek a další na položky každé z nich. Šablona totiž iterovala kolekci order.Items, kterou ORM dočítalo líně až při prvním přístupu. Po přidání explicitního načtení souvisejících dat jedním JOINem klesl počet dotazů na dva a doba odezvy na 180 milisekund. Tým si zároveň nastavil logování SQL ve vývojovém prostředí, aby podobný případ odhalil dřív než zákazník.

    // před: N+1 dotaz
    var orders = await db.Orders
        .Where(o => o.CreatedAt >= from)
        .ToListAsync();
    foreach (var o in orders)
        total += o.Items.Sum(i => i.Price); // dotaz na každou objednávku
    
    // po: jedno načtení včetně položek
    var orders = await db.Orders
        .Include(o => o.Items)
        .Where(o => o.CreatedAt >= from)
        .ToListAsync();
  2. Hromadná změna cen mimo ORM

    Marketing e-shopu potřeboval jednorázově zdražit 120 000 produktů o pět procent. První verze skriptu načetla všechny entity do paměti, upravila vlastnost Price a zavolala uložení změn. Proces spotřeboval přes tři gigabajty RAM a po dvaceti minutách skončil timeoutem transakce. Vývojář nakonec nahradil postup jediným UPDATE příkazem spuštěným přes ORM jako nativní SQL, který proběhl za necelé dvě sekundy. Zbytek administrace zůstal na ORM, protože u editace jednoho produktu se abstrakce vyplácí.

    // hromadná operace přímo v databázi
    await db.Database.ExecuteSqlRawAsync(
        "UPDATE Products SET Price = Price * 1.05 WHERE CategoryId = {0}",
        categoryId);

Časté omyly

MýtusORM znamená, že už nemusím umět SQL.
Ve skutečnostiORM omezuje množství ručně psaného SQL, ale znalost SQL zůstává nutná pro ladění výkonu, čtení vygenerovaných dotazů a návrh schématu databáze.
MýtusORM je vždy pomalejší než ručně napsané dotazy.
Ve skutečnostiORM může vytvořit méně efektivní dotaz, ale u běžných operací bývá rozdíl zanedbatelný. Kritické části aplikace se dají optimalizovat explicitním načítáním, projekcí sloupců nebo ručním SQL.
MýtusStačí zapnout lazy loading a databáze se o vše postará.
Ve skutečnostiLazy loading může nenápadně vytvořit velké množství dotazů. U seznamů a dashboardů je bezpečnější plánovat načtení souvisejících dat předem.

Časté dotazy

Kdy ORM nestačí a je lepší psát SQL ručně?
ORM nestačí hlavně u dotazů, kde je potřeba přesně ovlivnit plán provedení, využití indexů, zamykání nebo databázově specifické funkce. Ruční SQL bývá vhodnější pro reporty, velké agregace, dávkové úpravy dat a ladění extrémně zatížených částí aplikace. Běžné CRUD operace však ORM často pokryje přehledněji a s menším množstvím opakovaného kódu.
Způsobuje ORM pomalé aplikace?
ORM samo o sobě pomalou aplikaci nezpůsobuje. Výkon zhorší hlavně nevhodné dotazy, nečekané lazy loading dočítání, přenos příliš mnoha sloupců nebo práce s velkým objemem dat v paměti aplikace. Dobře nastavené ORM s kontrolou vygenerovaného SQL, stránkováním a správnými indexy může být pro běžné operace dostatečně rychlé.
Patří ORM do doménového modelu aplikace?
ORM patří nejčastěji do aplikační nebo infrastrukturní vrstvy, protože zprostředkovává komunikaci s databází. Doménový model by neměl být zbytečně svázaný s detaily konkrétní ORM knihovny, pokud projekt potřebuje čistší architekturu a testovatelnost. U menších aplikací se ale přímé použití entit ORM často přijímá jako rozumný kompromis.
Je ORM stejné jako query builder?
ORM a query builder řeší podobnou oblast, ale nejsou totéž. Query builder pomáhá skládat SQL dotazy programově a obvykle vrací řádky nebo jednoduché struktury. ORM navíc mapuje výsledky na entity, sleduje změny objektů a často řídí ukládání vztahů. Některé knihovny kombinují oba přístupy v jednom nástroji.

Zdroje

  1. Object–relational mapping(otevře se v novém okně)Wikipedia
  2. Entity Framework Core(otevře se v novém okně)Microsoft
  3. Java Persistence API(otevře se v novém okně)Oracle

Související pojmy

Potřebujete to vyřešit v praxi?

Poradíme, jak na to ve vašem projektu

Vysvětlit pojem je jedna věc, navrhnout kolem něj funkční řešení druhá. Ozvěte se a probereme, co dává smysl u vás.