본문 바로가기

카테고리 없음

FastAPI 학습 5일차

1. SQLAlchemy 사용법

  • 파이썬에서 가장 널리 사용되는 SQL Toolkit이자 ORM이다. SQL 쿼리를 Python 코드로 변환할 수 있게 해주고 DB 테이블과 Python 클래스 간의 매핑을 가능케한다.
# database/connection.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

DATABASE_URL = ""


engine = create_engine(DATABASE_URL, echo=True)
SessionFactory = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
  session = SessionFactory()
  try:
    yield session
  finally:
    session.close()

 

  • sqlalchemy의 시작은 Engine이라는 객체이다. 일반적으로 특정 DB 서버에 대해 한번만 생성되는 전역객체이며, URL 문자열로 DB호스트에 연결한다.
  • create_engine에 echo 옵션은 내부적으로 어떤 SQL 쿼리를 실행하는지 로그를 보여줘 디버깅, 로깅용으로 사용한다.
  • session은 객체지향적으로 db와 데이터를 주고 받을 수 있게 해준다. 아래는 user와 todo의 관계를 나타내는 orm이다.
  • 세션 생성 함수에서 autocommit은 트랜젝션이 자동으로 커밋될지 여부를, autoflush는 세션이 쿼리 실행전 자동으로 flush될 지 여부를, bind는 세션이 사용할 db 엔진을 설정한다.
  • 세션을 사용할 때는 보통 Depends() 키워드로 의존성주입하여 관리한다. get_db()는 요청이 들어올 때마다 새로운 세션을 생성하고 작업이 끝나면 종료한다.
# database/orm.py
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
from sqlalchemy.orm import declarative_base, relationship
from schema.request import CreateToDoRequest

Base = declarative_base()

class ToDo(Base):
  __tablename__ = "todo"

  id = Column(Integer, primary_key=True, index=True)
  contents = Column(String(256), nullable=False)
  is_done = Column(Boolean, nullable=False)
  user_id = Column(Integer, ForeignKey("user.id"))

  def __repr__(self):
    return f"ToDo(id={self.id}, contents={self.contents}, is_done={self.is_done})"
  
  @classmethod
  def create(cls, request: CreateToDoRequest) -> "ToDo":
    return cls(
      contents=request.contents,
      is_done=request.is_done
    )

class User(Base):
  __tablename__ = "user"

  id = Column(Integer, primary_key=True, index=True)
  username = Column(String(256), nullable=False)
  password = Column(String(256), nullable=False)
  todos = relationship("ToDo", lazy="joined")

  @classmethod
  def create(cls, username: str, hashed_password: str) -> "User":
    return cls(
      username=username,
      password=hashed_password
    )

 

  • declarative_base()는 orm 모델 클래스를 정의할 때 사용하는 베이스 클래스다. orm은 테이블을 나타내는 모델을 정의하는 파일로 __tablename__에는 테이블이 사용할 이름을 기입한다.
  • 외래키(foreign Key)는 다른 테이블의 컬럼을 참조하여 외래 키 제약을 설정한다. '테이블명.컬럼명' 형태로 어떤 컬럼을 참조할 지 자식테이블에 정의한다.
  • relationship : 테이블간 관계를 정의하는 데 사용, 외래 키 관계를 관리해준다. 연결할 대상 클래스 명을 가장 먼저 써주고 back_populates는 양방향 관계 설정 시 상대측 필드의 이름을 지정한다. secondary로 M:N관계에서 중간테이블을 지정할 수 있고 uselist가 True면 1:N(결과가 list가 된다.), False면 1:1관계이다.
  • cascade는 부모객체가 삭제될 때 자식객체에 대한 동작을 정의하고, lazy는 부모객체를 로드할 때 자식객체에 대한 동작을 정의한다.
  • lazy 로딩 방식
    • select : 기본값. 필요시 별도 select 쿼리로 불러옴
    • joined : join으로 즉시 불러옴
    • subquery : 서브 쿼리 join 사용
    • dynamic : Query 객체 반환
    • noload : 자동 로딩 안함
    • selectin : IN 절 기반 서브쿼리로 불러옴

2. SQLAlchemy의 CRUD

1. 방식 분류

  • ORM : 객체 중심적으로 작업해야 하는 경우. 데이터베이스와 python 객체의 상태동기화가 필요한 경우
  • SQL : 대량의 데이터를 처리하거나 성능이 중요한 경우. ORM이 필요없는 아주 간단한 작업.

2. Create/INSERT

1. orm 기본방식 : db.add()로 구현. 대량 데이터 삽입시 비효율적일 수 있어 SQL 방식이 좋음

2. orm 중복 데이터 처리

  • 수동확인 : select - excute - scalars로 대상 데이터를 뽑아낸 후 if문으로 예외처리
  • merge() 함수 사용
new_user = User(id=1, name="Tom", email="example@example.net")
# 기존 값이 존재하면 update하고 없으면 insert를 한다. nodejs의 upsert 메서드와 유사
merged_user = db.merge(new_user)
await db.commit()

 

3. sql 방식

  • 단일데이터 삽입 : insert() 메서드를 이용해 이루어진다.
  • 다량 데이터 삽입 : list[dict] 형식으로 데이터 다량 삽입
async def insert_multi_user(users: list[dict], db):
  bcd = insert(User)
  
  await db.excute(bcd, users)
  await db.commit()
  
async def main():
  async with get_db() as db:
    users = [
        {"name": "Mike", "email":exam@naver.com"},
        {"name": "Bob", "email":exam@google.com"}
    ]
    await insert_multi_user(users, db)

 

3. Read/SELECT

기본적인 쿼리 실행순서는 select() 함수를 통한 쿼리생성, .where()를 사용한 조건추가, db.excute()로 쿼리를 날리는 순으로 이루어진다. 

db.excute() 이후 결과처리를 위한 메서드
- fetchall() : 행의 리스트를 반환. 모든 결과를 반환하며, 단순 데이터 추출에 적합
- fetchone() : 첫번째 행을 반환.
- scalar() : 결과의 첫번째 행에서 첫번째 컬럼값을 반환
- scalars() : 스칼라 값 또는 ORM 객체의 제너레이터. 데이터를 한번에 처리하지 않고 필요한 때 한 개씩 가져옴

scalar() 메서드는 제너레이터이기에 추가로 메서드를 붙일 수 있음
- scalar.all() : ORM 객체의 list 형태로 모든 결과를 가져옴
- scalar.first() : ORM 객체의 단일 결과나 첫번째 행
- scalar.fetchmany(size) : 지정된 개수만큼 결과의 list를 가져올때
- scalar.partirtions() : 결과를 페이지네이션하거나 부분적으로 처리할 때 사용

 

4. Update

1. sql방식 : 기본적인 쿼리 실행순서는 update() 함수를 통한 쿼리생성, .where()를 사용한 조건추가, value()로 변경할 값을 전달한 후 db.excute()로 쿼리를 날리는 순으로 이루어진다. 

from sqlalchemy import update
stmt = (
  update(User)
  .where(User.id == 1)
  .value(name="New Name", email="newex@example.com")
)

await db.excute(stmt)
await db.commit()

 

2. ORM : 객체의 속성을 변경한 후 commit

stmt select(User).where(User.id == 1)
result = await db.excute(stmt)
user = result.scalars().first()

if user:
  user.name = "Updated Name"
  user.email = "Updated_email@example.com"
  await db.commit()

 

5. delete

1. sql 방식 : 기본적인 쿼리 실행순서는 delete() 함수를 통한 쿼리생성, .where()를 사용한 조건추가, db.excute()로 쿼리를 날리는 순으로 이루어진다. 

from sqlalchemy import delete

stmt = delete(User).where(User.id == 1)
await db.excute(stmt)
await db.commit()

2. ORM : 객체를 먼저 조회한 후 삭제

stmt = select(User).where(User.id == 1)
result = await db.excute(stmt)
user = result.scalars().first()

if user:
  awiat db.delete(user)
  await db.commit()