QT入门第十一天:数据库编程(上)SQLite入门与增删改查 | 零基础学QT

QT入门第十一天:数据库编程(上)SQLite入门与增删改查 | 零基础学QT

前言

前面十天我们学习了QT界面开发、事件、绘图、文件操作等内容。今天我们来学习一个非常重要的主题:数据库编程

为什么要学数据库?

  • 保存大量结构化数据(比如用户信息、订单记录)
  • 数据需要查询、排序、筛选
  • 数据要持久化保存
  • 多个数据之间有关联关系

文件操作虽然也能保存数据,但处理复杂的结构化数据时,数据库更方便、更高效。

由于数据库内容比较多,我们分成两天来学:

  • 今天(上):QT的SQL模块、SQLite入门、数据库连接、增删改查
  • 明天(下):QSqlQueryModel、QSqlTableModel、数据与视图绑定、综合实战

一、QT的SQL模块

1.1 QT支持哪些数据库

QT的SQL模块支持多种数据库:

  • SQLite:轻量级,无需服务器,文件型数据库(最适合入门和小型应用)
  • MySQL:流行的开源数据库
  • PostgreSQL:功能强大的开源数据库
  • Oracle:企业级数据库
  • SQL Server:微软的数据库
  • ODBC:通用数据库接口

💡 本教程用SQLite,因为它简单、免安装、QT内置支持,非常适合学习。

1.2 什么是SQLite

SQLite是一个轻量级的数据库:

  • 不需要安装数据库服务器
  • 整个数据库就是一个文件
  • QT内置支持,不用额外配置
  • 适合中小型应用

💡 生活类比:MySQL像是一个大型仓库(需要专门的管理员和场地),SQLite像是一个文件柜(一个文件就搞定)。

1.3 添加SQL模块

在.pro文件中添加SQL模块:

复制代码
QT += sql

如果用CMake(QT6推荐):

cmake 复制代码
find_package(Qt6 REQUIRED COMPONENTS Sql)
target_link_libraries(mytarget PRIVATE Qt6::Sql)

1.4 相关的类

QT的SQL模块主要有这些类:

用途
QSqlDatabase 数据库连接
QSqlQuery 执行SQL语句
QSqlQueryModel 只读的查询模型
QSqlTableModel 可读写的表模型
QSqlRecord 一条记录
QSqlField 一个字段
QSqlError 错误信息

二、连接数据库

2.1 建立数据库连接

cpp 复制代码
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>

// 添加SQLite数据库
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");

// 设置数据库文件名(不存在会自动创建)
db.setDatabaseName("mydata.db");

// 打开数据库
if (db.open()) {
    qDebug() << "数据库打开成功";
} else {
    qDebug() << "数据库打开失败:" << db.lastError().text();
}

💡 说明:

  • "QSQLITE" 是SQLite驱动的名称
  • setDatabaseName() 设置数据库文件,文件不存在会自动创建
  • open() 打开数据库连接

2.2 检查驱动是否可用

cpp 复制代码
// 查看支持哪些数据库驱动
qDebug() << QSqlDatabase::drivers();

// 检查SQLite驱动是否可用
if (QSqlDatabase::isDriverAvailable("QSQLITE")) {
    qDebug() << "SQLite驱动可用";
}

2.3 关闭数据库

cpp 复制代码
db.close();

程序结束时会自动关闭,但建议不用了就手动关闭。

三、创建表

3.1 什么是表

数据库里的数据是以"表"的形式组织的,就像Excel表格:

  • 每一列是一个"字段"(比如姓名、年龄)
  • 每一行是一条"记录"(比如一个学生的信息)

3.2 用SQL创建表

cpp 复制代码
QSqlQuery query;

// 创建学生表
QString sql = "CREATE TABLE IF NOT EXISTS student ("
              "id INTEGER PRIMARY KEY AUTOINCREMENT, "  // 主键,自增
              "name TEXT NOT NULL, "                     // 姓名,不能为空
              "age INTEGER, "                            // 年龄
              "score REAL, "                             // 分数(小数)
              "class TEXT)";                             // 班级

if (query.exec(sql)) {
    qDebug() << "创建表成功";
} else {
    qDebug() << "创建表失败:" << query.lastError().text();
}

3.3 SQLite常用数据类型

类型 说明
INTEGER 整数
REAL 浮点数
TEXT 文本
BLOB 二进制数据
NULL 空值

💡 常用约束:

  • PRIMARY KEY:主键(唯一标识)
  • AUTOINCREMENT:自动增长
  • NOT NULL:不能为空
  • UNIQUE:唯一
  • DEFAULT:默认值

四、增删改查(CRUD)

增删改查是数据库最核心的操作,也叫CRUD:

  • Create(增):INSERT
  • Read(查):SELECT
  • Update(改):UPDATE
  • Delete(删):DELETE

4.1 插入数据(增)

方法一:直接拼SQL

cpp 复制代码
QSqlQuery query;
QString sql = "INSERT INTO student (name, age, score, class) "
              "VALUES ('张三', 18, 90.5, '一班')";

if (query.exec(sql)) {
    qDebug() << "插入成功";
}

方法二:参数绑定(推荐!更安全)

cpp 复制代码
QSqlQuery query;
query.prepare("INSERT INTO student (name, age, score, class) "
              "VALUES (:name, :age, :score, :class)");

query.bindValue(":name", "李四");
query.bindValue(":age", 19);
query.bindValue(":score", 85.5);
query.bindValue(":class", "二班");

if (query.exec()) {
    qDebug() << "插入成功";
}

⚠️ 重要:一定要用参数绑定!这样可以防止SQL注入攻击,也能正确处理特殊字符。

方法三:位置绑定

cpp 复制代码
QSqlQuery query;
query.prepare("INSERT INTO student (name, age, score, class) "
              "VALUES (?, ?, ?, ?)");

query.addBindValue("王五");
query.addBindValue(20);
query.addBindValue(88.0);
query.addBindValue("三班");

query.exec();

4.2 批量插入

cpp 复制代码
QSqlQuery query;
query.prepare("INSERT INTO student (name, age) VALUES (?, ?)");

QVariantList names;
names << "赵六" << "钱七" << "孙八";

QVariantList ages;
ages << 18 << 19 << 20;

query.addBindValue(names);
query.addBindValue(ages);

// 批量执行
if (query.execBatch()) {
    qDebug() << "批量插入成功";
}

4.3 查询数据(查)

cpp 复制代码
QSqlQuery query;
query.exec("SELECT id, name, age, score FROM student");

// 遍历结果
while (query.next()) {
    int id = query.value(0).toInt();          // 按列索引取值
    QString name = query.value(1).toString();
    int age = query.value(2).toInt();
    double score = query.value(3).toDouble();
    
    qDebug() << id << name << age << score;
}

也可以用字段名取值:

cpp 复制代码
while (query.next()) {
    QString name = query.value("name").toString();
    int age = query.value("age").toInt();
    qDebug() << name << age;
}

4.4 条件查询

cpp 复制代码
QSqlQuery query;

// 查询年龄大于18的学生
query.prepare("SELECT name, age FROM student WHERE age > :age");
query.bindValue(":age", 18);
query.exec();

while (query.next()) {
    qDebug() << query.value("name").toString()
             << query.value("age").toInt();
}

常用的查询条件

sql 复制代码
-- 等于
SELECT * FROM student WHERE class = '一班'

-- 大于、小于
SELECT * FROM student WHERE score > 80

-- 范围
SELECT * FROM student WHERE age BETWEEN 18 AND 20

-- 模糊查询
SELECT * FROM student WHERE name LIKE '张%'

-- 多条件
SELECT * FROM student WHERE age > 18 AND score > 85

-- 排序
SELECT * FROM student ORDER BY score DESC

-- 限制数量
SELECT * FROM student LIMIT 10

4.5 更新数据(改)

cpp 复制代码
QSqlQuery query;
query.prepare("UPDATE student SET score = :score WHERE name = :name");
query.bindValue(":score", 95.0);
query.bindValue(":name", "张三");

if (query.exec()) {
    qDebug() << "更新成功,影响行数:" << query.numRowsAffected();
}

4.6 删除数据(删)

cpp 复制代码
QSqlQuery query;
query.prepare("DELETE FROM student WHERE name = :name");
query.bindValue(":name", "李四");

if (query.exec()) {
    qDebug() << "删除成功,影响行数:" << query.numRowsAffected();
}

⚠️ 警告:DELETE不加WHERE条件会删除所有数据!一定要小心。

五、事务处理

5.1 什么是事务

事务是一组操作,要么全部成功,要么全部失败。比如银行转账:A减钱、B加钱,这两个操作必须同时成功或同时失败。

💡 生活类比:事务就像一个"打包操作",要么全做完,要么一个都不做。

5.2 使用事务

cpp 复制代码
QSqlDatabase db = QSqlDatabase::database();

// 开始事务
db.transaction();

QSqlQuery query;
bool success = true;

// 执行多个操作
success &= query.exec("INSERT INTO student (name) VALUES ('A')");
success &= query.exec("INSERT INTO student (name) VALUES ('B')");

if (success) {
    db.commit();  // 提交事务(确认所有操作)
    qDebug() << "事务提交成功";
} else {
    db.rollback();  // 回滚事务(撤销所有操作)
    qDebug() << "事务回滚";
}

5.3 事务的好处

  1. 保证数据一致性:多个操作要么全成功要么全失败
  2. 提高批量操作性能:批量插入用事务包起来会快很多
cpp 复制代码
// 批量插入用事务,速度快很多!
db.transaction();
QSqlQuery query;
query.prepare("INSERT INTO student (name) VALUES (?)");
for (int i = 0; i < 10000; i++) {
    query.addBindValue(QString("学生%1").arg(i));
    query.exec();
}
db.commit();

六、综合实战:简易通讯录(数据层)

我们来写一个通讯录的数据操作类,封装增删改查。

6.1 完整代码

cpp 复制代码
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
#include <QString>

// 联系人结构
struct Contact {
    int id;
    QString name;
    QString phone;
    QString email;
};

// 通讯录数据管理类
class ContactManager
{
public:
    // 初始化数据库
    bool init() {
        m_db = QSqlDatabase::addDatabase("QSQLITE");
        m_db.setDatabaseName("contacts.db");
        
        if (!m_db.open()) {
            qDebug() << "打开数据库失败:" << m_db.lastError().text();
            return false;
        }
        
        // 创建表
        QSqlQuery query;
        QString sql = "CREATE TABLE IF NOT EXISTS contact ("
                      "id INTEGER PRIMARY KEY AUTOINCREMENT, "
                      "name TEXT NOT NULL, "
                      "phone TEXT, "
                      "email TEXT)";
        
        if (!query.exec(sql)) {
            qDebug() << "创建表失败:" << query.lastError().text();
            return false;
        }
        
        return true;
    }
    
    // 添加联系人
    bool addContact(const QString &name, const QString &phone, 
                    const QString &email) {
        QSqlQuery query;
        query.prepare("INSERT INTO contact (name, phone, email) "
                      "VALUES (:name, :phone, :email)");
        query.bindValue(":name", name);
        query.bindValue(":phone", phone);
        query.bindValue(":email", email);
        
        if (!query.exec()) {
            qDebug() << "添加失败:" << query.lastError().text();
            return false;
        }
        return true;
    }
    
    // 删除联系人
    bool deleteContact(int id) {
        QSqlQuery query;
        query.prepare("DELETE FROM contact WHERE id = :id");
        query.bindValue(":id", id);
        return query.exec();
    }
    
    // 更新联系人
    bool updateContact(int id, const QString &name, 
                       const QString &phone, const QString &email) {
        QSqlQuery query;
        query.prepare("UPDATE contact SET name = :name, "
                      "phone = :phone, email = :email WHERE id = :id");
        query.bindValue(":name", name);
        query.bindValue(":phone", phone);
        query.bindValue(":email", email);
        query.bindValue(":id", id);
        return query.exec();
    }
    
    // 查询所有联系人
    QList<Contact> getAllContacts() {
        QList<Contact> contacts;
        QSqlQuery query("SELECT id, name, phone, email FROM contact");
        
        while (query.next()) {
            Contact c;
            c.id = query.value("id").toInt();
            c.name = query.value("name").toString();
            c.phone = query.value("phone").toString();
            c.email = query.value("email").toString();
            contacts.append(c);
        }
        return contacts;
    }
    
    // 按姓名搜索
    QList<Contact> searchByName(const QString &keyword) {
        QList<Contact> contacts;
        QSqlQuery query;
        query.prepare("SELECT id, name, phone, email FROM contact "
                      "WHERE name LIKE :keyword");
        query.bindValue(":keyword", "%" + keyword + "%");
        query.exec();
        
        while (query.next()) {
            Contact c;
            c.id = query.value("id").toInt();
            c.name = query.value("name").toString();
            c.phone = query.value("phone").toString();
            c.email = query.value("email").toString();
            contacts.append(c);
        }
        return contacts;
    }

private:
    QSqlDatabase m_db;
};

6.2 使用示例

cpp 复制代码
ContactManager manager;
manager.init();

// 添加联系人
manager.addContact("张三", "13800138000", "zhangsan@qq.com");
manager.addContact("李四", "13900139000", "lisi@qq.com");

// 查询所有
QList<Contact> all = manager.getAllContacts();
for (const Contact &c : all) {
    qDebug() << c.id << c.name << c.phone << c.email;
}

// 搜索
QList<Contact> results = manager.searchByName("张");

// 更新
manager.updateContact(1, "张三丰", "13800138000", "new@qq.com");

// 删除
manager.deleteContact(2);

这个类封装了所有的数据库操作,界面层直接调用这些方法就行,数据和界面分离,代码更清晰。

七、今日总结

今天我们学习了QT数据库编程的上篇,主要是SQLite入门和增删改查。

知识点汇总

类/操作 用途
QSqlDatabase 数据库连接管理
QSqlQuery 执行SQL语句
INSERT 插入数据(增)
SELECT 查询数据(查)
UPDATE 更新数据(改)
DELETE 删除数据(删)
transaction/commit 事务处理

重要概念

  • QT += sql:使用SQL模块要先添加
  • QSqlDatabase:建立数据库连接
  • QSqlQuery:执行SQL语句
  • 参数绑定:用bindValue防止SQL注入
  • query.next():遍历查询结果
  • 事务:保证数据一致性,提高批量性能

经验分享

  1. 一定要用参数绑定:不要直接拼SQL字符串,会有安全隐患
  2. 检查每一步的返回值:数据库操作可能失败,要处理错误
  3. 批量操作用事务:速度会快很多
  4. 数据和界面分离:把数据库操作封装成类,界面层直接调用
  5. DELETE和UPDATE小心WHERE:不加条件会影响所有数据
  6. SQLite很适合入门:免安装,一个文件搞定

八、明日预告

明天我们将学习数据库编程(下):模型视图与数据展示

内容包括:

  • QSqlQueryModel:把查询结果显示到表格
  • QSqlTableModel:可编辑的表格模型
  • 数据库与QTableView绑定
  • 数据的排序、筛选
  • 综合实战:完整的学生管理系统(带界面)

明天我们把数据库和界面结合起来,做一个真正能用的管理系统!


📝 学习建议:数据库是很多应用的核心,一定要多练。

练习建议:

  • 把今天的通讯录数据类代码敲一遍
  • 试试自己设计一个"图书管理"的表,实现增删改查
  • 试试用事务批量插入1万条数据,感受一下速度
  • 试试写各种查询条件(排序、模糊查询、范围查询)

SQL和增删改查掌握了,明天我们把数据库和界面结合起来!明天见,继续加油!💪

相关推荐
Flynt4 小时前
Room 3.0 包名重构 + KMP 迁移:我把项目升级踩了个遍
android·数据库·kotlin
这个DBA有点耶20 小时前
NULL不是空——数据库里最反直觉的设计,90%新人踩过的坑
数据库·mysql·代码规范
这个DBA有点耶1 天前
AI写的SQL跑崩了生产库,这锅谁背?
数据库·人工智能·程序员
镜舟科技1 天前
Databricks 再提 LTAP,AI 时代的数据底座为何重回大一统叙事?
数据库·架构·agent
Databend1 天前
从湖仓升级为 Agent 时代的数据控制面,Snowflake 和 Databricks 有哪些布局
大数据·数据库·agent
ClouGence1 天前
SQL Server CDC 能放到 Always On 备库读吗?一文讲透原理与实践
数据库·sql server
先吃饱再说2 天前
存储的进化:从 MySQL 到浏览器缓存,数据到底住在哪?
数据库
Nturmoils2 天前
字段太多看不全,ksql 的展开模式和输出控制怎么用
数据库·后端
Databend2 天前
Agent 轨迹分析与归因的数据工程实践
大数据·数据库·agent