SQLAlchemy는 Python에서 가장 널리 사용되는 ORM(Object-Relational Mapping) 라이브러리 중 하나로, 데이터베이스 작업을 효율적이고 유연하게 수행할 수 있도록 지원합니다. 이 문서에서는 SQLAlchemy ORM을 사용하여 데이터베이스를 조작하는 방법에 대해 설명합니다.
설치
SQLAlchemy를 설치하려면 pip를 사용합니다.
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 예시
db_engine = create_engine('sqlite:///my_database.db', echo=True)
# PostgreSQL 예시
# db_engine = create_engine('postgresql://user:password@host:port/dbname')
# MySQL 예시
# db_engine = create_engine('mysql+mysqlconnector://user:password@host:port/dbname')
# 세션 생성 팩토리 설정
SessionFactory = sessionmaker(autocommit=False, autoflush=False, bind=db_engine)
# 세션 인스턴스 생성
db_session = SessionFactory()
데이터 모델 정의
데이터베이스 테이블 구조를 Python 클래스로 정의합니다.
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.orm import relationship, declarative_base
# 모델의 기반 클래스
BaseModel = declarative_base()
class Customer(BaseModel):
__tablename__ = 'customers'
customer_id = Column('id', Integer, primary_key=True, index=True)
full_name = Column(String(100), nullable=False)
email_address = Column(String(150), unique=True, index=True)
# 고객과 주문 간의 일대다 관계 정의
orders = relationship("Order", back_populates="customer")
class Product(BaseModel):
__tablename__ = 'products'
product_id = Column('id', Integer, primary_key=True, index=True)
product_name = Column(String(120), nullable=False)
price = Column(Integer) # 가격은 정수로 표현 (예: 센트 단위)
class Order(BaseModel):
__tablename__ = 'orders'
order_id = Column('id', Integer, primary_key=True, index=True)
order_date = Column(String) # 날짜 형식은 필요에 따라 조정
customer_id = Column(Integer, ForeignKey('customers.id'))
# 주문과 고객 간의 다대일 관계 정의
customer = relationship("Customer", back_populates="orders")
# 주문과 상품 간의 다대다 관계 (Order_Item을 통한 연결)
order_items = relationship("OrderItem", back_populates="order")
class OrderItem(BaseModel):
__tablename__ = 'order_items'
item_id = Column('id', Integer, primary_key=True, index=True)
order_id = Column(Integer, ForeignKey('orders.id'), primary_key=True)
product_id = Column(Integer, ForeignKey('products.id'), primary_key=True)
quantity = Column(Integer)
order = relationship("Order", back_populates="order_items")
product = relationship("Product")
데이터베이스 테이블 생성
정의된 모델을 바탕으로 데이터베이스 테이블을 생성합니다.
# 모든 테이블 생성
BaseModel.metadata.create_all(bind=db_engine)
# 모든 테이블 삭제 (주의해서 사용)
# BaseModel.metadata.drop_all(bind=db_engine)
기본 CRUD 작업
데이터 생성 (Create)
# 새 고객 추가
new_customer = Customer(full_name="김철수", email_address="chulsoo.kim@example.com")
db_session.add(new_customer)
db_session.commit()
# 여러 레코드 동시 추가
db_session.add_all([
Customer(full_name="박영희", email_address="younghee.park@example.com"),
Customer(full_name="이민준", email_address="minjun.lee@example.com")
])
db_session.commit()
데이터 읽기 (Read)
# 모든 고객 조회
all_customers = db_session.query(Customer).all()
# 첫 번째 고객 조회
first_customer = db_session.query(Customer).first()
# ID로 특정 고객 조회
customer_by_id = db_session.query(Customer).get(1)
데이터 수정 (Update)
# ID가 1인 고객의 이메일 주소 변경
target_customer = db_session.query(Customer).get(1)
if target_customer:
target_customer.email_address = "new.email@example.com"
db_session.commit()
# 특정 조건을 만족하는 고객 이름 일괄 변경
db_session.query(Customer).filter(Customer.full_name.like("박%")).update({"full_name": "박OO"}, synchronize_session=False)
db_session.commit()
데이터 삭제 (Delete)
# ID가 1인 고객 삭제
customer_to_delete = db_session.query(Customer).get(1)
if customer_to_delete:
db_session.delete(customer_to_delete)
db_session.commit()
# 이름이 "이민준"인 고객 일괄 삭제
db_session.query(Customer).filter(Customer.full_name == "이민준").delete(synchronize_session=False)
db_session.commit()
데이터 조회 (Query)
기본 조회
# 모든 레코드 조회
customers = db_session.query(Customer).all()
# 특정 필드만 조회
customer_names = db_session.query(Customer.full_name).all()
# 이름 기준 내림차순 정렬
sorted_customers = db_session.query(Customer).order_by(Customer.full_name.desc()).all()
# 결과 수 제한
limited_customers = db_session.query(Customer).limit(5).all()
# 특정 범위의 결과 조회 (페이징)
paged_customers = db_session.query(Customer).offset(10).limit(5).all()
필터링 조건
from sqlalchemy import or_
# 이름이 "김철수"인 고객 조회
customer = db_session.query(Customer).filter(Customer.full_name == "김철수").first()
# 이름이 "김"으로 시작하는 고객 조회
customers = db_session.query(Customer).filter(Customer.full_name.like("김%")).all()
# 이름이 특정 목록에 포함된 고객 조회
customers = db_session.query(Customer).filter(Customer.full_name.in_(["김철수", "박영희"])).all()
# 복합 조건 (AND)
customers = db_session.query(Customer).filter(
Customer.full_name == "김철수",
Customer.email_address.like("%@example.com")
).all()
# 복합 조건 (OR)
customers = db_session.query(Customer).filter(
or_(Customer.full_name == "김철수", Customer.full_name == "이민준")
).all()
# 같지 않음
customers = db_session.query(Customer).filter(Customer.full_name != "김철수").all()
집계 함수 사용
from sqlalchemy import func
# 고객 수 계산
customer_count = db_session.query(Customer).count()
# 고객별 주문 수 계산 (Join 및 Group By 사용)
customer_order_counts = db_session.query(
Customer.full_name,
func.count(Order.order_id)
).join(Order, Customer.customer_id == Order.customer_id, isouter=True).group_by(Customer.full_name).all()
# 평균 ID 값 계산
avg_customer_id = db_session.query(func.avg(Customer.customer_id)).scalar()
JOIN을 사용한 조회
# 고객과 주문 정보 INNER JOIN
results = db_session.query(Customer, Order).join(Order).filter(Order.order_date.like("2023-%")).all()
# 고객과 주문 정보 LEFT OUTER JOIN
results = db_session.query(Customer, Order).outerjoin(Order).all()
# 명시적 JOIN 조건 사용
results = db_session.query(Customer, Order).join(Order, Customer.customer_id == Order.customer_id).all()
관계(Relationship)를 활용한 작업
모델 간의 관계를 설정하고 활용합니다.
# 관계를 가진 객체 생성
customer = Customer(full_name="유관순", email_address="yks@example.com")
order = Order(order_date="2024-01-15", customer=customer) # customer 객체를 직접 할당
db_session.add(order)
db_session.commit()
# 관계를 통해 접근
print(f"주문 ID {order.order_id}의 고객: {order.customer.full_name}")
print(f"고객 {customer.full_name}의 주문 목록:")
for o in customer.orders:
print(f" - 주문 ID: {o.order_id}, 날짜: {o.order_date}")
# 다대다 관계 (OrderItem을 통해)
# Product와 OrderItem, Order 간의 관계 설정 필요 (위 모델 정의 참고)
# 예시: 특정 주문에 상품 추가
product_a = Product(product_name="노트북", price=120000)
order_item_a = OrderItem(order=order, product=product_a, quantity=1)
db_session.add(order_item_a)
db_session.commit()
# 주문에 포함된 상품 조회
print(f"주문 ID {order.order_id}에 포함된 상품:")
for item in order.order_items:
print(f" - 상품: {item.product.product_name}, 수량: {item.quantity}")
트랜잭션 관리
데이터베이스 트랜잭션을 안전하게 관리합니다.
# 자동 커밋/롤백 (try-except 구문 활용)
try:
new_customer = Customer(full_name="임시사용자", email_address="temp@example.com")
db_session.add(new_customer)
db_session.commit() # 성공 시 커밋
except Exception as e:
db_session.rollback() # 오류 발생 시 롤백
print(f"트랜잭션 오류 발생: {e}")
# 트랜잭션 컨텍스트 매니저 사용
from sqlalchemy.orm import Session
def add_new_customer(db: Session, name: str, email: str):
try:
customer = Customer(full_name=name, email_address=email)
db.add(customer)
db.commit()
return customer
except:
db.rollback()
raise
# 중첩 트랜잭션
with db_session.begin_nested():
customer1 = Customer(full_name="중첩1", email_address="nested1@example.com")
db_session.add(customer1)
# 필요 시 여기서 추가 작업 수행
# 세이브 포인트 (Savepoint)
save_point = db_session.begin_nested()
try:
customer2 = Customer(full_name="세이브포인트", email_address="savepoint@example.com")
db_session.add(customer2)
save_point.commit() # 세이브 포인트 커밋
except:
save_point.rollback() # 세이브 포인트 롤백
모범 사례
- 세션 관리: 각 요청마다 새로운 세션을 생성하고, 요청 완료 후 반드시 세션을 닫습니다.
- 예외 처리: 모든 데이터베이스 작업에서 발생할 수 있는 예외를 처리하고, 필요시 트랜잭션을 롤백합니다.
- 지연 로딩 최적화: N+1 쿼리 문제를 방지하기 위해 `joinedload` 또는 `selectinload`와 같은 Eager Loading 기법을 적절히 사용합니다.
- 연결 풀링: 데이터베이스 연결 풀의 크기와 타임아웃 설정을 최적화하여 성능을 향상시킵니다.
- 데이터 유효성 검사: 모델 정의 단계 또는 애플리케이션 로직에서 데이터의 유효성을 검사합니다.
컨텍스트 관리자를 활용한 세션 관리 예시:
from contextlib import contextmanager
@contextmanager
def manage_db_session():
session = SessionFactory()
try:
yield session
session.commit() # 성공 시 커밋
except Exception:
session.rollback() # 오류 시 롤백
raise
finally:
session.close() # 세션 종료
# 사용 예시
with manage_db_session() as current_session:
new_customer = Customer(full_name="컨텍스트사용자", email_address="context@example.com")
current_session.add(new_customer)
SQLAlchemy ORM은 강력하고 유연한 데이터베이스 작업 기능을 제공합니다. 이 가이드에서는 기본적인 설치, 모델 정의, CRUD 작업, 쿼리 작성, 관계 설정 및 트랜잭션 관리 방법을 다루었습니다.