FastAPI/데이터베이스

ORM 모델링

hyunai 2026. 6. 9. 22:57
from sqlalchemy import Boolean, Column, Integer, String
from sqlalchemy.orm import declarative_base

Base = declarative_base()

class ToDo(Base):
    __tablename__ = "todo"

    id = Column(Integer, primary_key=True, index=True)
    contents = Column(String(256), nullable=False)
    is_done = Column(Boolean, nullable=False)

    # 객체를 출력하기 위한 메소드
    def __repr__(self):
        return f"ToDo(id={self.id}, contents={self.contents}, is_done={self.is_done})"

declarative_base?

한 문장으로 표현하면:

"ORM 모델 클래스들의 부모 클래스를 만들어주는 공장"


왜 필요한지 먼저 이해하기

ORM의 역할은 Python 클래스 ↔ DB 테이블 을 연결하는 것

ORM 개념 시각화

Python 세계                DB 세계
──────────────────         ──────────────────
class ToDo:          ↔     TABLE todo
    id               ↔         id
    contents         ↔         contents  
    is_done          ↔         is_done

근데 Python 클래스가 "나는 DB 테이블이야" 라는 걸 SQLAlchemy한테 알려주려면 특정 부모 클래스를 상속받아야 된다. 그 부모 클래스를 만들어주는 게 declarative_base()


코드로 보기

declarative_base 사용 흐름

from sqlalchemy.orm import declarative_base

# 1. Base 클래스 생성 (공장에서 틀을 만드는 것)
Base = declarative_base()

# 2. Base를 상속받아서 ORM 모델 정의
class ToDo(Base):              # ← Base 상속!
    __tablename__ = "todo"     # ← 어떤 테이블과 연결할지

    id = Column(Integer, primary_key=True)
    contents = Column(String(256))
    is_done = Column(Boolean)

# 3. Base가 알고 있는 모든 모델로 테이블 생성
Base.metadata.create_all(bind=engine)

Base가 하는 역할 3가지

Base의 역할

역할 설명  예시
테이블 매핑 클래스와 DB 테이블 연결 __tablename__ = "todo"
컬럼 인식 Column()을 DB 컬럼으로 인식 id = Column(Integer)
테이블 관리 연결된 모든 테이블 생성/삭제 Base.metadata.create_all()

Base 없이 상속 안 하면?

Base 상속 안 할 때 차이

# 그냥 클래스 (SQLAlchemy가 모름)
class ToDo:
    id = 1
    contents = "할일"

# Base 상속 (SQLAlchemy가 DB 테이블로 인식)
class ToDo(Base):
    __tablename__ = "todo"
    id = Column(Integer, primary_key=True)

metadata가 뭔지도 같이 이해하기

Base.metadata 구조

Base
 └── metadata  ← Base에 연결된 모든 테이블 정보 저장소
       ├── ToDo 테이블 정보
       ├── User 테이블 정보  (나중에 추가하면)
       └── Post 테이블 정보  (나중에 추가하면)

Base.metadata.create_all()  → 저장된 모든 테이블 한번에 생성
Base.metadata.drop_all()    → 저장된 모든 테이블 한번에 삭제

실무에서 어떻게 쓰이냐면

실무 프로젝트 구조

# database/orm.py
Base = declarative_base()  # 여기서 한 번만 만들고

class ToDo(Base):          # 모든 모델이 같은 Base 상속
    __tablename__ = "todo"
    ...

class User(Base):          # 이것도 같은 Base
    __tablename__ = "user"
    ...

# 나중에 한방에 모든 테이블 생성
Base.metadata.create_all(bind=engine)  # ToDo, User 둘 다 생성

핵심 요약:
Base = declarative_base() 는 한 번만 만들고,
모든 ORM 모델은 이 Base를 상속받아서 "나는 DB 테이블이야"를 선언하는 것.

 

ORM 모델링

모델링을 위해 콘솔창을 켜준다.

from database.connection import SessionFactory
  • Session을 생성하기 위해 SessionFactory를 import 해준다.
session = SessionFactory()
  • session 객체 하나 생성
from sqlalchemy import select
  • select 함수 import
from database.orm import ToDo
  • ToDo 클래스를 import
session.scalars(select(ToDo))
======================================================
2026-06-09 22:48:18,134 INFO sqlalchemy.engine.Engine SELECT DATABASE()
2026-06-09 22:48:18,135 INFO sqlalchemy.engine.Engine [raw sql] {}
2026-06-09 22:48:18,145 INFO sqlalchemy.engine.Engine SELECT @@sql_mode
2026-06-09 22:48:18,145 INFO sqlalchemy.engine.Engine [raw sql] {}
2026-06-09 22:48:18,149 INFO sqlalchemy.engine.Engine SELECT @@lower_case_table_names
2026-06-09 22:48:18,149 INFO sqlalchemy.engine.Engine [raw sql] {}
2026-06-09 22:48:18,156 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2026-06-09 22:48:18,163 INFO sqlalchemy.engine.Engine SELECT todo.id, todo.contents, todo.is_done 
FROM todo
2026-06-09 22:48:18,163 INFO sqlalchemy.engine.Engine [generated in 0.00046s] {}
<sqlalchemy.engine.result.ScalarResult object at 0x000001B9710DD020>
  • 이런식으로 그냥 scalars(select(ToDo))를 해주게 되면 쿼리가 날아가는 것은 보이지만 마지막 줄에 ScalarResult 라는 object로 들어가 있어 보기가 힘들다.
list(session.scalars(select(ToDo)))
=====================================================
2026-06-09 22:50:35,977 INFO sqlalchemy.engine.Engine SELECT todo.id, todo.contents, todo.is_done 
FROM todo
2026-06-09 22:50:35,977 INFO sqlalchemy.engine.Engine [cached since 137.8s ago] {}
[ToDo(id=1, contents=FastAPI Section 0, is_done=True), ToDo(id=2, contents=FastAPI Section 0, is_done=True), ToDo(id=3, contents=FastAPI Section 1, is_done=True), ToDo(id=4, contents=FastAPI Section 2, is_done=False)]
  • 지금처럼 list에 scalar 객체를 담아서 보내면 id 1~4번에 해당하는 데이터 결과들이 출력되는 것을 확인할 수 있다.
todos = list(session.scalars(select(ToDo)))
=====================================================
2026-06-09 22:53:33,363 INFO sqlalchemy.engine.Engine SELECT todo.id, todo.contents, todo.is_done 
FROM todo
2026-06-09 22:53:33,363 INFO sqlalchemy.engine.Engine [cached since 315.2s ago] {}

todos
=====================================================
[ToDo(id=1, contents=FastAPI Section 0, is_done=True), ToDo(id=2, contents=FastAPI Section 0, is_done=True), ToDo(id=3, contents=FastAPI Section 1, is_done=True), ToDo(id=4, contents=FastAPI Section 2, is_done=False)]

for todo in todos:
    print(todo)
    
======================================================
ToDo(id=1, contents=FastAPI Section 0, is_done=True)
ToDo(id=2, contents=FastAPI Section 0, is_done=True)
ToDo(id=3, contents=FastAPI Section 1, is_done=True)
ToDo(id=4, contents=FastAPI Section 2, is_done=False)
  • 이렇게 변수에 할당을 해서 todos를 쳐보면 똑같이 나오게 된다.
  • 그리고 반복문을 돌려서 todos를 실제로 출력을 하게 되면 이렇게 todo가 정상적으로 파이썬 안에서 사용이 가능한 것을 확인할 수 있다.

 

'FastAPI > 데이터베이스' 카테고리의 다른 글

ORM 적용 - HTTP Response 처리  (1) 2026.06.14
ORM 적용 - GET 전체 조회 API / Python - Generator  (0) 2026.06.14
데이터베이스 연결  (0) 2026.06.04
DDL - Create Table  (0) 2026.06.04
MySQL 컨테이너 실행  (0) 2026.05.19