🗄️
데이터베이스
ORM
Object-Relational Mapping
객체와 관계형 DB를 매핑하는 기술. SQL 직접 작성 불필요. Prisma, SQLAlchemy, TypeORM.
Object-Relational Mapping
객체와 관계형 DB를 매핑하는 기술. SQL 직접 작성 불필요. Prisma, SQLAlchemy, TypeORM.
ORM(Object-Relational Mapping)은 객체 지향 프로그래밍 언어의 객체와 관계형 데이터베이스의 테이블을 자동으로 매핑해주는 기술입니다. 개발자가 SQL을 직접 작성하지 않고 프로그래밍 언어의 코드로 데이터베이스를 조작할 수 있게 해줍니다.
from sqlalchemy import Column, Integer, String, ForeignKey, create_engine
from sqlalchemy.orm import declarative_base, relationship, sessionmaker
Base = declarative_base()
# 모델 정의
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
email = Column(String(100), unique=True)
orders = relationship('Order', back_populates='user')
class Order(Base):
__tablename__ = 'orders'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('users.id'))
total = Column(Integer)
user = relationship('User', back_populates='orders')
# 세션 생성
engine = create_engine('postgresql://localhost/mydb')
Session = sessionmaker(bind=engine)
session = Session()
# CRUD 연산
# Create
user = User(name='Alice', email='alice@example.com')
session.add(user)
session.commit()
# Read
user = session.query(User).filter_by(email='alice@example.com').first()
users = session.query(User).filter(User.name.like('%Al%')).all()
# Update
user.name = 'Alice Kim'
session.commit()
# Delete
session.delete(user)
session.commit()
// schema.prisma
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
orders Order[]
createdAt DateTime @default(now())
}
model Order {
id Int @id @default(autoincrement())
total Int
user User @relation(fields: [userId], references: [id])
userId Int
}
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
// Create
const user = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
orders: {
create: [
{ total: 50000 },
{ total: 30000 }
]
}
},
include: { orders: true }
})
// Read with relations
const users = await prisma.user.findMany({
where: {
email: { contains: 'example.com' }
},
include: {
orders: {
where: { total: { gte: 10000 } }
}
},
orderBy: { createdAt: 'desc' },
take: 10
})
// Transaction
await prisma.$transaction(async (tx) => {
const user = await tx.user.update({
where: { id: 1 },
data: { name: 'Alice Kim' }
})
await tx.order.create({
data: { userId: user.id, total: 100000 }
})
})
# N+1 문제 발생 (1번 유저 쿼리 + N번 주문 쿼리)
users = session.query(User).all()
for user in users:
print(user.orders) # 매번 추가 쿼리 발생!
from sqlalchemy.orm import joinedload, selectinload # Eager Loading으로 해결 # 1. joinedload: JOIN으로 한 번에 가져오기 users = session.query(User).options(joinedload(User.orders)).all() # 2. selectinload: IN 절로 별도 쿼리 (추천) users = session.query(User).options(selectinload(User.orders)).all()
from sqlalchemy import text
# ORM으로 어려운 복잡한 쿼리는 Raw SQL 사용
result = session.execute(text("""
SELECT u.name, COUNT(o.id) as order_count, SUM(o.total) as total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.created_at >= :start_date
GROUP BY u.id
HAVING COUNT(o.id) > 5
ORDER BY total_spent DESC
"""), {'start_date': '2024-01-01'})
for row in result:
print(row.name, row.order_count, row.total_spent)