
0. 학습 목표
→ pyqtdb 안에 users 테이블을 만들고, 동작을 파일로 기록하는 로그 기능을 붙입니다.
0.1 이번 글에서 다룰 내용
이번 글은 실습 중심 강의입니다.
3강에서 만든 pyqtdb 안에 users 테이블을 만듭니다.
이번에는 데이터베이스가 이미 존재하므로 database를 지정해 연결한다는 점이 3강과 다릅니다.
또한 어떤 작업이 언제 실행됐는지 파일에 남기는 로그 기능을 추가해, 이후 강의에서 계속 재사용합니다.
| 구분 | 내용 |
| 이해할 것 | 데이터베이스와 테이블의 관계, 테이블 생성 시 database를 지정해 연결하는 이유 |
| 만들 것 | users 테이블을 만드는 버튼·메서드, 동작을 파일로 남기는 write_log_with_qt |
0.2 이번 강의 실습 내용
이번 강의에서 직접 해보는 것은 다음과 같습니다.
- 테이블 생성 버튼을 창에 추가합니다.
write_log_with_qt로그 함수를 작성합니다.create_users_table메서드로users테이블을 만듭니다.- MySQL 클라이언트와 로그 파일로 생성 결과를 확인합니다.
1. 무엇을 추가하는가
→ 3강 코드에서 이번에 바뀌는 버튼과 import 위치를 먼저 확인합니다.
이번에도 이전 기능은 손대지 않습니다.
로그 기록에 쓸 QFile 계열 import를 더하고, 버튼 하나와 시그널·슬롯 한 줄을 추가합니다.
그리고 클래스 안에 새 메서드 두 개(write_log_with_qt, create_users_table)를 추가합니다.

실제 diff 코드를 보기 전에, 이번에 손대는 자리를 먼저 지도로 확인합니다.
from PySide6.QtCore import ... ← ① QFile 계열 import 추가
class MySqlDemoWindow(QWidget):
def __init__(self):
... 입력 필드 (2강) ...
... 버튼 생성 ← ② users 테이블 생성 버튼 만들기
... main_layout 배치 ← ③ addWidget 으로 버튼 넣기
... 시그널 연결 ← ④ clicked.connect 연결
def connect_to_server_or_db(self): # 2강 메서드 (그대로)
...
def create_database(self): # 3강 메서드 (그대로)
...
# ⑤ write_log_with_qt 메서드를 여기(클래스 안)에 새로 추가
# ⑥ create_users_table 메서드를 여기(클래스 안)에 새로 추가
①~④는 __init__ 안에서 동시에 바뀌는 자리이고,
⑤~⑥은 클래스 안에 새로 추가되는 메서드입니다. 아래는 ①~④의 실제 코드입니다.
from PySide6.QtCore import QFile, QIODevice, QTextStream # 추가 (①, 로그 기록용)
# ── ② 버튼 생성 부분 ──
self.btn_create_db = QPushButton("데이터베이스 생성") # (3강)
self.btn_create_table = QPushButton("users 테이블 생성") # 추가 (DB 생성 버튼 바로 아래)
self.result_label = QLabel("연결 결과가 여기에 표시됩니다.") # (2강)
# ── ③ main_layout 배치 부분 ──
main_layout = QVBoxLayout(self) # (2강)
main_layout.addLayout(form_layout) # (2강)
main_layout.addWidget(self.btn_connect) # (2강)
main_layout.addWidget(self.btn_create_db) # (3강)
main_layout.addWidget(self.btn_create_table) # 추가 (DB 생성 버튼 위젯 바로 아래)
main_layout.addWidget(self.result_label) # (2강)
# ── ④ 시그널 연결 부분 ──
self.btn_connect.clicked.connect(self.connect_to_server_or_db) # (2강)
self.btn_create_db.clicked.connect(self.create_database) # (3강)
self.btn_create_table.clicked.connect(self.create_users_table) # 추가
이렇게 이전 기능은 손대지 않고 새 기능만 얹는 방식으로 구현합니다.
같은 입력 필드(host·user·password·db)를 연결 테스트·DB 생성·테이블 생성이 함께 사용합니다.
2. 테이블 생성 흐름 이해하기
→ 테이블 생성 시 연결 방식이 왜 달라지는지, 어떤 구조로 만들지 정리합니다.
2.1 이번엔 database를 지정해 연결한다
3강에서는 DB가 아직 없었기 때문에 database를 빼고 서버에만 연결했습니다.
이번에는 pyqtdb가 이미 있고,
테이블은 데이터베이스 안에 만들어지므로
database=pyqtdb를 지정해 연결합니다.
이는 MySQL 클라이언트에서 USE pyqtdb로 그 DB에 들어간 뒤 테이블을 만드는 것과 같은 의미입니다.
| 작업 | 연결 방식 |
| DB 생성 (3강) | database 없이 서버에만 연결 — 아직 그 DB가 없으므로 |
| 테이블 생성 (4강) | database=pyqtdb를 지정해 연결 — 테이블은 DB 안에 만들어지므로 |
2.2 users 테이블 구조 설계
앞으로 모든 기능이 사용할 users 테이블은 컬럼 세 개로 설계합니다.
이 구조는 이후 데이터 삽입·조회·검색·로그인에서 그대로 재사용됩니다.
| 컬럼 | 타입 | 의미 |
id |
INT AUTO_INCREMENT PRIMARY KEY |
자동으로 번호가 붙는 기본키 |
username |
VARCHAR(150) NOT NULL |
사용자 이름, 비어 있을 수 없음 |
password |
VARCHAR(150) NOT NULL |
비밀번호, 비어 있을 수 없음 |
이 구조를 SQL로 옮기면 다음과 같습니다.
테이블 이름 users는 사용자 입력이 아니라 코드에 고정된 값이므로, 3강의 DB 이름과 달리 별도 검증이 필요 없습니다.
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(150) NOT NULL,
password VARCHAR(150) NOT NULL
)
3. 동작 로그 남기기
→ Qt의 파일 입출력으로, 실행한 작업을 파일에 한 줄씩 기록하는 함수를 만듭니다.
어떤 작업이 언제 실행됐는지 파일로 남겨 두면, 나중에 문제가 생겼을 때 원인을 찾기 쉽습니다.
파이썬 기본 open 대신
Qt의 QFile과 QTextStream을 사용합니다.
파일은 Append 모드로 열어 기존 내용 뒤에 이어 붙입니다.
이 메서드는 위치 지도의 ⑤번 자리, 즉 클래스 안 create_database 메서드 아래에 새로 추가합니다.

# ⑤ create_database(3강) 메서드 아래에 새로 추가
def write_log_with_qt(self, message):
# db_log_all_steps.txt 파일을 이어쓰기(Append) 모드로 연다.
file = QFile("db_log_all_steps.txt")
if not file.open(QIODevice.Append | QIODevice.Text):
# 파일을 열 수 없으면 조용히 반환한다.
return
# 텍스트 스트림으로 메시지를 한 줄 기록한 뒤 파일을 닫는다.
stream = QTextStream(file)
stream << message << "\n"
file.close()
이 함수는 데이터베이스 생성, 테이블 생성, 이후의 데이터 삽입·조회 등 중요한 단계마다 호출해,
어떤 작업이 수행됐는지 한 줄씩 남깁니다.
성공뿐 아니라 실패 상황도 함께 기록하면 디버깅에 특히 유용합니다.
✔ 참고: stream << message의 <<는 스트림에 값을 흘려보내는 연산자입니다. 파일이 main.py를 실행한 폴더에 만들어지므로, 로그가 보이지 않으면 실행 위치를 먼저 확인하세요.
4. 테이블 생성 기능 구현하기
→ create_users_table 메서드를 작성해, 연결·생성·로그 기록을 한 번에 처리합니다.
클래스 안에 create_users_table 메서드를 추가합니다.
위치 지도의 ⑥번 자리, 즉 write_log_with_qt 메서드 아래입니다.
2·3강과 같은 conn = None + try-except-finally 뼈대를 그대로 쓰되,
성공과 실패 모두 write_log_with_qt로 기록하는 점이 새롭습니다.

# ⑥ write_log_with_qt 메서드 아래에 새로 추가
def create_users_table(self):
host = self.host_edit.text().strip()
user = self.user_edit.text().strip()
password = self.password_edit.text()
db_name = self.db_edit.text().strip()
# 테이블은 DB 안에 만들어지므로 데이터베이스 이름까지 필요하다.
if not host or not user or not db_name:
QMessageBox.warning(self, "입력 오류", "테이블을 만들 데이터베이스 이름까지 모두 입력해야 합니다.")
return
create_table_sql = """
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(150) NOT NULL,
password VARCHAR(150) NOT NULL
)
"""
conn = None
try:
# 이번에는 database를 지정해 연결한다. (USE pyqtdb 와 같은 효과)
conn = mc.connect(host=host, user=user, password=password, database=db_name)
cursor = conn.cursor()
cursor.execute(create_table_sql)
conn.commit()
msg = f"{db_name} 데이터베이스에 users 테이블이 생성되었습니다."
self.result_label.setText(msg)
QMessageBox.information(self, "테이블 생성 완료", msg)
self.write_log_with_qt(f"테이블 생성: {db_name}.users")
except mc.Error as e:
# 이미 같은 이름의 테이블이 있는 경우 등에서 예외가 발생한다.
self.result_label.setText("테이블 생성 중 오류가 발생했습니다.")
QMessageBox.critical(self, "테이블 생성 오류", f"users 테이블 생성에 실패했습니다.\n오류 내용: {e}")
self.write_log_with_qt(f"테이블 생성 실패: {db_name}.users, 오류: {e}")
finally:
if conn and conn.is_connected():
conn.close()
성공했을 때와 실패했을 때 각각 다른 메시지를 로그에 남기는 점을 눈여겨보세요.
나중에 로그 파일만 봐도 어떤 작업이 어디서 실패했는지 추적할 수 있습니다.
▶ 지금 해보세요
- 1에서 본 ①~④ 위치에 import·버튼·시그널을 __init__에 반영합니다.
- ⑤ 위치에
write_log_with_qt, ⑥ 위치에create_users_table메서드를 추가합니다. - 연결 방식이
database=db_name으로 바뀐 부분을 3강 코드와 비교해 봅니다.
5. 실행하고 결과 확인하기
→ 테이블을 만들고, MySQL 클라이언트와 로그 파일로 결과를 확인합니다.
main.py를 실행하고, 데이터베이스 이름에 pyqtdb가 들어 있는 상태에서 users 테이블 생성 버튼을 누릅니다.
성공하면 생성 완료 메시지가 뜨고, 같은 폴더의 db_log_all_steps.txt에 기록이 남습니다.

MySQL 클라이언트에서는 아래 명령으로 확인합니다.
USE pyqtdb; -- 실습용 데이터베이스로 이동
SHOW TABLES; -- users 테이블이 목록에 있는지 확인
DESCRIBE users; -- users 테이블의 컬럼 구조 확인
💡 팁: 버튼을 다시 누르면 3강 DB 생성과 마찬가지로 "이미 존재한다"는 오류가 납니다. CREATE TABLE IF NOT EXISTS users (...)를 쓰면 오류 없이 넘어가지만, 이번에도 예외 처리 동작을 확인하기 위해 IF NOT EXISTS 없이 진행합니다.
▶ 지금 해보세요
- 데이터베이스 이름에
pyqtdb를 넣고 users 테이블 생성 버튼을 눌러 성공 메시지를 확인합니다. - 실행 폴더에
db_log_all_steps.txt가 생기고 테이블 생성 기록이 있는지 확인합니다. - MySQL 클라이언트에서
SHOW TABLES;와DESCRIBE users;로 구조를 확인합니다. - 버튼을 한 번 더 눌러 오류 팝업과 실패 로그가 남는지 확인합니다.
✔ 정리: 이제 pyqtdb 안에 users 테이블이 준비됐고, 모든 작업이 로그로 남습니다. 여기까지가 데이터를 담기 위한 그릇을 만드는 단계였습니다.
6. 이번 강의 완성본
→ 2~4강까지 이어 붙인 전체 코드를 한 번에 확인합니다.
본문에서는 변경분만 다뤘습니다.
아래는 위치를 대조하거나 코드가 어긋났을 때 기준으로 삼을 mainwindow.py 전체입니다.
main.py는 2강에서 작성한 것과 동일하므로 여기서는 mainwindow.py만 싣습니다.
import sys
from PySide6.QtWidgets import (
QApplication, QWidget, QLabel, QLineEdit,
QPushButton, QVBoxLayout, QFormLayout, QMessageBox,
)
from PySide6.QtCore import QFile, QIODevice, QTextStream
import mysql.connector as mc
class MySqlDemoWindow(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("MySQL 연결 설정")
self.resize(420, 320)
# 접속 정보 입력 필드
self.host_edit = QLineEdit()
self.user_edit = QLineEdit()
self.password_edit = QLineEdit()
self.password_edit.setEchoMode(QLineEdit.Password)
self.db_edit = QLineEdit()
self.host_edit.setText("localhost")
self.user_edit.setText("root")
form_layout = QFormLayout()
form_layout.addRow("호스트", self.host_edit)
form_layout.addRow("사용자", self.user_edit)
form_layout.addRow("비밀번호", self.password_edit)
form_layout.addRow("데이터베이스 이름", self.db_edit)
self.btn_connect = QPushButton("연결 테스트")
self.btn_create_db = QPushButton("데이터베이스 생성")
self.btn_create_table = QPushButton("users 테이블 생성")
self.result_label = QLabel("연결 결과가 여기에 표시됩니다.")
main_layout = QVBoxLayout(self)
main_layout.addLayout(form_layout)
main_layout.addWidget(self.btn_connect)
main_layout.addWidget(self.btn_create_db)
main_layout.addWidget(self.btn_create_table)
main_layout.addWidget(self.result_label)
self.btn_connect.clicked.connect(self.connect_to_server_or_db)
self.btn_create_db.clicked.connect(self.create_database)
self.btn_create_table.clicked.connect(self.create_users_table)
def connect_to_server_or_db(self):
host = self.host_edit.text().strip()
user = self.user_edit.text().strip()
password = self.password_edit.text()
db_name = self.db_edit.text().strip()
if not host or not user:
QMessageBox.warning(self, "입력 오류", "호스트와 사용자 이름은 반드시 입력해야 합니다.")
return
conn = None
try:
if db_name:
conn = mc.connect(host=host, user=user, password=password, database=db_name)
else:
conn = mc.connect(host=host, user=user, password=password)
if conn.is_connected():
if db_name:
msg = f"MySQL 서버에 연결되었습니다.\n사용 중인 데이터베이스: {db_name}"
else:
msg = "MySQL 서버에 연결되었습니다.\n데이터베이스 이름은 지정되지 않았습니다."
self.result_label.setText(msg)
QMessageBox.information(self, "연결 성공", msg)
except mc.Error as e:
self.result_label.setText("연결 중 오류가 발생했습니다.")
QMessageBox.critical(self, "연결 오류", f"MySQL 연결에 실패했습니다.\n오류 내용: {e}")
finally:
if conn and conn.is_connected():
conn.close()
def create_database(self):
host = self.host_edit.text().strip()
user = self.user_edit.text().strip()
password = self.password_edit.text()
db_name = self.db_edit.text().strip()
if not host or not user or not db_name:
QMessageBox.warning(self, "입력 오류", "호스트, 사용자, 데이터베이스 이름을 모두 입력해야 합니다.")
return
if not db_name.isidentifier():
QMessageBox.warning(self, "입력 오류", "데이터베이스 이름은 영문·숫자·밑줄만 사용할 수 있습니다.")
return
conn = None
try:
conn = mc.connect(host=host, user=user, password=password)
cursor = conn.cursor()
cursor.execute(f"CREATE DATABASE {db_name}")
conn.commit()
msg = f"{db_name} 데이터베이스가 생성되었습니다."
self.result_label.setText(msg)
QMessageBox.information(self, "생성 완료", msg)
except mc.Error as e:
self.result_label.setText("데이터베이스 생성에 실패했습니다.")
QMessageBox.critical(self, "생성 오류", f"데이터베이스 생성에 실패했습니다.\n오류 내용: {e}")
finally:
if conn and conn.is_connected():
conn.close()
def write_log_with_qt(self, message):
file = QFile("db_log_all_steps.txt")
if not file.open(QIODevice.Append | QIODevice.Text):
return
stream = QTextStream(file)
stream << message << "\n"
file.close()
def create_users_table(self):
host = self.host_edit.text().strip()
user = self.user_edit.text().strip()
password = self.password_edit.text()
db_name = self.db_edit.text().strip()
if not host or not user or not db_name:
QMessageBox.warning(self, "입력 오류", "테이블을 만들 데이터베이스 이름까지 모두 입력해야 합니다.")
return
create_table_sql = """
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(150) NOT NULL,
password VARCHAR(150) NOT NULL
)
"""
conn = None
try:
conn = mc.connect(host=host, user=user, password=password, database=db_name)
cursor = conn.cursor()
cursor.execute(create_table_sql)
conn.commit()
msg = f"{db_name} 데이터베이스에 users 테이블이 생성되었습니다."
self.result_label.setText(msg)
QMessageBox.information(self, "테이블 생성 완료", msg)
self.write_log_with_qt(f"테이블 생성: {db_name}.users")
except mc.Error as e:
self.result_label.setText("테이블 생성 중 오류가 발생했습니다.")
QMessageBox.critical(self, "테이블 생성 오류", f"users 테이블 생성에 실패했습니다.\n오류 내용: {e}")
self.write_log_with_qt(f"테이블 생성 실패: {db_name}.users, 오류: {e}")
finally:
if conn and conn.is_connected():
conn.close()
→ 다음 강의 (5강): 방금 만든 users 테이블에 실제 데이터를 넣습니다. 이때 사용자 입력값을 %s로 안전하게 전달하는 파라미터 바인딩으로 SQL Injection을 막는 방법을 다룹니다. 3강에서 본 '식별자는 검증, 값은 바인딩' 규칙의 나머지 절반입니다.