from fastapi import FastAPI, Depends
from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.orm import declarative_base, sessionmaker, Session
from pydantic import BaseModel
# ---------------------------------------------------------
# 1. DB 설정 (application.yml + DataSource 빈 등록 역할)
# ---------------------------------------------------------
# mysql+pymysql 은 "MySQL(마리아DB) 언어를 쓸 거고, pymysql 번역기를 쓸 거야!" 라는 뜻
SQLALCHEMY_DATABASE_URL = "mysql+pymysql://my_id:my_password@127.0.0.1:3306/weather_db"
# oracle+oracledb 은 "오라클 언어와 oracledb 번역기를 쓸 거야!" 라는 뜻
# SQLALCHEMY_DATABASE_URL = "oracle+oracledb://my_id:my_password@127.0.0.1:1521/?service_name=my_service"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# ---------------------------------------------------------
# 2. Entity 모델 정의 (JPA의 @Entity 클래스 역할)
# ---------------------------------------------------------
class WeatherEntity(Base):
__tablename__ = "weather_data" # 실제 DB 테이블 이름
id = Column(Integer, primary_key=True, index=True)
region = Column(String, index=True)
rainfall = Column(Integer)
is_heavy_rain = Column(Boolean)
# 서버가 켜질 때 테이블이 없으면 자동으로 만들어줌 (ddl-auto: update 와 비슷함)
Base.metadata.create_all(bind=engine)
# ---------------------------------------------------------
# 3. DTO 및 FastAPI 설정
# ---------------------------------------------------------
app = FastAPI()
# 응답용 DTO
class WeatherResponse(BaseModel):
region: str
rainfall: int
is_heavy_rain: bool
class Config:
# 핵심: JPA Entity를 Pydantic DTO로 알아서 변환해주는 마법의 옵션!
from_attributes = True
# 매 요청마다 DB 세션을 열고 닫아주는 함수
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# ---------------------------------------------------------
# 4. API 엔드포인트 (@GetMapping 역할)
# ---------------------------------------------------------
@app.get("/api/weather", response_model=list[WeatherResponse])
def get_weather_data(db: Session = Depends(get_db)):
# JpaRepository의 findAll()과 완벽히 똑같음
# SELECT * FROM weather_data; 쿼리가 실행됨
weather_list = db.query(WeatherEntity).all()
return weather_list
-> 파이썬의 최신 웹 프레임워크
파이썬 백엔드 생태계에는 크게 '3대장' 프레임워크가 있다. 자바 생태계에 빗대서 표현한다면?
특징: 가장 전통적이고 무거움. ORM, 관리자 페이지, 인증 등 웹 개발에 필요한 모든 기능이 '풀세트'로 들어있음
어울리는 곳: 규모가 크고 복잡한 시스템을 처음부터 끝까지 다 만들어야 할 때
특징: 아주 가볍고 최소한의 기능만 제공. 필요한 라이브러리(DB 연결 등)는 개발자가 직접 골라서 조립해야 함
어울리는 곳: 아주 단순한 API 서버나 마이크로서비스를 만들 때
Depends는 스프링의 @Autowired(의존성 주입)와 인터셉터(Interceptor) 역할을 동시에 수행하는 FastAPI의 핵심 무기
get_db()에서는 왜 return이 아니라 yield를 쓸까? (라이프사이클 관리)
-> 자바의 try-with-resources나 @Transactional이 끝날 때 커넥션을 반환하는 것과 같은 원리
- get_db()에서 yield db를 만나면, 잠시 멈추고 db 통로를 API 함수로 빌려줌
- API 함수가 데이터 조회를 마치고 클라이언트에게 응답(return)을 성공적으로 보냄
- 그 직후, 다시 get_db()로 돌아와서 finally: db.close()를 실행해 DB 통로를 아주 안전하게 닫음
Depends는 단순히 객체를 주입하는 것뿐만 아니라, "이 API에 들어오기 전에 자격 검증을 해라"라는 수문장 역할도 함
from fastapi import Depends, HTTPException
# 1. 수문장 함수 (토큰 검사기)
def verify_token(token: str):
if token != "super-secret":
# 토큰이 틀리면 API 로직으로 넘어가지도 않고 여기서 400 에러를 튕겨냄
raise HTTPException(status_code=400, detail="인가되지 않은 사용자입니다!")
return "User_123"
# 2. Depends로 수문장 배치하기
@app.get("/api/secure-data")
def get_secure_data(user_id: str = Depends(verify_token)):
# 여기까지 들어왔다는 건, 이미 토큰 검사를 무사히 통과했다는 뜻
return {"message": f"환영합니다 {user_id}님, 여기 비밀 데이터가 있습니다."}
SQLAlchemy (시퀄알케미)는 파이썬 생태계에서 JPA와 완벽하게 같은 역할을 하는 가장 유명한 ORM 라이브러리
| 역할 | Java Spring (JPA) | Python FastAPI |
|---|---|---|
| ORM 기술 | Hibernate / Spring Data JPA | SQLAlchemy |
| DB 테이블 매핑 | @Entity 클래스 | Base 상속 클래스 |
| 데이터 조작 | JpaRepository 인터페이스 | Session 객체 (db.query()) |
| 의존성 주입 | @Autowired | Depends() |
| Entity -> DTO 변환 | MapStruct, ModelMapper 등 | Pydantic의 from_attributes |
역할: 데이터베이스(MySQL, Oracle 등)와 실제로 연결하는 '엔진'을 만듦
자바 매칭: DataSource 인터페이스나 application.yml에서 DB 접속 정보(url, username, password)를 세팅하던 것과 정확히 같은 역할
PyCharm이나 VS Code 같은 모던 에디터를 쓰면, id = Column(Integer)라고 타이핑하는 순간 에디터가 알아서 맨 윗줄에 Integer를 import 해줌
역할: 파이썬 클래스를 DB 테이블로 만들어주기 위한 '부모 클래스'를 찍어냄. 이 뼈대를 상속받은 클래스는 파이썬이 "아, 얘는 단순한 객체가 아니라 DB 테이블이구나!" 하고 인식하게 됨
자바 매칭: 클래스 위에 딱 붙이던 @Entity 어노테이션의 역할
역할: DB와 통신할 수 있는 '통로(Session)'를 계속해서 찍어내는 팩토리
자바 매칭: JPA의 EntityManagerFactory와 같음
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
위의 함수 안에 들어가는 옵션(파라미터)들은 필수일까?
-> bind=engine은 필수에 가깝고, autocommit=False와 autoflush=False는 문법적 필수는 아니지만 '안전한 웹 서버를 위한 실무 표준'
의미: "DB에 데이터를 변경(INSERT/UPDATE/DELETE)할 때, 내가 명시적으로 db.commit()을 호출하기 전까지는 절대 진짜로 반영(저장)하지 마!"
자바 비교: JDBC의 connection.setAutoCommit(false)와 같고, 스프링에서 @Transactional 어노테이션이 뒤에서 안전하게 처리해 주던 핵심 역할
왜 False로 할까?: 만약 A의 계좌에서 돈을 빼고 B의 계좌에 돈을 넣어야 하는데, B에게 넣기 직전에 서버가 터졌다고 가정해 보자. autocommit=True라면 A의 돈만 빠진 채로 DB에 저장되어버림. 개발자가 모든 비즈니스 로직이 성공적으로 끝난 후 한 번에 안전하게 커밋하기 위해 실무에서는 무조건 False로 둠.
의미: "내가 쿼리를 날리기 전에, 파이썬 메모리에만 들고 있던 변경 사항들을 알아서 DB에 밀어 넣지(Flush) 마!"
자바 비교: Hibernate의 영속성 컨텍스트(Persistence Context) 동기화 옵션인 FlushModeType.COMMIT과 아주 비슷한 개념
왜 False로 할까?: 파이썬 코드 상에서 객체의 값을 수정하면, SQLAlchemy는 그걸 즉시 DB에 쏘지 않고 잠시 메모리에 들고 있음. 만약 autoflush=True라면, 다른 쿼리를 조회하려고 할 때마다 눈치껏 메모리의 변경 사항을 DB에 억지로 동기화시킴. 이러면 내가 원하지 않는 타이밍에 쿼리가 무작위로 날아가서 성능이 떨어지거나 데이터가 꼬일 수 있기 때문에, 개발자가 완벽히 통제할 수 있도록 보통 False로 둠.
역할: 실제로 DB에 쿼리를 날리고(SELECT, INSERT 등), 데이터를 객체로 받아오는 실제 통로
자바 매칭: JPA의 EntityManager 객체 그 자체이자, JpaRepository가 내부적으로 데이터를 조작할 때 쓰는 핵심 객체
양옆에 언더바(Underscore)가 두 개씩(__) 붙은 변수나 함수를 던더라고 부르고 이건 일반 변수가 아니라, 시스템 내부에서 쓸 아주 특별한 설정값(또는 함수)이니까 철자 틀리지 말고 똑같이 적으라는 의미이다.
| 기능 | Java (JPA) | Python (SQLAlchemy) |
|---|---|---|
| 테이블 이름 지정 | @Table(name="users") | __tablename__ = "users" |
| 초기화(생성자) | public User() { ... } | def __init__(self): ... |
| 문자열 출력 | @Override public String toString() | def __str__(self): ... |
적는 위치(타이밍)가 매우 중요. 아무 데나 적으면 테이블이 생성되지 않거나 에러가 발생할 수 있음
"엔티티(모델)가 모두 정의된 직후, 서버가 켜지기 직전"
Base.metadata.create_all(bind=engine)이라는 코드는 쉽게 말해 "지금까지 내 뼈대(Base)를 상속받은 모든 클래스들을 모아서 DB에 테이블로 만들어라!"라는 명령. 따라서 반드시 아래 순서를 지켜야 함
from database import Base
from sqlalchemy import Column, Integer, String
class WeatherEntity(Base):
__tablename__ = "weather_data"
id = Column(Integer, primary_key=True)
# ...
from fastapi import FastAPI
from database import engine, Base
import models # 핵심: 이 줄에서 모델들을 읽어들임
# 모델이 다 읽힌 후, 테이블을 생성
Base.metadata.create_all(bind=engine)
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
자바에서 실무 프로젝트를 하실 때, 운영 서버 DB에 ddl-auto=update를 켜두지 않고 Flyway나 Liquibase 같은 DB 마이그레이션 툴을 사용하듯이 규모가 있는 실제 회사 코드에서는 Base.metadata.create_all(bind=engine) 아예 지워버리고, 파이썬 생태계의 Flyway인 Alembic (알렘빅) 이라는 도구를 사용해서 테이블 변경 이력을 버전별로 꼼꼼하게 관리한다.