home

Sqlalchemy relation 파라미터들

back_populates

back_populates는 두 relationship()이 서로를 가리키고 있음을 SQLAlchemy에 알려줍니다. 이를 통해 자동 동기화가 가능해집니다.

class User(Base):
    addresses = relationship("Address", back_populates="user")

class Address(Base):
    user_id = Column(ForeignKey("user.id"))
    user = relationship("User", back_populates="addresses")

이렇게 하면 user.address 로 접근이 가능하고, address.user 로도 서로 접근이 가능합니다.

중요한 것은 양쪽에 명시적으로 선언해야하고, 테이블 이름이 아니라 반대쪽 relationship 속성과 이름이 같아야 합니다.

primaryjoin와 foreign_keys

Base 모델에 FK관련 코드가 없거나, 실제로 FK가 걸려있지 않으면 이 두 개는 반드시 지정해야합니다.

class Order(Base):
    __tablename__ = 'orders'
    id = Column(Integer, primary_key=True)
    user_email = Column(String)  # ❌ ForeignKey 없음!

    # ❌ 이렇게 하면 에러: NoForeignKeysError
    # customer = relationship("User", back_populates="orders")

    customer = relationship("User", 
        foreign_keys="Order.user_email",
        primaryjoin="User.email == Order.user_email",
        back_populates="orders")

또한 동일 테이블에 다중 FK가 있을 때도 명시적으로 적어줘야 해요.

lazy, uselist, overlaps

lazy는 로딩 전략인데, 어떻게 연관된 데이터를 가져오냐를 명시해줍니다. 근데 중요한 것은 이걸 설정해줘도 selectinload()joinedload() 가 더 우선됩니다.

동작 언제 쓰나
'select' 접근 시 별도 SELECT (기본값) 대부분의 경우
'joined' JOIN으로 한 번에 로드 1:1, 자식 적을 때
'selectin' IN 절로 한 번에 로드 1:N 컬렉션
'dynamic' 쿼리 객체 반환 (.all() 해야 로드) 관계에서 필터링 필요
'raise' 접근 시 에러 발생 (디버깅용) N+1 탐지
'noload' 로드 안함 (성능 최적화) 확실히 안 쓸 때

따라서 lazy를 따로 설정해두지 않으면 기본이 select라 1:N 관계에서 for문을 돌리면 N+1 문제가 발생합니다. 조심

uselist는 True로 하면 list[Child]를 반환하고, 아니면 단일 객체를 바노한합니다. 즉, 1:N이나 N:1 관계에서는 True, 1:1 때는 False를 주면 됩니다.

overlaps는 중복 로딩 방지입니다. 기본 값은 False이고, 중첩된 eager 로딩에서만 의미가 있는 파라미터에요.

예를 들어서 이런 상황이에요.

# ❌ 중복 로딩 시도 → 성능 저하 또는 경고
stmt = select(User).options(
    joinedload(User.posts).joinedload(Post.comments),     # 1. JOIN으로 posts
    selectinload(User.posts).selectinload(Post.comments)  # 2. IN으로 posts (중복!)
)

여기에서 overlaps=True를 주먼, 첫 번째 로딩 결과를 재사용해서 이를 반환합니다. 아주 굿