Profile picture

[Python] ORM ์‹ค์Šต์œผ๋กœ ๊นŠ๊ฒŒ ํŒŒํ—ค์น˜๊ธฐ!

JaehyoJJAng2025๋…„ 01์›” 11์ผ

1. ORM์ด๋ž€? ๐Ÿค”

ORM (Object-Relational Mapping) ์˜ ํ•ต์‹ฌ ๊ฐœ๋…

ORM์€ ๊ฐ์ฒด-๊ด€๊ณ„ ๋งคํ•‘ ์˜ ์•ฝ์ž๋กœ, ํŒŒ์ด์ฌ์˜ ๊ฐ์ฒด์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”์„ ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค!


โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚          ๐Ÿ Python ์„ธ๊ณ„ (๊ฐ์ฒด ์ง€ํ–ฅ)          โ”‚
โ”‚                                              โ”‚
โ”‚  class User:                                โ”‚
โ”‚      id = 1                                 โ”‚
โ”‚      name = "ํ™๊ธธ๋™"                         โ”‚
โ”‚      email = "hong@example.com"             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                    โ†•๏ธ ORM์ด ์ž๋™ ๋ณ€ํ™˜
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚        ๐Ÿ’พ Database ์„ธ๊ณ„ (๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ)      โ”‚
โ”‚                                              โ”‚
โ”‚  users ํ…Œ์ด๋ธ”:                               โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”        โ”‚
โ”‚  โ”‚ id โ”‚ name   โ”‚ email            โ”‚        โ”‚
โ”‚  โ”œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค        โ”‚
โ”‚  โ”‚ 1  โ”‚ ํ™๊ธธ๋™  โ”‚ hong@example.com โ”‚        โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๋งคํ•‘ ๊ด€๊ณ„ ์ดํ•ดํ•˜๊ธฐ!

Python (๊ฐ์ฒด์ง€ํ–ฅ) โ†”๏ธ Database (๊ด€๊ณ„ํ˜•)
Class (ํด๋ž˜์Šค) = Table (ํ…Œ์ด๋ธ”)
Object (๊ฐ์ฒด) = Row (ํ–‰)
Attribute (์†์„ฑ) = Column (์ปฌ๋Ÿผ)

๐Ÿ’ก ์‰ฝ๊ฒŒ ์ดํ•ดํ•˜๊ธฐ

Python์—์„œ user = User(name="ํ™๊ธธ๋™") ์ด๋ผ๊ณ  ์“ฐ๋ฉด,

ORM์ด ์ž๋™์œผ๋กœ INSERT INTO users (name) VALUES ('ํ™๊ธธ๋™') SQL์„ ์ƒ์„ฑํ•ด์„œ ์‹คํ–‰ํ•ด์ฃผ๋Š”๊ฑฐ์ฃ !



2. ORM์„ ์“ฐ๋Š” ์ด์œ ?

Rawl SQL์˜ ๋ฌธ์ œ์ 

Raw SQL์˜ ๋ฌธ์ œ์ ์ด ๋ญ”์ง€๋ถ€ํ„ฐ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

1. SQL ๋ฌธ์ž์—ด์„ ์ง์ ‘ ์ž‘์„ฑํ•ด์•ผ ํ•จ

cursor.execute("INSERT INTO users (name, email) VALUES ('ํ™๊ธธ๋™', 'hong@example.com')")

2. ๐Ÿ˜ฑ ํƒ€์ž… ์‹ค์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฌ์›€

cursor.execute("INSERT INTO users (age) VALUES ('์Šค๋ฌผ๋‹ค์„ฏ')")  # ์—๋Ÿฌ!

3. ๐Ÿ˜ฑ SQL ์ธ์ ์…˜ ์œ„ํ—˜

user_input = "'; DROP TABLE users; --"
cursor.execute(f"SELECT * FROM users WHERE name = '{user_input}'")  # ์œ„ํ—˜!

4. ๐Ÿ˜ฑ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ ์‹œ ๋ชจ๋“  SQL ์ˆ˜์ • ํ•„์š”

  • SQLite โ†’ PostgreSQL ์ด๋™ ์‹œ ๋ฌธ๋ฒ• ์ฐจ์ด๋กœ ๋Œ€๊ทœ๋ชจ ์ˆ˜์ •

ORM์˜ ์žฅ์ 

1. ํŒŒ์ด์ฌ ๊ฐ์ฒด๋กœ ์ง๊ด€์ ์œผ๋กœ ์ž‘์—… ๊ฐ€๋Šฅ

user = User(name='ํ™๊ธธ๋™', email='hong@example.com')
db.session.add(user)
db.session.commit()

2. ํƒ€์ž… ์ฒดํฌ ์ž๋™!

user.age = "์Šค๋ฌผ๋‹ค์„ฏ"  # IDE๊ฐ€ ๋ฏธ๋ฆฌ ๊ฒฝ๊ณ !

3. SQL ์ธ์ ์…˜ ์ž๋™ ๋ฐฉ์ง€

User.query.filter_by(name=user_input).first()  # ์•ˆ์ „!

4. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ณ€๊ฒฝ์— ๊ฐ•ํ•จ

  • SQLite -> PostgreSQL๋กœ ๋ฐ”๊ฟ”๋„ ์ฝ”๋“œ ์ˆ˜์ • ์ตœ์†Œํ™” ๊ฐ€๋Šฅ

3. ์‹ค์Šต ํ™˜๊ฒฝ ์ค€๋น„

์ด๋ก ๋งŒ ๋ฐฑ๋‚  ๋“ค์–ด๋„ ํ•œ ๋ฒˆ์˜ ์‹ค์Šต๋งŒ ๋ชปํ•ฉ๋‹ˆ๋‹ค!


ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

orm_tutorial/
โ”œโ”€โ”€ requirements.txt           # ํ•„์š”ํ•œ ํŒจํ‚ค์ง€
โ”œโ”€โ”€ 1_raw_sql_crud.py         # Raw SQL ๋ฐฉ์‹
โ”œโ”€โ”€ 2_orm_crud.py             # ORM ๋ฐฉ์‹
โ”œโ”€โ”€ 3_comparison.py           # ๋น„๊ต ์‹ค์Šต
โ”œโ”€โ”€ raw_sql_database.db       # Raw SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
โ””โ”€โ”€ orm_database.db           # ORM ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค

ํŒจํ‚ค์ง€ ์„ค์น˜

1) requirements.txt ์ƒ์„ฑ

Flask-SQLAlchemy==3.1.1

4. Raw SQL๋กœ CRUD ๊ตฌํ˜„ํ•˜๊ธฐ

4-1. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ๊ด€๋ฆฌ

Rawl SQL ๋ฐฉ์‹์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ์ง์ ‘ ๊ด€๋ฆฌํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค ..!

import sqlite3
from datetime import datetime


class DatabaseManager:
    def __init__(self, db_name: str = "test.db"):
        self.db_name = db_name
        self.connection: None | sqlite3.Connection = None
        self.cursor: None | sqlite3.Cursor = None

    def connect(self) -> None:
        """๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ"""
        # SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๊ฒฐ
        self.connection = sqlite3.connect(self.db_name)

        # Row ํƒ€์ž…์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ์ฒ˜๋ฆฌ
        self.connection.row_factory = sqlite3.Row

        # cursor: SQL ๋ช…๋ น ์‹คํ–‰ํ•˜๋Š” ๊ฐ์ฒด
        self.cursor = self.connection.cursor()

        print(f"โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ: {self.db_name}")

    def disconnect(self) -> None:
        """๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ข…๋ฃŒ"""
        if self.connection:
            self.connection.close()
            print(f"โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ข…๋ฃŒ")

  • connection: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ ์—ฐ๊ฒฐ
  • cursor: SQL ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๋Š” ๋„๊ตฌ
  • row_factory: ๊ฒฐ๊ณผ๋ฅผ ์–ด๋–ค ํ˜•ํƒœ๋กœ ๋ฐ›์„์ง€ ์„ค์ •

ํ•œ ๋ฒˆ ์‹คํ–‰ํ•ด๋ณผ๊นŒ์š”?

if __name__ == "__main__":
    dbm = DatabaseManager()
    dbm.connect()
    dbm.disconnect()
โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ: test.db
โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ข…๋ฃŒ

4-2. ํ…Œ์ด๋ธ” ์ƒ์„ฑ

def create_table(self) -> None:
    """users ํ…Œ์ด๋ธ” ์ƒ์„ฑ"""

    # users ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์ฟผ๋ฆฌ ์ง์ ‘ ์ƒ์„ฑ
    create_table_sql = """
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            email TEXT UNIQUE NOT NULL,
            age INTEGER,
            created_at TEXT NOT NULL
        )
    """
    print(f"\nํ…Œ์ด๋ธ” ์ƒ์„ฑ SQL:\n{create_table_sql}")

    # SQL ์‹คํ–‰
    self.cursor.execute(create_table_sql)

    # ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ
    self.connection.commit()

    print("โœ… users ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์™„๋ฃŒ!")

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ

commit()์„ ํ•˜์ง€ ์•Š์œผ๋ฉด ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์ €์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค!


4-3. CREATE - ์‚ฌ์šฉ์ž ์ƒ์„ฑ

def create_user(self, name: str, email: str, age: int | None = None) -> str | None:
    """์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž ์ƒ์„ฑ (Create)"""
    print(f"\n์‚ฌ์šฉ์ž ์ƒ์„ฑ: {name}, {email}")

    created_at = datetime.now().isoformat()

    # SQL Insert ๋ฌธ ์ž‘์„ฑ
    # ?๋Š” ํ”Œ๋ ˆ์ด์Šคํ™€๋” (๊ฐ’์ด ๋“ค์–ด๊ฐˆ ์ž๋ฆฌ)
    insert_sql = """
        INSERT INTO users (name, email, age, created_at)
        VALUES (?, ?, ?, ?)
    """

    print(f"๐Ÿ“ ์‹คํ–‰ SQL:\n{insert_sql}")
    print(f"๐Ÿ“ ๊ฐ’: {(name, email, age, created_at)}")

    try:
        # SQL ์‹คํ–‰ - ํ”Œ๋ ˆ์ด์Šคํ™€๋”์— ๊ฐ’์„ ์ „๋‹ฌ
        self.cursor.execute(insert_sql, (name, email, age, created_at))

        # ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ €์žฅ
        self.connection.commit()

        # ๋ฐฉ๊ธˆ ์ธ์„œํŠธ๋œ ํ–‰์˜ ID ๊ฐ€์ ธ์˜ค๊ธฐ
        user_id = self.cursor.lastrowid

        print(f"โœ… ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์™„๋ฃŒ! ID: {user_id}")
        return user_id
    except sqlite3.IntegrityError as e:
        print(f"โŒ ์—๋Ÿฌ: {str(e)}")
        return None

๐Ÿ’ก ํ”Œ๋ ˆ์ด์Šคํ™€๋” (?) ์‚ฌ์šฉ ์ด์œ 

?๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด SQL ์ธ์ ์…˜์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‚˜์œ ์˜ˆ: f"INSERT INTO users VALUES ('{name}')"

์ข‹์€ ์˜ˆ: cursor.execute("INSERT INTO users VALUES (?)", (name,))


์‚ฌ์šฉ์ž๋ฅผ ํ•œ ๋ฒˆ ์ƒ์„ฑํ•ด๋ด…์‹œ๋‹ค.

if __name__ == "__main__":
    dbm = DatabaseManager()

    # DB ์—ฐ๊ฒฐ
    dbm.connect()

    # ํ…Œ์ด๋ธ” ์ƒ์„ฑ
    dbm.create_table()

    # ์œ ์ € ์ƒ์„ฑ (Create)
    user_id: str = dbm.create_user(
        name="jh", email="jh@jh.com", age=29
    )
์‚ฌ์šฉ์ž ์ƒ์„ฑ: jh, jh@jh.com
๐Ÿ“ ์‹คํ–‰ SQL:

            INSERT INTO users (name, email, age, created_at)
            VALUES (?, ?, ?, ?)
        
๐Ÿ“ ๊ฐ’: ('jh', 'jh@jh.com', 29, '2025-11-09T08:33:44.539447')

4-4. READ - ์‚ฌ์šฉ์ž ์กฐํšŒ

def get_user_by_id(self, user_id: int) -> dict|None:
    """ID๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ"""
    print(f"๐Ÿ” ID๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ: {user_id}")

    select_sql = """
        SELECT id, name, email, age, created_at FROM users WHERE id = ?
    """

    # SQL ์‹คํ–‰
    self.cursor.execute(select_sql, (user_id,))

    # ๊ฒฐ๊ณผ ๊ฐ€์ ธ์˜ค๊ธฐ
    row = self.cursor.fetchone()
    
    if row:
        user = dict(row) # Row๋ฅผ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ๋ณ€ํ™˜
        print(f"์‚ฌ์šฉ์ž ์ฐพ์Œ!: {user}")
        return user
    else:
        print(f"์‚ฌ์šฉ์ž ๋ชป์ฐพ์Œ!")
        return None

๐Ÿ’ก fetch ๋ฉ”์†Œ๋“œ ์ข…๋ฅ˜

  • fetchone(): ์ฒซ ๋ฒˆ์งธ ๊ฒฐ๊ณผ๋งŒ ๋ฐ˜ํ™˜
  • fetchall(): ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜
  • fetchmany(n): n๊ฐœ์˜ ๊ฒฐ๊ณผ๋งŒ ๋ฐ˜ํ™˜

์œ„ ์ฝ”๋“œ์—์„œ ์ถ”๊ฐ€๋กœ ํ™•์ธํ•ด๋ณผ๊ฒŒ ๋ญ๊ฐ€ ์žˆ์„๊นŒ์š”?


์ด์ „์— DatabaseManager ํด๋ž˜์Šค์—์„œ SQLite3์˜ connection ๊ฐ์ฒด์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์†์„ฑ์„ ๋„ฃ์—ˆ๋˜ ๊ฒƒ์ด ๊ธฐ์–ต๋‚˜์‹ญ๋‹ˆ๊นŒ?

# Row ํƒ€์ž…์œผ๋กœ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ์ฒ˜๋ฆฌ
self.connection.row_factory = sqlite3.Row

์—ฌ๊ธฐ์„œ ์œ„ ์†์„ฑ์„ ์ฃผ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  get_user_by_id ๋ฉ”์†Œ๋“œ์—์„œ row ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•ด๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐ์ดํ„ฐ๊ฐ€ ๋„˜์–ด์˜ต๋‹ˆ๋‹ค.

(1, 'jhlee37', 'jhlee37@cafe24corp.com', 29, '2025-11-09T08:00:46.327799')

๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•ด๋ดค์„ ๋•Œ ๋„˜์–ด์˜จ ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ค ์ปฌ๋Ÿผ์˜ ๋ฐ์ดํ„ฐ์ธ์ง€ ์•Œ์•„๋ณด๊ธฐ๊ฐ€ ์‰ฝ์ง€ ์•Š์ฃ ?


๊ทธ๋Ÿผ ๋‹ค์‹œ ์ฃผ์„ ์ฒ˜๋ฆฌ ํ–ˆ๋˜ self.connection.row_factory = sqlite3.Row๋ฅผ ์ฃผ์„ ํ•ด์ œํ•˜๊ณ  ๋‹ค์‹œ ์‹คํ–‰ํ•ด๋ณด๋ฉด?

{'id': 1, 'name': 'jhlee37', 'email': 'jhlee37@cafe24corp.com', 'age': 29, 'created_at': '2025-11-09T08:00:46.327799'}

์ด๋Ÿฐ ์‹์œผ๋กœ row ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณด๊ธฐ ์ข‹๊ฒŒ dict ํ˜•ํƒœ๋กœ ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!



๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒํ•˜๊ธฐ

์œ„ ์˜ˆ์‹œ๋Š” ๋‹จ์ผ ์‚ฌ์šฉ์ž๋งŒ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์—ˆ์ฃ ?


์ „์ฒด ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, fetchall()๋กœ ์ฟผ๋ฆฌ์— ๋Œ€ํ•œ ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•˜์—ฌ ๊ฐ€์ ธ์˜ค๋ฉด ๋ฉ๋‹ˆ๋‹ค.

def get_all_users(self) -> list[dict]:
    """๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ"""
    print("\n๐Ÿ“‹๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ")

    select_sql = """
    SELECT id, name, email, age, created_at FROM users ORDER BY id desc
    """
    self.cursor.execute(select_sql)

    # ๋ชจ๋“  ๊ฒฐ๊ณผ ํ–‰ ๊ฐ€์ ธ์˜ค๊ธฐ
    rows = self.cursor.fetchall()

    # ๊ฐ ํ–‰์„ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ์ „ํ™˜
    users = [dict(row) for row in rows]

    print(f"โœ… {len(users)} ๋ช…์˜ ์œ ์ €๋ฅผ ์ฐพ์Œ.")
    return users


4-5. UPDATE - ์‚ฌ์šฉ์ž ์ˆ˜์ •

def update_user(
    self, user_id: int, name: str = None, email: str = None, age: int = None
) -> bool:
    """์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •"""

    print(f"โœ๏ธ  ์‚ฌ์šฉ์ž ์ˆ˜์ •: ID {user_id}")

    # ์ˆ˜์ •ํ•  ํ•„๋“œ ๋ชฉ๋ก
    update_fields = []
    update_values = []

    # ์ „๋‹ฌ๋œ ๊ฐ’๋งŒ ์—…๋ฐ์ดํŠธ ๋ชฉ๋ก์— ์ถ”๊ฐ€
    if name is not None:
        update_fields.append("name = ?")
        update_values.append(name)

    if email is not None:
        update_fields.append("email = ?")
        update_values.append(email)

    if age is not None:
        update_fields.append("age = ?")
        update_values.append(age)

    # ์ˆ˜์ •ํ•  ๋‚ด์šฉ์ด ์—†๋Š” ๊ฒฝ์šฐ
    if not update_fields:
        print("โš ๏ธ ์ˆ˜์ •ํ•  ๋‚ด์šฉ์ด ์—†์Šต๋‹ˆ๋‹ค.")
        return False

    # WHERE ์ ˆ์˜ user_id๋„ ์ถ”๊ฐ€
    update_values.append(user_id)

    # SQL Update ๋ฌธ ๋™์  ์ƒ์„ฑ
    update_sql = f"""
        UPDATE users
        SET {", ".join(update_fields)}
        WHERE id = ?
    """

    print(f"์‹คํ–‰ํ•  SQL๋ฌธ!\n{update_sql}")
    print(f"๊ฐ’: {tuple(update_values)}")
    try:
        self.cursor.execute(update_sql, tuple(update_values))
        self.connection.commit()

        # ์‹ค์ œ๋กœ ์ˆ˜์ •๋œ ํ–‰์˜ ๊ฐœ์ˆ˜
        if self.cursor.rowcount > 0:
            print(f"โœ… ์‚ฌ์šฉ์ž ์ˆ˜์ • ์™„๋ฃŒ! (์—…๋ฐ์ดํŠธ๋œ ํ–‰: {self.cursor.rowcount})")
            return True
        else:
            print(f"โŒ ํ•ด๋‹น ID์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์—†์Œ!")
            return False

    except sqlite3.IntegrityError as e:
        print(f"์—๋Ÿฌ: {str(e)}")
        return False

id๊ฐ€ 4๋ฒˆ์ธ ์‚ฌ์šฉ์ž๋ฅผ ์ˆ˜์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

if __name__ == "__main__":
    # ...    
    # ์œ ์ € ์ •๋ณด ์—…๋ฐ์ดํŠธ
    dbm.update_user(user_id=4, name="up_jhlee37", email="up_jhlee37@com", age=30)
    dbm.get_user_by_id(user_id=4)  # ์ˆ˜์ •๋๋Š”์ง€ ํ™•์ธ

์ถœ๋ ฅ ๊ฐ’์„ ํ™•์ธํ•ด๋ณผ๊นŒ์š”?

โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ: test.db
โœ… users ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์™„๋ฃŒ!
โœ๏ธ  ์‚ฌ์šฉ์ž ์ˆ˜์ •: ID 4
์‹คํ–‰ํ•  SQL๋ฌธ!

            UPDATE users
            SET name = ?, email = ?, age = ?
            WHERE id = ?
        
๊ฐ’: ('up_jhlee37', 'up_jhlee37@com', 30, 4)
โœ… ์‚ฌ์šฉ์ž ์ˆ˜์ • ์™„๋ฃŒ! (์—…๋ฐ์ดํŠธ๋œ ํ–‰: 1)
๐Ÿ” ID๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ: 4
์œ ์ € ์ฐพ์Œ!: {'id': 4, 'name': 'up_jhlee37', 'email': 'up_jhlee37@com', 'age': 30, 'created_at': '2025-11-15T07:25:41.487555'}
โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ข…๋ฃŒ

์ •์ƒ์ ์œผ๋กœ id๊ฐ€ 4๋ฒˆ์ธ ์œ ์ €์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์ˆ˜์ •๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!



4-6. DELETE - ์‚ฌ์šฉ์ž ์‚ญ์ œ

๋งˆ์ง€๋ง‰์œผ๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•ด๋ณด๋ฉฐ, Raw SQL๋กœ CRUD๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์ฑ•ํ„ฐ๋ฅผ ๋งˆ๋ฌด๋ฆฌํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

def delete_user(self, user_id: int) -> bool:
    """์‚ฌ์šฉ์ž ์‚ญ์ œ"""
    print(f"\n๐Ÿ—‘๏ธ ์‚ฌ์šฉ์ž ์‚ญ์ œ: ID {user_id}")

    delete_sql = """
    DELETE FROM users
    WHERE id = ?
    """

    self.cursor.execute(delete_sql, (user_id,))
    self.connection.commit()

    if self.cursor.rowcount > 0:
        print(f"โœ… ์‚ฌ์šฉ์ž ์‚ญ์ œ ์™„๋ฃŒ! (์‚ญ์ œ๋œ ํ–‰: {self.cursor.rowcount})")
        return True
    else:
        print(f"โŒ ํ•ด๋‹น ID์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์—†์Œ!")
        return False

id๊ฐ€ 3๋ฒˆ์ธ ์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

if __name__ == "__main__":
    # ...    
    dbm.delete_user(user_id=3)

    # ์œ ์ €๊ฐ€ ์‚ญ์ œ๋๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ.
    users = dbm.get_all_users()
    print(users)

์ถœ๋ ฅ ๊ฐ’์„ ํ™•์ธํ•ด๋ด…์‹œ๋‹ค.

โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ: test.db
โœ… users ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์™„๋ฃŒ!

๐Ÿ—‘๏ธ ์‚ฌ์šฉ์ž ์‚ญ์ œ: ID 3
โœ… ์‚ฌ์šฉ์ž ์‚ญ์ œ ์™„๋ฃŒ! (์‚ญ์ œ๋œ ํ–‰: 1)

๐Ÿ“‹๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ
โœ… 1 ๋ช…์˜ ์œ ์ €๋ฅผ ์ฐพ์Œ.
[{'id': 2, 'name': 'jhlee38', 'email': 'jhlee38@cafe24corp.com', 'age': 29, 'created_at': '2025-11-09T08:02:06.148338'}]
โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ข…๋ฃŒ

id๊ฐ€ 3๋ฒˆ์ธ ์‚ฌ์šฉ์ž๊ฐ€ ์‚ญ์ œ๋˜์–ด, ๋”์ด์ƒ ์กฐํšŒ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.



5. ORM์œผ๋กœ CRUD ๊ตฌํ˜„ํ•˜๊ธฐ

์ด์ œ Raw SQL๋กœ ๊ตฌํ˜„ํ•œ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ORM์œผ๋กœ ๊ตฌํ˜„ํ•ด๋ณผ ๊ฒ๋‹ˆ๋‹ค.

์ฝ”๋“œ๊ฐ€ ์–ผ๋งˆ๋‚˜ ๊ฐ„๊ฒฐํ•ด์ง€๋Š”์ง€ ํ•œ ๋ฒˆ ๋น„๊ตํ•ด๋ด…์‹œ๋‹ค.



5-0. ๋ชจ๋“ˆ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import sessionmaker

ํ˜„์žฌ ๋ถˆ๋Ÿฌ์˜จ ํ•จ์ˆ˜์™€ ํด๋ž˜์Šค์— ๋Œ€ํ•œ ์„ค๋ช…์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๋ชจ๋“ˆ๋ช… ์„ค๋ช…
create_engine ํ•จ์ˆ˜ - SQLAlchemy์—์„œ DB์™€์˜ ์—ฐ๊ฒฐ์„ ์„ค์ •ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
- ์—ฐ๊ฒฐ ๋ฌธ์ž์—ด์„ ๋ฐ›์•„์„œ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—ฐ๊ฒฐํ•˜๋Š” ์—”์ง„(Engine) ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
- ์˜ˆ์‹œ) create_engine("mysql+pymysql://...")
declarative_base ํ•จ์ˆ˜ - ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ๋ณธ ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
- ์ด ๊ธฐ๋ณธ ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›์€ ํด๋ž˜์Šค๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”์„ ๋‚˜ํƒ€๋‚ด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
- Base = declarative_base()์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค
Column ํด๋ž˜์Šค - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”์—์„œ ๊ฐ ์—ด(Column)์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
์˜ˆ์‹œ) id = Column(Integer, primary_key=True)
sessionmaker ํด๋ž˜์Šค - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ ์„ธ์…˜์„ ์ƒ์„ฑํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
- ์„ธ์…˜์€ ํŠธ๋žœ์žญ์…˜๊ณผ ๊ด€๋ จ๋œ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ ์ƒํ˜ธ ์ž‘์šฉ๋„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.
์˜ˆ์‹œ) SessionLocal = sessionmaker(bind=engine) (engine์— ๋ฐ”์ธ๋”ฉ๋œ ์„ธ์…˜ ์—ฐ๊ฒฐ)


5-1. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐํ•˜๊ธฐ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๊ฒฝ์šฐ ์ด์ „์—๋Š” sqlite3๋ฅผ ์‚ฌ์šฉํ•˜์˜€์œผ๋‚˜,

์ด๋ฒˆ ์‹ค์Šต์—์„œ๋Š” MySQL๋กœ ์ „ํ™˜ํ•˜์—ฌ ์‹ค์Šต์„ ์ง„ํ–‰ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import sessionmaker
from datetime import datetime

# ํƒ€์ž…ํžŒํŠธ ์ฒ˜๋ฆฌ์šฉ ๋ชจ๋“ˆ
import sqlalchemy.orm as orm
import sqlalchemy.exc as exc
import sqlalchemy.engine as eng

# Base ํด๋ž˜์Šค ์ƒ์„ฑ
# ๋ชจ๋“  ORM์€ ์ด Base๋ฅผ ์ƒ์†๋ฐ›์Œ.
Base: orm.decl_api.DeclarativeMeta = declarative_base()

# MySQL ์—ฐ๊ฒฐ ์ •๋ณด
DB_USER: str = "root"
DB_PASS: str = "custom"
DB_HOST: str = "192.168.219.110"
DB_PORT: int = 3333
DB_NAME: str = "orm"

# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—”์ง„ ์ƒ์„ฑ
try:
    engine: eng.base.Engine = create_engine(
        f"mysql+pymysql://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}",
        echo=True,  # SQL ์ฟผ๋ฆฌ๋ฌธ์„ ์ž๋™์œผ๋กœ ์ถœ๋ ฅํ•ด์คŒ (ํ•™์Šต์šฉ)
    )

except exc.ArgumentError as e:
    print(f"[๐Ÿ”ด] Argument Error!\nReason: {str(e)}")

# Session ํด๋ž˜์Šค ์ƒ์„ฑ
# Session: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ ๋Œ€ํ™” (conversation)
SessionLocal: orm.session.sessionmaker = sessionmaker(bind=engine)


5-2. ํด๋ž˜์Šค(ํ…Œ์ด๋ธ”) ์ƒ์„ฑํ•˜๊ธฐ

ORM ๋ชจ๋ธ ์ •์˜ํ•˜๊ธฐ (ํ…Œ์ด๋ธ” ์Šคํ‚ค๋งˆ ์ •์˜)


# ------------------ ORM ๋ชจ๋ธ ์ •์˜ ------------------
class User(Base):
    """
    User ํ…Œ์ด๋ธ”์„ ๋‚˜ํƒ€๋‚ด๋Š” ORM ๋ชจ๋ธ

    User ํด๋ž˜์Šค:
    - ํ…Œ์ด๋ธ” ๊ตฌ์กฐ๋ฅผ ์ •์˜
    - ๊ฐ ์ธ์Šคํ„ด์Šค๊ฐ€ ํ…Œ์ด๋ธ”์˜ ํ•œ ํ–‰(row)์„ ๋‚˜ํƒ€๋ƒ„
    - Python ๊ฐ์ฒด์ฒ˜๋Ÿผ ๋‹ค๋ฃฐ ์ˆ˜ ์žˆ์Œ.
    """

    # ํ…Œ์ด๋ธ” ์ด๋ฆ„ ์ง€์ •
    __tablename__ = "users"

    # ์ปฌ๋Ÿผ ์ •์˜
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), nullable=False)
    email = Column(String(100), nullable=False, unique=True)
    age = Column(Integer)
    created_at = Column(DateTime, default=datetime.utcnow)

    def __repr__(self) -> str:
        """๊ฐ์ฒด๋ฅผ ๋ฌธ์ž์—ด๋กœ ํ‘œํ˜„ํ•จ"""
        return f"<User(id={self.id}, name='{self.name}', email='{self.email}')>"

    def to_dict(self) -> dict:
        """๊ฐ์ฒด๋ฅผ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ๋ณ€ํ™˜"""
        return {
            "id": self.id,
            "name": self.name,
            "email": self.email,
            "age": self.age,
            "created_at": self.created_at,
        }

๐Ÿ’ก ORM ๋ชจ๋ธ์˜ ๊ตฌ์กฐ

  • __tablename__: ํ…Œ์ด๋ธ” ์ด๋ฆ„
  • Column(): ๊ฐ ์ปฌ๋Ÿผ ์ •์˜
  • primary_key=True: ๊ธฐ๋ณธ ํ‚ค ์„ค์ •
  • nullable=False: NOT NULL ์ œ์•ฝ
  • unique=True: UNIQUE ์ œ์•ฝ
  • default: ๊ธฐ๋ณธ๊ฐ’ ์„ค์ •

ํ…Œ์ด๋ธ” ์ƒ์„ฑ

# ------------------ ํ…Œ์ด๋ธ” ์ƒ์„ฑ------------------
def create_tables() -> None:
    """์ •์˜๋œ ๋ชจ๋“  ๋ชจ๋ธ์˜ ํ…Œ์ด๋ธ” ์ƒ์„ฑ"""
    print("\n๐Ÿ“ ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์ค‘ ...")
    # Base.metadata.create_all():
    # - Base๋ฅผ ์ƒ์†๋ฐ›์€ ๋ชจ๋“  ํด๋ž˜์Šค๋ฅผ ์ฐพ์Œ
    # - ๊ฐ ํด๋ž˜์Šค์— ๋Œ€์‘ํ•˜๋Š” ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑ
    # - ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํ…Œ์ด๋ธ”์€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Œ
    Base.metadata.create_all(bind=engine)
    print("โœ… ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์™„๋ฃŒ!")

5-3. CREATE - ์‚ฌ์šฉ์ž ์ƒ์„ฑ

# ------------------ CRUD ------------------
class UserCRUD:
    """ORM์„ ์‚ฌ์šฉํ•œ User CRUD ์ž‘์—…"""

    def __init__(self):
        self.session: orm.session.sessionmaker = SessionLocal()

    def create_user(self, name: str, email: str, age: int = None) -> None | User:
        """
        ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž ์ƒ์„ฑ

        ORM ๋ฐฉ์‹:
        1. User ๊ฐ์ฒด ์ƒ์„ฑ (๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์กด์žฌ)
        2. session.add()๋กœ ์„ธ์…˜์— ์ถ”๊ฐ€
        3. session.commit()์œผ๋กœ DB์— ์‹ค์ œ ์ €์žฅ
        """

        print(f"\nโž• ์‚ฌ์šฉ์ž ์ƒ์„ฑ: {name}, {email}")

        # 1. User ๊ฐ์ฒด ์ƒ์„ฑ
        # ์ด ์‹œ์ ์—์„œ๋Š” ๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์กด์žฌ
        new_user = User(name=name, email=email, age=age)

        print(f"์ƒ์„ฑ๋œ ๊ฐ์ฒด: {new_user}")
        print(f"ID: {new_user.id}")  # None (์•„์ง DB์— ์—†์Œ!)

        try:
            # 2. Session์— ์ถ”๊ฐ€
            self.session.add(new_user)

            # 3. DB์— ์‹ค์ œ ์ €์žฅ
            # INSERT SQL์ด ์ž๋™ ์ƒ์„ฑ๋˜์–ด ์‹คํ–‰๋จ
            self.session.commit()

            # commit ํ›„์—๋Š” ID๊ฐ€ ์ƒ์„ฑ๋จ!
            print(f"์‚ฌ์šฉ์ž ์ƒ์„ฑ ์™„๋ฃŒ! ID: {new_user.id}")
            return new_user

        except Exception as e:
            print(f"[๐Ÿ”ด] {str(e)}")
            return None

๐Ÿ’ก ORM ์ƒ์„ฑ ํ”„๋กœ์„ธ์Šค

  1. ๊ฐ์ฒด ์ƒ์„ฑ: user = User(name="ํ™๊ธธ๋™") โ†’ ๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์กด์žฌ
  2. ์„ธ์…˜ ์ถ”๊ฐ€: session.add(user) โ†’ "์ €์žฅํ•  ์˜ˆ์ •" ํ‘œ์‹œ
  3. ์ปค๋ฐ‹: session.commit() โ†’ ์‹ค์ œ DB์— ์ €์žฅ

ํ•œ ๋ฒˆ User ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•ด๋ณด๋„๋ก ํ•ฉ์‹œ๋‹ค.

if __name__ == "__main__":
    """ORM CRUD ์‹ค์Šต"""
    print("=" * 80)
    print("SQLAlchemy ORM์„ ์‚ฌ์šฉํ•œ CRUD ์‹ค์Šต")
    print("=" * 80)

    # 1. ํ…Œ์ด๋ธ” ์ƒ์„ฑ
    create_tables()

    # 2. CRUD ๊ฐ์ฒด ์ƒ์„ฑ
    user_crud = UserCRUD()

    # 3. ์ƒˆ๋กœ์šด ์œ ์ € ์ƒ์„ฑ
    new_user: User | None = user_crud.create_user(
        name="jhlee04", email="jhlee04@jhlee04", age=29
    )
    print(
        f"์ƒˆ๋กœ์šด ์œ ์ € ์ •๋ณด: ID: {new_user.id}, NAME: {new_user.name}, EMAIL: {new_user.email}"
    )

์ถœ๋ ฅ ๊ฐ’์„ ํ•œ ๋ฒˆ ๋ณด๋„๋ก ํ• ๊ฒŒ์š”.

================================================================================
SQLAlchemy ORM์„ ์‚ฌ์šฉํ•œ CRUD ์‹ค์Šต
================================================================================

๐Ÿ“ ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์ค‘ ...
2025-11-15 10:09:51,140 INFO sqlalchemy.engine.Engine SELECT DATABASE()
2025-11-15 10:09:51,140 INFO sqlalchemy.engine.Engine [raw sql] {}
2025-11-15 10:09:51,141 INFO sqlalchemy.engine.Engine SELECT @@sql_mode
2025-11-15 10:09:51,141 INFO sqlalchemy.engine.Engine [raw sql] {}
2025-11-15 10:09:51,141 INFO sqlalchemy.engine.Engine SELECT @@lower_case_table_names
2025-11-15 10:09:51,142 INFO sqlalchemy.engine.Engine [raw sql] {}
2025-11-15 10:09:51,142 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-11-15 10:09:51,142 INFO sqlalchemy.engine.Engine DESCRIBE `orm`.`users`
2025-11-15 10:09:51,142 INFO sqlalchemy.engine.Engine [raw sql] {}
2025-11-15 10:09:51,144 INFO sqlalchemy.engine.Engine COMMIT
โœ… ํ…Œ์ด๋ธ” ์ƒ์„ฑ ์™„๋ฃŒ!

โž• ์‚ฌ์šฉ์ž ์ƒ์„ฑ: jhlee04, jhlee04@jhlee04
์ƒ์„ฑ๋œ ๊ฐ์ฒด: <User(id=None, name='jhlee04', email='jhlee04@jhlee04')>
ID: None
2025-11-15 10:09:51,145 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-11-15 10:09:51,146 INFO sqlalchemy.engine.Engine INSERT INTO users (name, email, age, created_at) VALUES (%(name)s, %(email)s, %(age)s, %(created_at)s)
2025-11-15 10:09:51,146 INFO sqlalchemy.engine.Engine [generated in 0.00013s] {'name': 'jhlee04', 'email': 'jhlee04@jhlee04', 'age': 29, 'created_at': datetime.datetime(2025, 11, 15, 10, 9, 51, 146782)}
2025-11-15 10:09:51,147 INFO sqlalchemy.engine.Engine COMMIT
2025-11-15 10:09:51,150 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-11-15 10:09:51,152 INFO sqlalchemy.engine.Engine SELECT users.id AS users_id, users.name AS users_name, users.email AS users_email, users.age AS users_age, users.created_at AS users_created_at 
FROM users 
WHERE users.id = %(pk_1)s
2025-11-15 10:09:51,152 INFO sqlalchemy.engine.Engine [generated in 0.00010s] {'pk_1': 12}
์‚ฌ์šฉ์ž ์ƒ์„ฑ ์™„๋ฃŒ! ID: 12
์ƒˆ๋กœ์šด ์œ ์ € ์ •๋ณด: ID: 12, NAME: jhlee04, EMAIL: jhlee04@jhlee04

์—ฌ๋Ÿฌ ์ถœ๋ ฅ๋ฌธ์ด ๋œจ๊ณ  ์žˆ์ฃ ?

SQL ๊ด€๋ จ ๋กœ๊ทธ๊ฐ€ ๋œจ๋Š” ๊ฒƒ์€ ์œ„์—์„œ ์„ค์ •ํ•œ ์•„๋ž˜ ์ฝ”๋“œ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

engine: eng.base.Engine = create_engine(
    f"mysql+pymysql://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}",
    echo=True,  # SQL ์ฟผ๋ฆฌ๋ฌธ์„ ์ž๋™์œผ๋กœ ์ถœ๋ ฅํ•ด์คŒ (ํ•™์Šต์šฉ)
)

echo ์†์„ฑ์„ True๋กœ ์คฌ๊ธฐ ๋•Œ๋ฌธ์— SQL ์ฟผ๋ฆฌ๋ฌธ์— ๋Œ€ํ•œ ๋กœ๊ทธ๊ฐ€ ๋œจ๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์–ด์š”.


Raw SQL๊ณผ ๋น„๊ต

Raw SQL์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ์™€๋Š” ์–ด๋–ค ์ ์ด ๋‹ค๋ฅธ์ง€ ์ฝ”๋“œ๋กœ ๋น„๊ตํ•ด๋ณผ๊นŒ์š”?

# Raw SQL: 19์ค„
insert_sql = """
INSERT INTO users (name, email, age, created_at)
VALUES (?, ?, ?, ?)
"""
created_at = datetime.now().isoformat()
try:
    cursor.execute(insert_sql, (name, email, age, created_at))
    connection.commit()
    user_id = cursor.lastrowid
    return user_id
except sqlite3.IntegrityError as e:
    print(f"์—๋Ÿฌ: {e}")
    return None

# ORM: 7์ค„
try:
    user = User(name=name, email=email, age=age)
    session.add(user)
    session.commit()
    return user
except Exception as e:
    session.rollback()
    return None


5-4. READ - ์‚ฌ์šฉ์ž ์กฐํšŒ

id ์ปฌ๋Ÿผ ๊ฐ’ ๊ธฐ๋ฐ˜ ๋‹จ์ผ ์‚ฌ์šฉ์ž ์กฐํšŒ

def get_user_by_id(self, user_id: int) -> User | None:
    """ID๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ"""
    print(f"\n๐Ÿ” ID๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ: {user_id}")
    # get(): ๊ธฐ๋ณธ ํ‚ค๋กœ ์กฐํšŒ
    # SELECT * FROM users WHERE id = ?์™€ ๋™์ผ
    user: User = self.session.get(User, user_id)
    if user:
        print(f"์‚ฌ์šฉ์ž ์ฐพ์Œ: {user.to_dict()}")
    else:
        print("์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ!")
    return user

id๊ฐ€ 1์ธ ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

if __name__ == "__main__":
    """ORM CRUD ์‹ค์Šต"""
    # ...
    # 4. ๋‹จ์ผ ์œ ์ € ์กฐํšŒ (user_id, READ)
    user: User = user_crud.get_user_by_id(user_id=1)
    print(user.to_dict())

์ถœ๋ ฅ ๊ฐ’์„ ์กฐํšŒํ•ด๋ณผ๊ฒŒ์š”.

์‚ฌ์šฉ์ž ์ฐพ์Œ: {'id': 1, 'name': 'jhlee01', 'email': 'jhlee01@jhlee01', 'age': 29, 'created_at': datetime.datetime(2025, 11, 15, 9, 28, 18)}
{'id': 1, 'name': 'jhlee01', 'email': 'jhlee01@jhlee01', 'age': 29, 'created_at': datetime.datetime(2025, 11, 15, 9, 28, 18)}

id๊ฐ€ 1๋ฒˆ์ธ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๊ฐ€ ์กฐํšŒ๋ฉ๋‹ˆ๋‹ค.

to_dict() ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ์ฒด์˜ ์ •๋ณด๊ฐ€ ๋”•์…”๋„ˆ๋ฆฌ๋กœ๋„ ์ •์ƒ์ ์œผ๋กœ ์กฐํšŒ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์–ด์š”.



๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ

def get_all_users(self) -> list[User]:
    """๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ"""
    print("\n๐Ÿ“‹ ๋ชจ๋“  ์‚ฌ์šฉ์ž ์กฐํšŒ")

    # query(User): User ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ ์ฟผ๋ฆฌ
    # order_by(User.id): ORDER BY id
    # all(): ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜
    users = self.session.query(User).order_by(User.id).all()
    print(f"{len(users)} ๋ช…์˜ ์‚ฌ์šฉ์ž ์ฐพ์Œ.")
    return users

๋ชจ๋“  ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ list๋กœ ๋ฆฌํ„ดํ•˜๋„๋ก ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.


๋ชจ๋“  ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ด๋ด…์‹œ๋‹ค.

if __name__ == "__main__":
    """ORM CRUD ์‹ค์Šต"""
    # ...
    # 5. ๋ชจ๋“  ์œ ์ € ์กฐํšŒ
    users: list = user_crud.get_all_users()
    print(users)

4 ๋ช…์˜ ์‚ฌ์šฉ์ž ์ฐพ์Œ.
[<User(id=1, name='jhlee01', email='jhlee01@jhlee01')>, <User(id=9, name='jhlee02', email='jhlee02@jhlee02')>, <User(id=10, name='jhlee03', email='jhlee03@jhlee03')>, <User(id=12, name='jhlee04', email='jhlee04@jhlee04')>]


์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰

def get_users_by_name(self, name_keyword: str) -> list[User]:
    """์œ ์ € ์ด๋ฆ„์œผ๋กœ ์กฐํšŒ"""
    print("\n๐Ÿ” ์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰")

    # filter(): WHERE ์ ˆ
    # like(): SQL์˜ LIKE ์—ฐ์‚ฐ์ž
    users = (
        self.session.query(User)
        .filter(User.name.like(f"%{name_keyword}%"))
        .order_by(User.name)
    ).all()
    print(f"ํ‚ค์›Œ๋“œ: '{name_keyword}' - {len(users)}๋ช…์˜ ์‚ฌ์šฉ์ž ์ฐพ์Œ.")
    return users

sessionmaker ๊ฐ์ฒด๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” filter ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด์„œ SQL์˜ LIKE ์—ฐ์‚ฐ์ž๋„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


jh ๋ผ๋Š” ๋ฌธ์ž์—ด์„ ํฌํ•จํ•˜๋Š” ์œ ์ €๋“ค์„ ์กฐํšŒํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

if __name__ == "__main__":
    """ORM CRUD ์‹ค์Šต"""
    # ...
    # 6. ์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰ (LIKE ์—ฐ์‚ฐ์ž)
    users = user_crud.get_users_by_name(name_keyword="jh")

ํ‚ค์›Œ๋“œ: jh - 4๋ช…์˜ ์‚ฌ์šฉ์ž ์ฐพ์Œ.
[<User(id=1, name='jhlee01', email='jhlee01@jhlee01')>, <User(id=9, name='jhlee02', email='jhlee02@jhlee02')>, <User(id=10, name='jhlee03', email='jhlee03@jhlee03')>, <User(id=12, name='jhlee04', email='jhlee04@jhlee04')>]


Raw SQL๊ณผ ๋น„๊ต

# ---- Raw SQL: ๋ณต์žกํ•œ ๋ฌธ์ž์—ด ์กฐ์ž‘
select_sql = """
SELECT id, name, email, age, created_at
FROM users
WHERE name LIKE ?
ORDER BY name ASC
"""
cursor.execute(select_sql, (f"%{name_keyword}%",))
rows = cursor.fetchall()
users = [dict(row) for row in rows]

# ---- ORM: ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹์œผ๋กœ ์ง๊ด€์ 
users = session.query(User)\
    .filter(User.name.like(f"%{name_keyword}%"))\
    .order_by(User.name)\
    .all()


5-5. UPDATE - ์‚ฌ์šฉ์ž ์ˆ˜์ •

id ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ!

def update_user(
    self, user_id: int, name: str = None, email: str = None, age: int = None
) -> None | User:
    """
    ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •

    ORM ๋ฐฉ์‹:
    1. ๊ฐ์ฒด ์กฐํšŒ
    2. ๊ฐ์ฒด์˜ ์†์„ฑ ์ˆ˜์ •
    3. commit()์œผ๋กœ ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ €์žฅ
    """

    print(f"\n โœ๏ธ ์‚ฌ์šฉ์ž ์ˆ˜์ •: ID: {user_id}")

    # 1. ์‚ฌ์šฉ์ž ์กฐํšŒ
    user: User = self.session.get(User, user_id)
    if not user:
        print(f"ํ•ด๋‹น ID({user_id})์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค")
        return None

    # ์ˆ˜์ • ์ „์˜ ์‚ฌ์šฉ์ž ์ •๋ณด ์ถœ๋ ฅ
    print(f"    ์ˆ˜์ • ์ „: {user.to_dict()}")

    # 2. ์†์„ฑ ์ˆ˜์ •
    # ๊ฐ์ฒด์˜ ์†์„ฑ์„ ์ง์ ‘ ์ˆ˜์ •
    # ORM์ด ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ž๋™์œผ๋กœ ์ถ”์ ํ•จ.
    if name is not None:
        user.name = name
        print(f"    ์ด๋ฆ„ ๋ณ€๊ฒฝ: {user.name}")

    if email is not None:
        user.email = email
        print(f"    ์ด๋ฉ”์ผ ๋ณ€๊ฒฝ: {user.email}")

    if age is not None:
        user.age = age
        print(f"    ๋‚˜์ด ๋ณ€๊ฒฝ: {user.age}")

    try:
        # 3. ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ ์šฉ
        # UPDATE SQL์ด ์ž๋™ ์ƒ์„ฑ๋จ.
        self.session.commit()

        print(f"โœ… ์‚ฌ์šฉ์ž ์ˆ˜์ • ์™„๋ฃŒ!")
        print(f"    ์ˆ˜์ • ํ›„: {user.to_dict()}")

        return user

    except Exception as e:
        self.session.rollback()  # ์ด์ „ ์„ค์ •์œผ๋กœ ๋˜๋Œ๋ฆฌ๊ธฐ
        print(f"์—๋Ÿฌ: {str(e)}")
        return None

id ๊ฐ’์ด 1๋ฒˆ์ธ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์ˆ˜์ •ํ•ด๋ณผ๊นŒ์š”?

if __name__ == "__main__":
    """ORM CRUD ์‹ค์Šต"""
    # ...    
    # 7. ์‚ฌ์šฉ์ž ์—…๋ฐ์ดํŠธ
    update_user: User = user_crud.update_user(user_id=1, name="jhlee_updated_01")
    read_user: User = user_crud.get_user_by_id(user_id=1)

์œ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  id๊ฐ€ 1๋ฒˆ์ธ ์œ ์ €์˜ name ํ•„๋“œ ๊ฐ’์ด jhlee_updated_01๋กœ ๋ณ€๊ฒฝ๋˜์–ด ์žˆ์œผ๋ฉด ์„ฑ๊ณต์ด๋„ค์š”?


๐Ÿ” ID๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ: 1
์‚ฌ์šฉ์ž ์ฐพ์Œ: {'id': 1, 'name': 'jhlee_updated_01', 'email': 'jhlee01@jhlee01', 'age': 29, 'created_at': datetime.datetime(2025, 11, 15, 9, 28, 18)}

์ •์ƒ์ ์œผ๋กœ id๊ฐ€ 1๋ฒˆ์ธ ์‚ฌ์šฉ์ž์˜ name ๊ฐ’์ด ๋ณ€๊ฒฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!


Raw SQL๊ณผ ๋น„๊ต

# Raw SQL: ๋ณต์žกํ•œ ๋™์  SQL ์ƒ์„ฑ
update_fields = []
update_values = []
if name is not None:
    update_fields.append("name = ?")
    update_values.append(name)
if email is not None:
    update_fields.append("email = ?")
    update_values.append(email)
update_values.append(user_id)
update_sql = f"UPDATE users SET {', '.join(update_fields)} WHERE id = ?"
cursor.execute(update_sql, tuple(update_values))
connection.commit()

# ORM: ์ง๊ด€์ ์ธ ์†์„ฑ ์ˆ˜์ •
user = session.get(User, user_id)
if name is not None:
    user.name = name
if email is not None:
    user.email = email
session.commit()

Raw SQL์˜ ๊ฒฝ์šฐ ์ง์ ‘ ์ฟผ๋ฆฌ๋ฌธ์„ ์ž‘์„ฑํ•˜์—ฌ ์‹คํ–‰ํ•˜๋‹ค๋ณด๋‹ˆ ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ง€๊ณ  ๊ฐ€๋…์„ฑ์ด ์ƒ๋‹นํžˆ ๋–จ์–ด์ง‘๋‹ˆ๋‹ค ...


5-6. DELETE - ์‚ฌ์šฉ์ž ์‚ญ์ œ

def delete_user(self, user_id: int) -> bool:
    """
    ์‚ฌ์šฉ์ž ์‚ญ์ œ

    ORM ๋ฐฉ์‹:
    1. ๊ฐ์ฒด ์กฐํšŒ
    2. session.delete()๋กœ ์‚ญ์ œ ํ‘œ์‹œ
    3. commit()์œผ๋กœ ์‹ค์ œ ์‚ญ์ œ
    """

    print(f"\n๐Ÿ—‘๏ธ ์‚ฌ์šฉ์ž ์‚ญ์ œ: {user_id}")

    # 1. ์‚ฌ์šฉ์ž ์กฐํšŒ
    user: User = self.session.get(User, user_id)
    if not user:
        print(f"ํ•ด๋‹น ID({user_id})์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค!")
        return False

    # 2. ์‚ญ์ œ ํ‘œ์‹œ
    self.session.delete(user)

    # 3. ์‹ค์ œ ์‚ญ์ œ
    # DELETE SQL์ด ์ž๋™ ์ƒ์„ฑ๋˜์–ด ์‹คํ–‰๋จ.
    self.session.commit()
    return True

id๊ฐ€ 1๋ฒˆ์ธ ์‚ฌ์šฉ์ž๋ฅผ ์‚ญ์ œํ•ด๋ณธ ํ›„, ์‹ค์ œ๋กœ ์‚ญ์ œ๋๋Š”์ง€ ์กฐํšŒํ•ด๋ด…์‹œ๋‹ค.

if __name__ == "__main__":
    """ORM CRUD ์‹ค์Šต"""
    # ...
    # 9. ์‚ฌ์šฉ์ž ์‚ญ์ œ
    user_delete: bool = user_crud.delete_user(user_id=1)
    user_crud.get_user_by_id(user_id=1)  # ์‹ค์ œ ์‚ญ์ œ๋˜์—ˆ๋Š”์ง€ id ๊ธฐ๋ฐ˜ ์กฐํšŒ
์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ!

id๊ฐ€ 1๋ฒˆ์ธ ์‚ฌ์šฉ์ž๊ฐ€ ์‚ญ์ œ๋˜์–ด ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ! ์ด๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ ์ถœ๋ ฅ๋˜๋„ค์š”!



๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์ง€๊ธˆ๊นŒ์ง€ Raw SQL๊ณผ ORM์„ ๊ฐ๊ฐ ๋น„๊ตํ•˜๋ฉฐ ์‹ค์Šต์„ ์ง„ํ–‰ํ•ด๋ดค์Šต๋‹ˆ๋‹ค.


ํ™•์‹คํžˆ ์ผ๋ฐ˜์ ์ธ CRUD์— ์žˆ์–ด์„œ๋Š” ORM์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ž‘์—… ํšจ์œจ์„ฑ์„ ๊ฝค ๋†’์ผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.


๋‹ค๋งŒ ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๋‚˜ ์„ฑ๋Šฅ์— ์žˆ์–ด์„œ ์ค‘์š”ํ•œ ๋กœ์ง ๋“ฑ์€ Raw SQL๊ณผ ๋ณ‘ํ–‰ํ•˜๋ฉฐ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ข‹์•„๋ณด์ž…๋‹ˆ๋‹ค!


์˜ค๋Š˜ ๋ฐฐ์šด ORM ๋‚ด์šฉ์„ ๊ธฐ๋ฐ˜์œผ๋กœ

์ถ”ํ›„์— ๊ฐ„๋‹จํ•œ ๋„์„œ ๊ด€๋ฆฌ API ๋ฅผ Flask ํ”„๋ ˆ์ž„์›Œํฌ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„ํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค!


๋‹ค์Œ์— ๋งŒ๋‚˜์š”!


Loading script...

ยฉ 2025, Built with Gatsby