TL;DR

  • SQLAlchemy - Python ORM의 핵심 개념과 사용 범위를 한눈에 정리
  • 등장 배경과 필요한 이유를 짚고 실무 적용 포인트를 연결
  • 주요 특징과 체크리스트를 빠르게 확인

1. 개념

┌─────────────────────────────────────────────────────────────┐ │ │ │ SQLAlchemy = Python에서 가장 널리 쓰이는 ORM/SQL 툴킷 │ │ │ │ 탄생: 2006년 (Mike Bayer 개발) │ │ 철학: "SQL is awesome, don't hide it" │ │ │ │ 두 가지 API 제공: │ │ ├── Core: 저수준 SQL 추상화 (SQL Expression Language) │ │ └── ORM: 고수준 객체-관계 매핑 │ │ │ │ 특징: │ │ ├── 데이터베이스 독립적 (MySQL, PostgreSQL, SQLite 등) │ │ ├── 유연함 (Core만 쓰거나 ORM만 쓰거나 둘 다) │ │ ├── 강력한 쿼리 빌더 │ │ └── 2.0 버전에서 완전히 새로운 스타일 도입 │ │ │ └─────────────────────────────────────────────────────────────┘

2. 배경

SQLAlchemy - Python ORM이(가) 등장한 배경과 기존 한계를 정리한다.

3. 이유

이 주제를 이해하고 적용해야 하는 이유를 정리한다.

4. 특징

  • 1 모델 정의
  • 2 CRUD 작업
  • 3 관계 쿼리 (Eager Loading)
  • 관련 키워드

5. 상세 내용

작성일: 2026-01-29 카테고리: Backend / Database / ORM 포함 내용: SQLAlchemy, ORM, Core, Session, Mapped, relationship, async, FastAPI 통합


1. SQLAlchemy란?

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  SQLAlchemy = Python에서 가장 널리 쓰이는 ORM/SQL 툴킷      │
│                                                             │
│  탄생: 2006년 (Mike Bayer 개발)                             │
│  철학: "SQL is awesome, don't hide it"                      │
│                                                             │
│  두 가지 API 제공:                                          │
│  ├── Core: 저수준 SQL 추상화 (SQL Expression Language)      │
│  └── ORM: 고수준 객체-관계 매핑                             │
│                                                             │
│  특징:                                                      │
│  ├── 데이터베이스 독립적 (MySQL, PostgreSQL, SQLite 등)     │
│  ├── 유연함 (Core만 쓰거나 ORM만 쓰거나 둘 다)              │
│  ├── 강력한 쿼리 빌더                                       │
│  └── 2.0 버전에서 완전히 새로운 스타일 도입                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

2. 왜 ORM이 필요한가? (배경)

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  문제: 객체지향 vs 관계형 DB의 불일치 (Impedance Mismatch)  │
│                                                             │
│  Python 객체:                    SQL 테이블:                │
│  ┌─────────────────┐            ┌─────────────────────┐    │
│  │ class User:     │            │ CREATE TABLE users  │    │
│  │   id: int       │  vs        │ (                   │    │
│  │   name: str     │            │   id INTEGER,       │    │
│  │   posts: List   │←──────────?│   name VARCHAR(100) │    │
│  │                 │            │ )                   │    │
│  └─────────────────┘            └─────────────────────┘    │
│                                                             │
│  Raw SQL의 문제점:                                          │
│  ├── SQL 인젝션 위험 (문자열 연결 시)                       │
│  ├── DB 종류별로 SQL 문법 다름                              │
│  ├── 코드 중복 (CRUD마다 SQL 작성)                          │
│  ├── 타입 안전성 부족                                       │
│  └── 객체로 변환하는 코드 반복                              │
│                                                             │
│  ─────────────────────────────────────────────────────────  │
│                                                             │
│  Raw SQL 예시 (위험!):                                      │
│                                                             │
│  # ❌ SQL 인젝션 취약!                                      │
│  cursor.execute(                                            │
│      f"SELECT * FROM users WHERE name = '{user_input}'"     │
│  )                                                          │
│  # user_input = "'; DROP TABLE users; --"                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

3. ORM의 역사

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  1990년대: 객체-관계 매핑 개념 등장                         │
│  ├── TopLink (Java, 1994)                                   │
│  └── 객체지향 프로그래밍의 확산                             │
│                                                             │
│  2000년대 초: ORM의 대중화                                  │
│  ├── Hibernate (Java, 2001) - 가장 영향력 있는 ORM          │
│  ├── Ruby on Rails ActiveRecord (2004)                      │
│  └── Django ORM (Python, 2005)                              │
│                                                             │
│  2006년: SQLAlchemy 탄생                                    │
│  ├── Mike Bayer가 개발                                      │
│  ├── "SQL을 숨기지 말자" 철학                               │
│  └── Core + ORM 이중 구조                                   │
│                                                             │
│  2010년대: ORM 논쟁                                         │
│  ├── "ORM은 안티패턴이다" vs "ORM이 생산성 높다"            │
│  └── SQLAlchemy는 중간 지점 제공 (Core로 SQL 직접 제어)     │
│                                                             │
│  2023년: SQLAlchemy 2.0                                     │
│  ├── 완전히 새로운 스타일 (async 네이티브 지원)             │
│  ├── 타입 힌트 완전 지원                                    │
│  └── 1.x 스타일은 레거시로 분류                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

4. SQLAlchemy 아키텍처

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│                    SQLAlchemy 구조                          │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                     ORM Layer                        │   │
│  │  (Session, Query, Relationship, Mapper)              │   │
│  │                                                     │   │
│  │  → 객체 ↔ 테이블 매핑                               │   │
│  │  → 관계 관리 (1:N, N:M)                             │   │
│  │  → 세션/트랜잭션 관리                               │   │
│  └───────────────────────┬─────────────────────────────┘   │
│                          │                                  │
│  ┌───────────────────────▼─────────────────────────────┐   │
│  │                    Core Layer                        │   │
│  │  (Engine, Connection, MetaData, Table, Column)       │   │
│  │                                                     │   │
│  │  → SQL 표현식 빌더                                  │   │
│  │  → 스키마 정의                                      │   │
│  │  → 연결 풀 관리                                     │   │
│  └───────────────────────┬─────────────────────────────┘   │
│                          │                                  │
│  ┌───────────────────────▼─────────────────────────────┐   │
│  │                    DBAPI Layer                       │   │
│  │  (psycopg2, mysqlclient, sqlite3, asyncpg 등)        │   │
│  │                                                     │   │
│  │  → 실제 DB 드라이버                                 │   │
│  └───────────────────────┬─────────────────────────────┘   │
│                          │                                  │
│  ┌───────────────────────▼─────────────────────────────┐   │
│  │                    Database                          │   │
│  │  (PostgreSQL, MySQL, SQLite, Oracle 등)              │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  핵심 개념:                                                 │
│  ├── ORM만 쓸 수도 있고                                     │
│  ├── Core만 쓸 수도 있고                                    │
│  └── 둘을 섞어 쓸 수도 있음 (유연함!)                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5. SQLAlchemy 2.0 스타일 (현대적)

5.1 모델 정의

from sqlalchemy import String, ForeignKey, create_engine
from sqlalchemy.orm import (
    DeclarativeBase,
    Mapped,
    mapped_column,
    relationship,
    Session
)
from typing import List, Optional
from datetime import datetime

# 베이스 클래스 정의
class Base(DeclarativeBase):
    pass

# 모델 정의 (SQLAlchemy 2.0 스타일)
class User(Base):
    __tablename__ = "users"

    # Mapped[타입] = 타입 힌트 + 컬럼 정의 통합
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(100))
    email: Mapped[str] = mapped_column(String(255), unique=True)
    created_at: Mapped[datetime] = mapped_column(default=datetime.now)

    # Optional = nullable
    bio: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)

    # 관계 정의 (1:N)
    posts: Mapped[List["Post"]] = relationship(back_populates="author")

    def __repr__(self) -> str:
        return f"User(id={self.id}, name={self.name})"


class Post(Base):
    __tablename__ = "posts"

    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str] = mapped_column(String(200))
    content: Mapped[str] = mapped_column(String(10000))

    # 외래키
    author_id: Mapped[int] = mapped_column(ForeignKey("users.id"))

    # 관계 (N:1)
    author: Mapped["User"] = relationship(back_populates="posts")

5.2 CRUD 작업

from sqlalchemy import select, update, delete
from sqlalchemy.orm import Session

# Engine 생성
engine = create_engine("postgresql://user:pass@localhost/mydb")

# 테이블 생성
Base.metadata.create_all(engine)

# ===== CREATE =====
with Session(engine) as session:
    new_user = User(name="김개발", email="kim@example.com")
    session.add(new_user)
    session.commit()
    print(f"생성된 ID: {new_user.id}")

# ===== READ (단건) =====
with Session(engine) as session:
    # 방법 1: Primary Key로 조회
    user = session.get(User, 1)

    # 방법 2: select 문
    stmt = select(User).where(User.email == "kim@example.com")
    user = session.scalar(stmt)
    print(user.name)

# ===== READ (다건) =====
with Session(engine) as session:
    stmt = (
        select(User)
        .where(User.name.like("%김%"))
        .order_by(User.created_at.desc())
    )
    users = session.scalars(stmt).all()

    for user in users:
        print(user)

# ===== UPDATE =====
with Session(engine) as session:
    # 방법 1: 객체 수정
    user = session.get(User, 1)
    user.name = "김수정"
    session.commit()

    # 방법 2: bulk update
    stmt = update(User).where(User.id > 100).values(bio="updated")
    session.execute(stmt)
    session.commit()

# ===== DELETE =====
with Session(engine) as session:
    # 방법 1: 객체 삭제
    user = session.get(User, 1)
    session.delete(user)
    session.commit()

    # 방법 2: bulk delete
    stmt = delete(User).where(User.created_at < datetime(2020, 1, 1))
    session.execute(stmt)
    session.commit()

5.3 관계 쿼리 (Eager Loading)

from sqlalchemy.orm import joinedload, selectinload

with Session(engine) as session:
    # N+1 문제 방지

    # 방법 1: joinedload (JOIN 사용)
    stmt = select(User).options(joinedload(User.posts)).where(User.id == 1)
    user = session.scalar(stmt)

    # 방법 2: selectinload (별도 쿼리)
    stmt = select(User).options(selectinload(User.posts))
    users = session.scalars(stmt).all()

    # 관계 데이터 접근
    for post in user.posts:
        print(f"{user.name}의 글: {post.title}")

6. 비동기 지원 (Async)

from sqlalchemy.ext.asyncio import (
    create_async_engine,
    AsyncSession,
    async_sessionmaker
)
from sqlalchemy import select

# 비동기 엔진 (asyncpg 드라이버 사용)
engine = create_async_engine(
    "postgresql+asyncpg://user:pass@localhost/mydb",
    echo=True,
)

# 비동기 세션 팩토리
async_session = async_sessionmaker(engine, expire_on_commit=False)

# 비동기 CRUD
async def create_user(name: str, email: str) -> User:
    async with async_session() as session:
        user = User(name=name, email=email)
        session.add(user)
        await session.commit()
        await session.refresh(user)
        return user

async def get_user(user_id: int) -> User | None:
    async with async_session() as session:
        return await session.get(User, user_id)

async def get_users_by_name(name: str) -> list[User]:
    async with async_session() as session:
        stmt = select(User).where(User.name.like(f"%{name}%"))
        result = await session.scalars(stmt)
        return result.all()

7. FastAPI와 통합

from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from contextlib import asynccontextmanager

# Engine & Session 설정
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/mydb")
async_session = async_sessionmaker(engine, expire_on_commit=False)

# 의존성 주입용 세션 팩토리
async def get_db() -> AsyncSession:
    async with async_session() as session:
        yield session

# Lifespan으로 앱 시작/종료 시 처리
@asynccontextmanager
async def lifespan(app: FastAPI):
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield
    await engine.dispose()

app = FastAPI(lifespan=lifespan)

# API 엔드포인트
@app.post("/users/")
async def create_user(
    name: str,
    email: str,
    db: AsyncSession = Depends(get_db)
):
    user = User(name=name, email=email)
    db.add(user)
    await db.commit()
    await db.refresh(user)
    return {"id": user.id, "name": user.name}

@app.get("/users/{user_id}")
async def read_user(
    user_id: int,
    db: AsyncSession = Depends(get_db)
):
    user = await db.get(User, user_id)
    if not user:
        raise HTTPException(404, "User not found")
    return user

8. Core vs ORM 비교

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  SQLAlchemy Core (저수준):                                  │
│                                                             │
│  from sqlalchemy import Table, Column, Integer, String      │
│  from sqlalchemy import select, insert, update, delete      │
│                                                             │
│  # 테이블 정의                                              │
│  users = Table(                                             │
│      "users", metadata,                                     │
│      Column("id", Integer, primary_key=True),               │
│      Column("name", String(100)),                           │
│  )                                                          │
│                                                             │
│  # 쿼리 실행                                                │
│  stmt = select(users).where(users.c.name == "김개발")       │
│  result = conn.execute(stmt)                                │
│                                                             │
│  장점: SQL 직접 제어, 복잡한 쿼리, 성능 튜닝                │
│                                                             │
│  ─────────────────────────────────────────────────────────  │
│                                                             │
│  SQLAlchemy ORM (고수준):                                   │
│                                                             │
│  class User(Base):                                          │
│      __tablename__ = "users"                                │
│      id: Mapped[int] = mapped_column(primary_key=True)      │
│      name: Mapped[str] = mapped_column(String(100))         │
│                                                             │
│  # 쿼리 실행                                                │
│  stmt = select(User).where(User.name == "김개발")           │
│  user = session.scalar(stmt)                                │
│                                                             │
│  장점: 객체지향적, 관계 자동 관리, 코드 가독성              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

9. SQLAlchemy vs 다른 ORM

┌──────────────────────────────────────────────────────────────────────────┐
│                                                                          │
│  ┌────────────────┬─────────────────┬─────────────────┬──────────────┐  │
│  │                │  SQLAlchemy     │  Django ORM     │  Tortoise    │  │
│  ├────────────────┼─────────────────┼─────────────────┼──────────────┤  │
│  │ 철학           │ SQL은 좋은 것   │ SQL 숨기기      │ Django 스타일│  │
│  ├────────────────┼─────────────────┼─────────────────┼──────────────┤  │
│  │ 프레임워크     │ 독립적          │ Django 전용     │ 독립적       │  │
│  ├────────────────┼─────────────────┼─────────────────┼──────────────┤  │
│  │ Async 지원     │ 2.0에서 완벽    │ 4.1+에서 제한적 │ 네이티브     │  │
│  ├────────────────┼─────────────────┼─────────────────┼──────────────┤  │
│  │ 복잡한 쿼리    │ 매우 강력       │ 한계 있음       │ 중간         │  │
│  ├────────────────┼─────────────────┼─────────────────┼──────────────┤  │
│  │ 학습 곡선      │ 가파름          │ 완만함          │ 중간         │  │
│  └────────────────┴─────────────────┴─────────────────┴──────────────┘  │
│                                                                          │
│  선택 가이드:                                                            │
│  ├── Django 프로젝트 → Django ORM                                        │
│  ├── FastAPI + 복잡한 쿼리 → SQLAlchemy                                  │
│  └── FastAPI + 간단한 CRUD → Tortoise 또는 SQLAlchemy                    │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

10. 성능 팁

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  1. N+1 문제 방지                                           │
│                                                             │
│     # ❌ N+1 (users 1번 + posts N번)                        │
│     users = session.scalars(select(User)).all()             │
│     for user in users:                                      │
│         print(user.posts)  # 매번 쿼리!                     │
│                                                             │
│     # ✓ Eager Loading (2번의 쿼리)                          │
│     stmt = select(User).options(selectinload(User.posts))   │
│     users = session.scalars(stmt).all()                     │
│                                                             │
│  2. Bulk 작업 사용                                          │
│                                                             │
│     # ❌ 느림 (1000번의 INSERT)                             │
│     for i in range(1000):                                   │
│         session.add(User(name=f"user{i}"))                  │
│                                                             │
│     # ✓ 빠름 (1번의 INSERT)                                 │
│     session.execute(                                        │
│         insert(User),                                       │
│         [{"name": f"user{i}"} for i in range(1000)]         │
│     )                                                       │
│                                                             │
│  3. 필요한 컬럼만 조회                                      │
│     select(User.id, User.name) vs select(User)              │
│                                                             │
│  4. 인덱스 활용                                             │
│     mapped_column(index=True)                               │
│                                                             │
│  5. Connection Pool 설정                                    │
│     pool_size, max_overflow 적절히 설정                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

11. 정리

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  SQLAlchemy 핵심 요약:                                      │
│                                                             │
│  1. Python 최고의 ORM/SQL 툴킷 (2006년~)                    │
│                                                             │
│  2. 이중 구조: Core (저수준) + ORM (고수준)                 │
│                                                             │
│  3. 2.0 스타일 = 현대적, 타입 힌트, async 지원              │
│     └── Mapped[타입] + mapped_column()                      │
│                                                             │
│  4. FastAPI와 찰떡궁합                                      │
│     └── async_session + Depends 패턴                        │
│                                                             │
│  5. 성능 핵심: N+1 방지, Bulk 작업, 필요한 것만 조회        │
│                                                             │
│  6. 철학: "SQL은 좋은 것, 숨기지 말자"                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

관련 키워드

SQLAlchemy, ORM, Object-Relational Mapping, Core, Session, Mapped, mapped_column, relationship, async, AsyncSession, Engine, select, N+1 문제, Eager Loading, joinedload, selectinload