SQLAlchemy는 Python에서 가장 널리 사용되는 ORM(객체 관계 매핑) 프레임워크 중 하나로, 효율적이고 유연한 데이터베이스 작업을 제공합니다. 본문에서는 SQLAlchemy ORM을 사용하여 데이터베이스 작업을 수행하는 방법을 자세히 설명합니다.
목차
- SQLAlchemy 설치
- 핵심 개념
- 데이터베이스 연결
- 데이터 모델 정의
- 데이터베이스 테이블 생성
- 기본 CRUD 작업
- 데이터 조회
- 관계형 작업
- 트랜잭션 관리
- 모범 사례
설치
pip install sqlalchemy
특정 데이터베이스에 연결해야 할 경우, 해당 드라이버를 추가로 설치해야 합니다:
# PostgreSQL
pip install psycopg2-binary
# MySQL
pip install mysql-connector-python
# SQLite (Python 표준 라이브러리에 포함되어 있어 별도 설치 불필요)
핵심 개념
- Engine: 데이터베이스 연결 엔진으로, 데이터베이스와의 통신을 담당합니다
- Session: 데이터베이스 세션으로, 모든 지속화 작업을 관리합니다
- Model: 데이터 모델 클래스로, 데이터베이스의 테이블에 해당합니다
- Query: 쿼리 객체로, 데이터베이스 쿼리 구축 및 실행에 사용됩니다
데이터베이스 연결
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/mydb')
# MySQL 예시
# engine = create_engine('mysql+mysqlconnector://user:pass@localhost:3306/mydb')
# 세션 팩토리 생성
SessionFactory = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 세션 인스턴스 생성
db_session = SessionFactory()
데이터 모델 정의
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, declarative_base
# 기본 클래스 생성
DataModel = declarative_base()
class Person(DataModel):
__tablename__ = 'persons'
id = Column(Integer, primary_key=True, index=True)
fullname = Column(String(50), nullable=False)
email = Column(String(100), unique=True, index=True)
# 일대다 관계 정의
articles = relationship("Article", back_populates="writer")
class Article(DataModel):
__tablename__ = 'articles'
id = Column(Integer, primary_key=True, index=True)
heading = Column(String(100), nullable=False)
content = Column(String(500))
writer_id = Column(Integer, ForeignKey('persons.id'))
# 다대일 관계 정의
writer = relationship("Person", back_populates="articles")
# 다대다 관계 정의 (연결 테이블을 통해)
categories = relationship("Category", secondary="article_categories", back_populates="articles")
class Category(DataModel):
__tablename__ = 'categories'
id = Column(Integer, primary_key=True, index=True)
name = Column(String(30), unique=True, nullable=False)
articles = relationship("Article", secondary="article_categories", back_populates="categories")
# 연결 테이블 (다대다 관계용)
class ArticleCategory(DataModel):
__tablename__ = 'article_categories'
article_id = Column(Integer, ForeignKey('articles.id'), primary_key=True)
category_id = Column(Integer, ForeignKey('categories.id'), primary_key=True)
데이터베이스 테이블 생성
# 모든 테이블 생성
DataModel.metadata.create_all(bind=engine)
# 모든 테이블 삭제
# DataModel.metadata.drop_all(bind=engine)
기본 CRUD 작업
데이터 생성
# 새로운 사용자 생성
new_person = Person(fullname="김철수", email="chulsoo@example.com")
db_session.add(new_person)
db_session.commit()
# 일괄 생성
db_session.add_all([
Person(fullname="이영희", email="younghee@example.com"),
Person(fullname="박민준", email="minjun@example.com")
])
db_session.commit()
데이터 읽기
# 모든 사용자 조회
people = db_session.query(Person).all()
# 첫 번째 사용자 조회
first_person = db_session.query(Person).first()
# ID로 사용자 조회
person = db_session.query(Person).get(1)
데이터 업데이트
# 조회 후 업데이트
person = db_session.query(Person).get(1)
person.fullname = "김철수 modified"
db_session.commit()
# 일괄 업데이트
db_session.query(Person).filter(Person.fullname.like("김%")).update({"fullname": "김씨"}, synchronize_session=False)
db_session.commit()
데이터 삭제
# 조회 후 삭제
person = db_session.query(Person).get(1)
db_session.delete(person)
db_session.commit()
# 일괄 삭제
db_session.query(Person).filter(Person.fullname == "이영희").delete(synchronize_session=False)
db_session.commit()
데이터 조회
기본 조회
# 모든 레코드 조회
people = db_session.query(Person).all()
# 특정 필드 조회
names = db_session.query(Person.fullname).all()
# 정렬
people = db_session.query(Person).order_by(Person.fullname.desc()).all()
# 결과 수 제한
people = db_session.query(Person).limit(10).all()
# 오프셋
people = db_session.query(Person).offset(5).limit(10).all()
필터링 조회
from sqlalchemy import or_
# 동등 필터링
person = db_session.query(Person).filter(Person.fullname == "김철수").first()
# 패턴 매칭
people = db_session.query(Person).filter(Person.fullname.like("김%")).all()
# IN 쿼리
people = db_session.query(Person).filter(Person.fullname.in_(["김철수", "이영희"])).all()
# 다중 조건 쿼리
people = db_session.query(Person).filter(
Person.fullname == "김철수",
Person.email.like("%@example.com")
).all()
# OR 조건
people = db_session.query(Person).filter(
or_(Person.fullname == "김철수", Person.fullname == "이영희")
).all()
# 같지 않음
people = db_session.query(Person).filter(Person.fullname != "김철수").all()
집계 조회
from sqlalchemy import func
# 카운트
count = db_session.query(Person).count()
# 그룹별 카운트
person_article_count = db_session.query(
Person.fullname,
func.count(Article.id)
).join(Article).group_by(Person.fullname).all()
# 합계, 평균 등
avg_id = db_session.query(func.avg(Person.id)).scalar()
조인 조회
# 내부 조인
results = db_session.query(Person, Article).join(Article).filter(Article.heading.like("%Python%")).all()
# 외부 조인
results = db_session.query(Person, Article).outerjoin(Article).all()
# 조인 조건 지정
results = db_session.query(Person, Article).join(Article, Person.id == Article.writer_id).all()
관계형 작업
# 관계가 있는 객체 생성
writer = Person(fullname="조현우", email="hyunwoo@example.com")
article = Article(heading="첫 번째 블로그 글", content="안녕하세요, 세계!", writer=writer)
db_session.add(article)
db_session.commit()
# 관계를 통해 접근
print(f"'{article.heading}' 글의 작성자: {article.writer.fullname}")
print(f"{writer.fullname}의 모든 글:")
for a in writer.articles:
print(f" - {a.heading}")
# 다대다 관계 작업
python_category = Category(name="Python")
sqlalchemy_category = Category(name="SQLAlchemy")
article.categories.append(python_category)
article.categories.append(sqlalchemy_category)
db_session.commit()
print(f"'{article.heading}' 글의 카테고리:")
for category in article.categories:
print(f" - {category.name}")
트랜잭션 관리
# 자동 커밋 트랜잭션
try:
person = Person(fullname="테스트 사용자", email="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 add_person(session: Session, fullname: str, email: str):
try:
person = Person(fullname=fullname, email=email)
session.add(person)
session.commit()
return person
except:
session.rollback()
raise
# 중첩 트랜잭션
with db_session.begin_nested():
person = Person(fullname="중첩 트랜잭션 사용자", email="nested@example.com")
db_session.add(person)
# 저장점
savepoint = db_session.begin_nested()
try:
person = Person(fullname="저장점 사용자", email="savepoint@example.com")
db_session.add(person)
savepoint.commit()
except:
savepoint.rollback()
모범 사례
- 세션 관리: 각 요청에 대해 새 세션을 생성하고 요청 종료 후 닫습니다
- 예외 처리: 항상 예외를 처리하고 적절하게 트랜잭션을 롤백합니다
- 지연 로딩: N+1 쿼리 문제에 주의하고, eager loading으로 최적화합니다
- 연결 풀: 연결 풀 크기 및 시간 초과 설정을 합리적으로 구성합니다
- 데이터 검증: 모델 레이어 또는 애플리케이션 레이어에서 데이터 무결성을 검증합니다
# 컨텍스트 관리기를 사용한 세션 관리
from contextlib import contextmanager
@contextmanager
def get_database_session():
session = SessionFactory()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
# 사용 예시
with get_database_session() as db:
person = Person(fullname="컨텍스트 사용자", email="context@example.com")
db.add(person)
결론
SQLAlchemy ORM은 강력하고 유연한 데이터베이스 작업 방식을 제공하며, 본 가이드를 통해 다음을 할 수 있게 되었습니다:
- SQLAlchemy 설치 및 구성
- 데이터 모델과 관계 정의
- 기본 CRUD 작업 수행
- 복잡한 쿼리 구축
- 데이터베이스 트랜잭션 관리
- 모범 사례 따르기
SQLAlchemy에는 혼합 속성, 이벤트 리스너, 사용자 정의 쿼리 등 더 많은 고급 기능이 있으며, 추가 탐구 학습이 가치가 있습니다.