클라우드응용SW개발

[Cloud] 7주차-2. MySQL 데이터베이스로 Todo 앱 만들기 실습

tryinto_gmlt 2026. 4. 17. 23:29

⭐ 기본 세팅하기

1) [Cloud] 7주차-1. Streamlit 웹 앱 Azure App Service 배포 실습 내용을 참고하여  
파이썬 가상환경을 구성하고, 필요한 패키지를 설치한 후  
Azure Web App과 연동하여 Streamlit 애플리케이션을 배포할 수 있도록 설정한다.

필요 패키지
pip install streamlit
pip install mysql-connector-python

 

2) [Cloud] 3주차-2. 오픈소스 데이터베이스 내용을 참고하여  
Azure Database for MySQL 서버를 생성하고, 방화벽 규칙을 설정한 후  
MySQL Workbench에서 해당 데이터베이스에 연결을 설정한다.

 

3) MySQL Workbench에서 아래 SQL문을 실행하여 데이터베이스와 테이블을 생성한다.

CREATE DATABASE todo_app;
USE todo_app;

CREATE TABLE todos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    task VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    completed BOOLEAN DEFAULT FALSE
);

 

⭐ MySQL과 연결 확인하기

1) 아래 코드를 작성하여 MySQL 데이터베이스와의 연결을 위한 기본 정보를 설정한다.

import streamlit as st
import mysql.connector
from mysql.connector import Error

# MySQL과 연결
def get_db_connection():
    return mysql.connector.connect(
        host = "[MySQL 엔드포인트 주소]",
        user = "[사용자 계정]",
        password = "[비밀번호]",
        database = "[데이터베이스명]",
        use_pure = True
    )

connection = get_db_connection()
print(connection)

 

2) streamlit run app.py 명령어를 통해 애플리케이션을 실행한다.  
connection 객체가 정상적으로 생성되면 데이터베이스 연결이 성공한 것이다.

 


RuntimeError: Failed raising error 오류 ❕

host, user, password, database만 작성하고 Streamlit 애플리케이션 실행했을 때
MySQL 연결 과정에서 사진과 같은 오류가 발생하였다.
이를 해결하기 위해 mysql.connector.connect() 함수에 use_pure=True 옵션을 추가하였다.

mysql-connector-python 라이브러리는 두 가지 방식의 연결 엔진을 제공하는데
기본값인 C Extension 방식 대신 Pure Python 방식으로 실행하기 위해 해당 옵션을 설정하였다.

❕ MySQL Connector 연결 방식 ❕

1) C Extension (기본값)
기본적으로 MySQL Connector는 C 언어로 구현된 엔진을 사용한다.
이 방식은 성능이 빠르다는 장점이 있지만, OS나 네트워크 환경에 영향을 많이 받아
일부 환경에서는 연결 오류가 발생할 수 있다.

2) Pure Python 방식
use_pure=True 옵션을 설정하면 MySQL Connector가 순수 Python 기반으로 동작하게 된다.
이 방식은 C Extension보다 속도는 느릴 수 있으나,
Python 표준 라이브러리를 기반으로 동작하기 때문에 다양한 환경에서 높은 호환성을 가진다.
특히 IPv6 환경이나 복잡한 네트워크 환경에서도 안정적으로 연결이 가능하다는 장점이 있다.

 

⭐ Todo 앱 코드 작성하기

1) 입력 데이터를 데이터베이스에 추가하는 기능을 구현한다.

더보기

❕수정한 코드❕

1) add_task 함수 수정

# 수정 전
cursor.execute(query, task)
# 수정 후
cursor.execute(query, (task,))


에러: str(빨래하기), it must be of type list, tuple or dict
해결방안: task를 문자열 그대로 전달했기 때문에 발생한 오류이다. MySQL Connector에서 SQL문의 파라미터는 문자열 그대로 전달할 수 없으며, 리스트(list), 튜플(tuple), 딕셔너리(dict) 형태로 전달해야 한다.

2) 성공 메시지 출력 조건 수정

# 수정 전
def add_task(task):
    try:
    	...
    except Error as e:
        ...
        
if st.button("Add Task") and new_task:
    add_task(new_task)
    st.success(f"Task '{new_task}' added!")

 

# 수정 후
def add_task(task):
    try:
    	...
        return True
    
    except Error as e:
        ...
        return False
        
if st.button("Add Task") and new_task:
    if add_task(new_task):
        st.success(f"Task '{new_task}' added!")


에러:
 데이터베이스 저장 과정에서 오류가 발생하더라도 성공 메시지가 항상 출력된다.
해결방안: 데이터가 정상적으로 추가된 경우에만 성공 메시지가 출력되도록 조건문을 추가하고, add_task() 함수가 성공 여부를 반환하도록 수정한다.

import streamlit as st
import mysql.connector
from mysql.connector import Error

# MySQL과 연결
def get_db_connection():
    return mysql.connector.connect(
        host = "[MySQL 엔드포인트 주소]",
        user = "[사용자 계정]",
        password = "[비밀번호]",
        database = "[데이터베이스명]",
        use_pure = True
    )

# Task를 저장하는 함수
def add_task(task):
    try:
        connection = get_db_connection()
        cursor = connection.cursor()
        query = "INSERT INTO todos(task) VALUES(%s)"
        cursor.execute(query, (task,))
        connection.commit()
        cursor.close()
        connection.close()
        return True
    
    except Error as e:
        st.error(f'Error: {e}')
        return False

connection = get_db_connection()

# User Interface
st.header("Welcome to Super Todo App", divider = "rainbow")

# 새로운 Todo 입력
new_task = st.text_input("New task")

if st.button("Add Task") and new_task:
    if add_task(new_task):
        st.success(f"Task '{new_task}' added!")

 

2) MySQL Workbench에서 SELECT 문을 실행하여 
애플리케이션을 통해 입력된 데이터가 정상적으로 저장되었는지 확인한다.

SELECT * FROM todos;

 

3) 나머지 기능을 추가하여 앱을 완성한다.

전체 기능

  • 할 일 추가 (INSERT)
    • 사용자가 입력한 새로운 Todo 데이터를 데이터베이스에 저장
  • 할 일 조회 (SELECT)
    • 완료되지 않은 할 일만 조회하여 리스트로 가져옴
    • 생성 날짜 기준으로 최신순 정렬
  • 할 일 완료 처리 (UPDATE)
    • 특정 ID를 가진 할 일을 완료 상태로 변경
import streamlit as st
import mysql.connector
from mysql.connector import Error

# MySQL과 연결
def get_db_connection():
    return mysql.connector.connect(
        host = "[MySQL 엔드포인트 주소]",
        user = "[사용자 계정]",
        password = "[비밀번호]",
        database = "[데이터베이스명]",
        use_pure = True
    )

# Task를 저장하는 함수
def add_task(task):
    try:
        connection = get_db_connection()
        cursor = connection.cursor()
        query = "INSERT INTO todos(task) VALUES(%s)"
        cursor.execute(query, (task,))
        connection.commit()
        cursor.close()
        connection.close()
        return True
    
    except Error as e:
        st.error(f'Error: {e}')
        return False

connection = get_db_connection()

# Task 목록을 가져오는 함수
def get_task():
    try:
        connection = get_db_connection()
        cursor = connection.cursor()
        cursor.execute("SELECT * FROM todos WHERE completed = FALSE ORDER BY created_at DESC")
        tasks = cursor.fetchall()
        cursor.close()
        connection.close()
        return tasks

    except Error as e:
        st.error(f"Error: {e}")
        return []

# 작업을 완료하는 부분
def mark_task_completed(task_id):
    try:
        connection = get_db_connection()
        cursor = connection.cursor()
        query = "UPDATE todos SET completed = TRUE WHERE id = %s"
        cursor.execute(query, (task_id,))
        connection.commit()
        cursor.close()
        connection.close()
        return True
    
    except Error as e:
        st.error(f'Error: {e}')
        return False

# User Interface
st.header("Welcome to Super Todo App", divider = "rainbow")

# 새로운 Todo 입력
new_task = st.text_input("New task")

if st.button("Add Task") and new_task:
    if add_task(new_task):
        st.success(f"Task '{new_task}' added!")

# Task를 완료하는 부분
task_id = st.text_input("task id")

if st.button("Complete") and task_id:
    if mark_task_completed(task_id):
        st.success(f"Task '{task_id}' updated!")

tasks = get_task()

st.write(tasks)