ORM - SQLAlchemy

Kjjeddยท2026๋…„ 1์›” 26์ผ

ORM

๋ชฉ๋ก ๋ณด๊ธฐ
6/8
post-thumbnail

๐Ÿ SQLAlchemy ๊ธฐ๋ณธ ๋ฌธ๋ฒ• (ํ•œ ๋ฒˆ์— ๋๋‚ด๊ธฐ)

1) SQLAlchemy๋Š” ๋ฌด์—‡์„ ํ•ด๊ฒฐํ•˜๋‚˜? ๐Ÿงช

SQLAlchemy๋Š” Python ๊ฐ์ฒด๋กœ ์ž‘์—…ํ•˜๋ฉด, ๋‚ด๋ถ€์—์„œ SQL์„ ์ž๋™ ์ƒ์„ฑํ•ด DB์™€ ๋Œ€ํ™”ํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค.
์ฆ‰, โ€œ๊ฐ์ฒด์ง€ํ–ฅ ์„ธ๊ณ„โ€์™€ โ€œ๊ด€๊ณ„ํ˜• DB ์„ธ๊ณ„โ€ ์‚ฌ์ด์˜ ๋ฒˆ์—ญ(๋งคํ•‘)์„ ์ž๋™ํ™”ํ•œ๋‹ค.

๐Ÿ“Œ ์ „ํ†ต์ ์ธ ๋ฐฉ์‹ vs ORM ๋ฐฉ์‹

๊ตฌ๋ถ„ ์ง์ ‘ SQL ORM (SQLAlchemy)
์ฟผ๋ฆฌ ์ž‘์„ฑ SQL ๋ฌธ์ž์—ด์„ ์ง์ ‘ ์ž‘์„ฑ Python ์ฝ”๋“œ(๋ชจ๋ธ/์ฟผ๋ฆฌ ๊ฐ์ฒด)๋กœ ์ž‘์„ฑ
๊ฒฐ๊ณผ ํ˜•ํƒœ ํŠœํ”Œ/๋”•์…”๋„ˆ๋ฆฌ Python ๊ฐ์ฒด
๋ณ€๊ฒฝ ๋Œ€์‘ DB ๊ต์ฒด/์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ์‹œ ์ˆ˜์ •๋Ÿ‰ ํผ ์—ฐ๊ฒฐ ๋ฌธ์ž์—ด/๋ชจ๋ธ ์ค‘์‹ฌ์œผ๋กœ ๋Œ€์‘

2) Engine โ†’ Base/Model โ†’ Session โ†’ CRUD ๐Ÿ”„

ํ๋ฆ„๋„

[Python App]
   |
   v
+-------------------+
| Engine (DB ์—ฐ๊ฒฐ)   | create_engine("...")
+-------------------+
   |
   v
+-------------------+
| Session (๋Œ€ํ™”์ฐฝ๊ตฌ)  | Session(engine)
+-------------------+
   |
   v
+-------------------+
| Model (ํ…Œ์ด๋ธ” ๋งคํ•‘)  | class User(Base): ...
+-------------------+
   |
   v
+-------------------+
| CRUD + Commit/RB  | add/get/select/delete
+-------------------+

ํ•ต์‹ฌ ๊ด€์ฐฐ ํฌ์ธํŠธ โœ…
โ€ข Engine์€ โ€œDB๋กœ ๊ฐ€๋Š” ๊ธธโ€
โ€ข Session์€ โ€œDB์™€ ๋Œ€ํ™”ํ•˜๋Š” ์ฐฝ๊ตฌโ€์ด์ž โ€œ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ชจ์•„๋‘๋Š” ์ž‘์—…๋Œ€โ€
โ€ข Model์€ โ€œํ…Œ์ด๋ธ” โ†” ํด๋ž˜์Šคโ€ ๋งคํ•‘ ๊ทœ์น™
โ€ข commit()์ด ์‹คํ–‰๋˜๋Š” ์ˆœ๊ฐ„ INSERT/UPDATE/DELETE๊ฐ€ ์‹ค์ œ๋กœ ๋ฐ˜์˜๋จ


3) ์„ค์น˜ ๋ฐ ์ค€๋น„ ๐Ÿงฐ

ํ•„์š” ํŒจํ‚ค์ง€
โ€ข SQLAlchemy (ORM)
โ€ข DB ๋“œ๋ผ์ด๋ฒ„ (MySQL/MariaDB๋ฉด PyMySQL ๋“ฑ)

pip install sqlalchemy pymysql

4) ์—ฐ๊ฒฐ ๋ฌธ์ž์—ด(DSN) ๊ตฌ์กฐ ์ดํ•ดํ•˜๊ธฐ ๐Ÿ”Œ

์—ฐ๊ฒฐ ๋ฌธ์ž์—ด์€ ์ด๋Ÿฐ ํ˜•ํƒœ๋‹ค.

DB์ข…๋ฅ˜+๋“œ๋ผ์ด๋ฒ„://์‚ฌ์šฉ์ž:๋น„๋ฐ€๋ฒˆํ˜ธ@ํ˜ธ์ŠคํŠธ:ํฌํŠธ/DB์ด๋ฆ„
"mysql+pymysql://root:1234@localhost:3306/mydb",
์˜ˆ์‹œ ์กฐ๊ฐ ์˜๋ฏธ
mysql+pymysql MySQL + PyMySQL ๋“œ๋ผ์ด๋ฒ„ ์‚ฌ์šฉ
root:1234 ๊ณ„์ •/๋น„๋ฐ€๋ฒˆํ˜ธ
localhost:3306 ํ˜ธ์ŠคํŠธ/ํฌํŠธ
mydb DB ์Šคํ‚ค๋งˆ ์ด๋ฆ„

์ฃผ์˜ โš ๏ธ
๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ฝ”๋“œ์— ๋ฐ•์•„๋‘๋ฉด ๋ฐ”๋กœ ์‚ฌ๊ณ ๋กœ ์ด์–ด์ง„๋‹ค.
.env / OS env / Secrets Manager ๋“ฑ์œผ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ์Šต๊ด€์ด ์ค‘์š”!


5) Engine/Base/Model/Session/CRUD ํ•œ ๋ฒˆ์— ๐Ÿงฉ

DB๋Š” MySQL/MariaDB ๊ธฐ์ค€์ด๊ณ , DB๋ช…/๊ณ„์ •/๋น„๋ฒˆ์€ ํ™˜๊ฒฝ์— ๋งž๊ฒŒ ๋ฐ”๊ฟ”์•ผ ํ•œ๋‹ค.

# sqlalchemy_basic.py
# SQLAlchemy ๊ธฐ๋ณธ ํ๋ฆ„: Engine -> Base/Model -> Session -> CRUD
# ์‹คํ–‰ ์ „:
# pip install sqlalchemy pymysql

from datetime import datetime
from sqlalchemy import create_engine, Column, Integer, String, Boolean, DateTime, select
from sqlalchemy.orm import declarative_base, Session

# ============================================================
# 1) Engine: DB๋กœ ๊ฐ€๋Š” "์—ฐ๊ฒฐ ํ†ต๋กœ"
# ============================================================
# echo=True : SQLAlchemy๊ฐ€ ์ƒ์„ฑํ•œ SQL์„ ์ฝ˜์†”์— ์ฐ์–ด์ค€๋‹ค (ํ•™์Šต ์‹œ ์œ ์šฉ)
# pool_pre_ping=True : ์ฃฝ์€ ์ปค๋„ฅ์…˜์„ ์ž๋™ ๊ฐ์ง€ (์‹ค๋ฌด์—์„œ ๊ฝค ๋„์›€๋จ)
engine = create_engine(
    "mysql+pymysql://root:1234@localhost:3306/mydb",
    echo=True,
    pool_pre_ping=True,
)

# ============================================================
# 2) Base: ๋งคํ•‘ ์ž๋™ํ™”์˜ ์ถœ๋ฐœ์ 
# ============================================================
# Base๋ฅผ ์ƒ์†๋ฐ›๋Š” ํด๋ž˜์Šค๋Š” "ํ…Œ์ด๋ธ”๊ณผ ์—ฐ๊ฒฐ๋  ํ›„๋ณด"๊ฐ€ ๋œ๋‹ค
Base = declarative_base()

# ============================================================
# 3) Model: ํ…Œ์ด๋ธ”์„ ํด๋ž˜์Šค๋กœ ์ •์˜
# ============================================================
class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, autoincrement=True)
    username = Column(String(50), nullable=False)
    email = Column(String(255), unique=True, nullable=False)

    is_active = Column(Boolean, default=True, nullable=False)
    created_at = Column(DateTime, default=datetime.now, nullable=False)

    def __repr__(self):
        return f"User(id={self.id}, username='{self.username}', email='{self.email}')"

# ============================================================
# 4) ํ…Œ์ด๋ธ” ์ƒ์„ฑ: ๋ชจ๋ธ -> ์‹ค์ œ DB ๋ฐ˜์˜
# ============================================================
# create_all()์€ "์—†์„ ๋•Œ๋งŒ" ๋งŒ๋“ ๋‹ค
# ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ(์ปฌ๋Ÿผ ์ถ”๊ฐ€/์ˆ˜์ •/์‚ญ์ œ)์€ ๋ณดํ†ต Alembic ๊ฐ™์€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋„๊ตฌ๋กœ ํ•œ๋‹ค
Base.metadata.create_all(engine)

# ============================================================
# 5) Session: DB์™€ ๋Œ€ํ™”ํ•˜๋Š” ์ฐฝ๊ตฌ + ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ชจ์œผ๋Š” ์ž‘์—…๋Œ€(Unit of Work)
# ============================================================
# ํ•ต์‹ฌ:
# - add()/delete()๋Š” "์˜ˆ์•ฝ"์— ๊ฐ€๊น๋‹ค
# - commit()์—์„œ ์‹ค์ œ๋กœ INSERT/UPDATE/DELETE๊ฐ€ ๋ฐ˜์˜๋œ๋‹ค
# - ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ rollback()์ด ์•ˆ์ „๋ฒจํŠธ๋‹ค

def main():
    # -----------------------------
    # Create (INSERT)
    # -----------------------------
    with Session(engine) as session:
        try:
            user = User(username="jay", email="jay@example.com")
            session.add(user)

            # flush๋Š” "DB์— SQL์„ ๋ณด๋‚ด PK๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๋‹จ๊ณ„"๊นŒ์ง€ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค
            # commit ์ „์— user.id๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์œ ์šฉํ•˜๋‹ค
            session.flush()
            print("์ƒ์„ฑ๋œ PK:", user.id)

            session.commit()
        except Exception as e:
            session.rollback()
            raise e

    # -----------------------------
    # Read (SELECT)
    # -----------------------------
    with Session(engine) as session:
        # 1) PK๋กœ ๋‹จ์ผ ์กฐํšŒ
        found = session.get(User, 1)
        print("PK ์กฐํšŒ:", found)

        # 2) ์กฐ๊ฑด ์กฐํšŒ (SQLAlchemy 2.0 ์Šคํƒ€์ผ)
        stmt = select(User).where(User.username == "jay")
        user_jay = session.scalars(stmt).first()
        print("์กฐ๊ฑด ์กฐํšŒ:", user_jay)

        # 3) ์ „์ฒด ์กฐํšŒ
        stmt_all = select(User)
        users = session.scalars(stmt_all).all()
        print("์ „์ฒด ์กฐํšŒ:", users)

    # -----------------------------
    # Update (UPDATE)
    # -----------------------------
    with Session(engine) as session:
        try:
            target = session.get(User, 1)
            if target:
                target.username = "jay_edited"
                # ๋ณ€๊ฒฝ ๊ฐ์ง€๋Š” SQLAlchemy๊ฐ€ ํ•œ๋‹ค (dirty tracking)
                session.commit()
                print("์ˆ˜์ • ์™„๋ฃŒ:", target)
            else:
                print("์ˆ˜์ • ๋Œ€์ƒ ์—†์Œ")
        except Exception as e:
            session.rollback()
            raise e

    # -----------------------------
    # Delete (DELETE)
    # -----------------------------
    with Session(engine) as session:
        try:
            target = session.get(User, 1)
            if target:
                session.delete(target)
                session.commit()
                print("์‚ญ์ œ ์™„๋ฃŒ:", target)
            else:
                print("์‚ญ์ œ ๋Œ€์ƒ ์—†์Œ")
        except Exception as e:
            session.rollback()
            raise e

if __name__ == "__main__":
    main()
    

6) โ€œcommit() ์ „์—๋Š” DB์— ๋ฐ˜์˜ ์•ˆ ๋œ๋‹คโ€์˜ ์˜๋ฏธ ๐Ÿ’พ

add()๋Š” ๊ณง๋ฐ”๋กœ INSERT๊ฐ€ ์•„๋‹ˆ๋ผ โ€œ์„ธ์…˜ ์ž‘์—…๋Œ€์— ์˜ฌ๋ ค๋‘๋Š” ํ–‰์œ„โ€๋‹ค.

session.add(user)
=> [Session ์ž‘์—…๋Œ€]์— user๊ฐ€ ์˜ฌ๋ผ๊ฐ (์•„์ง DB ๋ฐ˜์˜ X)

session.flush()
=> INSERT SQL์ด ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ์Œ (PK ํ•„์š”ํ•  ๋•Œ ์œ ์šฉ)

session.commit()
=> ํŠธ๋žœ์žญ์…˜ ํ™•์ •, ์‹ค์ œ DB ๋ฐ˜์˜ O

์˜ˆ์™ธ ๋ฐœ์ƒ
=> session.rollback()์œผ๋กœ ๋˜๋Œ๋ฆผ

7) ์ปฌ๋Ÿผ ํƒ€์ž… ๋งคํ•‘ ๐Ÿงพ

SQL SQLAlchemy ์˜ˆ์‹œ
INT Integer Column(Integer)
VARCHAR(n) String(n) Column(String(100))
TEXT Text Column(Text)
BOOLEAN Boolean Column(Boolean, default=True)
DATETIME DateTime Column(DateTime, default=datetime.now)
FLOAT Float Column(Float)

์ปฌ๋Ÿผ ์˜ต์…˜

from sqlalchemy import Column, Integer, String, Boolean, DateTime
from datetime import datetime

class User(Base):
    __tablename__ = 'users'
    
    id = Column(Integer, primary_key=True, autoincrement=True)  # ์ž๋™ ์ฆ๊ฐ€
    username = Column(String(50), nullable=False)                # NOT NULL
    email = Column(String(255), unique=True)                     # UNIQUE
    is_active = Column(Boolean, default=True)                    # ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •
    created_at = Column(DateTime, default=datetime.now)          # ์ƒ์„ฑ ์‹œ๊ฐ„ ์ž๋™ ๊ธฐ๋ก

8) Session ์‚ฌ์šฉ๋ฒ•

session์€ DB์™€์˜ ๋Œ€ํ™” ์ฐฝ๊ตฌ์ด๋‹ค.

from sqlalchemy.orm import Session

# ๋ฐฉ๋ฒ• 1: with ๋ฌธ ์‚ฌ์šฉ (๊ถŒ์žฅ)
with Session(engine) as session:
    # ์ž‘์—… ์ˆ˜ํ–‰
    pass
    # with ๋ธ”๋ก ์ข…๋ฃŒ ์‹œ ์ž๋™์œผ๋กœ session ๋‹ซํž˜

######################################

# ๋ฐฉ๋ฒ• 2: ์ง์ ‘ ์ƒ์„ฑ/์ข…๋ฃŒ
session = Session(engine)
try:
    # ์ž‘์—… ์ˆ˜ํ–‰
    pass
finally:
    session.close()

9) ์ •๋ฆฌ โœ…

SQLAlchemy๋Š” โ€œ๊ฐ์ฒด๋กœ ์ž‘์—…ํ•˜๋ฉด SQL์„ ๋งŒ๋“ค์–ด์ฃผ๋Š” ORMโ€์ด๊ณ , ๊ธฐ๋ณธ ๋ผˆ๋Œ€๋Š” Engine โ†’ Base/Model โ†’ Session โ†’ CRUD๋‹ค.

  • Engine: DB ์—ฐ๊ฒฐ ์ •๋ณด
  • Base/Model: ํ…Œ์ด๋ธ” ๋งคํ•‘ ๊ทœ์น™
  • Session: ๋Œ€ํ™”์ฐฝ๊ตฌ + ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ถ”์ 
  • commit(): ์‹ค์ œ DB ๋ฐ˜์˜ ํŠธ๋ฆฌ๊ฑฐ
  • rollback(): ์˜ˆ์™ธ ์‹œ ์•ˆ์ „๋ฒจํŠธ

๋‹ค์Œ ํŒŒํŠธ์—์„œ ๋ณดํ†ต ์ด์–ด์ง€๋Š” ์ฃผ์ œ๋Š” relationship(), lazy/eager loading, ์„ธ์…˜ ์ƒ๋ช…์ฃผ๊ธฐ, ํŠธ๋žœ์žญ์…˜ ์ „๋žต์ด๋‹ค.
์ด๊ฑธ ๋ฐฐ์šฐ๋ฉด โ€œ์ (.) ์ ‘๊ทผ์ด ์™œ ์œ„ํ—˜ํ•  ์ˆ˜๋„ ์žˆ๋Š”์ง€โ€๊ฐ€ ์™„์ „ํžˆ ์ •๋ฆฌ๋œ๋‹ค.

profile
Gongbuhaja

0๊ฐœ์˜ ๋Œ“๊ธ€