DB/Sqlalchemy

[SQLAlchemy] (3) 모델 선언 방법 (선언형, 명령형)

yubi5050 2024. 9. 8. 17:10

개요

SQLAlchemy에서 모델과 DB를 연결하기 위해서는, 객체-관계 매핑 (ORM)을 수행하는데 

'사용 방법(문법)과 코드 스타일'에 있어 명령형, 선언형 두가지 방향성을 제시한다.

물론 뻔한말로 프로젝트의 요구사항과 팀 방향성에 따라 선택 될 수 있다.

 

Declarative Mapping은 더 간결하고, 유지보수가 쉬우며, 대부분의 일반적인 사용 사례에 적합한 반면,

Imperative Mapping은 더 복잡하고, 맞춤형 데이터베이스 매핑이 필요한 경우 등에 유리

두 방법을 혼합하여 사용할 수도 있으며, 상황에 따라 적절한 방식을 선택하는 것이 중요

 

Declarative Mapping 방식에서도 기존에 사용하던 방식 외에 2.0이 되면서 Mapped, mapped_column 이란 문법을 통해 좀더 orm을 잘 쓸 수 있게 업그레이드 되었는데, 특징도 살펴본다.

 

SQLAlchemy - Imperative Mapping (명령형) 방식

코드 스타일 : 클래스와, 테이블을 개별적으로 정의하여 명시적인 매핑

메타데이터 관리 : registry()의 metadata를 직접 관리 해주어야 한다.

매핑 설정 : map_imperatively와 같은 함수로 테이블과 클래스를 직접 매핑한다.

비교적 잘 사용 안되는 방식

from sqlalchemy import create_engine, Table, Column, Integer, String
from sqlalchemy.orm import registry, sessionmaker

# 메타데이터 객체 관리
mapper_registry = registry()

# DB 유저 테이블
user_table = Table(
    "user",
    mapper_registry.metadata,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
	Column("age", Integer),
)

# Python 유저 객체
class User:
	id:int
	name:str
	age:int

# 테이블과 클래스의 매핑 설정
mapper_registry.map_imperatively(User, user_table)

# 데이터베이스 설정
engine = create_engine('sqlite:///:memory:')
mapper_registry.metadata.create_all(engine)  # 테이블 생성
Session = sessionmaker(bind=engine)
session = Session()

# 데이터 삽입 예시
user = User(name="Bob", age=30)
session.add(user)
session.commit()

 

 

SQLAlchemy - Declarative Mapping (선언형) - 2.0 이전 

클래스와 테이블을 동시에 정의하고

클래스 선언시 테이블의 이름, 컬럼 타입, 제약 조건 등을 클래스 속성(__tablename__, Column(Integer))으로 정의

Base 라는 모든 ORM 클래스의 공통 베이스 클래스를 declarative_base()를 통해 생성

Base.metadata를 이용한 자동화된 메타데이터 관리로 편의성이 좋음 

별도의 매핑 없이 클래스 정의 자체가 매핑을 포함함 

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import declarative_base, sessionmaker

# 1. engine 생성
engine = create_engine('sqlite:///example.db')

# 2. Base 객체 생성
Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    age = Column(Integer)

# 3. (Optional) 해당 Engine의 DB에 Metadata를 바탕으로 테이블 생성
Base.metadata.create_all(engine)

# 4. 세션 팩토리 및 세션 객체 생성
Session = sessionmaker(bind=engine)
session = Session()

# 데이터 추가
new_user = User(name='Alice', age=30)
session.add(new_user)
session.commit()

 

SQLAlchemy - Declarative Mapping (선언형) - 2.0 이후

SQLAlcheym 2.0 이 되면서 PEP-484 (Type Hinting 에 관한 내용)에 따라 Mapped와 mapped_column() 이란 문법을 이용하는 방법이 추가 되었다

 

목표는 Python 객체 모델과 Database 모델 간에 실제 SQL 테이블을 설명할 수 있는 메타데이터(MetaData)를 동시에 정의함. 

즉 ex) Python int 객체는 해당 DB 테이블에서의 Integer 를 뜻하는 정보를 MetaData에 넣어놓는다.

 

조금 더 기능적으로 다양성을 위해 ORM Layer에서 간결하게 사용할 수 있도록 추가된 문법

Column 으로 선언하던 것이 -> Mapped와 mapped_column 으로 바뀜

from typing import Optional
from sqlalchemy import Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

# declarative base class
class Base(DeclarativeBase):
    pass

# an example mapping using the base
class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    fullname: Mapped[str] = mapped_column(String(30))
    nickname: Mapped[Optional[str]]