SQLAlchemy

[SQLALchemy] 1:1, 1:N, N:M 관계에서 테이블 정의와 relationship의 활용 방법

bluebamus 2025. 2. 1.

1. SQLAlchemy의 relationship 개념 및 사용법

   - SQLAlchemy에서 relationship은 테이블 간의 관계를 정의하는 기능으로, ORM에서 객체 간의 연결을 쉽게 관리할 수 있도록 돕는다.

 

   1) Foreign Key (외래 키)

      - ForeignKey("target_table.column") :

         - 다른 테이블의 column을 참조하는 속성을 정의한다.

         - 관계의 방향을 결정하며, 이를 기반으로 relationship을 설정할 수 있다.

   2) relationship()

      - 테이블 간 관계를 객체 수준에서 다룰 수 있도록 설정 한다.

      - 기본적으로 lazy-loading 방식으로 작동하며, back_populates를 통해 양방향 관계를 정의할 수 있다.

      - 주요 옵션 :
         - back_populates : 서로 연결된 두 개의 모델에서 관계를 명확히 정의할 때 사용한다.
         - backref : 관계를 자동으로 역방향으로 생성한다. (명시적으로 back_populates 권장)
         - uselist : 리스트 형식(True, 기본값) 또는 단일 객체(False)로 관계를 정의한다.
         - cascade : 연관된 객체에 대해 삭제, 병합 등의 작업을 설정한다. (all, delete, delete-orphan 등)
         - lazy : 데이터 로딩 방식 지정한다. (joined, subquery, select, dynamic 등)

         - secondary: N:M 관계에서 중간 테이블을 명시한다.

 

   3) Cascade (일관성 유지 옵션)

      - 부모 테이블의 변경이 자식 테이블에 미치는 영향을 결정한다.
      - 주요 옵션 :
         - "save-update" (기본값): 부모 객체 변경 시, 자식 객체 자동 반영
         - "delete": 부모 객체 삭제 시, 연결된 자식 객체 삭제
         - "delete-orphan": 부모 없이 남은 객체 자동 삭제
         - "all": 위 모든 옵션 포함

 

2. 1:1 (One-to-One) 관계

   1) 설정 방법

      - PK를 공유할 수도 있고, 별도 FK + Unique 제약을 걸 수도 있음
      - relationship()에서 uselist=False 설정하여 단일 객체만 참조하도록 강제
      - 외래 키(FK)에 unique=True를 추가해야 완벽한 1:1 관계가 보장됨

 

      1. 예제

from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
from sqlalchemy.orm import relationship
from database import Base

class User(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True)
    profile = relationship("UserProfile", back_populates="user", uselist=False, cascade="all, delete")

class UserProfile(Base):
    __tablename__ = "user_profiles"
    
    id = Column(Integer, ForeignKey("users.id"), primary_key=True)  # PK = FK (완벽한 1:1 관계)
    bio = Column(String)
    user = relationship("User", back_populates="profile")

    __table_args__ = (UniqueConstraint('id'),)  # Unique 보장

 

      2. uselist=False란?

         - relationship()에서 uselist=False를 설정하면 단일 객체로만 관계를 유지한다.
         - 리스트가 아닌 단일 객체로 반환되므로 profile[0] 같은 접근 방식이 필요 없다.

 

      3. unique=True 필요 여부

         - FK에 unique=True를 추가하면 완벽한 1:1 관계 보장한다.
         - 만약 unique=True가 없다면 실제로 1:N 관계가 허용될 위험이 있다.
         - PK = FK 방식이면 unique=True가 없어도 무관하다.

 

3. 1:N (One-to-Many) 관계

   1) 설정 방법

      - 부모 테이블이 Primary Key, 자식 테이블이 Foreign Key를 가진다.
      - relationship에서 back_populates를 설정하여 양방향 연결을 한다.
      - Cascade 옵션을 통해 삭제/수정 동작을 설정한다.

 

   2) 예제

class User(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True)
    posts = relationship("Post", back_populates="author", cascade="all, delete-orphan")  # 부모 삭제 시, 자식도 삭제됨

class Post(Base):
    __tablename__ = "posts"
    
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String)
    content = Column(String)
    user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"))  # 부모 삭제 시, 자식 삭제
    author = relationship("User", back_populates="posts")

 

   3) Cascade 설정

      - "all, delete-orphan": 부모 삭제 시, 자식도 자동 삭제된다.
      - ondelete="CASCADE": DB 레벨에서 FK 제약 조건 적용한다.

 

4. N:M (Many-to-Many) 관계

   1) 설정 방법

      - 중간 테이블(Association Table)을 사용한다.
      - relationship()에서 secondary 옵션을 사용한다.

 

   2) 예제

from sqlalchemy import Table, ForeignKey

user_groups = Table(
    "user_groups",
    Base.metadata,
    Column("user_id", Integer, ForeignKey("users.id", ondelete="CASCADE"), primary_key=True),
    Column("group_id", Integer, ForeignKey("groups.id", ondelete="CASCADE"), primary_key=True),
)

class User(Base):
    __tablename__ = "users"
    
    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True)
    groups = relationship("Group", secondary=user_groups, back_populates="users")

class Group(Base):
    __tablename__ = "groups"
    
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, unique=True, index=True)
    users = relationship("User", secondary=user_groups, back_populates="groups")

 

   3) Cascade 주의점

      - N:M 관계는 cascade="all, delete-orphan"을 사용할 수 없다.
      - ondelete="CASCADE"만 사용하여 직접 FK 삭제 연쇄를 처리한다.

 

5. 정리 (관계별 설정 요약)

관계 유형 Foreign Key uselist 설정 unique=True 필요 여부 Cascade 설정
1:01 PK or FK (Unique 필요) uselist=False 필요 "all, delete"
1:N FK 사용 기본값 (True) 불필요 "all, delete-orphan"
N:M 중간 테이블 사용 secondary 사용 불필요 ondelete="CASCADE"

 

댓글