파이썬에서 가장 널리 사용되는 ORM 프레임워크 중 하나인 SQLAlchemy는 데이터베이스 작업을 효율적이고 유연하게 수행할 수 있도록 지원합니다. 본 문서에서는 SQLAlchemy ORM을 활용한 데이터베이스 운영 방법을 다룹니다.
목차
- SQLAlchemy 설치
- 핵심 개념
- 데이터베이스 연결
- 모델 정의
- 테이블 생성
- 기본 CRUD 연산
- 데이터 조회
- 관계 처리
- 트랜잭션 관리
- 최선의 실천 방법
설치 방법
bash
pip install sqlalchemy
특정 데이터베이스에 연결하려면 해당 드라이버도 설치해야 합니다:
bash
# PostgreSQL
pip install psycopg2-binary
# MySQL
pip install mysql-connector-python
# SQLite (파이썬 표준 라이브러리에 포함되어 있어 별도 설치 필요 없음)
핵심 요소
- 엔진 : 데이터베이스와의 통신을 담당하는 커넥션 생성기
- 세션 : 지속성 작업을 관리하는 컨텍스트
- 모델 : 데이터베이스 테이블과 매핑되는 클래스
- 쿼리 : 데이터베이스 쿼리를 구성하고 실행하는 객체
데이터베이스 연결 설정
python
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# SQLite 연결 예제
engine = create_engine('sqlite:///sample.db', echo=True)
# PostgreSQL 연결 예제
# engine = create_engine('postgresql://user:pass@localhost:5432/dbname')
# MySQL 연결 예제
# engine = create_engine('mysql+mysqlconnector://user:pass@localhost:3306/dbname')
# 세션 생성자 생성
SessionClass = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 세션 인스턴스 생성
db_session = SessionClass()
모델 정의 예시
python
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, declarative_base
# 기본 클래스 생성
Base = declarative_base()
class Person(Base):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True, index=True)
full_name = Column(String(50), nullable=False)
email_address = Column(String(100), unique=True, index=True)
# 1:N 관계 정의
articles = relationship("Article", back_populates="writer")
class Article(Base):
__tablename__ = 'articles'
id = Column(Integer, primary_key=True, index=True)
title_name = Column(String(100), nullable=False)
content_text = Column(String(500))
writer_id = Column(Integer, ForeignKey('persons.id'))
# N:1 관계 정의
writer = relationship("Person", back_populates="articles")
# N:N 관계 정의(중간 테이블 사용)
tags = relationship("Tag", secondary="article_tags", back_populates="articles")
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True, index=True)
tag_name = Column(String(30), unique=True, nullable=False)
articles = relationship("Article", secondary="article_tags", back_populates="tags")
# 중간 테이블 정의(다대다 관계용)
class ArticleTag(Base):
__tablename__ = 'article_tags'
article_id = Column(Integer, ForeignKey('articles.id'), primary_key=True)
tag_id = Column(Integer, ForeignKey('tags.id'), primary_key=True)
테이블 생성 및 삭제
python
# 모든 테이블 생성
Base.metadata.create_all(bind=engine)
# 모든 테이블 삭제
# Base.metadata.drop_all(bind=engine)
기본 CRUD 연산
데이터 삽입
python
# 새 사용자 추가
new_person = Person(full_name="김철수", email_address="kim@example.com")
db_session.add(new_person)
db_session.commit()
# 대량 삽입
db_session.add_all([
Person(full_name="박영희", email_address="park@example.com"),
Person(full_name="최민수", email_address="choi@example.com")
])
db_session.commit()
데이터 조회
python
# 모든 사용자 조회
all_people = db_session.query(Person).all()
# 첫 번째 항목 조회
first_person = db_session.query(Person).first()
# ID로 조회
person = db_session.query(Person).get(1)
데이터 수정
python
# 조회 후 수정
person = db_session.query(Person).get(1)
person.full_name = "김철수삼"
db_session.commit()
# 대량 수정
db_session.query(Person).filter(Person.full_name.like("김%")).update({"full_name": "김씨"}, synchronize_session=False)
db_session.commit()
데이터 삭제
python
# 조회 후 삭제
person = db_session.query(Person).get(1)
db_session.delete(person)
db_session.commit()
# 대량 삭제
db_session.query(Person).filter(Person.full_name == "박영희").delete(synchronize_session=False)
db_session.commit()
데이터 쿼리 기법
기본 쿼리
python
# 전체 조회
people = db_session.query(Person).all()
# 특정 필드 조회
full_names = db_session.query(Person.full_name).all()
# 정렬
people = db_session.query(Person).order_by(Person.full_name.desc()).all()
# 결과 제한
people = db_session.query(Person).limit(10).all()
# 오프셋 설정
people = db_session.query(Person).offset(5).limit(10).all()
필터링 쿼리
python
from sqlalchemy import or_
# 정확한 값 검색
person = db_session.query(Person).filter(Person.full_name == "김철수").first()
# 패턴 검색
people = db_session.query(Person).filter(Person.full_name.like("김%")).all()
# IN 조건
people = db_session.query(Person).filter(Person.full_name.in_(["김철수", "박영희"])).all()
# 다중 조건
people = db_session.query(Person).filter(
Person.full_name == "김철수",
Person.email_address.like("%example.com")
).all()
# OR 조건
people = db_session.query(Person).filter(
or_(Person.full_name == "김철수", Person.full_name == "박영희")
).all()
# 부등식 조건
people = db_session.query(Person).filter(Person.full_name != "김철수").all()
집계 쿼리
python
from sqlalchemy import func
# 카운트
total = db_session.query(Person).count()
# 그룹별 카운트
user_article_count = db_session.query(
Person.full_name,
func.count(Article.id)
).join(Article).group_by(Person.full_name).all()
# 합계, 평균 계산
avg_id = db_session.query(func.avg(Person.id)).scalar()
조인 쿼리
python
# 내부 조인
results = db_session.query(Person, Article).join(Article).filter(Article.title.like("%파이썬%")).all()
# 외부 조인
results = db_session.query(Person, Article).outerjoin(Article).all()
# 조인 조건 명시
results = db_session.query(Person, Article).join(Article, Person.id == Article.writer_id).all()
관계 처리 예시
python
# 관계를 가지는 객체 생성
person = Person(full_name="이미자", email_address="lee@example.com")
article = Article(title_name="처음 작성한 글", content_text="안녕하세요", writer=person)
db_session.add(article)
db_session.commit()
# 관계를 통한 접근
print(f"글 '{article.title_name}'의 작성자는 {article.writer.full_name}")
print(f"{person.full_name}의 모든 글:")
for a in person.articles:
print(f" - {a.title_name}")
# 다대다 관계 처리
python_tag = Tag(tag_name="파이썬")
sqlalchemy_tag = Tag(tag_name="SQLAlchemy")
article.tags.append(python_tag)
article.tags.append(sqlalchemy_tag)
db_session.commit()
print(f"글 '{article.title_name}'의 태그:")
for tag in article.tags:
print(f" - {tag.tag_name}")
트랜잭션 관리
python
# 자동 커밋 트랜잭션
try:
person = Person(full_name="테스트 사용자", email_address="test@example.com")
db_session.add(person)
db_session.commit()
except Exception as e:
db_session.rollback()
print(f"오류 발생: {e}")
# 트랜잭션 컨텍스트 매니저 사용
from sqlalchemy.orm import Session
def create_person(session: Session, name: str, email: str):
try:
person = Person(full_name=name, email_address=email)
session.add(person)
session.commit()
return person
except:
session.rollback()
raise
# 중첩 트랜잭션
with db_session.begin_nested():
person = Person(full_name="중첩 사용자", email_address="nested@example.com")
db_session.add(person)
# 세이브포인트 사용
savepoint = db_session.begin_nested()
try:
person = Person(full_name="세이브포인트 사용자", email_address="savepoint@example.com")
db_session.add(person)
savepoint.commit()
except:
savepoint.rollback()
최선의 실천 방법
- 세션 관리 : 요청별로 새로운 세션 생성 후 종료
- 예외 처리 : 모든 예외를 잡고 적절히 롤백
- 지연 로딩 : N+1 문제 주의, 즉시 로딩 사용 권장
- 커넥션 풀 : 적절한 크기와 타임아웃 설정
- 데이터 검증 : 모델 레이어 또는 애플리케이션 레이어에서 검증
python
# 컨텍스트 매니저로 세션 관리
from contextlib import contextmanager
@contextmanager
def get_db():
db = SessionClass()
try:
yield db
db.commit()
except Exception:
db.rollback()
raise
finally:
db.close()
# 사용 예시
with get_db() as db:
person = Person(full_name="컨텍스트 사용자", email_address="context@example.com")
db.add(person)
요약
SQLAlchemy ORM은 강력하고 유연한 데이터베이스 운영 기능을 제공합니다. 본 문서를 통해 다음을 수행할 수 있습니다:
- SQLAlchemy 설치 및 구성
- 데이터 모델 및 관계 정의
- 기본 CRUD 연산 수행
- 복잡한 쿼리 작성
- 트랜잭션 관리
- 최선의 실천 방법 적용
SQLAlchemy는 혼합 속성, 이벤트 감지, 커스텀 쿼리 등 고급 기능도 제공하므로 추가 탐구가 권장됩니다.