다대다 링크로 모델 만들기

이제 이런 링크 테이블을 사용해서 다대다 관계를 지원할 거예요:

다대다 테이블 관계

클래스 모델을 정의하는 것부터 시작할게요. 링크 테이블 모델도 포함해서요.

출처: 공식문서

링크 테이블 모델

다대다 관계를 지원하고 싶으니, 이제 그 둘을 연결하는 링크 테이블이 필요해요.

다른 SQLModel처럼 그냥 만들면 돼요:

from sqlmodel import Field, Relationship, Session, SQLModel, create_engine

class HeroTeamLink(SQLModel, table=True):
    team_id: int | None = Field(default=None, foreign_key="team.id", primary_key=True)
    hero_id: int | None = Field(default=None, foreign_key="hero.id", primary_key=True)

이건 다른 것과 똑같은 SQLModel 클래스 모델 테이블이에요.

team_idhero_id 두 필드가 있어요.

둘 다 각자 테이블을 가리키는 외래 키예요. 그 모델들은 잠시 후 만들 거지만, 어떻게 동작하는지는 이미 알죠.

그리고 두 필드 모두 기본 키예요. 이건 이전에 써본 적 없던 거예요. 🤓

Team 모델

Team 모델을 볼게요. 이전 버전과 거의 같지만, 약간의 변화가 있어요:

class Team(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    headquarters: str

    heroes: list["Hero"] = Relationship(back_populates="teams", link_model=HeroTeamLink)

관계 속성 heroes 는 여전히 히어로의 리스트로, list["Hero"]로 어노테이션되어 있어요. 다시, 코드의 이 시점에는 아직 그 클래스를 선언하지 않았으니 "Hero"를 따옴표로 감싸 써요(하지만 아시다시피 에디터와 SQLModel은 그걸 이해해요).

똑같은 Relationship() 함수를 써요.

back_populates="teams" 를 사용해요. 전에는 team 속성을 참조했지만, 이제 여러 개를 가질 수 있으니 Hero 모델을 만들 때 이름을 teams로 바꿀 거예요.

그리고 다대다 관계를 허용하는 중요한 부분이에요. link_model=HeroTeamLink 를 써요. 그게 전부예요. ✨

Hero 모델

반대쪽을 볼게요. 여기 Hero 모델이 있어요:

class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)

    teams: list[Team] = Relationship(back_populates="heroes", link_model=HeroTeamLink)

이제 관계가 링크 테이블로 이뤄지니, 이전의 team_id 필드(열)를 제거했어요. 🔥

관계 속성 이름도 이제 여러 팀을 지원하니 team 대신 teams 가 됐어요.

타입도 더는 Team | None이 아니라 팀의 리스트예요. 이제 list[Team] 로 선언하죠.

여기서도 Relationship() 을 써요.

전처럼 back_populates="heroes" 를 여전히 갖고 있어요.

그리고 이제 link_model=HeroTeamLink 가 있어요. ✨

테이블 만들기

이전과 마찬가지로, engine 을 만들고 모든 테이블을 만드는 함수 create_db_and_tables()를 위한 나머지 코드가 있을 거예요:

sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

그리고 이전 예제들처럼 그 함수를 main() 함수에 추가하고, main 블록에서 그 main() 함수를 호출할게요:

def main():
    create_db_and_tables()
    create_heroes()

if __name__ == "__main__":
    main()

코드 실행하기

커맨드 라인에서 코드를 실행하면 이렇게 출력돼요:

$ uv run python app.py

// 부수적인 코드는 생략했어요 😉

INFO Engine
CREATE TABLE team (
        id INTEGER,
        name VARCHAR NOT NULL,
        headquarters VARCHAR NOT NULL,
        PRIMARY KEY (id)
)


INFO Engine [no key 0.00033s] ()
INFO Engine
CREATE TABLE hero (
        id INTEGER,
        name VARCHAR NOT NULL,
        secret_name VARCHAR NOT NULL,
        age INTEGER,
        PRIMARY KEY (id)
)


INFO Engine [no key 0.00016s] ()
INFO Engine

// 우리의 반짝이는 새 링크 테이블 ✨
CREATE TABLE heroteamlink (
        team_id INTEGER,
        hero_id INTEGER,
        PRIMARY KEY (team_id, hero_id),
        FOREIGN KEY(team_id) REFERENCES team (id),
        FOREIGN KEY(hero_id) REFERENCES hero (id)
)


INFO Engine [no key 0.00031s] ()
INFO Engine COMMIT

요약

링크 테이블을 선언해서 테이블 사이의 다대다 관계를 지원할 수 있어요.

다른 SQLModel 클래스와 같은 방식으로 만들고, Relationship()link_model 매개변수에 쓰면 돼요.

이제 다음 챕터들에서 이 모델들로 데이터를 다뤄 볼게요. 🤓

더 알아보기 (Learn more)