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

본문
[이번 수업]
백엔드는 주문·잔액처럼 관련된 여러 값을 바꿉니다. 이번에는 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
백엔드는 주문·잔액처럼 관련된 여러 값을 바꿉니다. 이번에는 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
- 이전글[DB-007][중급] PostgreSQL 설치하고 상태 확인하기 26.09.02
- 다음글[WEB-007][중급] 상태를 바꾸고 화면을 다시 그리기 26.09.02
댓글목록
등록된 댓글이 없습니다.
