8. RDB 연동
RDB
데이터를 저장할 때 가장 많이 사용하는 시스템이 DBMS이다. 특히 전통적으로 RDB를 많이 사용하고 있다. RDB(Relational Database)란 관계형 데이터 모델에 기초를 둔 데이타베이스이다. 관계형 데이터 모델은 key와 value들의 관계를 2차원의 테이블 형태로 표현한다. RDBMS(Relational Database Management System)는 관계형 데이터베이스를 생성하고 수정하고 관리할 수 있는 소프트웨어이다.
요새는 백엔드에서 DB에 쿼리를 실행할 때 ORM(Object Relation Mapper)이란 일종의 중계 프로그램을 사용한다. 말 그대로 객체와 관계를 매핑해주는 프로그램이라 생각하면 된다. 파이썬에서 가장 유명한 ORM 중 하나인 SQLAlchemy를 이용해 MySQL과 연결해 보자.
MySQL 도커 컨테이너 띄우기
--name
: 컨테이너 이름-e
: 환경 변수-d
: 백그라운드 모드(Detached mode)mysql:8.0
: 이미지 이름- –character-set-server=utf8mb4: (MySQL 설정) utf8은 3바이트인데 요즘 이모지를 많이 사용하니깐 mb4로 4 바이트 까지 확장 가능하도록 설정
- –collation-server=utf8mb4_unicode_ci: (MySQL 설정) 정렬할 char는 utf8mb4 형태이고 ci(character insensivity) 로 대소문자 구분 안하도록 설정
(fastapi) PS D:\workspace\fastapi> docker run --name fastapi-mysql -e MYSQL_ROOT_PASSWORD=1234 -d mysql:8.0 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
이제 만든 컨테이너를 실행시켜보자.
-it
: interactive tty로 컨테이너와 입출력을 받고, 터미널과 비슷한 환경을 조성해 준다. 컨테이너 내부로 진입하도록 attach 가 가능한 상태로 설정- mysql: mysql을 실행
- uroot: (MySQL 명령어) user root로 root 계정으로 들어가는 설정
-p
: 비밀번호를 입력하겠다는 옵션
(fastapi) PS D:\workspace\fastapi> docker exec -it fastapi-mysql mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.30 MySQL Community Server - GPL
Copyright (c) 2000, 2022, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
mysql> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> select user, host from user;
+------------------+-----------+
| user | host |
+------------------+-----------+
| root | % |
| mysql.infoschema | localhost |
| mysql.session | localhost |
| mysql.sys | localhost |
| root | localhost |
+------------------+-----------+
5 rows in set (0.00 sec)
SQLAlchemy로 MySQL과 연결
- main.py: FastAPI 작성 파일
- database.py: SQLAlchemy 설정
- models.py: SQLAlchemy Models (테이블 표현)
- schemas.py: Pydantic Models
MySQL 도커 컨테이너 실행
$ docker run -d --name fastapi-db \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=1234 \
-e MYSQL_DATABASE=dev \
-e MYSQL_USER=admin \
-e MYSQL_PASSWORD=1234 \
mysql:8.0 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
(fastapi) PS D:\workspace\fastapi> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
NAMES
c1b056bb7674 mysql:8.0 "docker-entrypoint.s…" 53 seconds ago Up 52 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp fastapi-db
PyMySQL, SQLAlchemy 설치
PyMySQL (MySQL과 파이썬을 연결하기 위한 라이브러리) 설치
(fastapi) PS D:\workspace\fastapi> pip install PyMySQL
SQLAlchemy 설치
(fastapi) PS D:\workspace\fastapi> pip install sqlalchemy
database.py
create_engine
함수를 이용해 DB 연결할 엔진 생성- 데이터베이스의 주소를 입력으로 받음
- dialect+driver://username:password@host:port/database
sessionmaker
함수를 이용해 세션 생성- 만든 엔진의 세션을 생성
declarative_base
함수로 모델 정의를 위한 부모 클래스 생성- ORM을 사용할 때 처리할 데이터베이스의 테이블을 설명하고 해당 테이블에 매핑(Mapping) 될 클래스를 정의하는 작업이 필요
- ORM에게 다룰 테이블을 알려줌과 동시에 데이터베이스의 테이블에 매핑될 테이블 클래스들을 코드로 구현
- 데이터베이스의 구조와 코드를 연결
- 데이터베이스에 user라는 테이블이 존재하면 코드 상에도 이 테이블과 정상적으로 연결 될 수 있도록 user 테이블을 코드로 구현함
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
engine = create_engine("mysql+pymysql://admin:1234@0.0.0.0:3306/dev")
SessionLocal = sessionmaker(
bind=engine,
autocommit=False,
autoflush=False,
)
Base = declarative_base()
models.py
DB의 테이블을 정의하는 파일이다.
클래스를 생성할 때는 __tablename__
과 Primary key
가 반드시 필요하다.
from sqlalchemy import Boolean, Column, Integer, String
from .database import Base
# Mapping 클래스 생성
class User(Base): # 식별하기 쉽게 테이블명으로 지정
__tablename__ = "user" # 테이블명
id = Column(Integer, primary_key=True)
email = Column(String(255), unique=True, index=True)
password = Column(String(255))
is_active = Column(Boolean, default=True)
schemas.py
Pydantic 모델을 정의한 파일이다. SQLAlchemy와 Pydantic 모두 “모델”이란 용어를 사용하는데 FastAPI 공식 문서에는 pydantic “model”을 “schema”로 표현했다.
from pydantic import BaseModel
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class User(UserBase):
id: int
email: str
is_active: bool
# pydantic이 SQLAlchemy 모델을 사용할 수 있게 함
class Config:
orm_mode = True
main.py
FastAPI를 실행할 메인 파일이다.
from typing import List
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import models, schemas
from .database import SessionLocal, engine
# DB에 model.py에서 정의한 테이블을 생성
models.Base.metadata.create_all(bind=engine)
app = FastAPI()
# 의존성 주입을 사용하기 위한 함수
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# 유저를 생성하는 API
@app.post("/users", response_model=schemas.User)
def create_user(user: schemas.UserCreate, db: Session = Depends(get_db)):
# filter_by라는 메서드로 User 테이블에 user.email이라는 이메일이 존재하는지 확인
existed_user = db.query(models.User).filter_by(
email=user.email
).first()
if existed_user:
raise HTTPException(status_code=400, detail="Email already registered")
# 입력받은 email, password로 유저 생성
user = models.User(email=user.email, password=user.password)
db.add(user) # DB에 insert
db.commit() # DB에 반영 (SessionLocal에서 autocommit을 false로 했기 때문)
return user
# 유저를 읽는 API
@app.get("/users", response_model=List[schemas.User])
def read_users(db: Session = Depends(get_db)):
# db에서 쿼리를 날려서 User table에 있는 모든 레코드를 가져옴
return db.query(models.User).all()
API 실행
(fastapi) PS D:\workspace\fastapi> uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload