Найти в Дзене

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

ORM (Object-Relational Mapping) — это технология, которая позволяет работать с базой данных через объекты в коде, а не через сырые SQL-запросы. Основные причины её использования: 1. Абстракция: ORM скрывает детали SQL, позволяя фокусироваться на бизнес-логике. 2. Безопасность: Автоматическое экранирование входных данных снижает риск SQL-инъекций. 3. Переносимость: ORM абстрагирует специфику СУБД (например, различия между PostgreSQL и SQLite). 4. Скорость разработки: Генерация CRUD-операций, миграций и связей между таблицами. Когда использовать чистый SQL: - Сложные аналитические запросы (оконные функции, агрегация). - Использование специфичных для СУБД функций (например, JSONB в PostgreSQL). - Критичные к производительности операции. Пример с 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(
Оглавление

Зачем нужен 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 Record

1. Модель содержит и данные, и логику сохранения (например, `user.save()`).

2. Прост в освоении (Django, Ruby on Rails).

3. Прямая привязка к структуре БД.

Data Mapper

1. Логика сохранения отделена от модели (есть отдельный "маппер").

2. Гибкость для сложных сценариев (Hibernate, SQLAlchemy ORM).

3. Модель не зависит от схемы БД.

Пример:

- 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) зависят от масштаба и требований проекта.

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