Зачем нужен 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) зависят от масштаба и требований проекта.
Правильный выбор инструментов и подходов значительно ускоряет разработку и упрощает поддержку кода!