Object-Relational Mapping
Работа с БД через объекты
ORM — технология программирования, которая связывает базы данных с объектно-ориентированными языками программирования, создавая "виртуальную объектную базу данных".
# ❌ Работа с SQL напрямую
import psycopg2
conn = psycopg2.connect("dbname=test user=postgres")
cur = conn.cursor()
# Создание
cur.execute("INSERT INTO users (name, email) VALUES (%s, %s)",
("Alice", "alice@example.com"))
conn.commit()
# Чтение
cur.execute("SELECT * FROM users WHERE id = %s", (1,))
row = cur.fetchone()
user = {"id": row[0], "name": row[1], "email": row[2]}
# Много boilerplate кода
# Нет type safety
# Сложно поддерживать
# ✅ Работа с объектами
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Создание
user = User(name="Alice", email="alice@example.com")
session.add(user)
session.commit()
# Чтение
user = session.query(User).filter(User.id == 1).first()
print(user.name) # "Alice"
# Type safe
# Меньше кода
# Легче поддерживать
-- Структура: таблицы, связи, ACID
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100)
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
total DECIMAL(10, 2)
);
# Структура: коллекции, документы
{
"_id": "123",
"name": "Alice",
"email": "alice@example.com",
"orders": [
{"id": 1, "total": 100.0},
{"id": 2, "total": 200.0}
]
}
# Хранение векторов для similarity search
# Используется в ML для:
# - Semantic search
# - Recommendations
# - RAG (Retrieval Augmented Generation)
vector = [0.1, 0.2, 0.3, ..., 0.9] # 768 dimensions
db.insert("document_123", vector, metadata={"text": "..."})
Самая популярная ORM для Python
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
email = Column(String(100), unique=True, nullable=False)
# Relationships
orders = relationship("Order", back_populates="user")
class Order(Base):
__tablename__ = 'orders'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
total = Column(Numeric(10, 2), nullable=False)
# Relationships
user = relationship("User", back_populates="orders")
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Создание engine
engine = create_engine('postgresql://user:pass@localhost/db')
Session = sessionmaker(bind=engine)
session = Session()
# Создание
user = User(name="Alice", email="alice@example.com")
session.add(user)
session.commit()
# Чтение
user = session.query(User).filter(User.id == 1).first()
users = session.query(User).filter(User.name.like('%Alice%')).all()
# Обновление
user.name = "Bob"
session.commit()
# Удаление
session.delete(user)
session.commit()
# One-to-Many
user = session.query(User).filter(User.id == 1).first()
orders = user.orders # Автоматически загружает связанные заказы
# Many-to-One
order = session.query(Order).filter(Order.id == 1).first()
user = order.user # Автоматически загружает пользователя
# Joins
users_with_orders = session.query(User)\
.join(Order)\
.filter(Order.total > 100)\
.all()
# Eager loading
from sqlalchemy.orm import joinedload
users = session.query(User)\
.options(joinedload(User.orders))\
.all() # Загружает пользователей и их заказы одним запросом
# Создание миграции
alembic revision --autogenerate -m "Add users table"
# Применение миграции
alembic upgrade head
# Откат миграции
alembic downgrade -1
# Файл миграции
def upgrade():
op.create_table(
'users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(100), nullable=False),
sa.Column('email', sa.String(100), nullable=False),
sa.PrimaryKeyConstraint('id')
)
def downgrade():
op.drop_table('users')
ORM для Django фреймворка
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='orders')
total = models.DecimalField(max_digits=10, decimal_places=2)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
# Создание
user = User.objects.create(name="Alice", email="alice@example.com")
# Чтение
user = User.objects.get(id=1)
users = User.objects.filter(name__contains="Alice")
users = User.objects.all()
# Обновление
user.name = "Bob"
user.save()
# Удаление
user.delete()
# Aggregations
from django.db.models import Count, Sum
users_with_orders = User.objects.annotate(
order_count=Count('orders'),
total_spent=Sum('orders__total')
)
Object Document Mapping для MongoDB
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017/')
db = client['mydatabase']
users = db['users']
# Создание
users.insert_one({
"name": "Alice",
"email": "alice@example.com",
"orders": [
{"id": 1, "total": 100.0},
{"id": 2, "total": 200.0}
]
})
# Чтение
user = users.find_one({"email": "alice@example.com"})
users_list = users.find({"name": {"$regex": "Alice"}})
# Обновление
users.update_one(
{"email": "alice@example.com"},
{"$set": {"name": "Bob"}}
)
# Удаление
users.delete_one({"email": "alice@example.com"})
from mongoengine import Document, StringField, EmailField, ListField, EmbeddedDocumentField
class Order(EmbeddedDocument):
id = IntField()
total = FloatField()
class User(Document):
name = StringField(required=True, max_length=100)
email = EmailField(required=True, unique=True)
orders = ListField(EmbeddedDocumentField(Order))
meta = {
'collection': 'users',
'indexes': ['email']
}
# Создание
user = User(name="Alice", email="alice@example.com")
user.orders = [Order(id=1, total=100.0)]
user.save()
# Чтение
user = User.objects(email="alice@example.com").first()
users = User.objects(name__contains="Alice")
# Обновление
user.name = "Bob"
user.save()
# Удаление
user.delete()
import pinecone
from sentence_transformers import SentenceTransformer
# Инициализация
pinecone.init(api_key="your-api-key", environment="us-west1-gcp")
index = pinecone.Index("my-index")
# Модель для embeddings
model = SentenceTransformer('all-MiniLM-L6-v2')
# Создание векторов
texts = ["This is a document", "Another document"]
vectors = model.encode(texts)
# Вставка
index.upsert([
("doc1", vectors[0].tolist(), {"text": texts[0]}),
("doc2", vectors[1].tolist(), {"text": texts[1]})
])
# Поиск
query_vector = model.encode(["search query"]).tolist()
results = index.query(query_vector, top_k=5, include_metadata=True)
import chromadb
from sentence_transformers import SentenceTransformer
# Инициализация
client = chromadb.Client()
collection = client.create_collection("documents")
# Модель
model = SentenceTransformer('all-MiniLM-L6-v2')
# Добавление документов
documents = ["This is a document", "Another document"]
embeddings = model.encode(documents).tolist()
collection.add(
ids=["doc1", "doc2"],
embeddings=embeddings,
documents=documents
)
# Поиск
query_embedding = model.encode(["search query"]).tolist()
results = collection.query(
query_embeddings=query_embedding,
n_results=5
)
# ❌ N+1 запросов
users = session.query(User).all() # 1 запрос
for user in users:
print(user.orders) # N запросов (по одному на каждого пользователя)
# ✅ Решение: Eager loading
from sqlalchemy.orm import joinedload
users = session.query(User)\
.options(joinedload(User.orders))\
.all() # 1 запрос с JOIN
| Сценарий | Рекомендация |
|---|---|
| CRUD операции | ✅ ORM отлично подходит |
| Сложные запросы | ⚠️ Может быть проще написать SQL |
| Высокая производительность | ⚠️ Сырой SQL может быть быстрее |
| Быстрая разработка | ✅ ORM ускоряет разработку |
| Миграции схемы | ✅ ORM упрощает миграции |
"ORM — это инструмент. Используй его там, где он помогает, и не используй там, где мешает.
Для простых операций — ORM. Для сложных запросов — SQL."