ORM (Object-Relational Mapping, объектно-реляционное отображение) — слой абстракции между кодом приложения и реляционной базой данных. ORM позволяет работать с данными как с объектами языка программирования, не писать SQL вручную. Запрос к БД превращается в вызов метода объекта: User.where(active: true).first вместо SELECT * FROM users WHERE active=1 LIMIT 1.
Как работает ORM
ORM-библиотека описывает соответствие между классами (моделями) и таблицами БД, между атрибутами объекта и столбцами. При вызове метода ORM генерирует SQL, выполняет запрос к СУБД через драйвер, получает результат и преобразует строки обратно в объекты. Связи между таблицами (один-к-одному, один-ко-многим, многие-ко-многим) описываются декларативно.
Пример Eloquent (PHP, Laravel):
$users = User::with('posts')->where('role', 'admin')->get();
ORM генерирует два запроса: SELECT * FROM users WHERE role='admin' и SELECT * FROM posts WHERE user_id IN (...) — eager loading для предотвращения N+1 проблемы.
Популярные ORM по языкам
| Язык | ORM | СУБД |
|---|---|---|
| PHP | Eloquent (Laravel), Doctrine (Symfony) | MySQL, PostgreSQL, SQLite |
| Python | SQLAlchemy, Django ORM, Peewee | MySQL, PostgreSQL, SQLite |
| Ruby | ActiveRecord (Rails) | MySQL, PostgreSQL, SQLite |
| Java | Hibernate, JPA, MyBatis | Любая JDBC-совместимая |
| Node.js | Prisma, Sequelize, TypeORM | MySQL, PostgreSQL, SQLite |
| Go | GORM, Ent | MySQL, PostgreSQL, SQLite |
История
Термин Object-Relational Mapping появился в начале 1990-х с первыми попытками интегрировать объектно-ориентированные языки с реляционными СУБД. Hibernate для Java вышел в 2001 году и установил стандарт. JPA (Java Persistence API) стандартизировал интерфейс ORM для Java в 2006 году (EJB 3.0). ActiveRecord в Ruby on Rails (2004) прославил паттерн Active Record. Doctrine для PHP вышел в 2006 году; Eloquent (Laravel) — в 2012 году.
N+1 проблема
Основная ловушка ORM: для N объектов с связанными данными ORM делает N+1 запрос вместо одного JOIN. Решение — eager loading: User.includes(:posts) в ActiveRecord, with('posts') в Eloquent, joinedload() в SQLAlchemy. Без eager loading запрос к 100 пользователям с постами делает 101 SELECT вместо 2.
На что обращать внимание
ORM ускоряет разработку и снижает вероятность SQL-инъекций (параметризованные запросы по умолчанию). Но генерируемые ORM запросы неоптимальны при сложной аналитике — для OLAP лучше писать SQL вручную. Для критичных по производительности операций используй raw SQL через DB::raw() или named queries. При работе с MySQL и PostgreSQL через ORM включай query logging в dev-среде: часто обнаруживаются неожиданные N+1 и полные table scan без индексов.
История ORM
Концепция ORM появилась в 1980-х с объектно-ориентированными языками. Hibernate (Java) — один из первых массовых ORM-фреймворков (2001). ActiveRecord (Ruby on Rails, 2004) популяризовал Active Record паттерн. SQLAlchemy (Python, 2006) реализует Data Mapper паттерн с явным отделением модели от схемы БД. Eloquent (Laravel PHP, 2013) стал стандартом для PHP-разработки. В 2020-е растёт популярность «thin ORM» — Prisma (Node.js), Drizzle — с типобезопасностью на уровне TypeScript и генерацией схемы из кода.
ORM и производительность
N+1 проблема — главный подводный камень ORM: загрузка списка пользователей и затем отдельный запрос на каждого пользователя для получения его постов. Решение — eager loading через JOIN или IN-запросы. В Laravel: User::with('posts'), в Django: User.objects.prefetch_related('posts'). Индексы БД создаются отдельно — ORM их не создаёт автоматически при запросах. Query logging в dev: DB::listen() в Laravel, Django Debug Toolbar — обязательный инструмент для выявления избыточных запросов.