Programming Language/Python

[SQLAlchemy] 3. MetaData

LeeJaeJun 2024. 6. 22. 13:17
728x90
반응형

MetaData

  • MetaData란 데이터베이스 구조와 schema를 기술하는 데이터에 대한 데이터
  • Database table, column, data type, index, restriction, trigger, view 등을 설명하는 정보 포함
  • 데이터베이스의 스키마를 정의하고 관리하는 역할
  • SQLAlchemy Core(SQL을 직접 작성, Connection 사용)과 ORM(객체 지향 접근 방식, Session 사용)에서 모두 MetaData를 사용하여 SQL 쿼리를 작성하고 실행 가능
    • Core의 경우 MetaData 객체를 생성하는 방식으로 사용되며, 주로 Connection 객체와 함께 사용
      • Connection 객체는 낮은 수준의 SQL 작업을 수행하는 데 적합
      • Session 객체를 사용할 수 있긴 함. 이 경우, Connection 객체를 내부적으로 사용하여 데이터베이스와 상호작용 하지만 트랜잭션 관리, 연결 풀 관리, 작업의 일관성 유지 등 여러 장점을 제공받을 수 있음
    • ORM의 경우 베이스클래스를 상속받는 방식으로 사용되며, 주로 Session 객체와 사용
      • Session 객체는 트랜잭션 관리, 연결 풀 관리, 작업의 일관성 유지 등을 제공
  • 주요 구성 요소: Table, Column, Primary Key, Foreign Key, Index, Unique Constraint, Check Constraint, View, Trigger

 

1. MetaData 객체 생성 및 기본 테이블 정의

from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, ForeignKey, Date

# 엔진 생성
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)

# MetaData 객체 생성
metadata = MetaData()

# 고객 테이블 정의
customer_table = Table(
    'customer', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(50), nullable=False),
    Column('email', String(100), nullable=False, unique=True)
)

# 주문 테이블 정의
order_table = Table(
    'order', metadata,
    Column('id', Integer, primary_key=True),
    Column('order_date', Date, nullable=False),
    Column('customer_id', Integer, ForeignKey('customer.id'), nullable=False)
)

# 데이터베이스에 테이블 생성
metadata.create_all(engine)

2. 데이터 삽입 및 조회

with engine.connect() as connection:
    connection.execute(customer_table.insert().values(id=1, name='John Doe', email='john@example.com'))
    connection.execute(customer_table.insert().values(id=2, name='Jane Smith', email='jane@example.com'))
    connection.execute(order_table.insert().values(id=1, order_date=date.today(), customer_id=1))
    connection.execute(order_table.insert().values(id=2, order_date=date.today(), customer_id=2))

# 데이터 조회
with engine.connect() as connection:
    result = connection.execute(select([customer_table]))
    for row in result:
        print(row)
  • Core에서 Session 사용
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, select
from sqlalchemy.orm import sessionmaker

# 데이터베이스 엔진 생성
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)

# MetaData 객체 생성
metadata = MetaData()

# 테이블 정의
user_table = Table(
    'user_account', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(30)),
    Column('fullname', String)
)

# 데이터베이스에 테이블 생성
metadata.create_all(engine)

# 세션 생성기 생성
SessionLocal = sessionmaker(bind=engine)

# 데이터 삽입 및 조회
with SessionLocal() as session:
    # 데이터 삽입
    session.execute(user_table.insert().values(id=1, name='John', fullname='John Doe'))
    session.execute(user_table.insert().values(id=2, name='Jane', fullname='Jane Doe'))
    session.commit()

    # 데이터 조회
    result = session.execute(select([user_table]))
    for row in result:
        print(row)

 

Base Class

  • Core를 사용할 때는 MetaData 객체를 생성하여 메타데이터를 관리하지만 ORM을 사용할 때는 베이스 클래스를 생성해서 관리
    • Core는 주로 더 낮은 수준의 SQL 작업을 수행하는데 적합
    • ORM은 더 높은 수준의 객체 지향적 데이터베이스 작업을 수행하는데 적합
    • 상황에 따라 두 방법 중 하나를 선택하거나 혼용하여 사용
  • declarative_base() 함수를 통해 베이스 클래스 생성하거나 DecalrativeBase 클래스를 사용하여 메타데이터 관리
    • 내부적으로 MetaData 객체를 포함하고 있음
    • 베이스 클래스를 상속받아 데이터베이스 테이블과 매핑되는 ORM 클래스를 정의
    • SQLAlchemy 2.0부터 DecalarativeBase를 사용하여 베이스 클래스를 정의하는 것이 권장
# declarative_base()를 사용하는 방식
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

# 데이터베이스 엔진 생성
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)

# 베이스 클래스 생성
Base = declarative_base()

# ORM 매핑 클래스 정의
class User(Base):
    __tablename__ = 'user_account'
    id = Column(Integer, primary_key=True)
    name = Column(String(30))
    fullname = Column(String)

# 데이터베이스 테이블 생성
Base.metadata.create_all(engine)

# 세션 생성기 생성
SessionLocal = sessionmaker(bind=engine)

# 데이터 삽입 및 조회
with SessionLocal() as session:
    new_user = User(name='John', fullname='John Doe')
    session.add(new_user)
    session.commit()
    
    users = session.query(User).all()
    for user in users:
        print(f"{user.name}, {user.fullname}")
# DeclarativeBase 클래스를 사용하는 방식
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import DeclarativeBase, sessionmaker, mapped_column

# 데이터베이스 엔진 생성
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)

# 베이스 클래스 생성
class Base(DeclarativeBase):
    pass

# ORM 매핑 클래스 정의
class User(Base):
    __tablename__ = 'user_account'
    id: int = mapped_column(Integer, primary_key=True)
    name: str = mapped_column(String(30))
    fullname: str = mapped_column(String)

    def __repr__(self):
        return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

# 메타데이터를 사용하여 테이블 생성
Base.metadata.create_all(engine)

# 세션 생성기 생성
SessionLocal = sessionmaker(bind=engine)

# 데이터 삽입 및 조회
with SessionLocal() as session:
    new_user = User(name='John', fullname='John Doe')
    session.add(new_user)
    session.commit()
    
    users = session.query(User).all()
    for user in users:
        print(f"{user.name}, {user.fullname}")

 

왜 Core와 ORM을 구분해서 사용할까?

  • 성능: Core는 SQL 쿼리를 직접 제어할 수 있으므로 성능 최적화가 필요한 경우 유리
  • 복잡한 쿼리: Core는 복잡한 SQL 쿼리를 작성하고 실행할 수 있는 유연성을 제공
  • 추상화 수준: ORM은 높은 수준의 추상화를 제공하여 개발 생산성을 높이지만, 모든 상황에 적합하지 않을 수 있음
  • 특정 요구사항: 일부 프로젝트에서는 ORM의 높은 추상화 수준이 필요하지 않을 수 있으며, Core의 세밀한 제어가 필요한 경우 존재

 

Table Reflection

  • SQLAlchemy에서 기존 데이터베이스의 테이블 구조를 읽어와서 Python 객체로 생성하는 과정
  • SQLAlchemy를 사용하여 Python에서 해당 테이블을 매핑하고 사용하려면 테이블 정의를 포함한 메타데이터를 여전히 정의 해야 함
    • 데이터베이스와 상호작용하기 위해 테이블의 구조를 알고 있어야 하기 때문
  • 직접 테이블을 생성하는 대신, 데이터베이스에 존재하는 테이블의 메타데이터를 reflection을 통해 자동으로 불러올 수 있음
  • Core 스타일에서의 리플렉션
from sqlalchemy import create_engine, MetaData, Table

# 데이터베이스 엔진 생성
engine = create_engine("sqlite:///existing_database.db", echo=True)

# MetaData 객체 생성
metadata = MetaData()

# 리플렉션을 사용하여 테이블 로드
# user_account 테이블의 구조를 자동으로 로드하여 메타데이터 객체로 생성
user_table = Table('user_account', metadata, autoload_with=engine)

# 데이터 조회
with engine.connect() as connection:
    result = connection.execute(user_table.select())
    for row in result:
        print(row)
  • ORM 스타일에서의 리플렉션
from sqlalchemy import create_engine, Table
from sqlalchemy.orm import DeclarativeBase, sessionmaker

# 데이터베이스 엔진 생성
engine = create_engine("sqlite:///existing_database.db", echo=True)

# Declarative Base 생성
class Base(DeclarativeBase):
    pass

# ORM 매핑 클래스 정의
# 직접 컬럼을 정의하는 대신 __table__ 속성을 사용하여 테이블을 로드
class User(Base):
    __tablename__ = 'user_account'
    __table__ = Table(__tablename__, Base.metadata, autoload_with=engine)

    def __repr__(self):
        return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"

# 세션 생성기 생성
SessionLocal = sessionmaker(bind=engine)

# 데이터 조회
with SessionLocal() as session:
    users = session.query(User).all()
    for user in users:
        print(user)
728x90
반응형