5. Data 삽입

1. 목표
더보기
이 단계의 학습 목표
- QLineEdit, QPushButton을 사용해 간단한 입력 폼을 구성할 수 있다.
- MySQL INSERT SQL 문을 이해하고 직접 작성할 수 있다.
- 파라미터 바인딩을 사용해 SQL Injection을 방지할 수 있다.
- PySide6의 QMessageBox로 성공, 실패 메시지를 표시할 수 있다.
- QFile, QTextStream을 사용해 삽입 로그를 파일로 남길 수 있다.
전제 조건
- 데이터베이스 pyqtdb가 이미 생성되어 있다.
- pyqtdb 안에 users 테이블이 이미 생성되어 있다.
(id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(150), password VARCHAR(150))
2. 전체 로직
더보기
import sys
from PySide6.QtWidgets import (
QApplication,
QWidget,
QLabel,
QLineEdit,
QPushButton,
QVBoxLayout,
QFormLayout,
QMessageBox,
QGroupBox,
)
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("PySide6 MySQL 연동 통합 데모")
self.resize(540, 460)
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")
conn_form = QFormLayout()
conn_form.addRow("호스트", self.host_edit)
conn_form.addRow("사용자", self.user_edit)
conn_form.addRow("비밀번호", self.password_edit)
conn_form.addRow("데이터베이스 이름", self.db_edit)
self.btn_connect = QPushButton("연결 테스트")
self.btn_create_db = QPushButton("데이터베이스 생성 (TestDB)")
self.btn_create_table = QPushButton("users 테이블 생성")
conn_button_layout = QVBoxLayout()
conn_button_layout.addWidget(self.btn_connect)
conn_button_layout.addWidget(self.btn_create_db)
conn_button_layout.addWidget(self.btn_create_table)
conn_group_layout = QVBoxLayout()
conn_group_layout.addLayout(conn_form)
conn_group_layout.addLayout(conn_button_layout)
conn_group = QGroupBox("1~3단계: 연결·DB·테이블 생성")
conn_group.setLayout(conn_group_layout)
self.username_edit = QLineEdit()
self.password_user_edit = QLineEdit()
self.password_user_edit.setEchoMode(QLineEdit.Password)
insert_form = QFormLayout()
insert_form.addRow("사용자 이름", self.username_edit)
insert_form.addRow("비밀번호", self.password_user_edit)
self.btn_insert_user = QPushButton("users 테이블에 사용자 추가")
insert_group_layout = QVBoxLayout()
insert_group_layout.addLayout(insert_form)
insert_group_layout.addWidget(self.btn_insert_user)
insert_group = QGroupBox("4단계: PySide6 폼에서 데이터 입력 후 MySQL 저장")
insert_group.setLayout(insert_group_layout)
self.result_label = QLabel("결과 메시지가 여기에 표시됩니다.")
main_layout = QVBoxLayout(self)
main_layout.addWidget(conn_group)
main_layout.addWidget(insert_group)
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)
self.btn_insert_user.clicked.connect(self.insert_user)
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
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.handle_mysql_error(e, "연결 중 오류가 발생했습니다.", "연결 오류")
finally:
try:
if conn.is_connected():
conn.close()
except Exception:
pass
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
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)
self.write_log_with_qt(f"데이터베이스 생성: {db_name}")
except mc.Error as e:
self.handle_mysql_error(e, "데이터베이스 생성에 실패했습니다.", "데이터베이스 생성 오류")
finally:
try:
if conn.is_connected():
conn.close()
except Exception:
pass
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
)
"""
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.handle_mysql_error(e, "테이블 생성 중 오류가 발생했습니다.", "테이블 생성 오류")
finally:
try:
if conn.is_connected():
conn.close()
except Exception:
pass
def insert_user(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()
username = self.username_edit.text().strip()
user_password = self.password_user_edit.text().strip()
if not host or not user or not db_name:
QMessageBox.warning(self, "입력 오류", "먼저 접속 정보와 데이터베이스 이름을 모두 입력해야 합니다.")
return
if not username or not user_password:
QMessageBox.warning(self, "입력 오류", "사용자 이름과 비밀번호를 모두 입력해야 합니다.")
return
try:
conn = mc.connect(
host=host,
user=user,
password=password,
database=db_name,
)
cursor = conn.cursor()
query = "INSERT INTO users (username, password) VALUES (%s, %s)"
values = (username, user_password)
cursor.execute(query, values)
conn.commit()
msg = f"사용자 {username} 정보가 저장되었습니다."
self.result_label.setText(msg)
QMessageBox.information(self, "저장 완료", msg)
self.write_log_with_qt(f"사용자 추가 성공: {db_name}.users, username={username}")
self.username_edit.clear()
self.password_user_edit.clear()
self.username_edit.setFocus()
except mc.Error as e:
self.handle_mysql_error(e, "데이터 저장 중 오류가 발생했습니다.", "저장 오류", username=username)
finally:
try:
if conn.is_connected():
conn.close()
except Exception:
pass
def handle_mysql_error(self, error, ui_message, title, username=None):
self.result_label.setText(ui_message)
if hasattr(error, "errno") and error.errno == 1045:
detail = (
"접속이 거부되었습니다.\n\n"
"가능한 원인\n"
"1. 사용자 이름이 잘못되었습니다.\n"
"2. 비밀번호가 잘못되었거나 비밀번호가 필요한데 비워 둔 상태입니다.\n"
"3. 현재 MySQL 서버 설정에서 root 계정 외의 별도 계정이 필요할 수 있습니다.\n\n"
"해결 방법\n"
"1. MySQL 클라이언트에서 직접 로그인해 사용자와 비밀번호를 확인합니다.\n"
"2. 이 창의 사용자와 비밀번호 입력란에 동일한 정보를 입력합니다.\n"
"3. 필요하다면 애플리케이션 전용 계정을 새로 생성하고 권한을 부여합니다."
)
QMessageBox.critical(
self,
title,
f"{ui_message}\n\n오류 코드: 1045\n오류 내용: {error}\n\n{detail}",
)
else:
QMessageBox.critical(
self,
title,
f"{ui_message}\n\n오류 내용: {error}",
)
if username is not None:
self.write_log_with_qt(f"사용자 추가 실패: {username}, 오류: {error}")
else:
self.write_log_with_qt(f"MySQL 작업 실패, 오류: {error}")
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()
# main.py
import sys
from PySide6.QtWidgets import QApplication
from mainwindow import MySqlDemoWindow
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MySqlDemoWindow()
w.show()
sys.exit(app.exec())
3. 필요한 import + 주석 설명
더보기
동일
4. GUI 구현부 소스코드와 주석
더보기
- 위쪽 그룹: 1~3단계 (연결, DB 생성, 테이블 생성)
- 아래쪽 그룹: 4단계 (INSERT)
class MySqlInsertDemoWindow(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("PySide6 MySQL 연동 통합 데모")
self.resize(540, 460)
# 이전 단계와 동일한 형태의 접속 정보 입력란이다.
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")
# 연결, DB, 테이블 생성에 공통으로 사용할 입력 UI를 폼 레이아웃에 배치한다.
conn_form = QFormLayout()
conn_form.addRow("호스트", self.host_edit)
conn_form.addRow("사용자", self.user_edit)
conn_form.addRow("비밀번호", self.password_edit)
conn_form.addRow("데이터베이스 이름", self.db_edit)
# 1단계: 연결 테스트용 버튼
self.btn_connect = QPushButton("연결 테스트")
# 2단계: 데이터베이스 생성용 버튼
self.btn_create_db = QPushButton("데이터베이스 생성 (TestDB)")
# 3단계: users 테이블 생성용 버튼
self.btn_create_table = QPushButton("users 테이블 생성")
conn_button_layout = QVBoxLayout()
conn_button_layout.addWidget(self.btn_connect)
conn_button_layout.addWidget(self.btn_create_db)
conn_button_layout.addWidget(self.btn_create_table)
conn_group_layout = QVBoxLayout()
conn_group_layout.addLayout(conn_form)
conn_group_layout.addLayout(conn_button_layout)
conn_group = QGroupBox("1~3단계: 연결·DB·테이블 생성")
conn_group.setLayout(conn_group_layout)
# 4단계에서 사용할 사용자 이름과 비밀번호 입력란이다.
self.username_edit = QLineEdit()
self.password_user_edit = QLineEdit()
self.password_user_edit.setEchoMode(QLineEdit.Password)
insert_form = QFormLayout()
insert_form.addRow("사용자 이름", self.username_edit)
insert_form.addRow("비밀번호", self.password_user_edit)
# users 테이블에 데이터를 추가하는 버튼이다.
self.btn_insert_user = QPushButton("users 테이블에 사용자 추가")
insert_group_layout = QVBoxLayout()
insert_group_layout.addLayout(insert_form)
insert_group_layout.addWidget(self.btn_insert_user)
insert_group = QGroupBox("4단계: PySide6 폼에서 데이터 입력 후 MySQL 저장")
insert_group.setLayout(insert_group_layout)
# 전체 결과 메시지를 보여 줄 라벨이다.
self.result_label = QLabel("결과 메시지가 여기에 표시됩니다.")
# 메인 레이아웃에 위쪽 그룹과 아래쪽 그룹을 차례로 배치한다.
main_layout = QVBoxLayout(self)
main_layout.addWidget(conn_group)
main_layout.addWidget(insert_group)
main_layout.addWidget(self.result_label)
5. 시그널과 슬롯 연결 부분
더보기
# 4단계: users 테이블에 데이터 INSERT
self.btn_insert_user.clicked.connect(self.insert_user)
6. <Data 삽입> 주요 구조
더보기
6.1 <Data 삽입> 핵심 구조 요약
# 1. 연결
conn = mc.connect(서버_및_DB_접속_정보)
# 2. 확인
if conn.is_connected():
성공_처리()
# 3. SQL 실행
cursor = conn.cursor()
cursor.execute(INSERT_SQL, VALUES)
conn.commit()
# 4. 종료
conn.close()
6.2 <Data 삽입> 흐름
import mysql.connector as mc
def users_insert_flow():
# 1. DB 서버 및 데이터베이스 접속 정보 준비
host = "localhost"
user = "root"
password = "password"
database = "test_db"
# 2. INSERT에 사용할 데이터 준비 (GUI 입력값에 해당)
username = "sample_user"
user_password = "1234"
# 3. INSERT SQL 준비 (값은 %s 플레이스홀더 사용)
insert_sql = "INSERT INTO users (username, password) VALUES (%s, %s)"
values = (username, user_password)
# 4. 지정된 데이터베이스에 연결
conn = mc.connect(
host=host,
user=user,
password=password,
database=database
)
# 5. 서버와의 연결 상태 확인
if conn.is_connected():
print("DB 연결 성공")
# 6. SQL 실행을 위한 커서 생성
cursor = conn.cursor()
# 7. INSERT SQL 실행
cursor.execute(insert_sql, values)
# 8. 데이터 변경이므로 반드시 커밋 수행
conn.commit()
# 9. 모든 DB 작업이 끝난 후 연결 종료
conn.close()
# users 테이블 데이터 삽입 흐름 실행
users_insert_flow()
- DB 접속 정보 준비
- 삽입할 데이터 준비
- INSERT SQL 작성
- 데이터베이스까지 포함하여 연결
- 연결 상태 확인
- 커서 생성
- INSERT SQL 실행
- commit으로 데이터 저장 확정
- 연결 종료
7. 기능 구현 소스코드
더보기
7.1 연결 테스트 함수
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
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.handle_mysql_error(e, "연결 중 오류가 발생했습니다.", "연결 오류")
finally:
try:
if conn.is_connected():
conn.close()
except Exception:
pass
이 함수에서 비밀번호를 GUI에서 받아오기 때문에
root에 비밀번호가 있으면 반드시 여기 입력해야 한다.
7.2 INSERT 함수에서의 개선
def insert_user(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()
username = self.username_edit.text().strip()
user_password = self.password_user_edit.text().strip()
if not host or not user or not db_name:
QMessageBox.warning(self, "입력 오류", "먼저 접속 정보와 데이터베이스 이름을 모두 입력해야 합니다.")
return
if not username or not user_password:
QMessageBox.warning(self, "입력 오류", "사용자 이름과 비밀번호를 모두 입력해야 합니다.")
return
try:
conn = mc.connect(
host=host,
user=user,
password=password,
database=db_name,
)
cursor = conn.cursor()
query = "INSERT INTO users (username, password) VALUES (%s, %s)"
values = (username, user_password)
cursor.execute(query, values)
conn.commit()
msg = f"사용자 {username} 정보가 저장되었습니다."
self.result_label.setText(msg)
QMessageBox.information(self, "저장 완료", msg)
self.write_log_with_qt(f"사용자 추가 성공: {db_name}.users, username={username}")
self.username_edit.clear()
self.password_user_edit.clear()
self.username_edit.setFocus()
except mc.Error as e:
self.handle_mysql_error(e, "데이터 저장 중 오류가 발생했습니다.", "저장 오류", username=username)
finally:
try:
if conn.is_connected():
conn.close()
except Exception:
pass
7.3 1045 오류 처리
def handle_mysql_error(self, error, ui_message, title, username=None):
self.result_label.setText(ui_message)
if hasattr(error, "errno") and error.errno == 1045:
detail = (
"접속이 거부되었습니다.\n\n"
"가능한 원인\n"
"1. 사용자 이름이 잘못되었습니다.\n"
"2. 비밀번호가 잘못되었거나 비밀번호가 필요한데 비워 둔 상태입니다.\n"
"3. 현재 MySQL 서버 설정에서 root 계정 외의 별도 계정이 필요할 수 있습니다.\n\n"
"해결 방법\n"
"1. MySQL 클라이언트에서 직접 로그인해 사용자와 비밀번호를 확인합니다.\n"
"2. 이 창의 사용자와 비밀번호 입력란에 동일한 정보를 입력합니다.\n"
"3. 필요하다면 애플리케이션 전용 계정을 새로 생성하고 권한을 부여합니다."
)
QMessageBox.critical(
self,
title,
f"{ui_message}\n\n오류 코드: 1045\n오류 내용: {error}\n\n{detail}",
)
else:
QMessageBox.critical(
self,
title,
f"{ui_message}\n\n오류 내용: {error}",
)
if username is not None:
self.write_log_with_qt(f"사용자 추가 실패: {username}, 오류: {error}")
else:
self.write_log_with_qt(f"MySQL 작업 실패, 오류: {error}")
8. 실행 테스트
더보기
단계별 버튼 실행 순서
- 호스트, 사용자, 비밀번호, 데이터베이스 이름(pyqtdb 등)을 입력한다.
- 연결 테스트 버튼을 눌러 1045 오류가 없는지 먼저 확인한다.
- 데이터베이스 생성 버튼으로 DB를 만든다.
이미 있으면 오류가 날 수 있으나 로그로 남겨도 좋다. - users 테이블 생성 버튼으로 테이블을 만든다.
- 아래쪽 사용자인풋 영역에서 username, password를 입력한 뒤
users 테이블에 사용자 추가 버튼을 누른다.
9.학습 주요 포인트
더보기
- 1045 Access denied 오류는
거의 항상 계정이나 비밀번호, 권한 문제다. - 접속 정보(host, user, password, db_name)를
코드에 하드코딩하지 않고,
하나의 UI에서 공통으로 입력받아
연결, DB 생성, 테이블 생성, INSERT에서 모두 재사용하는 구조 - 이전 단계에서 만든 기능을
새로운 단계의 코드에 흡수·통합하여 프로젝트가 확장 - 파라미터 쿼리 사용, QTextStream 기반 로그 기록, QMessageBox 피드백은 그대로 유지하면서
접속 정보 부분만 개선해 현재 환경에 맞게 동작하도록 만들었다. - 지금 단계까지 완료되면,
이후 QTableWidget을 사용한 조회, WHERE 검색, 로그인 기능 구현도
같은 UI 구조를 그대로 재사용하면서 확장할 수 있다.
단계별 완성 파일