관계 속성 정의하기
관계 속성 정의하기 (Define Relationship Attributes)
드디어 SQLModel에서 가장 흥미로운 부분 중 하나에 도달했어요. 바로 관계 속성(Relationship Attributes) 이야기예요. ✨ 지금까지는 team_id 같은 외래 키 컬럼으로 테이블을 직접 연결해서 다뤘다면, 이번 장부터는 모델 안에서 관계를 더 편리하게 다루는 방법을 배워볼게요.
출처: 공식문서
지금까지의 상황
우리가 다루는 team 테이블은 이렇게 생겼어요:
| id | name | headquarters |
|---|---|---|
| 1 | Preventers | Sharp Tower |
| 2 | Z-Force | Sister Margaret's Bar |
그리고 hero 테이블은 이렇게 생겼죠:
| id | name | secret_name | age | team_id |
|---|---|---|---|---|
| 1 | Deadpond | Dive Wilson | null | 2 |
| 2 | Rusty-Man | Tommy Sharp | 48 | 1 |
| 3 | Spider-Boy | Pedro Parqueador | null | 1 |
테이블이 밑에서 어떻게 동작하는지, 모델 클래스가 그 테이블을 어떻게 나타내는지는 이미 배웠어요. 이제 코드에서 많은 작업을 훨씬 간단하게 만들어 줄 작은 편의 기능을 하나 더해볼 차례예요.
관계 속성 선언하기
지금까지는 쿼리에서 select()로 테이블을 연결할 때 team_id 컬럼만 사용했었죠:
from sqlmodel import Field, Session, SQLModel, create_engine
class Team(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
headquarters: str
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)
team_id: int | None = Field(default=None, foreign_key="team.id")
# Code below omitted 👇
team_id는 다른 필드들과 똑같은 평범한 필드예요. 모두 테이블의 컬럼 하나씩을 나타내죠.
이제 이 모델 클래스들에 몇 가지 새로운 특별한 속성을 추가해 볼게요. 바로 관계(Relationship) 속성입니다.
먼저 sqlmodel에서 Relationship을 가져와요:
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine
# Code below omitted 👇
그다음 이 Relationship으로 모델 클래스 안에 새 속성을 선언합니다:
from sqlmodel import Field, Relationship, Session, SQLModel, create_engine
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="team")
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)
team_id: int | None = Field(default=None, foreign_key="team.id")
team: Team | None = Relationship(back_populates="heroes")
# Code below omitted 👇
이 관계 속성들이 뭘까요?
이 새 속성들은 필드와는 달라요. 데이터베이스의 컬럼 하나를 직접 나타내지 않고, 그 값도 정수 같은 단일 값이 아니에요. 이 속성의 값은 실제로 연결된 전체 객체 그 자체입니다.
즉, Hero 인스턴스에서 hero.team을 호출하면, 이 히어로가 속한 Team 인스턴스 객체 전체를 돌려받아요. ✨
예를 들어 어떤 히어로가 팀에 속해 있는지(.team이 None이 아닌지) 확인하고, 그 팀 이름을 출력할 수도 있겠죠:
if hero.team:
print(hero.team.name)
관계 속성 또는 None
Hero 클래스에서 team의 타입 어노테이션이 Team | None이라는 점을 눈여겨보세요.
이 말은 이 속성이 None일 수도, 완전한 Team 객체일 수도 있다는 뜻이에요.
관련된 team_id 역시 None(데이터베이스의 NULL)일 수 있기 때문이죠.
만약 Hero가 무조건 Team에 속해야 했다면, team_id는 int | None이 아니라 int가 되고, 그 Field는 Field(default=None, foreign_key="team.id")가 아니라 Field(foreign_key="team.id")가 되며, team 속성도 Team | None이 아니라 Team이 되었을 거예요.
리스트를 가진 관계 속성
반대로 Team 클래스에서 heroes 속성은 Hero 객체들의 리스트로 어노테이션되어 있어요. 실제로 그렇게 담기기 때문이죠.
SQLModel(정확히는 SQLAlchemy)은 team_id라는 외래 키가 hero 테이블에서 team 테이블을 가리키는 걸 보고, 관계가 그 team_id로 맺어진다는 걸 알아서 처리해요. 그래서 여기서 그것을 명시적으로 지정할 필요가 없답니다.
💡 팁
list["Hero"]와back_populates에 대해서는 다음 몇 개 장에서 다시 확인하게 될 거예요.지금은 일단 이 관계 속성들을 어떻게 사용하는지부터 살펴볼게요.
다음 단계
다음 장들에서 이 새로운 관계 속성들을 실제로 활용하는 예시들을 하나씩 살펴볼게요. ✨