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.
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
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();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
- Object–relational mapping(otevře se v novém okně)
- Entity Framework Core(otevře se v novém okně)
- Java Persistence API(otevře se v novém okně)