Python

[Python] SQLAlchemy와 SQLModel의 모든 것: 데이터베이스 작업의 핵심 도구

dud9902 2025. 1. 26. 15:52

이전글과 이어지는 내용이다. SQLAlchemy과 SQLModel에 대하여 알아보자.

 

SQLAlchemy란?

Python에서 가장 널리 사용되는 데이터베이스 ORM(Object Relational Mapper) 및 SQL 툴킷이다. SQLAlchemy는 Python 코드와 데이터베이스 간의 상호작용을 단순화하고, SQLAlchemy Core와 ORM(Object Relational Mapping)이라는 두 가지 주요 컴포넌트를 제공한다.

 

ORM이란?

객체 지향 프로그래밍 언어의 객체데이터베이스의 테이블과 매핑하여, SQL을 직접 작성하지 않고도 데이터베이스를 조작할 수 있게 해주는 도구나 기술이다.

 

1. SQLAlchemy의 주요 컴포넌트

  1. SQLAlchemy Core:
    • SQLAlchemy Core는 SQL 표현 언어로, SQL 쿼리를 Python 코드로 작성하고 실행할 수 있도록 도와준다.
    • SQLAlchemy Core는 SQLAlchemy ORM 없이도 독립적으로 사용할 수 있다.
  2. SQLAlchemy ORM:
    • SQLAlchemy ORM은 Python 클래스와 데이터베이스 테이블을 매핑하여 객체 지향 방식으로 데이터를 관리한다.
    • ORM을 사용하면 SQL을 직접 작성하지 않고도 데이터를 처리할 수 있다.

 

2. SQLAlchemy의 장점

  1. 강력한 데이터베이스 지원:
    • MySQL, PostgreSQL, SQLite, Oracle, Microsoft SQL Server 등 다양한 데이터베이스와 호환된다.
  2. 고성능 쿼리 지원:
    • 저수준 SQL 작업(Core)과 고수준 ORM 작업을 동시에 지원하여 다양한 요구를 충족한다.
  3. 관계 설정:
    • 테이블 간의 관계(1:1, 1:N, N:M 등)를 손쉽게 설정할 수 있다.
  4. 트랜잭션 관리:
    • 데이터 작업의 무결성을 유지할 수 있도록 트랜잭션 관리를 제공한다.
  5. 커스터마이징:
    • 사용자가 SQLAlchemy의 동작을 세밀하게 제어할 수 있다.

 

3. SQLAlchemy가 사용되는 경우

  • 대규모 프로젝트에서 복잡한 데이터베이스 작업이 필요한 경우
  • SQL 작성 및 실행이 중요한 애플리케이션
  • 성능과 유연성이 중요한 데이터베이스 중심 애플리케이션

 

4. SQLAlchemy의 예제 코드

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

Base = declarative_base()

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

# 데이터베이스 연결
engine = create_engine('sqlite:///example.db')
Base.metadata.create_all(engine)

# 세션 생성
Session = sessionmaker(bind=engine)
session = Session()

# 데이터 삽입
new_user = User(name="YooJaeseok", email="YooJaeseok@naver.com")
session.add(new_user)
session.commit()

 

 

SQLModel이란?

SQLAlchemy와 Pydantic의 기능을 결합하여 더 간결하고 Pythonic한 방식으로 데이터베이스 작업을 처리할 수 있는 ORM 라이브러리이다. SQLAlchemy의 강력한 기능을 그대로 활용하면서, Pydantic의 데이터 검증 및 JSON 직렬화 기능을 추가로 제공한다.

1. SQLModel의 주요 특징

  1. SQLAlchemy 기반:
    • SQLAlchemy ORM과 Core의 모든 기능을 그대로 지원한다.
  2. Pydantic 통합:
    • Python 타입 힌팅과 데이터 검증을 결합하여 안전하고 직관적인 데이터 처리를 제공한다.
    • JSON 직렬화/역직렬화 기능이 기본 제공된다.
  3. 간결한 문법:
    • SQLAlchemy보다 간단한 문법으로 테이블 정의와 데이터 작업을 처리할 수 있다.
  4. 자동 검증:
    • 모델 정의 시 타입 힌트를 기반으로 입력값을 자동으로 검증한다.

 

2. SQLModel의 장점

  1. 간결성과 가독성:
    • Pythonic한 문법 덕분에 코드가 간단하고 읽기 쉬워진다.
  2. 타입 힌팅 기반 모델:
    • Python의 타입 힌팅을 완벽히 지원하여, IDE의 자동 완성과 정적 분석 도구와 잘 통합된다.
  3. 데이터 검증:
    • Pydantic의 데이터 유효성 검사 기능이 내장되어, 잘못된 데이터 입력을 방지한다.
  4. SQLAlchemy 호환성:
    • SQLAlchemy의 모든 기능(Core, ORM)을 그대로 사용할 수 있다.

 

3. SQLModel이 사용되는 경우

  • 데이터 검증과 JSON 직렬화가 중요한 경우
  • 타입 힌팅을 활용해 안전한 데이터 모델을 정의하고 싶은 경우
  • 더 간단한 문법으로 SQLAlchemy의 기능을 사용하고 싶은 경우

 

4. SQLModel의 예제 코드

from sqlmodel import SQLModel, Field, create_engine, Session

class User(SQLModel, table=True):
    id: int = Field(default=None, primary_key=True)
    name: str
    email: str

# 데이터베이스 연결
engine = create_engine('sqlite:///example.db')
SQLModel.metadata.create_all(engine)

# 세션 생성
with Session(engine) as session:
    # 데이터 삽입
    new_user = User(name="YooJaeseok", email="YooJaeseok@naver.com")
    session.add(new_user)
    session.commit()

 

표로 정리하자면 다음과 같다.

항목 SQLAlchemy SQLModel
기능 SQLAlchemy Core(SQL 표현 언어)와 ORM 제공 SQLAlchemy ORM에 Pydantic 기능(검증, 직렬화)을 통합
사용성 SQL 쿼리 작성 및 ORM 사용 모두 가능하지만 설정이 더 복잡 더 간결하고 직관적인 문법 제공
타입 힌팅 지원 Python 타입 힌팅을 지원하지 않음 (추가적인 라이브러리로 지원 가능) 타입 힌팅을 완벽히 지원 (Pydantic 기반)
데이터 검증 데이터 검증 기능이 기본적으로 제공되지 않음 Pydantic의 데이터 검증 기능을 통해 모델에서 자동 검증 가능
직렬화/JSON 지원 ORM 객체를 JSON으로 직렬화하려면 별도의 코딩 필요 Pydantic의 JSON 직렬화 기능이 기본 제공
주요 사용 사례 고성능이 필요한 프로젝트, SQL 작성이 중요한 경우 타입 안전성과 데이터 검증이 중요한 프로젝트, 더 간결한 코드 작성이 필요한 경우
코드 복잡도 SQLModel보다 복잡 (컬럼 정의, 관계 설정 등에서 더 많은 설정 필요) SQLAlchemy의 기능을 확장하여 더 간단한 문법으로 동일한 작업을 수행 가능

 

SQLModel은 SQLAlchemy를 기반으로 Pydantic의 데이터 검증과 직렬화 기능을 결합하여 확장된 라이브러리다. 즉, SQLModel 안에 SQLAlchemy가 포함되어 있으며, SQLAlchemy의 모든 기능을 활용하면서도 더 간단하고 Pythonic한 사용성을 제공한다.

 

더 이해하기 쉽게 비유하자면, SQLAlchemy는 데이터베이스와 직접 소통하는 강력한 "엔진"이고, SQLModel은 이 엔진을 기반으로 누구나 쉽게 운전할 수 있는 "자동차"와 같다. SQLAlchemy 없이는 SQLModel이 동작할 수 없지만, SQLModel을 사용하면 SQLAlchemy의 복잡성을 숨기고 더 편리하게 데이터 작업을 처리할 수 있다.

 

 


 

내가 사용하고 있는 data_model.py에서 사용된 SQLAlchmey와 SQLModel은 이렇다.

from datetime import datetime, time
from typing import List, Optional
import phonenumbers
from pydantic import field_validator
from sqlmodel import Field, Relationship, SQLModel
from sqlalchemy import text
from pydantic import validator


class AdministrativeDivision(SQLModel, table=True):
    __tablename__ = "administrative_division"
    id: int | None = Field(default=None, primary_key=True)
    city_province: str = Field(max_length=50)
    city_county: str = Field(max_length=50)

class Member(SQLModel, table=True):
    __tablename__ = "member"
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(..., max_length=50, nullable=False)  # VARCHAR(50)
    email: str = Field(..., max_length=255)  # VARCHAR(255)
    access_token: str = Field(max_length=2083)
    refresh_token: str = Field(max_length=2083)
    oauth: str = Field(max_length=50)
    nickname: Optional[str] = Field(default=None, max_length=50)
    sex: Optional[str] = Field(default=None, max_length=10)
    picture_url: Optional[str] = Field(default=None, max_length=2083)
    birth: Optional[datetime] = None
    address: Optional[str] = Field(default=None, max_length=255)
    zip: Optional[str] = Field(default=None, max_length=10)
    phone_number: Optional[str] = Field(default=None, max_length=20)
    voice: Optional[str] = Field(default=None, max_length=255)
    role: Optional[str] = Field(default=None, max_length=10)
    created_at: datetime = Field(
        sa_column_kwargs={"server_default": text("CURRENT_TIMESTAMP"), "nullable": False}
    )
    updated_at: datetime = Field(
        sa_column_kwargs={"server_default": text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"), "nullable": False}
    )

    plans: List["Plan"] = Relationship(back_populates="member")


    # 전화번호 유효성 검사
    @field_validator("phone_number")
    def check_phone_number(cls, values):
        phone_number = values.get('phone_number')
        try:
            parsed_number = phonenumbers.is_valid_number(phone_number)
            if not parsed_number:
                raise ValueError(f"Invalid phone number: {phone_number}")
        except phonenumbers.phonenumberutil.NumberParseException as e:
            raise ValueError(f"Invalid phone number: {phone_number}") from e
        return values

 

1. 클래스 상속 및 테이블 정의

  • SQLModel을 상속받은 클래스는 데이터베이스 테이블로 매핑된다.
  • table=True를 통해 클래스가 데이터베이스 테이블로 사용된다는 것을 명시한다.
class Member(SQLModel, table=True):

 

2. 필드 정의

  • Field는 SQLAlchemy의 Column과 동일한 역할을 한다.
  • 데이터 타입과 제약 조건(예: primary_key, nullable, max_length)을 설정한다.
id: int | None = Field(default=None, primary_key=True)
name: str = Field(..., max_length=50, nullable=False)

 

3. 관계 설정

  • Relationship은 SQLAlchemy의 relationship 기능을 사용하여 테이블 간 관계를 정의한다.
  • Member 테이블과 Plan 테이블 간에 일대다 관계를 설정
plans: List["Plan"] = Relationship(back_populates="member")

 

4. SQLAlchemy Core의 text 사용

  • SQLAlchemy Core에서 제공하는 text()를 사용하여 SQL 문법을 명시적으로 정의
  • 예: CURRENT_TIMESTAMP를 기본값으로 설정
from sqlalchemy import text

created_at: datetime = Field(
        sa_column_kwargs={"server_default": text("CURRENT_TIMESTAMP"), "nullable": False}
    )

 

5. 데이터 타입 관리

  • SQLAlchemy는 Field 정의 시 데이터 타입을 SQL 타입으로 매핑한다.
  • 예: str → VARCHAR, datetime → DATETIME.