SQLAlchemy ORM 활용을 위한 Python 데이터베이스 인터랙션 가이드

환경 준비 및 필수 라이브러리 설치

Python 생태계에서 관계형 데이터베이스와의 상호작용을 원활하게 하기 위해 SQLAlchemy 라이브러리가 표준적으로 사용됩니다. 이를 사용하려면 우선 파이썬 패키지 매니저를 통해 주 모듈을 설치해야 합니다.

pip install sqlalchemy

연결하려는 데이터베이스 엔진에 따라 추가적인 드라이버가 필요할 수 있습니다. 예를 들어 PostgreSQL 의 경우 psycopg2-binary, MySQL 의 경우 pymysql 또는 mysql-connector-python 패키지를 별도로 가져와야 합니다.

핵심 아키텍처 이해

ORM 패턴을 구현하는 SQLAlchemy 는 몇 가지 주요 컴포넌트로 구성됩니다.

  • Connection Engine: 데이터베이스 서버와의 물리적 연결을 담당하며 SQL 명령을 전달합니다.
  • Session Object: 영속성을 관리하고 변경 사항을 추적하여 커밋하거나 롤백합니다.
  • Base Model: 데이터베이스 테이블 구조를 클래스로 나타내는 추상 베이스입니다.
  • Statement Builder: 복잡한 쿼리 논리를 객체 지향적으로 생성합니다.

데이터 소스 연결 설정

엔진은 URI 형식을 사용하여 연결 정보를 정의합니다. SQLite 는 파일 기반이므로 경로를 지정하고, 타겟 DB 는 호스트, 포트, 인증 정보를 포함해야 합니다.

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

# 엔진 인스턴스 생성 (SQLite 예시, Echo 옵션으로 SQL 로그 출력)
db_connect = create_engine('sqlite:///data_storage.db', echo=False)

# PostgreSQL 예시 주석
# db_connect = create_engine('postgresql://user:pass@host/dbname')

# 세션 팩토리 클래스 정의
LocalSessionFactory = sessionmaker(bind=db_connect, autocommit=False)

# 활성 세션 생성 함수
def get_active_session():
    return LocalSessionFactory()

데이터 모델 및 스키마 설계

테이블 구조를 Python 클래스로 매핑합니다. 여기서 Primary Key 와 ForeignKey 를 명시하여 관계성을 정의합니다.

from sqlalchemy import Column, Integer, String, ForeignKey, Table
from sqlalchemy.orm import relationship, DeclarativeBase, Mapped

class ModelBase(DeclarativeBase):
    pass

# 사용자 계정 테이블
class Member(ModelBase):
    __tablename__ = 'members'

    uid: Mapped[int] = Column(Integer, primary_key=True, autoincrement=True)
    nickname: Mapped[str] = Column(String(50), nullable=False)
    contact_email: Mapped[str] = Column(String(100), unique=True)

    # 작성한 글 목록 (일대다 관계)
    authored_entries = relationship("Article", back_populates="writer")

# 게시판 항목 테이블
class Article(ModelBase):
    __tablename__ = 'articles'

    aid: Mapped[int] = Column(Integer, primary_key=True)
    headline: Mapped[str] = Column(String(150))
    body_text: Mapped[str] = Column(String(2000))
    creator_id: Mapped[int] = Column(ForeignKey('members.uid'))

    writer = relationship("Member", back_populates="authored_entries")

# 태그 매핑 테이블 (다대다 관계를 위한 중간 테이블)
association_table = Table(
    'article_tags',
    ModelBase.metadata,
    Column('article_ref', Integer, ForeignKey('articles.aid'), primary_key=True),
    Column('tag_ref', Integer, ForeignKey('tags.id'), primary_key=True)
)

class Tag(ModelBase):
    __tablename__ = 'tags'

    id: Mapped[int] = Column(Integer, primary_key=True)
    label_name: Mapped[str] = Column(String(30), unique=True)

    linked_articles = relationship(
        "Article", 
        secondary=association_table,
        backref="applied_tags"
    )

스키마 적용 및 초기화

정의된 클래스를 바탕으로 실제 데이터베이스 내에 테이블을 생성합니다.

# 모든 모델을 기반으로 테이블 생성
ModelBase.metadata.create_all(bind=db_connect)

데이터 생명주기 관리 (CRUD)

세션을 통해 데이터를 추가, 조회, 수정, 삭제하는 작업을 수행합니다. 트랜잭션은 각 작업 단위로 분리하여 안정성을 보장합니다.

기록 생성

current_db = get_active_session()

new_member = Member(nickname="developer_kim", contact_email="kim@example.com")
current_db.add(new_member)

# 여러 건의 데이터 한 번에 로드
bulk_items = [
    Member(nickname="dev_lee", contact_email="lee@test.com"),
    Member(nickname="dev_park", contact_email="park@test.com")
]
current_db.add_all(bulk_items)
current_db.commit()

정보 조회

# 전체 목록
all_members = current_db.query(Member).all()

# 특정 조건 만족 첫 번째 레코드
target = current_db.query(Member).filter_by(nickname="developer_kim").first()

# 기본 키로 직접 접근
specific_user = current_db.get(Member, target.uid)

내역 갱신

# 개체 수정 후 저장
existing_user = current_db.get(Member, 1)
if existing_user:
    existing_user.nickname = "updated_kim"
    current_db.commit()

# 배치 업데이트 (SQL 레벨)
current_db.query(Member).filter(Member.nickname.like("dev_%")).update(
    {Member.nickname: "team_member"}, 
    synchronize_session='fetch'
)
current_db.commit()

항목 제거

# 단일 삭제
to_delete = current_db.get(Member, 1)
current_db.delete(to_delete)
current_db.commit()

# 조건부 배치 삭제
current_db.query(Member).filter(Member.contact_email.endswith("@temp.com")).delete(synchronize_session=False)
current_db.commit()

고급 쿼리 연산

데이터 필터링, 정렬, 집계 기능을 활용하여 복잡한 분석을 수행합니다.

from sqlalchemy import func, or_, desc

# 필터링 및 정렬
sorted_list = current_db.query(Member).filter(
    or_(Member.nickname == "A", Member.nickname == "B")
).order_by(desc(Member.uid)).limit(5).all()

# 집계 함수 활용 (총 인원 계산)
total_count = current_db.query(func.count(Member.uid)).scalar()

# 조인을 통한 복합 조회
joined_data = current_db.query(Member, Article).join(Article).filter(
    Article.headline.contains("Tutorial")
).all()

관계성 탐색 및 조작

정의된 외래 키와 역참조 속성을 통해 객체 그래프를 탐색하고 연결 데이터를 관리합니다.

# 새로운 게시글 등록 시 작성자 자동 연결
author_obj = current_db.get(Member, 1)
new_post = Article(headline="New Tech", body="Details...", creator_id=author_obj.uid)
# 또는 관계 속성 활용
author_obj.authored_entries.append(new_post)
current_db.add(author_obj)
current_db.commit()

# 다대다 관계 처리: 글에 태그 부여
python_tag = Tag(label_name="Python")
java_tag = Tag(label_name="Java")
new_post.applied_tags.extend([python_tag, java_tag])
current_db.commit()

# 조회 시 관계 확인
print(f"{new_post.headline} 관련 태그:")
for t in new_post.applied_tags:
    print(f"- {t.label_name}")

트랜잭션 제어 전략

실패 시 이전 상태로 복구되도록 try-except 블록이나 컨텍스트 매니저를 사용하여 상태 무결성을 보호합니다.

def safe_registration(session, name, email):
    try:
        candidate = Member(nickname=name, contact_email=email)
        session.add(candidate)
        session.commit()
        return candidate.uid
    except Exception as err:
        session.rollback()
        raise RuntimeError(f"회원가입 실패: {err}")

# 컨텍스트 매니저를 통한 자동 클로징 패턴
from contextlib import contextmanager

@contextmanager
def managed_transaction():
    sess = get_active_session()
    try:
        yield sess
        sess.commit()
    except Exception:
        sess.rollback()
        raise
    finally:
        sess.close()

# 활용 예시
with managed_transaction() as db:
    db.add(Member(nickname="safe_user", contact_email="safe@mail.com"))

태그: python sqlalchemy ORM database-management CRUD

8월 14일 15:28에 게시됨