[SQLALchemy] 1:1, 1:N, N:M 관계에서 테이블 정의와 relationship의 활용 방법
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" |
'SQLAlchemy' 카테고리의 다른 글
sqlalchemy의 declarative_base()를 fastapi에서 사용하는 이유 (0) | 2025.02.08 |
---|---|
[SQLALchemy] 트랜잭션(Transaction) 그리고 Database Lock (0) | 2025.02.04 |
[SQLALchemy] 비동기 함수, 비동기 Database 그리고 Pool의 정리 (0) | 2025.02.01 |
[SQLALchemy] 세션(Session), 비동기 세션(Async Session) 그리고 scoped_session의 정리 (0) | 2025.02.01 |
[SQLALchemy] Alembic을 이용한 마이그레이션 관리 방법 (0) | 2025.01.31 |
댓글