KR_ORM - somaz94/python-study GitHub Wiki

Python ORM ๊ฐœ๋… ์ •๋ฆฌ


1๏ธโƒฃ ORM ๊ธฐ์ดˆ

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 ์ฝ”๋“œ ์ž‘์„ฑ ์—†์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐ์ž‘
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋…๋ฆฝ์ ์ธ ์ฝ”๋“œ ์ž‘์„ฑ ๊ฐ€๋Šฅ
  • ๊ฐ์ฒด ์ง€ํ–ฅ์  ๋ฐ์ดํ„ฐ ์ ‘๊ทผ
  • ์ž๋™ ์Šคํ‚ค๋งˆ ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ
  • ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‹œ์Šคํ…œ ์ง€์›
  • ์—ฐ๊ฒฐ ํ’€๋ง ๋ฐ ์„ธ์…˜ ๊ด€๋ฆฌ
  • ํƒ€์ž… ์•ˆ์ „์„ฑ ์ œ๊ณต


2๏ธโƒฃ ๊ธฐ๋ณธ CRUD ์ž‘์—…

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 ์—ฐ์‚ฐ ์ง€์›
  • ์ž๋™ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ
  • ์„ธ์…˜ ๊ธฐ๋ฐ˜ ๊ฐ์ฒด ์ถ”์ 
  • ๋Œ€๋Ÿ‰ ์ž‘์—… ์ตœ์ ํ™”
  • ์„ธ์…˜ ์ปจํ…์ŠคํŠธ ๊ด€๋ฆฌ
  • ์กฐ๊ฑด๋ถ€ ์ฟผ๋ฆฌ ๋นŒ๋”
  • ํŽ˜์ด์ง• ๋ฐ ์ •๋ ฌ ๊ธฐ๋Šฅ
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐ ๋กค๋ฐฑ ์ž๋™ํ™”


3๏ธโƒฃ ๊ด€๊ณ„ ์„ค์ •

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="[email protected]")
        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)
  • ์กฐ์ธ ํ…Œ์ด๋ธ” ๋งคํ•‘
  • ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„ ํƒ์ƒ‰


4๏ธโƒฃ ๊ณ ๊ธ‰ ์ฟผ๋ฆฌ

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 ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•


5๏ธโƒฃ ์Šคํ‚ค๋งˆ ๊ด€๋ฆฌ์™€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

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

โœ… ํŠน์ง•:

  • ์ž๋™ํ™”๋œ ์Šคํ‚ค๋งˆ ์ƒ์„ฑ
  • ๋ฒ„์ „ ๊ด€๋ฆฌ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
  • ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์ถ”์ 
  • ๋กค๋ฐฑ ๊ธฐ๋Šฅ
  • ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ๊ฒ€์‚ฌ
  • ํ…Œ์ด๋ธ” ๋ฐ ์ปฌ๋Ÿผ ์ •๋ณด ์ ‘๊ทผ
  • ๋™์  ๋ชจ๋ธ ์ƒ์„ฑ
  • ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ ์ž๋™ํ™”


6๏ธโƒฃ ์„ฑ๋Šฅ ์ตœ์ ํ™”

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": "[email protected]"},
            {"name": "์ด์˜ํฌ", "email": "[email protected]"}
        ]
        
        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

โœ… ํŠน์ง•:

  • ์ฟผ๋ฆฌ ์‹คํ–‰ ๊ณ„ํš ๋ถ„์„
  • ๋ฒŒํฌ ์—ฐ์‚ฐ ํ™œ์šฉ
  • ์„ธ์…˜ ์บ์‹ฑ ํ™œ์šฉ
  • ๋ถ€๋ถ„ ์ปฌ๋Ÿผ ์„ ํƒ์  ๋กœ๋“œ
  • ๊ด€๊ณ„ ๋กœ๋”ฉ ์ตœ์ ํ™”
  • ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ
  • ์ธ๋ฑ์Šค ํšจ์œจ์  ํ™œ์šฉ
  • ์ปค์Šคํ…€ ์ตœ์ ํ™” ์ „๋žต


7๏ธโƒฃ ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ†ตํ•ฉ

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='[email protected]')
        self.assertIsNotNone(user.id)
        self.assertEqual(user.name, 'ํ…Œ์ŠคํŠธ')
    
    def test_get_user(self):
        user = self.repo.create(name='ํ…Œ์ŠคํŠธ', email='[email protected]')
        fetched_user = self.repo.get(user.id)
        self.assertEqual(fetched_user.name, 'ํ…Œ์ŠคํŠธ')
    
    def test_update_user(self):
        user = self.repo.create(name='ํ…Œ์ŠคํŠธ', email='[email protected]')
        updated_user = self.repo.update(user.id, name='์ˆ˜์ •๋จ')
        self.assertEqual(updated_user.name, '์ˆ˜์ •๋จ')
    
    def test_delete_user(self):
        user = self.repo.create(name='ํ…Œ์ŠคํŠธ', email='[email protected]')
        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 ๋“ฑ ์ „๋ฌธ ๋„๊ตฌ ํ™œ์šฉ
    • ๋ฒ„์ „ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ๊ณผ ํ†ตํ•ฉ
    • ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ…Œ์ŠคํŠธ ์ž๋™ํ™”
    • ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œ ๊ฒฝ๋กœ ์œ ์ง€
  • ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ
    • ์›์ž์  ์—ฐ์‚ฐ์— ํŠธ๋žœ์žญ์…˜ ์‚ฌ์šฉ
    • ๋ช…์‹œ์  ์ปค๋ฐ‹/๋กค๋ฐฑ ๊ด€๋ฆฌ
    • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์™€ ๊ฒฐํ•ฉ
    • ์ค‘์ฒฉ ํŠธ๋žœ์žญ์…˜ ์ดํ•ด


โš ๏ธ **GitHub.com Fallback** โš ๏ธ