Flask-SQLAlchemy 모델, 관계 및 트랜잭션 관리를 사용한 견고한 애플리케이션 구축
Grace Collins
Solutions Engineer · Leapcell

소개: 현대 웹 애플리케이션의 기반
끊임없이 진화하는 백엔드 개발 환경에서 효과적인 데이터 관리는 견고하고 확장 가능한 애플리케이션 구축의 초석입니다. 데이터베이스와의 상호 작용은 기본적인 요구 사항이며, Flask 마이크로프레임워크를 활용하는 Python 개발자에게 Flask-SQLAlchemy는 없어서는 안 될 도구로 부상합니다. Python 코드와 기본 데이터베이스 간의 격차를 매끄럽게 연결하여 Python 객체를 데이터베이스 행으로, 그 반대로 변환합니다. 이러한 원활한 통합을 통해 개발자는 애플리케이션 데이터 모델을 직관적으로 정의하고, 정보의 서로 다른 조각 간의 복잡한 관계를 관리하며, 무엇보다도 강력한 트랜잭션 관리를 통해 데이터 일관성과 안정성을 보장할 수 있습니다. 이 가이드에서는 Flask-SQLAlchemy의 이러한 핵심 측면을 깊이 있게 다루고 웹 프로젝트에서 해당 기능을 최대한 활용하는 방법을 조명할 것입니다.
핵심 개념: 빌딩 블록 이해하기
실질적인 내용으로 들어가기 전에 Flask-SQLAlchemy에 대한 논의의 기초가 되는 주요 용어를 명확히 이해해야 합니다.
- ORM (객체 관계 매퍼): ORM은 객체 지향 프로그래밍 언어를 사용하여 호환되지 않는 유형 시스템 간의 데이터를 변환하는 프로그래밍 기법입니다. 간단히 말해, SQL 쿼리가 아닌 Python 객체를 사용하여 데이터베이스와 상호 작용할 수 있습니다. Flask-SQLAlchemy는 ORM입니다.
- 모델: Flask-SQLAlchemy에서 모델은 데이터베이스의 테이블을 나타내는 Python 클래스입니다. 이 클래스의 각 인스턴스는 해당 테이블의 행에 해당하며, 클래스의 각 속성은 열에 해당합니다.
- 관계: 관계는 모델(따라서 테이블)이 데이터베이스에서 어떻게 연결되는지를 정의합니다. 일반적인 유형에는 일대일, 일대다, 다대다 관계가 포함됩니다.
- 세션: 세션은 데이터베이스에서 로드된 모든 객체 또는 데이터베이스와 관련된 모든 객체의 스테이징 영역 역할을 합니다. 이를 통해 여러 데이터베이스 작업(객체 추가, 객체 업데이트, 객체 삭제 등)을 수집하여 단일 원자 단위로 커밋할 수 있습니다.
- 트랜잭션: 트랜잭션은 단일 논리적 작업 단위로 수행되는 일련의 작업입니다. 데이터를 완전히 완료(커밋)하거나 전혀 영향을 미치지 않습니다(롤백). 데이터 무결성을 보장합니다.
Flask-SQLAlchemy로 데이터 모델 정의하기
Flask-SQLAlchemy의 핵심은 Python 클래스로 데이터베이스 테이블을 정의하는 기능입니다. 이 객체 지향 접근 방식은 원시 SQL을 직접 작성하는 것보다 데이터 조작을 직관적이고 오류가 적게 만듭니다.
사용자 및 게시물 엔터티가 있는 간단한 블로그 애플리케이션을 생각해 보겠습니다.
from flask import Flask from flask_sqlalchemy import SQLAlchemy from datetime import datetime app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 수정 추적 비활성화 db = SQLAlchemy(app) class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) # Post와의 일대다 관계 정의 posts = db.relationship('Post', backref='author', lazy=True) def __repr__(self): return f'<User {self.username}> ' class Post(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(100), nullable=False) content = db.Column(db.Text, nullable=False) date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) def __repr__(self): return f'<Post {self.title}> ' # 테이블을 생성하려면 일반적으로 Python 셸에서다음과 같이 실행합니다. # with app.app_context(): # db.create_all()
이 예제에서:
SQLAlchemy
를 가져와 Flask 앱으로 초기화합니다.User
및Post
클래스는db.Model
을 상속하여 SQLAlchemy 모델로 표시합니다.db.Column
은 데이터 유형(예:db.Integer
,db.String
), 제약 조건(예:primary_key=True
,unique=True
,nullable=False
) 및 기본값을 지정하여 각 테이블의 열을 정의합니다.
모델 간 관계 관리
실제 애플리케이션은 독립적인 데이터를 거의 다루지 않습니다. 정보는 서로 연결되어 있습니다. Flask-SQLAlchemy는 이러한 관계를 정의하는 강력한 도구를 제공하여 데이터 그래프를 자연스럽게 탐색할 수 있게 합니다.
일대다 관계를 설명하기 위해 User
및 Post
예제를 확장해 보겠습니다.
# 이전 섹션의 app, db, User, Post 정의 코드) # 객체 생성 및 연결 예제 with app.app_context(): # 테이블이 존재하지 않으면 생성합니다. # db.create_all() # 새 사용자 생성 user1 = User(username='john_doe', email='john@example.com') db.session.add(user1) db.session.commit() # user1의 ID를 가져오기 위해 커밋 # 이 사용자에 대한 게시물 생성 post1 = Post(title='My First Blog Post', content='This is the content of my first post.', author=user1) post2 = Post(title='Another Post', content='More great content here!', author=user1) db.session.add_all([post1, post2]) db.session.commit() # 관련 데이터 액세스 retrieved_user = User.query.filter_by(username='john_doe').first() print(f'User: {retrieved_user.username}') for post in retrieved_user.posts: # 'posts' 관계를 사용합니다. print(f' - Post: {post.title} by {post.author.username}') # 'author' 백레퍼런스와 'username' 속성을 통해 액세스 retrieved_post = Post.query.filter_by(title='My First Blog Post').first() print(f'Post Author: {retrieved_post.author.username}') # 백레퍼런스를 통해 작성자 액세스
관계 정의에 대한 설명은 다음과 같습니다.
User.posts = db.relationship('Post', backref='author', lazy=True)
:User
모델의 이 줄은 일대다 관계를 정의합니다.'Post'
는 이 사용자가 많은Post
객체를 관련시킬 수 있음을 나타냅니다.backref='author'
는Post
모델에author
속성을 자동으로 추가하여 특정 게시물을 소유한User
객체(예:post.author
)에 액세스할 수 있게 합니다.lazy=True
(또는lazy='select'
)는 관련 객체가 처음 액세스될 때 데이터베이스에서 로드됨(지연 로딩)을 의미합니다. 다른 옵션으로는lazy='joined'
(즉시 로딩) 또는lazy='subquery'
가 있습니다.
Post.user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
: 이것은 Post 모델의 기본 키 정의로, 각 게시물을id
를 통해 특정 사용자와 연결합니다.
Flask-SQLAlchemy는 복잡한 SQL JOIN 및 데이터 로딩을 백그라운드에서 처리하여 Python 객체로 자연스럽게 작업할 수 있습니다.
데이터 무결성을 위한 트랜잭션 관리
트랜잭션은 특히 여러 데이터베이스 작업이 서로 의존하는 경우 데이터 일관성을 유지하는 데 매우 중요합니다. 하나의 작업이 실패하면 전체 작업 집합이 롤백되어 데이터베이스가 유효한 상태를 유지해야 합니다.
Flask-SQLAlchemy는 트랜잭션 관리를 위해 데이터베이스 세션(db.session
)을 활용합니다. 세션을 통해 수행된 모든 변경 사항은 수집된 다음 커밋되거나 롤백됩니다.
두 계정 간의 자금 이체를 원하는 시나리오를 생각해 보겠습니다. 여기에는 한 계정의 잔액을 줄이고 다른 계정의 잔액을 늘리는 것이 포함됩니다. 두 작업 모두 함께 성공하거나 실패해야 합니다.
# 계정 모델이 있다고 가정 class Account(db.Model): id = db.Column(db.Integer, primary_key=True) account_number = db.Column(db.String(20), unique=True, nullable=False) balance = db.Column(db.Float, nullable=False, default=0.0) def __repr__(self): return f'<Account {self.account_number} Balance: {self.balance)> ' # (db 및 app가 이전에 초기화되었고 테이블이 생성되었다고 가정) def transfer_funds(source_account_id, target_account_id, amount): if amount <= 0: raise ValueError("Transfer amount must be positive.") try: source_account = Account.query.get(source_account_id) target_account = Account.query.get(target_account_id) if not source_account: raise ValueError(f"Source account {source_account_id} not found.") if not target_account: raise ValueError(f"Target account {target_account_id} not found.") if source_account.balance < amount: raise ValueError(f"Insufficient funds in source account {source_account_id}.") source_account.balance -= amount target_account.balance += amount # 모든 작업이 세션에 수집됩니다. # 예외가 발생하지 않으면 커밋합니다. db.session.commit() print(f"Successfully transferred {amount} from {source_account.account_number} to {target_account.account_number}.") return True except Exception as e: # 어떤 예외가 발생하든이 트랜잭션에서 수행된 모든 변경 사항을 롤백합니다. db.session.rollback() print(f"Transaction failed: {e}. All changes rolled back.") return False # 사용 예제 with app.app_context(): # db.create_all() # Account 테이블이 존재하는지 확인 # 테스트용 계정 초기화 # account_a = Account(account_number='ACC1001', balance=1000.0) # account_b = Account(account_number='ACC1002', balance=500.0) # db.session.add_all([account_a, account_b]) # db.session.commit() # 초기 설정 후 계정 ID 1과 2가 있다고 가정 print("Before transfer:") print(Account.query.get(1)) print(Account.query.get(2)) transfer_funds(1, 2, 200.0) # 성공적인 이체 transfer_funds(1, 2, 900.0) # 잔액 부족, 롤백될 것입니다. print("\nAfter transfers attempted:") print(Account.query.get(1)) print(Account.query.get(2))
이 transfer_funds
함수에서:
- 모든 수정 사항(
source_account.balance -= amount
,target_account.balance += amount
)은 현재db.session
의 객체에 적용됩니다. 이러한 변경 사항은 즉시 데이터베이스에 기록되지 않습니다. db.session.commit()
은 보류 중인 모든 변경 사항을 단일 트랜잭션으로 데이터베이스에 기록하려고 시도합니다. 데이터베이스 엔진이 모든 작업을 성공적으로 처리하면 트랜잭션이 커밋됩니다.- 작업 중 오류가 발생하면(예: 잔액 부족에 대한
ValueError
또는 데이터베이스 제약 조건 위반),except
블록이 트리거됩니다. db.session.rollback()
은 세션의 모든 보류 중인 변경 사항을 폐기하여 마지막 커밋 또는 롤백 이후 수행된 모든 수정을 효과적으로 취소하고 데이터베이스가 일관된 상태를 유지하도록 보장합니다.
이 try...except...db.session.rollback()
패턴은 데이터 무결성이 중요한 안정적인 애플리케이션을 구축하는 데 중요합니다.
결론: Flask-SQLAlchemy로 데이터 영속성 마스터하기
Flask-SQLAlchemy는 Flask 애플리케이션에서 데이터베이스와 상호 작용하는 강력하고 우아하며 Pythonic한 방법을 제공합니다. 모델 정의, 복잡한 관계 관리, 강력한 트랜잭션 관리 구현 기능을 숙달함으로써 개발자는 유지 관리가 용이하고 확장 가능하며 데이터 일관성이 뛰어난 백엔드 시스템을 구축할 수 있습니다. 이는 데이터베이스 상호 작용이라는 어려운 작업을 즐겁고 직관적인 경험으로 변환하여 기본 SQL과 씨름하는 대신 비즈니스 논리에 집중할 수 있게 합니다. Flask-SQLAlchemy를 채택하여 다음 웹 개발 프로젝트를 위한 견고하고 신뢰할 수 있는 데이터 기반을 마련하십시오.