-
Notifications
You must be signed in to change notification settings - Fork 0
KR_ORM
somaz edited this page Apr 21, 2025
·
3 revisions
ORM(Object Relational Mapping)은 객체와 관계형 데이터베이스를 매핑하는 기술이다.
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# 데이터베이스 연결
engine = create_engine('sqlite:///example.db', echo=True) # echo=True로 실행되는 SQL 출력
Base = declarative_base()
Session = sessionmaker(bind=engine)
# 모델 정의
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
email = Column(String(100), unique=True)
def __repr__(self):
return f"<User(name='{self.name}', email='{self.email}')>"
# 다양한 데이터베이스 연결 방법
postgres_engine = create_engine('postgresql://username:password@localhost:5432/mydatabase')
mysql_engine = create_engine('mysql+mysqlconnector://username:password@localhost/mydatabase')
# 환경 변수를 사용한 연결 문자열 관리
import os
from dotenv import load_dotenv
load_dotenv() # .env 파일에서 환경 변수 로드
db_url = os.getenv('DATABASE_URL')
secure_engine = create_engine(db_url)
# 연결 풀링 설정
pooled_engine = create_engine(
'sqlite:///example.db',
pool_size=10, # 풀에 유지할 연결 수
max_overflow=20, # 풀 크기를 초과하여 생성할 수 있는 연결 수
pool_timeout=30, # 연결 대기 시간(초)
pool_recycle=1800 # 연결 재사용 시간(초)
)✅ 특징:
- 객체-관계 매핑(ORM) 패러다임
- SQL 코드 작성 없이 데이터베이스 조작
- 데이터베이스 독립적인 코드 작성 가능
- 객체 지향적 데이터 접근
- 자동 스키마 생성 및 관리
- 다양한 데이터베이스 시스템 지원
- 연결 풀링 및 세션 관리
- 타입 안전성 제공
ORM을 사용하여 데이터베이스의 기본 작업인 생성(Create), 조회(Read), 수정(Update), 삭제(Delete) 기능을 구현한다.
# 테이블 생성
Base.metadata.create_all(engine)
# 데이터 생성 (Create)
def create_user(name, email):
session = Session()
try:
user = User(name=name, email=email)
session.add(user)
session.commit()
return user.id
except Exception as e:
session.rollback()
print(f"사용자 생성 오류: {e}")
raise
finally:
session.close()
# 여러 객체 한 번에 생성
def create_many_users(users_data):
session = Session()
try:
users = [User(name=data['name'], email=data['email']) for data in users_data]
session.add_all(users)
session.commit()
return [user.id for user in users]
except Exception as e:
session.rollback()
print(f"다수 사용자 생성 오류: {e}")
raise
finally:
session.close()
# 데이터 조회 (Read)
def get_user(user_id):
session = Session()
try:
return session.query(User).filter(User.id == user_id).first()
finally:
session.close()
# 모든 사용자 조회
def get_all_users():
session = Session()
try:
return session.query(User).all()
finally:
session.close()
# 조건부 조회
def find_users_by_email_domain(domain):
session = Session()
try:
return session.query(User).filter(User.email.like(f'%@{domain}')).all()
finally:
session.close()
# 페이징 처리
def get_users_paginated(page=1, per_page=10):
session = Session()
try:
return session.query(User).order_by(User.id).offset((page - 1) * per_page).limit(per_page).all()
finally:
session.close()
# 데이터 수정 (Update)
def update_user(user_id, name=None, email=None):
session = Session()
try:
user = session.query(User).filter(User.id == user_id).first()
if user:
if name:
user.name = name
if email:
user.email = email
session.commit()
return True
return False
except Exception as e:
session.rollback()
print(f"사용자 업데이트 오류: {e}")
raise
finally:
session.close()
# 대량 업데이트
def update_many_users(email_domain, new_domain):
session = Session()
try:
# filter + update를 사용한 벌크 업데이트
count = session.query(User).filter(
User.email.like(f'%@{email_domain}')
).update(
{User.email: User.email.op('REPLACE')(f'@{email_domain}', f'@{new_domain}')},
synchronize_session=False
)
session.commit()
return count
except Exception as e:
session.rollback()
print(f"다수 사용자 업데이트 오류: {e}")
raise
finally:
session.close()
# 데이터 삭제 (Delete)
def delete_user(user_id):
session = Session()
try:
user = session.query(User).filter(User.id == user_id).first()
if user:
session.delete(user)
session.commit()
return True
return False
except Exception as e:
session.rollback()
print(f"사용자 삭제 오류: {e}")
raise
finally:
session.close()
# 대량 삭제
def delete_users_by_email_domain(domain):
session = Session()
try:
count = session.query(User).filter(
User.email.like(f'%@{domain}')
).delete(synchronize_session=False)
session.commit()
return count
except Exception as e:
session.rollback()
print(f"다수 사용자 삭제 오류: {e}")
raise
finally:
session.close()
# 컨텍스트 매니저를 사용한 세션 관리
from contextlib import contextmanager
@contextmanager
def session_scope():
"""트랜잭션 관리를 위한 세션 컨텍스트 매니저"""
session = Session()
try:
yield session
session.commit()
except Exception as e:
session.rollback()
raise
finally:
session.close()
# 컨텍스트 매니저 사용 예시
def create_user_with_context(name, email):
with session_scope() as session:
user = User(name=name, email=email)
session.add(user)
return user.id✅ 특징:
- 완전한 CRUD 연산 지원
- 자동 트랜잭션 관리
- 세션 기반 객체 추적
- 대량 작업 최적화
- 세션 컨텍스트 관리
- 조건부 쿼리 빌더
- 페이징 및 정렬 기능
- 예외 처리 및 롤백 자동화
ORM에서 테이블 간의 관계를 객체지향적으로 표현하는 방법이다.
from sqlalchemy import ForeignKey, DateTime, Text
from sqlalchemy.orm import relationship
from datetime import datetime
# 일대다 관계 (One-to-Many)
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
email = Column(String(100), unique=True)
created_at = Column(DateTime, default=datetime.utcnow)
# 관계 설정 - 사용자가 삭제되면 게시물도 함께 삭제 (cascade)
posts = relationship("Post", back_populates="user", cascade="all, delete-orphan")
comments = relationship("Comment", back_populates="user", cascade="all, delete-orphan")
def __repr__(self):
return f"<User(name='{self.name}', email='{self.email}')>"
class Post(Base):
__tablename__ = 'posts'
id = Column(Integer, primary_key=True)
title = Column(String(200), nullable=False)
content = Column(Text)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
# 관계 설정
user = relationship("User", back_populates="posts")
comments = relationship("Comment", back_populates="post", cascade="all, delete-orphan")
tags = relationship("Tag", secondary="post_tags", back_populates="posts")
def __repr__(self):
return f"<Post(title='{self.title}')>"
# 다대다 관계 (Many-to-Many)
from sqlalchemy import Table
# 연결 테이블 정의
post_tags = Table('post_tags', Base.metadata,
Column('post_id', Integer, ForeignKey('posts.id'), primary_key=True),
Column('tag_id', Integer, ForeignKey('tags.id'), primary_key=True)
)
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True)
name = Column(String(50), unique=True, nullable=False)
# 다대다 관계
posts = relationship("Post", secondary="post_tags", back_populates="tags")
def __repr__(self):
return f"<Tag(name='{self.name}')>"
# 추가 관계 - 댓글
class Comment(Base):
__tablename__ = 'comments'
id = Column(Integer, primary_key=True)
content = Column(Text, nullable=False)
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
post_id = Column(Integer, ForeignKey('posts.id'), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
# 관계 설정
user = relationship("User", back_populates="comments")
post = relationship("Post", back_populates="comments")
def __repr__(self):
return f"<Comment(content='{self.content[:20]}...')>"
# 자기참조 관계 (Self-Referential)
class Employee(Base):
__tablename__ = 'employees'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
manager_id = Column(Integer, ForeignKey('employees.id'), nullable=True)
# 자기참조 관계
manager = relationship("Employee", remote_side=[id], backref="subordinates")
def __repr__(self):
return f"<Employee(name='{self.name}')>"
# 관계 데이터 사용 예시
def relation_examples():
with session_scope() as session:
# 사용자 생성
user = User(name="홍길동", email="hong@example.com")
session.add(user)
# 게시물 생성 및 연결
post = Post(title="ORM 관계 설정", content="관계 설정 방법을 알아봅시다.", user=user)
session.add(post)
# 태그 생성 및 연결
tag1 = Tag(name="ORM")
tag2 = Tag(name="SQLAlchemy")
post.tags.extend([tag1, tag2])
# 댓글 생성 및 연결
comment = Comment(content="좋은 글입니다!", user=user, post=post)
session.add(comment)
# 직원 관계 설정
manager = Employee(name="김관리")
session.add(manager)
employee1 = Employee(name="이사원", manager=manager)
employee2 = Employee(name="박직원", manager=manager)
session.add_all([employee1, employee2])
# 관계 쿼리 예시
def query_relations():
with session_scope() as session:
# 특정 사용자의 모든 게시물 조회
user = session.query(User).filter_by(name="홍길동").first()
if user:
for post in user.posts:
print(f"게시물: {post.title}")
# 게시물의 댓글 조회
for comment in post.comments:
print(f" 댓글: {comment.content} (작성자: {comment.user.name})")
# 게시물의 태그 조회
for tag in post.tags:
print(f" 태그: {tag.name}")
# 특정 태그가 있는 모든 게시물 조회
orm_tag = session.query(Tag).filter_by(name="ORM").first()
if orm_tag:
for post in orm_tag.posts:
print(f"ORM 태그 게시물: {post.title} (작성자: {post.user.name})")
# 관리자와 부하 직원 조회
manager = session.query(Employee).filter_by(name="김관리").first()
if manager:
print(f"관리자: {manager.name}")
for emp in manager.subordinates:
print(f" 부하직원: {emp.name}")✅ 특징:
- 다양한 관계 유형 지원 (일대다, 다대다, 자기참조)
- 양방향 관계 설정
- 외래 키 제약 조건
- 관계 캐스케이드 동작
- 지연 로딩(Lazy Loading)
- 즉시 로딩(Eager Loading)
- 조인 테이블 매핑
- 객체 그래프 탐색
ORM을 사용한 복잡한 쿼리 작성 및 최적화 방법이다.
from sqlalchemy import and_, or_, not_, func, distinct, text, desc, asc
from sqlalchemy.orm import aliased, contains_eager, joinedload, selectinload
def advanced_query_examples():
with session_scope() as session:
# 복합 조건 필터링
users = session.query(User).filter(
and_(
User.name.like('홍%'),
User.email.contains('@example.com'),
or_(
User.created_at > datetime(2021, 1, 1),
not_(User.email.endswith('gmail.com'))
)
)
).all()
# 정렬
users = session.query(User).order_by(User.created_at.desc(), User.name).all()
# 집계 함수
user_count = session.query(func.count(User.id)).scalar()
post_stats = session.query(
func.count(Post.id).label('total_posts'),
func.avg(func.length(Post.content)).label('avg_length')
).first()
# 그룹화
post_counts = session.query(
User.name,
func.count(Post.id).label('post_count')
).join(Post).group_by(User.id).order_by(desc('post_count')).all()
# 서브쿼리
from sqlalchemy.sql import exists
active_users = session.query(User).filter(
exists().where(Post.user_id == User.id)
).all()
# 별칭을 사용한 자기 조인
mgr_alias = aliased(Employee, name='manager')
results = session.query(
Employee.name, mgr_alias.name
).join(
mgr_alias, Employee.manager_id == mgr_alias.id
).all()
# 윈도우 함수 (SQLAlchemy 1.4+)
from sqlalchemy.sql import functions as func
stmt = session.query(
Post,
func.row_number().over(
order_by=Post.created_at
).label('row_number'),
func.rank().over(
partition_by=Post.user_id,
order_by=Post.created_at.desc()
).label('rank')
).subquery()
ranked_posts = session.query(stmt).filter(stmt.c.rank == 1).all()
# 원시 SQL 쿼리
raw_results = session.query(User).from_statement(
text("SELECT * FROM users WHERE name LIKE :name")
).params(name='홍%').all()
return {
'users': users,
'user_count': user_count,
'post_stats': post_stats,
'post_counts': post_counts,
'active_users': active_users,
'reporting_chain': results,
'ranked_posts': ranked_posts,
'raw_results': raw_results
}
# N+1 문제 해결 - 지연 로딩 vs 즉시 로딩
def n_plus_one_problem():
# N+1 문제가 발생하는 예제
with session_scope() as session:
users = session.query(User).all() # 1번 쿼리
# 각 사용자별로 추가 쿼리 실행 (N번의 추가 쿼리)
for user in users:
print(f"{user.name}의 게시물: {len(user.posts)}")
# 해결책 1: 즉시 로딩(Eager Loading) - joinedload
with session_scope() as session:
users = session.query(User).options(joinedload(User.posts)).all() # JOIN을 통해 1번의 쿼리로 모두 로드
for user in users:
print(f"{user.name}의 게시물: {len(user.posts)}") # 추가 쿼리 없음
# 해결책 2: 즉시 로딩 - selectinload (추가 SELECT IN 쿼리 사용)
with session_scope() as session:
users = session.query(User).options(selectinload(User.posts)).all() # 2번의 효율적인 쿼리로 로드
for user in users:
print(f"{user.name}의 게시물: {len(user.posts)}") # 추가 쿼리 없음
# 해결책 3: 맞춤 조인 쿼리
with session_scope() as session:
users_with_post_count = session.query(
User, func.count(Post.id).label('post_count')
).outerjoin(Post).group_by(User.id).all()
for user, post_count in users_with_post_count:
print(f"{user.name}의 게시물: {post_count}")✅ 특징:
- 복잡한 조건 쿼리 구성
- 고급 조인 기법
- 집계 및 분석 함수
- 서브쿼리 및 중첩 쿼리
- 윈도우 함수 지원
- 원시 SQL 쿼리 실행
- 쿼리 최적화 기법
- N+1 문제 해결 방법
ORM을 사용한 데이터베이스 스키마 관리 및 변경 추적 방법이다.
# Alembic을 사용한 마이그레이션 (SQLAlchemy와 함께 사용되는 주요 마이그레이션 도구)
"""
설치: pip install alembic
초기화: alembic init migrations
"""
# Alembic 설정 예시 (alembic.ini)
"""
[alembic]
script_location = migrations
sqlalchemy.url = sqlite:///example.db
"""
# 환경 설정 (migrations/env.py)
"""
from alembic import context
from sqlalchemy import engine_from_config, pool
from myapp.models import Base # 애플리케이션의 모델이 정의된 Base 객체 가져오기
target_metadata = Base.metadata # 메타데이터 설정
def run_migrations_online():
connectable = engine_from_config(
context.config.get_section(context.config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
"""
# 마이그레이션 생성 및 실행 명령어
"""
마이그레이션 생성: alembic revision --autogenerate -m "Create users table"
마이그레이션 실행: alembic upgrade head
이전 버전으로 롤백: alembic downgrade -1
"""
# 마이그레이션 파일 예시 (migrations/versions/xxxx_create_users_table.py)
"""
def upgrade():
op.create_table(
'users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=100), nullable=False),
sa.Column('email', sa.String(length=100), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email')
)
def downgrade():
op.drop_table('users')
"""
# 프로그래밍 방식으로 스키마 조작
def schema_manipulation():
from sqlalchemy import inspect, MetaData
with session_scope() as session:
# 테이블 메타데이터 검사
inspector = inspect(engine)
# 테이블 목록 가져오기
tables = inspector.get_table_names()
print(f"데이터베이스 테이블: {tables}")
# 테이블 컬럼 정보 가져오기
for table in tables:
columns = inspector.get_columns(table)
print(f"\n{table} 테이블 컬럼:")
for column in columns:
print(f" {column['name']}: {column['type']}")
# 인덱스 정보 가져오기
for table in tables:
indexes = inspector.get_indexes(table)
if indexes:
print(f"\n{table} 테이블 인덱스:")
for index in indexes:
print(f" {index['name']} ({', '.join(index['column_names'])})")
# 외래 키 정보 가져오기
for table in tables:
fks = inspector.get_foreign_keys(table)
if fks:
print(f"\n{table} 테이블 외래 키:")
for fk in fks:
print(f" {fk['constrained_columns']} -> {fk['referred_table']}.{fk['referred_columns']}")
# 동적 모델 생성
def create_dynamic_model(table_name, columns):
"""동적으로 ORM 모델 클래스 생성"""
attrs = {
'__tablename__': table_name,
'__table_args__': {'extend_existing': True}
}
# ID 열 추가
attrs['id'] = Column(Integer, primary_key=True)
# 지정된 열 추가
for col_name, col_type in columns.items():
if col_type == 'string':
attrs[col_name] = Column(String(100))
elif col_type == 'integer':
attrs[col_name] = Column(Integer)
elif col_type == 'datetime':
attrs[col_name] = Column(DateTime)
elif col_type == 'boolean':
attrs[col_name] = Column(Boolean)
# 동적 모델 클래스 생성
DynamicModel = type(table_name.capitalize(), (Base,), attrs)
# 테이블이 없으면 생성
if not inspect(engine).has_table(table_name):
DynamicModel.__table__.create(engine)
return DynamicModel✅ 특징:
- 자동화된 스키마 생성
- 버전 관리 마이그레이션
- 스키마 변경 추적
- 롤백 기능
- 메타데이터 검사
- 테이블 및 컬럼 정보 접근
- 동적 모델 생성
- 테이블 변경 자동화
ORM 사용 시 성능을 향상시키는 다양한 최적화 기법이다.
# 쿼리 실행 계획 확인
def analyze_query_performance():
with session_scope() as session:
# 쿼리 실행 전 출력
stmt = session.query(User).join(Post).filter(Post.title.like('%ORM%'))
print(str(stmt))
# 쿼리 실행 시간 측정
import time
start = time.time()
result = stmt.all()
end = time.time()
print(f"쿼리 실행 시간: {end - start:.4f}초, 결과 수: {len(result)}")
# 벌크 연산 사용
def bulk_operations():
with session_scope() as session:
# 벌크 삽입
from sqlalchemy.dialects.postgresql import insert as pg_insert
# PostgreSQL의 경우 (UPSERT 기능 지원)
users_data = [
{"name": "김철수", "email": "kim@example.com"},
{"name": "이영희", "email": "lee@example.com"}
]
stmt = pg_insert(User.__table__).values(users_data)
stmt = stmt.on_conflict_do_update(
index_elements=['email'],
set_=dict(name=stmt.excluded.name)
)
session.execute(stmt)
# 벌크 업데이트 - dialect 독립적 방법
session.query(User).filter(
User.email.like('%@example.com')
).update(
{"name": User.name + " (수정됨)"},
synchronize_session=False
)
# 벌크 삭제
deleted = session.query(Post).filter(
Post.created_at < datetime(2021, 1, 1)
).delete(synchronize_session=False)
print(f"{deleted}개의 게시물이 삭제되었습니다.")
# 세션 캐싱 활용
def session_caching_example():
with session_scope() as session:
# 첫 번째 쿼리 - 데이터베이스에서 로드
user1 = session.query(User).filter_by(id=1).first()
# 두 번째 쿼리 - 세션 캐시에서 로드 (추가 쿼리 없음)
user2 = session.query(User).filter_by(id=1).first()
print(f"동일 객체 여부: {user1 is user2}") # True
# 세션 캐시 무효화
session.expire_all()
# 세 번째 쿼리 - 캐시가 무효화되어 다시 데이터베이스에서 로드
user3 = session.query(User).filter_by(id=1).first()
# 쿼리 최적화 - 필요한 컬럼만 조회
def select_only_needed_columns():
with session_scope() as session:
# 모든 컬럼 조회
users_full = session.query(User).all()
# 필요한 컬럼만 조회
users_partial = session.query(User.id, User.name).all()
# 개별 엔티티 대신 딕셔너리 형태로 조회
users_dict = session.query(
User.id, User.name, User.email
).all()
# 커스텀 쿼리 옵티마이저
class QueryOptimizer:
def __init__(self, session):
self.session = session
def optimize_query(self, query, limit=None):
"""자동으로 쿼리를 최적화"""
# 관계 감지 및 자동 조인 로드 추가
from sqlalchemy.inspection import inspect
# 쿼리 대상 엔티티 확인
entities = []
for entity in query.column_descriptions:
if hasattr(entity['type'], '__tablename__'):
entities.append(entity['type'])
if not entities:
return query
# 주 엔티티 선택
main_entity = entities[0]
# 관계 검사
mapper = inspect(main_entity)
relationships = list(mapper.relationships)
# 쿼리에서 사용 중인 관계 감지
loaded_relationships = []
for rel in relationships:
attr_name = rel.key
if hasattr(main_entity, attr_name) and attr_name in query._joinpoint.entities:
loaded_relationships.append(attr_name)
# 관계가 로드되면 자동으로 joinedload 추가
for rel_name in loaded_relationships:
query = query.options(joinedload(getattr(main_entity, rel_name)))
# 필요한 경우 제한 추가
if limit is not None:
query = query.limit(limit)
return query✅ 특징:
- 쿼리 실행 계획 분석
- 벌크 연산 활용
- 세션 캐싱 활용
- 부분 컬럼 선택적 로드
- 관계 로딩 최적화
- 트랜잭션 관리
- 인덱스 효율적 활용
- 커스텀 최적화 전략
ORM을 실제 애플리케이션에 통합하는 모범 사례와 패턴이다.
# 리포지토리 패턴 구현
class Repository:
"""데이터 액세스 로직을 캡슐화하는 리포지토리 패턴"""
def __init__(self, model, session_factory):
self.model = model
self.session_factory = session_factory
def get(self, id):
with session_scope() as session:
return session.query(self.model).filter_by(id=id).first()
def get_all(self):
with session_scope() as session:
return session.query(self.model).all()
def find_by(self, **kwargs):
with session_scope() as session:
return session.query(self.model).filter_by(**kwargs).all()
def create(self, **kwargs):
with session_scope() as session:
instance = self.model(**kwargs)
session.add(instance)
session.commit()
session.refresh(instance)
return instance
def update(self, id, **kwargs):
with session_scope() as session:
instance = session.query(self.model).filter_by(id=id).first()
if instance:
for key, value in kwargs.items():
setattr(instance, key, value)
session.commit()
return instance
return None
def delete(self, id):
with session_scope() as session:
instance = session.query(self.model).filter_by(id=id).first()
if instance:
session.delete(instance)
session.commit()
return True
return False
# 애플리케이션 구성 예시 - Flask 웹 애플리케이션
"""
from flask import Flask, request, jsonify
from myapp.models import Base, User
from myapp.repository import Repository
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
app = Flask(__name__)
# 데이터베이스 설정
engine = create_engine('sqlite:///app.db')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
# 리포지토리 생성
user_repo = Repository(User, Session)
@app.route('/users', methods=['GET'])
def get_users():
users = user_repo.get_all()
return jsonify([{
'id': user.id,
'name': user.name,
'email': user.email
} for user in users])
@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
user = user_repo.get(user_id)
if user:
return jsonify({
'id': user.id,
'name': user.name,
'email': user.email
})
return jsonify({'error': 'User not found'}), 404
@app.route('/users', methods=['POST'])
def create_user():
data = request.json
try:
user = user_repo.create(**data)
return jsonify({
'id': user.id,
'name': user.name,
'email': user.email
}), 201
except Exception as e:
return jsonify({'error': str(e)}), 400
@app.route('/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
data = request.json
user = user_repo.update(user_id, **data)
if user:
return jsonify({
'id': user.id,
'name': user.name,
'email': user.email
})
return jsonify({'error': 'User not found'}), 404
@app.route('/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
success = user_repo.delete(user_id)
if success:
return '', 204
return jsonify({'error': 'User not found'}), 404
if __name__ == '__main__':
app.run(debug=True)
"""
# 단위 테스트
"""
import unittest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from myapp.models import Base, User
from myapp.repository import Repository
class TestUserRepository(unittest.TestCase):
def setUp(self):
# 메모리 내 SQLite DB
self.engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(self.engine)
Session = sessionmaker(bind=self.engine)
self.session = Session()
self.repo = Repository(User, Session)
def tearDown(self):
Base.metadata.drop_all(self.engine)
def test_create_user(self):
user = self.repo.create(name='테스트', email='test@example.com')
self.assertIsNotNone(user.id)
self.assertEqual(user.name, '테스트')
def test_get_user(self):
user = self.repo.create(name='테스트', email='test@example.com')
fetched_user = self.repo.get(user.id)
self.assertEqual(fetched_user.name, '테스트')
def test_update_user(self):
user = self.repo.create(name='테스트', email='test@example.com')
updated_user = self.repo.update(user.id, name='수정됨')
self.assertEqual(updated_user.name, '수정됨')
def test_delete_user(self):
user = self.repo.create(name='테스트', email='test@example.com')
success = self.repo.delete(user.id)
self.assertTrue(success)
self.assertIsNone(self.repo.get(user.id))
"""✅ 특징:
- 리포지토리 패턴으로 데이터 접근 추상화
- 웹 애플리케이션 통합 예시
- 단위 테스트 작성 방법
- 세션 관리 전략
- API 엔드포인트 연동
- 개발-프로덕션 환경 분리
- ORM 모범 사례 적용
- 코드 구조화 및 모듈화
✅ 모범 사례:
- 세션 관리 주의
- 세션 스코프를 명확히 정의하고 항상 닫기
- 컨텍스트 매니저 패턴 활용
- 하나의 요청/응답 주기에 하나의 세션 사용
- 장기 실행 세션 피하기
- 적절한 인덱스 사용
- 자주 조회하는 컬럼에 인덱스 추가
- 복합 인덱스 활용
- 외래 키 컬럼에 인덱스 적용
- 불필요한 인덱스 제거
- Lazy Loading 이해
- 지연 로딩의 장단점 파악
- N+1 쿼리 문제 인식
- 즉시 로딩 적절히 활용
- 필요에 따라 조인 전략 선택
- N+1 문제 주의
- joinedload, selectinload 활용
- 관계 조회 시 로딩 전략 지정
- 다수의 개체 조회 시 특히 주의
- 쿼리 최적화로 불필요한 로딩 방지
- 벌크 연산 활용
- 다수 레코드 처리 시 벌크 연산 사용
- 메모리 사용량 최소화
- ORM 오버헤드 감소
- 데이터베이스 부하 분산
- 캐싱 전략 수립
- 세션 캐시 활용
- 외부 캐시(Redis 등) 통합
- 읽기 빈도가 높은 데이터 캐싱
- 적절한 캐시 무효화 전략 구현
- 마이그레이션 관리
- Alembic 등 전문 도구 활용
- 버전 관리 시스템과 통합
- 마이그레이션 테스트 자동화
- 다운그레이드 경로 유지
- 트랜잭션 처리
- 원자적 연산에 트랜잭션 사용
- 명시적 커밋/롤백 관리
- 예외 처리와 결합
- 중첩 트랜잭션 이해