Зачем нужен ORM? Преимущества перед чистым SQL

Зачем нужен ORM? Преимущества перед чистым SQL


Active Record vs Data Mapper: Паттерны ORM, их особенности и применение


Зачем нужен ORM? Преимущества перед чистым SQL

ORM (Object-Relational Mapping) — это технология, которая позволяет работать с базой данных через объекты в коде, а не через сырые SQL-запросы. Основные причины её использования:

  1. Абстракция: ORM скрывает детали SQL, позволяя фокусироваться на бизнес-логике.
  2. Безопасность: Автоматическое экранирование входных данных снижает риск SQL-инъекций.
  3. Переносимость: ORM абстрагирует специфику СУБД (например, различия между PostgreSQL и SQLite).
  4. Скорость разработки: Генерация CRUD-операций, миграций и связей между таблицами.

Когда использовать чистый SQL:

  • Сложные аналитические запросы (оконные функции, агрегация).
  • Использование специфичных для СУБД функций (например, JSONB в PostgreSQL).
  • Критичные к производительности операции.

Подключение к БД и простые запросы через ORM

Пример с SQLAlchemy:

from sqlalchemy import create_engine, Column, Integer, String  
from sqlalchemy.orm import declarative_base, sessionmaker  

# Подключение  
engine = create_engine("sqlite:///db.sqlite")  
Session = sessionmaker(bind=engine)  
session = Session()  

# Модель  
Base = declarative_base()  
class User(Base):  
    __tablename__ = "users"  
    id = Column(Integer, primary_key=True)  
    name = Column(String)  

# Запрос  
users = session.query(User).filter(User.name == "Алиса").all()  

Пример с Django ORM:

from django.db import models  

class User(models.Model):  
    name = models.CharField(max_length=100)  

# Запрос  
users = User.objects.filter(name="Алиса")  

Связи между таблицами

1. Внешние ключи (один-ко-многим)

# SQLAlchemy  
class Post(Base):  
    __tablename__ = "posts"  
    user_id = Column(Integer, ForeignKey("users.id"))  
    user = relationship("User", back_populates="posts")  

User.posts = relationship("Post", back_populates="user")  

# Django  
class Post(models.Model):  
    user = models.ForeignKey(User, on_delete=models.CASCADE)  

2. Связь многие-ко-многим

# SQLAlchemy  
user_group = Table(  
    "user_group", Base.metadata,  
    Column("user_id", ForeignKey("users.id")),  
    Column("group_id", ForeignKey("groups.id"))  
)  

class Group(Base):  
    users = relationship("User", secondary=user_group)  

# Django  
class Group(models.Model):  
    users = models.ManyToManyField(User)  

Миграции: Зачем они нужны?

Миграции — это версионирование схемы БД. Они решают проблемы:

  • Изменение структуры таблиц (добавление/удаление столбцов).
  • Согласованность схемы между разными окружениями (dev, prod).
  • Откат изменений при ошибках.

Инструменты:

  • Alembic (для SQLAlchemy):
    • Генерация миграций: alembic revision --autogenerate.
    • Применение: alembic upgrade head.
  • Django Migrations:
    • Автоматическая генерация при изменении моделей.
    • Применение: python manage.py migrate.

Active Record vs Data Mapper

Active RecordData Mapper
Модель содержит и данные, и логику сохранения (например, user.save()).Логика сохранения отделена от модели (есть отдельный “маппер”).
Прост в освоении (Django, Ruby on Rails).Гибкость для сложных сценариев (Hibernate, SQLAlchemy ORM).
Прямая привязка к структуре БД.Модель не зависит от схемы БД.

Пример:

  • Active Record (Django):
    user = User(name="Алиса")  
    user.save()  # Метод модели  
  • Data Mapper (SQLAlchemy):
    user = User(name="Алиса")  
    session.add(user)  # Сессия управляет сохранением  
    session.commit()  

SQLAlchemy Core vs ORM

  • SQLAlchemy Core:

    • Низкоуровневый инструмент для построения SQL-запросов.
    • Подходит для сложных запросов или работы с представлениями (views).
    from sqlalchemy import select, table, column  
    
    users = table("users", column("id"), column("name"))  
    query = select(users.c.name).where(users.c.id == 1)  
    result = engine.execute(query)  
  • SQLAlchemy ORM:

    • Высокоуровневая абстракция с моделями и сессиями.
    • Идеален для объектно-ориентированного подхода.

Когда использовать Core:

  • Динамические запросы (например, фильтры, заданные пользователем).
  • Работа с представлениями или объединением данных из нескольких таблиц.

Расширенные возможности ORM

1. Гибридные свойства (SQLAlchemy):

class User(Base):  
    first_name = Column(String)  
    last_name = Column(String)  

    @hybrid_property  
    def full_name(self):  
        return f"{self.first_name} {self.last_name}"  

2. Самореферентные таблицы:

# Django  
class Employee(models.Model):  
    manager = models.ForeignKey("self", on_delete=models.SET_NULL, null=True)  

# SQLAlchemy  
class Employee(Base):  
    id = Column(Integer, primary_key=True)  
    manager_id = Column(Integer, ForeignKey("employees.id"))  
    manager = relationship("Employee", remote_side=[id])  

3. Динамическое создание таблиц:

# SQLAlchemy  
from sqlalchemy import MetaData, Table, Column  

metadata = MetaData()  
dynamic_table = Table(  
    "temp_data", metadata,  
    Column("id", Integer, primary_key=True),  
    Column("value", String)  
)  
metadata.create_all(engine)  

Архитектурные подходы к работе с БД

  1. DB-Centric Models:

    • Плюсы: Быстрое прототипирование, минимум кода.
    • Минусы: Бизнес-логика смешивается с данными, сложное тестирование.
  2. Repository Pattern:

    • Суть: Отдельный класс (репозиторий) управляет всеми операциями с БД.
    • Плюсы: Чистая архитектура, легко подменять источник данных.
    • Минусы: Дополнительная абстракция.
    class UserRepository:  
        def get_by_id(self, user_id):  
            return session.query(User).get(user_id)  
  3. Перенос логики в БД (процедуры, триггеры):

    • Когда использовать:
      • Сложная валидация данных.
      • Высокие требования к производительности.
    • Минусы: Усложнение отладки, привязка к конкретной СУБД.

Итоги

  • Active Record подходит для простых приложений с предсказуемой структурой.
  • Data Mapper выбирайте для сложных проектов, где важна гибкость и разделение слоёв.
  • SQLAlchemy Core используйте для аналитики или работы с динамическими запросами.
  • Миграции — обязательный инструмент для командной работы.
  • Архитектурные паттерны (репозиторий, DB-centric) зависят от масштаба и требований проекта.

Правильный выбор инструментов и подходов значительно ускоряет разработку и упрощает поддержку кода!