MFC实战:从零构建MySQL数据库增删改查应用(一)

张开发
2026/4/17 10:46:56 15 分钟阅读

分享文章

MFC实战:从零构建MySQL数据库增删改查应用(一)
1. 环境准备与项目创建第一次用MFC操作MySQL时我踩了不少坑。记得当时连最基本的数据库连接都搞不定弹出的错误提示看得一头雾水。后来才发现问题出在环境配置这个最基础的环节。下面我就把完整的配置过程分享给大家帮你避开这些新手陷阱。首先确保你的开发环境已经安装好以下组件Visual Studio 2019或更高版本社区版就够用MySQL Server 8.0建议使用官方安装包MySQL Connector/C 8.0安装MySQL时有个细节特别重要一定要勾选Development Components选项。我就因为漏选了这个导致后来死活找不到mysql.h头文件。安装完成后建议把MySQL的bin目录添加到系统PATH环境变量这样后续调试会方便很多。在VS中新建MFC项目时选择基于对话框的应用类型。我建议项目命名时加上MySQL字样比如StudentManager_MySQL这样后续查找代码时更容易定位。创建完成后先别急着写代码我们需要配置几个关键属性右键项目→属性→C/C→常规在附加包含目录添加MySQL的include路径链接器→常规→附加库目录添加MySQL的lib路径链接器→输入→附加依赖项添加libmysql.lib注意32位和64位的库文件要区分清楚我曾经因为混用导致一堆LNK2019错误2. 数据库连接实现2.1 基础连接代码连接数据库就像打电话得先拨对号码才能通话。在MFC中这个拨号过程需要几个关键步骤// 在对话框头文件中声明数据库对象 MYSQL m_mysql; CString m_strHost _T(localhost); CString m_strUser _T(root); CString m_strPwd _T(123456); CString m_strDB _T(testdb); unsigned int m_nPort 3306; // 连接按钮的点击事件 void CMySQLDemoDlg::OnBnClickedButtonConnect() { mysql_init(m_mysql); mysql_options(m_mysql, MYSQL_SET_CHARSET_NAME, utf8mb4); if(!mysql_real_connect(m_mysql, m_strHost.GetString(), m_strUser.GetString(), m_strPwd.GetString(), m_strDB.GetString(), m_nPort, NULL, 0)) { CString strError(mysql_error(m_mysql)); AfxMessageBox(_T(连接失败: ) strError); return; } AfxMessageBox(_T(数据库连接成功)); }这段代码有几个易错点字符集建议用utf8mb4而不是gb2312能更好支持emoji等特殊字符GetString()方法将CString转为LPCSTR这是必须的转换错误处理要用mysql_error()获取具体原因我之前只用MessageBox提示失败结果排查半天2.2 连接池优化实际项目中频繁创建销毁连接很耗资源。我后来改用了连接池方案性能提升明显。这里分享一个简易实现class CMySQLConnPool { private: std::vectorMYSQL* m_vecConn; CCriticalSection m_cs; public: MYSQL* GetConnection() { CSingleLock lock(m_cs, TRUE); if(!m_vecConn.empty()) { MYSQL* pConn m_vecConn.back(); m_vecConn.pop_back(); return pConn; } return CreateNewConnection(); } void ReleaseConnection(MYSQL* pConn) { CSingleLock lock(m_cs, TRUE); m_vecConn.push_back(pConn); } };使用时记得在程序退出时调用mysql_close()关闭所有连接。我曾经忘记关闭导致MySQL服务端积累了上百个僵尸连接。3. 数据查询与展示3.1 基础查询实现查询数据就像去图书馆找书得先告诉管理员你要什么。在MFC中我们通常用List Control来展示查询结果void CMySQLDemoDlg::LoadStudentData() { CListCtrl* pList (CListCtrl*)GetDlgItem(IDC_LIST_DATA); pList-DeleteAllItems(); CString strSQL _T(SELECT id,name,score FROM students); if(mysql_query(m_mysql, strSQL.GetString()) ! 0) { // 错误处理... return; } MYSQL_RES* pResult mysql_store_result(m_mysql); if(!pResult) return; int nRow 0; MYSQL_ROW row; while((row mysql_fetch_row(pResult))) { pList-InsertItem(nRow, row[0]); pList-SetItemText(nRow, 1, CA2T(row[1])); pList-SetItemText(nRow, 2, row[2]); nRow; } mysql_free_result(pResult); }这里有几个实用技巧使用CA2T宏转换ANSI到Unicode避免中文乱码mysql_store_result()会把结果集缓存到客户端适合数据量小的场景大数据量应该用mysql_use_result()边读取边处理3.2 分页查询优化当数据量达到上万条时必须实现分页查询。这是我的实现方案void CMySQLDemoDlg::LoadDataByPage(int nPage, int nPageSize) { CString strSQL; strSQL.Format(_T(SELECT * FROM students LIMIT %d,%d), (nPage-1)*nPageSize, nPageSize); // 执行查询... // 同时获取总记录数 CString strCount _T(SELECT COUNT(*) FROM students); // 执行计数查询... }界面可以添加上一页下一页按钮配合一个显示当前页数的静态文本控件。记得在翻页时禁用按钮防止重复点击这个细节能有效避免很多异常情况。4. 增删改功能实现4.1 数据插入操作插入数据就像往表格里填写新行但要注意数据校验。我推荐使用参数化查询防止SQL注入void CMySQLDemoDlg::OnBnClickedButtonAdd() { CString strName, strScore; GetDlgItemText(IDC_EDIT_NAME, strName); GetDlgItemText(IDC_EDIT_SCORE, strScore); // 参数化查询 CString strSQL; strSQL.Format(_T(INSERT INTO students(name,score) VALUES(%s,%s)), strName, strScore); if(mysql_query(m_mysql, strSQL.GetString()) 0) { AfxMessageBox(_T(添加成功)); LoadStudentData(); // 刷新列表 } else { AfxMessageBox(_T(添加失败)); } }实际项目中应该添加输入验证姓名不能为空分数必须是数字字符串要转义特殊字符4.2 数据更新与删除更新和删除操作最关键的是获取选中行的ID。我的做法是在List Control的第一列存储ID但隐藏显示void CMySQLDemoDlg::OnBnClickedButtonDelete() { CListCtrl* pList (CListCtrl*)GetDlgItem(IDC_LIST_DATA); POSITION pos pList-GetFirstSelectedItemPosition(); if(!pos) { AfxMessageBox(_T(请先选择要删除的记录)); return; } int nItem pList-GetNextSelectedItem(pos); CString strID pList-GetItemText(nItem, 0); CString strSQL; strSQL.Format(_T(DELETE FROM students WHERE id%s), strID); if(mysql_query(m_mysql, strSQL.GetString()) 0) { AfxMessageBox(_T(删除成功)); LoadStudentData(); } else { AfxMessageBox(mysql_error(m_mysql)); } }更新操作类似不过需要先弹出编辑对话框。建议把常用操作封装成单独的函数比如bool ExecuteSQL(const CString strSQL) { if(mysql_query(m_mysql, strSQL.GetString()) ! 0) { CString strError(mysql_error(m_mysql)); AfxMessageBox(_T(SQL执行失败: ) strError); return false; } return true; }5. 异常处理与调试技巧5.1 常见错误排查在开发过程中我遇到过各种稀奇古怪的错误。这里总结几个典型问题连接失败检查MySQL服务是否启动确认用户名密码正确查看防火墙是否阻止了3306端口中文乱码确保数据库、表、字段都使用utf8mb4字符集连接后立即执行SET NAMES utf8mb4界面控件也要设置对应的字体内存泄漏每个mysql_store_result()都要配对的mysql_free_result()程序退出前关闭所有数据库连接5.2 日志记录方案好的日志系统能极大提升调试效率。这是我的简易实现void WriteLog(const CString strMsg) { CFile file; if(file.Open(_T(mysql_operation.log), CFile::modeCreate|CFile::modeNoTruncate|CFile::modeWrite)) { file.SeekToEnd(); CString strLog; strLog.Format(_T([%s] %s\r\n), CTime::GetCurrentTime().Format(%Y-%m-%d %H:%M:%S), strMsg); file.Write(strLog, strLog.GetLength()*sizeof(TCHAR)); file.Close(); } }关键SQL操作前后都可以调用这个函数记录状态。当程序出现异常时这个日志文件就是最好的破案线索。

更多文章