1. 목표

더보기

이 단계의 학습 목표

  • 기존 1~6단계 통합 예제에 로그인 확인 기능을 자연스럽게 확장해서 붙이기
  • QLineEdit 비밀번호 모드와 QPushButton을 활용해 로그인 UI 만들기
  • SELECT ... WHERE 조건문으로 사용자 아이디/비밀번호를 검사하는 로직 구현
  • 로그인 성공/실패를 QLabel, QMessageBox로 사용자에게 명확하게 피드백하기
  • 이미 구현한 MySQL 연결 예외 처리, 로그 기록 함수(handle_mysql_error, write_log_with_qt)를 재사용해 일관된 구조 유지

이제 최종 통합 GUI는 다음 기능을 한 창에서 모두 처리합니다.

  • 1단계: MySQL 서버 연결 테스트
  • 2단계: 데이터베이스 생성
  • 3단계: users 테이블 생성
  • 4단계: 사용자 추가(INSERT)
  • 5단계: 전체 사용자 조회(SELECT)
  • 6단계: 검색어로 사용자 검색(LIKE)
  • 7단계: 아이디/비밀번호로 로그인 확인(SELECT WHERE)

 

2. 전체 로직

더보기
import sys

from PySide6.QtWidgets import (
    QApplication,
    QWidget,
    QLabel,
    QLineEdit,
    QPushButton,
    QVBoxLayout,
    QHBoxLayout,
    QFormLayout,
    QMessageBox,
    QGroupBox,
    QTableWidget,
    QTableWidgetItem,
    QHeaderView,
    QAbstractItemView,
)
from PySide6.QtCore import QFile, QIODevice, QTextStream, Qt

import mysql.connector as mc


class MySqlDemoWindow(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowTitle("PySide6 MySQL 사용자 관리 데모")
        self.resize(820, 780)

        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("데이터베이스 생성")
        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.table_users = QTableWidget()
        self.table_users.setColumnCount(3)
        self.table_users.setHorizontalHeaderLabels(["id", "username", "password"])
        self.table_users.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.table_users.setSelectionBehavior(
            QAbstractItemView.SelectionBehavior.SelectRows
        )
        self.table_users.setEditTriggers(
            QAbstractItemView.EditTrigger.NoEditTriggers
        )

        self.btn_load_users = QPushButton("users 테이블 전체 조회")

        self.search_edit = QLineEdit()
        self.search_edit.setPlaceholderText("사용자 이름 검색어 입력")
        self.btn_search = QPushButton("검색")

        search_layout = QHBoxLayout()
        search_layout.addWidget(QLabel("검색어"))
        search_layout.addWidget(self.search_edit)
        search_layout.addWidget(self.btn_search)

        table_group_layout = QVBoxLayout()
        table_group_layout.addLayout(search_layout)
        table_group_layout.addWidget(self.btn_load_users)
        table_group_layout.addWidget(self.table_users)

        table_group = QGroupBox("5~6단계: 데이터 조회 및 검색 결과 표시")
        table_group.setLayout(table_group_layout)

        self.login_username_edit = QLineEdit()
        self.login_password_edit = QLineEdit()
        self.login_password_edit.setEchoMode(QLineEdit.Password)

        login_form = QFormLayout()
        login_form.addRow("로그인 아이디", self.login_username_edit)
        login_form.addRow("로그인 비밀번호", self.login_password_edit)

        self.btn_login_check = QPushButton("로그인 확인")
        self.login_result_label = QLabel("로그인 결과가 여기에 표시됩니다.")
        self.login_result_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)

        login_group_layout = QVBoxLayout()
        login_group_layout.addLayout(login_form)
        login_group_layout.addWidget(self.btn_login_check)
        login_group_layout.addWidget(self.login_result_label)

        login_group = QGroupBox("7단계: PySide6 + MySQL 로그인 확인")
        login_group.setLayout(login_group_layout)

        self.result_label = QLabel("결과 메시지가 여기에 표시됩니다.")
        self.result_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)

        main_layout = QVBoxLayout(self)
        main_layout.addWidget(conn_group)
        main_layout.addWidget(insert_group)
        main_layout.addWidget(table_group)
        main_layout.addWidget(login_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)
        self.btn_load_users.clicked.connect(self.load_users)
        self.btn_search.clicked.connect(self.search_users)
        self.btn_login_check.clicked.connect(self.check_login)

    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.handle_mysql_error(e, "연결 중 오류가 발생했습니다.", "연결 오류")
        finally:
            try:
                if conn and 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

        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)
            self.write_log_with_qt(f"데이터베이스 생성: {db_name}")

        except mc.Error as e:
            self.handle_mysql_error(e, "데이터베이스 생성에 실패했습니다.", "데이터베이스 생성 오류")
        finally:
            try:
                if conn and 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
        )
        """

        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.handle_mysql_error(e, "테이블 생성 중 오류가 발생했습니다.", "테이블 생성 오류")
        finally:
            try:
                if conn and 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

        conn = None

        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 and conn.is_connected():
                    conn.close()
            except Exception:
                pass

    def load_users(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

        conn = None

        try:
            conn = mc.connect(
                host=host,
                user=user,
                password=password,
                database=db_name,
            )

            cursor = conn.cursor()
            query = "SELECT id, username, password FROM users ORDER BY id ASC"
            cursor.execute(query)
            rows = cursor.fetchall()

            self.table_users.setRowCount(len(rows))

            for row_index, row_data in enumerate(rows):
                id_value, username_value, password_value = row_data

                item_id = QTableWidgetItem(str(id_value))
                item_username = QTableWidgetItem(username_value)
                item_password = QTableWidgetItem(password_value)

                item_id.setTextAlignment(Qt.AlignCenter)
                item_username.setTextAlignment(Qt.AlignCenter)
                item_password.setTextAlignment(Qt.AlignCenter)

                self.table_users.setItem(row_index, 0, item_id)
                self.table_users.setItem(row_index, 1, item_username)
                self.table_users.setItem(row_index, 2, item_password)

            msg = f"총 {len(rows)}개의 사용자 데이터를 조회했습니다."
            self.result_label.setText(msg)
            self.write_log_with_qt(f"사용자 조회: {db_name}.users, 행 수={len(rows)}")

        except mc.Error as e:
            self.handle_mysql_error(e, "데이터 조회 중 오류가 발생했습니다.", "조회 오류")
        finally:
            try:
                if conn and conn.is_connected():
                    conn.close()
            except Exception:
                pass

    def search_users(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()

        keyword = self.search_edit.text().strip()

        if not host or not user or not db_name:
            QMessageBox.warning(self, "입력 오류", "먼저 접속 정보와 데이터베이스 이름을 모두 입력해야 합니다.")
            return

        if not keyword:
            QMessageBox.information(self, "검색어 없음", "검색어가 비어 있습니다.\n전체 목록을 조회합니다.")
            self.load_users()
            return

        conn = None

        try:
            conn = mc.connect(
                host=host,
                user=user,
                password=password,
                database=db_name,
            )

            cursor = conn.cursor()

            query = """
            SELECT id, username, password
            FROM users
            WHERE username LIKE %s
            ORDER BY id ASC
            """
            like_value = f"%{keyword}%"

            cursor.execute(query, (like_value,))
            rows = cursor.fetchall()

            self.table_users.setRowCount(len(rows))

            for row_index, row_data in enumerate(rows):
                id_value, username_value, password_value = row_data

                item_id = QTableWidgetItem(str(id_value))
                item_username = QTableWidgetItem(username_value)
                item_password = QTableWidgetItem(password_value)

                item_id.setTextAlignment(Qt.AlignCenter)
                item_username.setTextAlignment(Qt.AlignCenter)
                item_password.setTextAlignment(Qt.AlignCenter)

                self.table_users.setItem(row_index, 0, item_id)
                self.table_users.setItem(row_index, 1, item_username)
                self.table_users.setItem(row_index, 2, item_password)

            if rows:
                msg = f"검색어 {keyword} 에 대한 결과가 {len(rows)}건 있습니다."
            else:
                msg = f"검색어 {keyword} 에 대한 결과가 없습니다."
            self.result_label.setText(msg)
            self.write_log_with_qt(f"사용자 검색: {db_name}.users, keyword={keyword}, 행 수={len(rows)}")

        except mc.Error as e:
            self.handle_mysql_error(e, "데이터 검색 중 오류가 발생했습니다.", "검색 오류")
        finally:
            try:
                if conn and conn.is_connected():
                    conn.close()
            except Exception:
                pass

    def check_login(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()

        login_username = self.login_username_edit.text().strip()
        login_password = self.login_password_edit.text().strip()

        if not host or not user or not db_name:
            QMessageBox.warning(self, "입력 오류", "먼저 접속 정보와 데이터베이스 이름을 모두 입력해야 합니다.")
            return

        if not login_username or not login_password:
            QMessageBox.warning(self, "입력 오류", "로그인 아이디와 비밀번호를 모두 입력해야 합니다.")
            return

        conn = None

        try:
            conn = mc.connect(
                host=host,
                user=user,
                password=password,
                database=db_name,
            )

            cursor = conn.cursor()

            query = """
            SELECT id, username
            FROM users
            WHERE username = %s AND password = %s
            LIMIT 1
            """
            cursor.execute(query, (login_username, login_password))
            row = cursor.fetchone()

            if row:
                msg = f"{login_username} 사용자가 정상적으로 인증되었습니다."
                self.login_result_label.setText(msg)
                self.result_label.setText("로그인 성공")
                QMessageBox.information(self, "로그인 성공", msg)
                self.write_log_with_qt(f"로그인 성공: {db_name}.users, username={login_username}")
            else:
                msg = "아이디 또는 비밀번호가 올바르지 않습니다."
                self.login_result_label.setText(msg)
                self.result_label.setText("로그인 실패")
                QMessageBox.warning(self, "로그인 실패", msg)
                self.write_log_with_qt(f"로그인 실패: {db_name}.users, username={login_username}")

        except mc.Error as e:
            self.handle_mysql_error(e, "로그인 처리 중 오류가 발생했습니다.", "로그인 오류")
        finally:
            try:
                if conn and 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 구현부 소스코드와 주석

더보기
class MySqlUserManagerWindow(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        # ... 1~6단계용 위젯 초기화는 생략 ...

        # 로그인용 아이디/비밀번호 입력창 생성
        self.login_username_edit = QLineEdit()
        self.login_password_edit = QLineEdit()
        # 로그인 비밀번호는 화면에 그대로 보이지 않도록 비밀번호 모드로 설정
        self.login_password_edit.setEchoMode(QLineEdit.Password)

        # 로그인 입력 폼 레이아웃
        login_form = QFormLayout()
        login_form.addRow("로그인 아이디", self.login_username_edit)
        login_form.addRow("로그인 비밀번호", self.login_password_edit)

        # 로그인 확인 버튼
        self.btn_login_check = QPushButton("로그인 확인")

        # 로그인 결과를 보여줄 전용 라벨
        self.login_result_label = QLabel("로그인 결과가 여기에 표시됩니다.")
        self.login_result_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)

        # 로그인 그룹박스 레이아웃 구성
        login_group_layout = QVBoxLayout()
        login_group_layout.addLayout(login_form)
        login_group_layout.addWidget(self.btn_login_check)
        login_group_layout.addWidget(self.login_result_label)

        # 로그인 관련 위젯들을 하나의 그룹으로 묶어서 하단 영역으로 사용
        login_group = QGroupBox("7단계: PySide6 + MySQL 로그인 확인")
        login_group.setLayout(login_group_layout)

        # 기존 결과 메시지 라벨 (전체 단계 공용 상태 출력)
        self.result_label = QLabel("결과 메시지가 여기에 표시됩니다.")
        self.result_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)

        # 메인 레이아웃에 순서대로 배치
        main_layout = QVBoxLayout(self)
        main_layout.addWidget(conn_group)      # 1~3단계
        main_layout.addWidget(insert_group)    # 4단계
        main_layout.addWidget(table_group)     # 5~6단계
        main_layout.addWidget(login_group)     # 7단계: 새로 추가된 로그인 영역
        main_layout.addWidget(self.result_label)

위치는 테이블 그룹(table_group) 아래, 메인 레이아웃 하단에 배치했습니다.

 

  • login_username_edit, login_password_edit: 로그인 시 사용할 입력 값
  • btn_login_check: 로그인 검사 실행 버튼
  • login_result_label: 로그인 성공/실패 결과를 하단 영역에서 직관적으로 보여주는 라벨
  • login_group: 위 세 요소를 묶어 7단계 로그인 영역으로 사용

 


 

5. 시그널과 슬롯 연결 부분

더보기
class MySqlUserManagerWindow(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        # ... 위젯 생성 코드 생략 ...

        # 기존 단계에서 사용하던 버튼 시그널 연결
        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)
        self.btn_load_users.clicked.connect(self.load_users)
        self.btn_search.clicked.connect(self.search_users)

        # 7단계: 로그인 확인 버튼 시그널과 슬롯 연결
        # 사용자가 로그인 확인 버튼을 클릭하면 check_login 메서드가 호출되어
        # 입력한 아이디/비밀번호가 users 테이블에 존재하는지 검사한다.
        self.btn_login_check.clicked.connect(self.check_login)

 

  • 서버 연결, DB 생성, 테이블 생성, 데이터 입력, 조회, 검색 → 기존 1~6단계 버튼
  • 로그인 확인 → 새로 추가된 btn_login_check

 


 

6. <로그인> 주요 구조

더보기

1단계: MySQL 서버 연결 테스트 주요 구조

# 1. 연결
conn = mc.connect(host=host, user=user, password=password)

# 2. 확인
if conn.is_connected():
    성공_처리()

# 3. 종료
conn.close()

 

 

 

 

2단계: 데이터베이스 생성 주요 구조

# 1. 연결 (DB는 아직 없을 수 있으므로 database 파라미터 없이 서버만 연결)
conn = mc.connect(host=host, user=user, password=password)

# 2. 확인
if conn.is_connected():
    cursor = conn.cursor()

    # 3. SQL 실행 (DDL)
    cursor.execute(f"CREATE DATABASE {db_name}")
    conn.commit()

# 4. 종료
conn.close()

 

 

 

 

3단계: users 테이블 생성 주요 구조

create_table_sql = """
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(150) NOT NULL,
    password VARCHAR(150) NOT NULL
)
"""

# 1. 연결 (테이블은 특정 DB 내부에 생성되므로 database 지정)
conn = mc.connect(host=host, user=user, password=password, database=db_name)

# 2. 확인
if conn.is_connected():
    cursor = conn.cursor()

    # 3. SQL 실행 (DDL)
    cursor.execute(create_table_sql)
    conn.commit()

# 4. 종료
conn.close()

 

 

 

 

4단계: 사용자 추가(INSERT) 주요 구조

insert_sql = "INSERT INTO users (username, password) VALUES (%s, %s)"
values = (username, user_password)

# 1. 연결
conn = mc.connect(host=host, user=user, password=password, database=db_name)

# 2. 확인
if conn.is_connected():
    cursor = conn.cursor()

    # 3. SQL 실행 (DML)
    cursor.execute(insert_sql, values)
    conn.commit()

# 4. 종료
conn.close()

 

 

 

 

5단계: 전체 사용자 조회(SELECT) 주요 구조

select_sql = "SELECT id, username, password FROM users ORDER BY id ASC"

# 1. 연결
conn = mc.connect(host=host, user=user, password=password, database=db_name)

# 2. 확인
if conn.is_connected():
    cursor = conn.cursor()

    # 3. SQL 실행 (SELECT)
    cursor.execute(select_sql)

    # 4. 결과 조회
    rows = cursor.fetchall()

    # 5. 결과 사용 (예: 테이블 UI에 출력)
    결과_표시(rows)

# 6. 종료
conn.close()

 

 

 

 

6단계: 검색어로 사용자 검색(LIKE) 주요 구조

search_sql = """
SELECT id, username, password
FROM users
WHERE username LIKE %s
ORDER BY id ASC
"""
like_value = f"%{keyword}%"

# 1. 연결
conn = mc.connect(host=host, user=user, password=password, database=db_name)

# 2. 확인
if conn.is_connected():
    cursor = conn.cursor()

    # 3. SQL 실행 (조건 검색)
    cursor.execute(search_sql, (like_value,))

    # 4. 결과 조회
    rows = cursor.fetchall()

    # 5. 결과 사용 (예: 테이블 UI에 출력)
    결과_표시(rows)

# 6. 종료
conn.close()

 

 

 

 

7단계: 아이디/비밀번호로 로그인 확인(SELECT WHERE) 주요 구조

login_sql = """
SELECT id, username
FROM users
WHERE username = %s AND password = %s
LIMIT 1
"""
params = (login_username, login_password)

# 1. 연결
conn = mc.connect(host=host, user=user, password=password, database=db_name)

# 2. 확인
if conn.is_connected():
    cursor = conn.cursor()

    # 3. SQL 실행 (조건 조회)
    cursor.execute(login_sql, params)

    # 4. 결과 1건 조회
    row = cursor.fetchone()

    # 5. 로그인 판정 (row가 있으면 성공)
    if row:
        로그인_성공_처리()
    else:
        로그인_실패_처리()

# 6. 종료
conn.close()

 

 

 

 

단계별 핵심 차이 요약

  • 1단계: 연결 확인만 수행
  • 2~3단계: DDL이므로 commit 필요
  • 4단계: INSERT(DML)이므로 commit 필요
  • 5~7단계: SELECT이므로 commit 불필요, fetch로 결과 수집
  • 6단계: LIKE 조건 검색
  • 7단계: WHERE 조건 + fetchone으로 로그인 판정

 

7. 기능 구현 소스코드

더보기
def check_login(self):
    # 1) 상단 접속 정보와 DB 이름 읽기
    host = self.host_edit.text().strip()
    user = self.user_edit.text().strip()
    password = self.password_edit.text()
    db_name = self.db_edit.text().strip()

    # 2) 로그인 입력 영역의 아이디, 비밀번호 읽기
    login_username = self.login_username_edit.text().strip()
    login_password = self.login_password_edit.text().strip()

    # 3) 접속 정보와 DB 이름 기본 검증
    if not host or not user or not db_name:
        QMessageBox.warning(self, "입력 오류", "먼저 접속 정보와 데이터베이스 이름을 모두 입력해야 합니다.")
        return

    # 4) 로그인 아이디/비밀번호 필수 입력 검증
    if not login_username or not login_password:
        QMessageBox.warning(self, "입력 오류", "로그인 아이디와 비밀번호를 모두 입력해야 합니다.")
        return

    conn = None

    try:
        # 5) MySQL 데이터베이스 연결
        conn = mc.connect(
            host=host,
            user=user,
            password=password,
            database=db_name,
        )

        cursor = conn.cursor()

        # 6) 로그인용 SELECT WHERE 쿼리 작성
        #    username과 password가 모두 일치하는 레코드 1개만 조회한다.
        query = """
        SELECT id, username
        FROM users
        WHERE username = %s AND password = %s
        LIMIT 1
        """
        # 파라미터 바인딩을 사용해 SQL 인젝션을 방지한다.
        cursor.execute(query, (login_username, login_password))

        # 7) 결과 한 행만 가져온다.
        row = cursor.fetchone()

        if row:
            # 8-1) 로그인 성공
            msg = f"{login_username} 사용자가 정상적으로 인증되었습니다."
            # 로그인 영역 전용 결과 라벨에 성공 메시지 출력
            self.login_result_label.setText(msg)
            # 전체 상태 라벨에도 간단히 로그인 성공 상태 표시
            self.result_label.setText("로그인 성공")
            # 알림 창으로도 사용자에게 성공 알림
            QMessageBox.information(self, "로그인 성공", msg)
            # 로그 파일에 성공 기록 추가
            self.write_log_with_qt(f"로그인 성공: {db_name}.users, username={login_username}")
        else:
            # 8-2) 로그인 실패
            msg = "아이디 또는 비밀번호가 올바르지 않습니다."
            self.login_result_label.setText(msg)
            self.result_label.setText("로그인 실패")
            QMessageBox.warning(self, "로그인 실패", msg)
            self.write_log_with_qt(f"로그인 실패: {db_name}.users, username={login_username}")

    except mc.Error as e:
        # 9) MySQL 관련 오류는 기존 예외 처리 공통 함수 재사용
        self.handle_mysql_error(e, "로그인 처리 중 오류가 발생했습니다.", "로그인 오류")
    finally:
        # 10) 연결이 열려 있으면 안전하게 닫아준다.
        try:
            if conn and conn.is_connected():
                conn.close()
        except Exception:
            pass
  • 접속 정보(host, user, password, db_name)를 1~6단계와 동일하게 상단 입력값에서 재사용
  • username, password를 WHERE 조건으로 비교해 정확히 일치하는 사용자가 있는지 확인
  • SQL 인젝션 방지를 위해 문자열 포맷 대신 파라미터 바인딩 사용
  • 로그인 전용 결과 라벨(login_result_label)과 공용 상태 라벨(result_label)을 함께 사용해 상태를 분리해서 보여줌
  • 예외 발생 시 handle_mysql_error를 그대로 재사용해, 로그인 단계에서도 동일한 오류 메시지 스타일과 로그 포맷 유지

 

8. 실행 테스트

더보기

로그인 성공 케이스 테스트

  • 7단계 로그인 영역에서
    • 로그인 아이디: testuser
    • 로그인 비밀번호: 1234
  • 로그인 확인 버튼 클릭
  • 기대 결과
    • 로그인 결과 라벨: testuser 사용자가 정상적으로 인증되었습니다.
    • 하단 상태 라벨: 로그인 성공
    • 로그인 성공 메시지 박스 표시
    • db_log_all_steps.txt 파일에 로그인 성공 로그 기록

 

로그인 실패 케이스 테스트

  • 로그인 아이디: testuser
  • 로그인 비밀번호: 잘못된 값 예) 9999
  • 로그인 확인 클릭
  • 기대 결과
    • 로그인 결과 라벨: 아이디 또는 비밀번호가 올바르지 않습니다.
    • 하단 상태 라벨: 로그인 실패
    • 로그인 실패 메시지 박스 표시
    • 로그 파일에 로그인 실패 로그 기록


9.학습 주요 포인트

더보기

로그인 기능도 결국 하나의 SELECT WHERE 쿼리라는 점

  • INSERT, SELECT, LIKE 검색에 이어
  • WHERE username = 값 AND password = 값 패턴을 통해 인증을 구현했다.

 

단계별 학습 흐름 완성

  • 1단계: 연결 → 2단계: DB 생성 → 3단계: 테이블 생성 → 4단계: 데이터 입력
  • 5단계: 조회 → 6단계: 검색 → 7단계: 로그인 확인
  • 학생 입장에서 데이터베이스 연동의 전 과정을 한 프로젝트 안에서 경험하도록 설계된 구조이다.

 

단계별 완성 파일