[BACK-007][중급] 데이터베이스 연결과 트랜잭션 묶기 > IT 기술 공유

본문 바로가기
사이트 내 전체검색

IT 기술 공유

[BACK-007][중급] 데이터베이스 연결과 트랜잭션 묶기

페이지 정보

profile_image
작성자 기술팀장
댓글 0건 조회 152회 작성일 26-09-02 14:39

본문

[이번 수업]
백엔드는 주문·잔액처럼 관련된 여러 값을 바꿉니다. 이번에는 Python `sqlite3`로 연결하고 두 변경을 한 트랜잭션으로 처리합니다.

[선수지식]
BACK-004의 서비스 역할, BACK-005의 입력 검증, DB-002의 UPDATE, DB-006의 트랜잭션을 알고 Python 3를 실행할 수 있어야 합니다.

[학습목표]
1. 연결과 트랜잭션의 역할을 구분한다.
2. 성공 시 커밋하고 실패 시 롤백한다.
3. 매개변수 바인딩과 변경 행 수 확인을 적용한다.

[핵심개념]
연결(connection)은 애플리케이션과 데이터베이스가 SQL과 결과를 주고받는 통로입니다. 작업 뒤에는 닫아 자원을 돌려줍니다.

트랜잭션(transaction)은 여러 읽기·쓰기를 한 단위로 묶습니다. 모두 성공하면 `commit()`으로 확정하고, 실패하면 `rollback()`으로 되돌립니다. 송금의 차감과 증가는 함께 확정해야 하며 필요한 SQL만 짧게 묶습니다.

SQL 값은 문자열 조립 대신 `?` 자리표시자와 튜플로 전달합니다. 이 매개변수 바인딩은 값과 SQL 구조를 분리합니다. UPDATE 대상이 없을 수도 있어 `rowcount`도 확인합니다.

[따라하기]
아래 내용을 transfer.py로 저장합니다. 현재 폴더에 lesson.db가 만들어집니다.

```python
import sqlite3


def transfer(con, sender, receiver, amount):
    if amount <= 0:
        raise ValueError("금액은 0보다 커야 합니다")
    try:
        con.execute("BEGIN")
        debited = con.execute(
            "UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?",
            (amount, sender, amount),
        )
        if debited.rowcount != 1:
            raise ValueError("계정이 없거나 잔액이 부족합니다")

        credited = con.execute(
            "UPDATE accounts SET balance = balance + ? WHERE id = ?",
            (amount, receiver),
        )
        if credited.rowcount != 1:
            raise ValueError("받는 계정이 없습니다")
        con.commit()
    except Exception:
        con.rollback()
        raise


def balances(con):
    return con.execute(
        "SELECT id, balance FROM accounts ORDER BY id"
    ).fetchall()


con = sqlite3.connect("lesson.db")
try:
    con.executescript("""
        DROP TABLE IF EXISTS accounts;
        CREATE TABLE accounts(
            id INTEGER PRIMARY KEY,
            balance INTEGER NOT NULL CHECK(balance >= 0)
        );
        INSERT INTO accounts VALUES(1, 10000);
        INSERT INTO accounts VALUES(2, 5000);
    """)

    transfer(con, 1, 2, 3000)
    print("성공 후:", balances(con))

    try:
        transfer(con, 1, 2, 999999)
    except ValueError as error:
        print("실패:", error)
    print("실패 후:", balances(con))
finally:
    con.close()
```

macOS·Linux는 `python3 transfer.py`, Windows PowerShell은 `py transfer.py` 또는 `python transfer.py`를 실행합니다. 예상 결과입니다.

```text
성공 후: [(1, 7000), (2, 8000)]
실패: 계정이 없거나 잔액이 부족합니다
실패 후: [(1, 7000), (2, 8000)]
```

실패 뒤에도 잔액이 같습니다. 롤백이 부분 변경을 없앤 결과입니다.

[흔한 실수]
- 여러 UPDATE 사이에서 먼저 `commit()`한다.
- 예외 뒤 `rollback()`하지 않는다.
- SQL에 입력값을 문자열로 이어 붙인다.
- UPDATE 뒤 `rowcount`를 확인하지 않는다.

[보안 주의]
입력은 형식·범위를 검증하고 매개변수로 바인딩합니다. 데이터베이스 계정에는 필요한 권한만 주고 비밀번호를 코드에 적지 않습니다. 오류 응답에 SQL문·접속 정보를 노출하지 마세요. 실습은 본인 소유 로컬 파일에서만 진행합니다.

[직접 해볼 과제]
받는 계정 ID를 99로 바꿔 실행하세요. 오류가 나도 잔액이 그대로인지 확인하고, 호출 전후 잔액 합계도 출력합니다.

[확인문제]
1. 연결과 트랜잭션은 각각 어떤 역할을 하나요?
2. 두 번째 UPDATE가 실패하면 무엇을 호출해야 하나요?
3. SQL 값에 자리표시자를 쓰는 이유는 무엇인가요?

[다음 학습]
다음 DB-007에서는 백업·복구·마이그레이션으로 데이터를 안전하게 옮기고 되돌리는 방법을 배웁니다.

[공식 참고 자료]
- Python sqlite3 공식 문서: https://docs.python.org/3/library/sqlite3.html
- SQLite 트랜잭션 공식 문서: https://www.sqlite.org/lang_transaction.html

댓글목록

등록된 댓글이 없습니다.

회원로그인

회원가입

사이트 정보

회사명 : 회사명 / 대표 : 대표자명
주소 : OO도 OO시 OO구 OO동 123-45
사업자 등록번호 : 123-45-67890
전화 : 02-123-4567 팩스 : 02-123-4568
통신판매업신고번호 : 제 OO구 - 123호
개인정보관리책임자 : 정보책임자명

접속자집계

오늘
4,940
어제
6,862
최대
16,772
전체
770,891
Copyright © 소유하신 도메인. All rights reserved.