관계 and ORM 매퍼 구성

관계 and ORM 매퍼 구성 (ORM Relationships)

SQLAlchemy ORM의 강점 중 하나는 테이블 간 관계를 파이썬 객체 그래프로 다루는 것이에요. relationship()을 사용하면 외래 키로 연결된 두 매핑 클래스 사이의 관계를 정의하고, 객체 속성 접근만으로 연관 데이터를 읽고 쓸 수 있어요.

출처: SQLAlchemy — Relationships

관계 정의하기

두 테이블이 외래 키로 연결돼 있을 때, 한 매핑 클래스에 relationship()을 선언해서 관계를 드러내요. 예를 들어 UserAddress가 있고, Addressuser_id 외래 키로 User를 가리킨다면 이렇게 표현해요.

from sqlalchemy.orm import relationship

class User(Base):
    __tablename__ = "user_account"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(30))
    addresses: Mapped[list["Address"]] = relationship(back_populates="user")

class Address(Base):
    __tablename__ = "address"

    id: Mapped[int] = mapped_column(primary_key=True)
    user_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
    user: Mapped["User"] = relationship(back_populates="addresses")

back_populates는 양방향 관계의 양쪽을 서로 연결해 줘요. User.addresses로 해당 사용자의 주소 목록에 접근하고, Address.user로 주소가 속한 사용자에 접근할 수 있어요.

관계 유형

  • 일대다(one-to-many) — 한 쪽이 컬렉션(list), 다른 쪽이 단일 참조.
  • 다대일(many-to-one) — 기본값. 외래 키가 있는 쪽에서 단일 객체를 가리킴.
  • 다대다(many-to-many)secondary로 조인 테이블을 지정해 양쪽이 컬렉션을 가짐.
authors: Mapped[list["Book"]] = relationship(secondary=book_author_table)

지연 로딩과 로딩 전략

기본적으로 관계는 접근하는 시점에 데이터를 읽는 **지연 로딩(lazy loading)**을 사용해요. 필요한 연관 데이터를 함께 가져오고 싶다면 selectinload() 같은 로딩 옵션으로 쿼리 시점에 미리 로드할 수 있어요.

from sqlalchemy.orm import selectinload

stmt = select(User).options(selectinload(User.addresses))

이렇게 하면 N+1 문제를 피하면서 연관 데이터를 효율적으로 가져올 수 있어요. 관계 설계는 쿼리 성능과 밀접하니, 접근 패턴에 맞는 로딩 전략을 고르는 것이 중요해요.

더 알아보기