背景介绍

本人使用过PHP/C#/VB/VB.NET/JAVA等编程语言开发过基于数据库方面的应用,相对于其它编程语言来说,个人感觉QT的数据库访问框架在设计时有些混乱,所以在最开始使用的时候一度有些困惑。在后来多次使用QT开发应用的过程中,才慢慢使用了QT的数据库访问框架。

功能对比

一般来说,涉及到数据库访问的框架会有如下功能的类:数据库驱动管理类、数据库连接类、执行增删改查的数据库操作类、用于接收执行查询操作返回的结果集的类、数据库事务管理类以及数据库异常方面的类。

例如在Java和C#中大概的对应关系如下:


 



Java


C#



数据库驱动层



java.sql.Driver



System.Data.Common.DbProviderFactory



数据库连接层



java.sql.Connection



System.Data.Common.DbConnection



数据库执行层



java.sql.Statement

java.sql.PreparedStatement



System.Data.Common.DbCommand

System.Data.Common.DbDataAdapter



结果集表示层



java.sql.ResultSet



System.Data.Common.DataReader

System.Data.Common.DataTable

System.Data.DataRow

System.Data.DataColumn



数据库事务层



java.sql.Connection.commit();

java.sql.Connection.rollback();



System.Data.Common.DbTransaction.commit()

System.Data.Common.DbTransaction.rollback()



数据库异常



java.sql.SQLException



System.Data.Common.DbException




说明:在Java SE中定义的以接口居多,需要具体的数据库实现。在C#中微软分别提供了接口(位于System.Data命名空间下)、抽象类(位于System.Data.Common命名空间下)和针对一些具体数据库的实现类。在上表中C#部分主要是抽象类。另外,由于语言的不同,很难将相关的类或接口完全严格地填入上面的表中,只能是近似。

在QT的数据库编程访问体系中,主要提供了QSqlDatabase、QSqlDriver、QSqlError、QSqlField、QSqlIndex、QSqlQuery、QSqlQueryModel、QSqlRecord、QSqlResult、QSqlTableModel类,除了上述类之外还有QSqlDriverCreator、QSqlDriverCreatorBase、QSqlRelationalTableModel类。如果按照上述的功能划分,那么表格基本可以理解:


 



QT



说明



数据库驱动层



QSqlDriver



 



数据库连接层



QSqlDatabase



QSqlDatabase.exec(const QString &query = QString())可以直接执行SQL语句



数据库执行层



QSqlQuery



 



结果集表示层



QSqlRecord

QSqlField

QSqlIndex

QSqlResult

QSqlQueryModel

QSqlTableModel



 



数据库事务层



QSqlDatabase.commit();

QSqlDatabase.rollback();



 



数据库异常



QSqlError



 




QT中数据库操作常用类介绍

在QT的帮助文档里,QT将上述的数据库相关的类分为三个层级,最底层的一个层级为驱动层,在这个层级的类有:QSqlDriver、QSqlDriverCreator、QSqlDriverCreatorBase等,在驱动层之上是SQL接口层,在这个层级的类有QSqlDatabase、QSqlQuery、QSqlField、QSqlRecord、QSqlIndex、QSqlError等,在SQL接口层之上是用户接口层,用户接口层的类有QSqlQueryModel、QSqlTableModel及QSqlRelationalTableModel等。

对于上面的类的作用介绍如下:


序号





说明



1



QSqlDriver



这个类一般不会直接使用,除非想开发自己的数据库驱动。它是用于访问特定数据库的抽象类。



2



QSqlDatabase



用于连接数据库的类,提供了数据库连接、访问和事务控制等功能,并且还支持获取当前支持的驱动(QSqlDatabase::drivers())、直接支持SQL语句(QSqlDatabase.exec(const QString &query = QString()))、获取指定表的主键(QSqlDatabase::primaryIndex(const QString &tablename))、获取指定表的字段信息(QSqlDatabase::record(const QString &tablename))等操作。



3



QSqlQuery



用于执行SQL语句的类,支持通过QSqlQuery::boundValue()的重载形式来执行参数化SQL语句。通过QSqlQuery::record()方法返回结果集中的一行记录,通过QSqlResult *QSqlQuery::result()返回结果集,通过QSqlQuery::value()重载形式返回指定字段的值。



4



QSqlRecord



用于封装数据库记录的类,QSqlRecord封装了数据库表或视图中的一行记录,一个QSqlRecord可能包含0到多个QSqlField,可以用QSqlRecord::field()的重载形式获取QSqlRecord中包含的QSqlField,也可以用QSqlRecord::value()的重载形式获取QSqlRecord各字段的值(返回值为QVariant,需要根据实际字段类型进行转换)



5



QSqlField



用于封装数据库表或视图中的字段,通过QSqlField类可以获取字段的名称、所属表名、是否允许为空、默认值、字段长度、是否为空、字段类型、字段值等信息。



6



QSqlIndex



用于封装数据库索引的类,通过QSqlIndex类可以获取索引的名称、索引是降序或升序等信息。



7



QSqlResult



用于封装数据库特定的数据而抽象的类,通常情况应尽量使用QSqlQuery而不是QSqlResultQSqlQueryQSqlResult的基础上提供了一层封装。如果需要实现自己的数据库驱动,那么就需要实现自己的QSqlResult类,一般情况下尽量使用QSqlQuery吧。



8



QSqlError



用于封装数据库错误信息的类,可以通过QSqlError::isValid()来判断是否发生了数据库错误,在发生错误的情况下可以通过QSqlError::databaseText()获取数据库报告的错误,通过QSqlError::driverText()获取驱动程序报告的错误,还可以通过QSqlError::nativeErrorCode()获取数据库层面的本地错误代码,QSqlError::nativeErrorCode()用于获取QT层面的数据库错误划分。在获取数据库错误描述文字信息方面还有一个比较便捷的方法QSqlError::nativeErrorCode(),它直接将QSqlError::databaseText()QSqlError::driverText()连接成一条语句。




用法实例

数据准备

创建一个MySQL数据库,并在库中创建表tbluserinfo,创建tbluserinfo表的SQL语句如下:


CREATE TABLE `tbluserinfo` (
`Id` INT(11) NOT NULL AUTO_INCREMENT,
`OrgnizationId` VARCHAR(20) NOT NULL COLLATE 'utf8_general_ci',
`AccountName` VARCHAR(20) NOT NULL COLLATE 'utf8_general_ci',
`RealName` VARCHAR(20) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`Password` VARCHAR(50) NOT NULL COLLATE 'utf8_general_ci',
`LoginCount` INT(11) NOT NULL,
`Email` VARCHAR(20) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`Telephone` VARCHAR(20) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`CreateAt` DATETIME NOT NULL,
`LoginAt` DATETIME NULL DEFAULT NULL,
`IsValid` INT(1) NOT NULL,
`Json` TEXT NULL DEFAULT NULL COLLATE 'utf8_general_ci',
PRIMARY KEY (`Id`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
ROW_FORMAT=DYNAMIC;

创建实体类

实体类按照常规方式分为UserInfo.h文件和UserInfo.cpp文件,其中UserInfo.h文件内容如下:


#ifndef USERINFO_H
#define USERINFO_H


#include <QString>
#include <QDateTime>


/**
* @brief The UserInfo class 用户表对应的实体类
*/
class UserInfo
{
private:
int m_id;
QString m_accountName;
QString m_realName;
QString m_password;
int m_loginCount;
QString m_email;
QString m_telephone;
QDateTime m_createAt;
QDateTime m_loginAt;
int m_isValid;
QString m_json;
QString m_orgnization;


public:
UserInfo();
int id() const;
void setId(int id);
QString accountName() const;
void setAccountName(const QString& accountName);
QString realName() const;
void setRealName(const QString& realName);
QString password() const;
void setPassword(const QString& password);
int loginCount() const;
void setLoginCount(int loginCount);
QString email() const;
void setEmail(const QString& email);
QString telephone() const;
void setTelephone(const QString& telephone);
QDateTime createAt() const;
void setCreateAt(const QDateTime& createAt);
QDateTime loginAt() const;
void setLoginAt(const QDateTime& loginAt);
int isValid() const;
void setIsValid(int isValid);
QString json() const;
void setJson(const QString& json);
QString orgnization() const;
void setOrgnization(const QString& orgnization);


bool operator ==(const UserInfo& other) const
{
return this->m_id == other.m_id;
}
};


#endif // USERINFO_H

UserInfo.cpp文件的内容如下:


#include "UserInfo.h"


int UserInfo::id() const
{
return m_id;
}


void UserInfo::setId(int id)
{
m_id = id;
}


QString UserInfo::accountName() const
{
return m_accountName;
}


void UserInfo::setAccountName(const QString& accountName)
{
m_accountName = accountName;
}


QString UserInfo::realName() const
{
return m_realName;
}


void UserInfo::setRealName(const QString& realName)
{
m_realName = realName;
}


QString UserInfo::password() const
{
return m_password;
}


void UserInfo::setPassword(const QString& password)
{
m_password = password;
}


int UserInfo::loginCount() const
{
return m_loginCount;
}


void UserInfo::setLoginCount(int loginCount)
{
m_loginCount = loginCount;
}


QString UserInfo::email() const
{
return m_email;
}


void UserInfo::setEmail(const QString& email)
{
m_email = email;
}


QString UserInfo::telephone() const
{
return m_telephone;
}


void UserInfo::setTelephone(const QString& telephone)
{
m_telephone = telephone;
}


QDateTime UserInfo::createAt() const
{
return m_createAt;
}


void UserInfo::setCreateAt(const QDateTime& createAt)
{
m_createAt = createAt;
}


QDateTime UserInfo::loginAt() const
{
return m_loginAt;
}


void UserInfo::setLoginAt(const QDateTime& loginAt)
{
m_loginAt = loginAt;
}


int UserInfo::isValid() const
{
return m_isValid;
}


void UserInfo::setIsValid(int isValid)
{
m_isValid = isValid;
}


QString UserInfo::json() const
{
return m_json;
}


void UserInfo::setJson(const QString& json)
{
m_json = json;
}


QString UserInfo::orgnization() const
{
return m_orgnization;
}


void UserInfo::setOrgnization(const QString& orgnization)
{
m_orgnization = orgnization;
}


UserInfo::UserInfo()
{


}

创建数据库访问类

数据库访问类主要负责与数据库打交道,通常以实体类或基本数据类型作为载体,在Java中通常称之为DAO(Data Access Object,DAO),在C#开发人员中也有称之为DAL(Data Access Layer,DAL)的。一般来说,为便于项目代码的维护,通常数据库中的一张表会编写一个对应的实体类和一个与之对应的数据库访问类,在这里数据库访问类的名称为QtDatabaseDemo,它分为声明文件QtDatabaseDemo.h和实现文件QtDatabaseDemo.cpp。

其中QtDatabaseDemo.h的代码如下:


#ifndef QTDATABASEDEMO_H
#define QTDATABASEDEMO_H
#include <QSql>
#include <QSqlDatabase>
#include <QSqlError>
#include <QSqlField>
#include <QSqlIndex>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QDebug>
#include <QDateTime>
#include <QCryptographicHash>
#include <QString>
#include <QByteArray>


#include "UserInfo.h"


class QtDatabaseDemo
{
private:
/**
* @brief readUserInfo 将结果集中的一条记录转换成实体类
* @param sqlRecord 结果集中的一条记录
* @param userInfo 结果集对应的实体类
*/
void readUserInfo(QSqlRecord& sqlRecord, UserInfo& userInfo);
/**
* @brief readUserInfo 将结果集中的一条记录转换成实体类,通过next()实现对所有结果集的遍历
* 此方法比使用QSqlRecord方法略快,
* 即readUserInfo(QSqlRecord& sqlRecord, UserInfo& userInfo)性能略低,因其还有个循环设置QSqlRecord过程
* @param sqlQuery QSqlQuery实例
* @param userInfo 结果集对应的实体类
*/
void readUserInfo(QSqlQuery& sqlQuery, UserInfo& userInfo);
public:
QtDatabaseDemo();
~QtDatabaseDemo();


/**
* @brief showCreateConnection 创建数据库连接的例子
* @param db QSqlDatabase实例
*/
void showCreateConnection(QSqlDatabase& db);
/**
* @brief showTablesDetails 显示数据库表信息,包括数据库中的表以及各表中的各字段信息
*/
void showTablesDetails(void);


/**
* @brief showInsert Insert的例子
*/
void showInsert(void);


/**
* @brief showUpdate Update的例子
*/
void showUpdate(void);


/**
* @brief showDelete Delete的例子
*/
void showDelete(void);


/**
* @brief showQuery 查询的例子
*/
void showQuery(void);


/**
* @brief queryUserInfo 查询用户信息
* @param userId 用户Id
* @param userInfo 返回的查询结果
*/
void queryUserInfo(int userId, UserInfo& userInfo);


/**
* @brief queryMaxUserId 查询最大的用户id
* @return
*/
int queryMaxUserId(void);


/**
* @brief md5Hash 对字符串计算md5值
* @param src 原始字符串
* @param dest 结果字符串
*/
void md5Hash(QString& src, QString& dest);
};


#endif // QTDATABASEDEMO_H

QtDatabaseDemo.cpp文件的内容如下:


#include "QtDatabaseDemo.h"


QtDatabaseDemo::QtDatabaseDemo()
{


}


QtDatabaseDemo::~QtDatabaseDemo()
{


}


void QtDatabaseDemo::showCreateConnection(QSqlDatabase& db)
{
QString connectionName = "QT_MySQL";//数据库连接名
//数据库类型,这个是有固定格式的,
//可能值有:QDB2、QIBASE、QMYSQL、QOCI、QODBC、QPSQL、QSQLITE、QSQLITE2、QTDS
QString databaseType = "QMYSQL";
if (!QSqlDatabase::contains(connectionName))
{
//注册类型为QMYSQL、名称为QT_MySQL的QSqlDatabase
//后面可用QT_MySQL这个名称方法获取
db = QSqlDatabase::addDatabase(databaseType, connectionName);
db.setDatabaseName("dbName");//数据库名,请根据实际情况设定
db.setHostName("192.168.0.*");//数据库服务器IP或主机名,请根据实际情况设定
db.setPassword("password");//连接数据库服务器的账户密码,请根据情况设定
db.setPort(3306);//连接数据库服务器的端口号,请根据情况设定
db.setUserName("userName");//连接数据库服务器的账户名称,请根据情况设定
}
else
{
//直接用QT_MySQL这个名称获取,并且默认不打开数据库连接
db = QSqlDatabase::database(connectionName, false);
}


}


void QtDatabaseDemo::showTablesDetails()
{
QSqlDatabase db;
//初始化数据库连接
showCreateConnection(db);
//如果数据库驱动程序无效,则不继续执行
if (!db.isValid())
{
qWarning() << "Database driver is not valid.";
return;
}


//如果无法打开数据库,就输出出错原因
if (!db.open())
{
QSqlError sqlError = db.lastError();
if (sqlError.isValid())//如果错误,则输出错误消息
{
qWarning() << "Can not open database,error message is:" << sqlError.text();
}


return ;
}


//获取当前库的所有表的名称
QStringList tableNames = db.tables(QSql::Tables);


//对每个表进行遍历,输出主键信息和表中每个字段信息
for (QString tablename : tableNames)
{
qDebug() << QString("----------------------------") << tablename;
//获取主键信息
QSqlIndex sqlIndex = db.primaryIndex(tablename);
//如果设置主键,则输出主键的信息
if (!sqlIndex.isEmpty())
{
qDebug() << QString("Name:%1,cursorName:%2")
.arg(sqlIndex.name()).arg(sqlIndex.cursorName());
}


//获取表中每个字段的信息
QSqlRecord sqlRecord = db.record(tablename);
//如果表中含有字段,则输出每个字段的信息
if (!sqlRecord.isEmpty())
{
int count = sqlRecord.count();
//循环输出每个字段的信息
for (int i = 0; i < count; i++)
{
//按下标获取字段信息
QSqlField sqlField = sqlRecord.field(i);
QVariant type = sqlField.type();
QVariant value = sqlField.value();
qDebug() << QString("Name:%1,isNull:%2,length:%3,defaultValue:%4,required:%5,type:%6,value:%7")
.arg(sqlField.name()).arg(sqlField.isNull()).arg(sqlField.length()).arg(sqlField.defaultValue().toString())
.arg(sqlField.requiredStatus()).arg(type.toString()).arg(value.toString());
}
}
}
db.close();
}


void QtDatabaseDemo::showInsert()
{
QString insertSql = QString("INSERT INTO `tbluserinfo` "
"(`OrgnizationId`,"
"`AccountName`,"
"`RealName`,"
"`Password`,"
"`LoginCount`,"
"`Email`,"
"`Telephone`,"
" `CreateAt`,"
"`IsValid`,"
"`Json`)"
"VALUES"
"(:OrgnizationId,"
":AccountName,"
":RealName,"
":Password,"
":LoginCount,"
":Email,"
":Telephone,"
":CreateAt,"
":IsValid,"
":Json)");
QSqlDatabase db;
showCreateConnection(db);
//如果无法打开数据库,就输出出错原因
if (!db.open())
{
QSqlError sqlError = db.lastError();
if (sqlError.isValid())//如果错误,则输出错误消息
{
qWarning() << "Can not open database,error message is:" << sqlError.text();
}
return ;
}
// QSqlQuery sqlQuery(insertSql, db);//这种方式不适合参数化SQL语句
QSqlQuery sqlQuery(db);
sqlQuery.prepare(insertSql);//参数化SQL语句必须调用prepare方法,然后再绑定参数值
QString pwdPlain = "zhoufoxcn@2021.com";
QString pwdSecurit;
md5Hash(pwdPlain, pwdSecurit);//对密码进行处理
// sqlQuery.bindValue(":OrgnizationId", 1);//OrgnizationId列为varchar,不知道为什么赋值类型不对时为什么不报错
sqlQuery.bindValue(":OrgnizationId", "1");
sqlQuery.bindValue(":AccountName", "welcome");
sqlQuery.bindValue(":RealName", "helloworld");
sqlQuery.bindValue(":Password", pwdSecurit);
sqlQuery.bindValue(":LoginCount", 1);
sqlQuery.bindValue(":Email", "hellowolrd@qt.com");
sqlQuery.bindValue(":Telephone", "12345678");
sqlQuery.bindValue(":CreateAt", QDateTime::currentDateTime());
sqlQuery.bindValue(":IsValid", 1);
sqlQuery.bindValue(":Json", "{number:1}");
if (sqlQuery.exec())
{
int numRowsAffected = sqlQuery.numRowsAffected();
long long lastInsertId = sqlQuery.lastInsertId().toLongLong();
qDebug() << QString("numRowsAffected:%1,lastInsertId:%2")
.arg(numRowsAffected).arg(lastInsertId);
}
else
{
QSqlError sqlError = sqlQuery.lastError();
// qWarning() << "Can not insert data,error message is:" << sqlError.text();
if (sqlError.isValid())
{
qWarning() << "Can not insert data,error message is:" << sqlError.text();
}
}
db.close();
}


void QtDatabaseDemo::showUpdate()
{
QString updateSql = QString("UPDATE `tbluserinfo`"
" SET `OrgnizationId` = :OrgnizationId,"
"`AccountName` = :AccountName,"
"`RealName` =:RealName,"
"`Password` =:Password,"
"`LoginCount` =:LoginCount,"
"`Email` = :Email,"
"`Telephone` = :Telephone,"
"`CreateAt` = :CreateAt,"
"`LoginAt` = :LoginAt,"
"`IsValid` = :IsValid, "
"`Json` = :Json "
"WHERE `Id` =:Id");
QSqlDatabase db;
showCreateConnection(db);
//如果无法打开数据库,就输出出错原因
if (!db.open())
{
QSqlError sqlError = db.lastError();
if (sqlError.isValid())//如果错误,则输出错误消息
{
qWarning() << "Can not open database,error message is:" << sqlError.text();
}
return ;
}
// QSqlQuery sqlQuery(updateSql, db);
QSqlQuery sqlQuery(db);
sqlQuery.prepare(updateSql);
QString pwdPlain = "zhoufoxcn@2021.com";
QString pwdSecurit;
md5Hash(pwdPlain, pwdSecurit);//对密码进行处理
sqlQuery.bindValue(":OrgnizationId", "1");
sqlQuery.bindValue(":AccountName", "welcome");
sqlQuery.bindValue(":RealName", "helloworld");
sqlQuery.bindValue(":Password", pwdSecurit);
sqlQuery.bindValue(":LoginCount", 1);
sqlQuery.bindValue(":Email", "hellowolrd@qt.com");
sqlQuery.bindValue(":Telephone", "12345678");
sqlQuery.bindValue(":CreateAt", QDateTime::currentDateTime());
sqlQuery.bindValue(":IsValid", 1);
sqlQuery.bindValue(":Json", "{number:1}");
sqlQuery.bindValue(":Id", 25);//注意这里为省事直接写了一个数字,实际情况应该写实际的Id
if (sqlQuery.exec())
{
int numRowsAffected = sqlQuery.numRowsAffected();
qDebug() << QString("Updated UserInfo,numRowsAffected:%1.")
.arg(numRowsAffected);
}
else
{
QSqlError sqlError = sqlQuery.lastError();
if (sqlError.isValid())
{
qWarning() << "Can not update data,error message is:" << sqlError.text();
}


}
db.close();
}


void QtDatabaseDemo::showDelete()
{
//在一些Access等数据库中不支持使用":name"这种名称占位符的方式来使用SQL语句中的参数,只能用"?"来作为占位符
QString deleteSql = QString("DELETE FROM tbluserinfo where LoginCount = ? AND IsValid=? AND Telephone=?");
QSqlDatabase db;
showCreateConnection(db);
//如果无法打开数据库,就输出出错原因
if (!db.open())
{
QSqlError sqlError = db.lastError();
if (sqlError.isValid())//如果错误,则输出错误消息
{
qWarning() << "Can not open database,error message is:" << sqlError.text();
}
return ;
}
// QSqlQuery sqlQuery(querySql, db);
QSqlQuery sqlQuery(db);
sqlQuery.prepare(deleteSql);
//注意参数占位符的索引号从0开始,和数组下标从0开始一样
sqlQuery.bindValue(0, 1); //给第1个"?"占位符指定参数值,参数值为1
sqlQuery.bindValue(1, 1); //给第2个"?"占位符指定参数值,参数值为1
sqlQuery.bindValue(2, "12345678"); //给第3个"?"占位符指定参数值,参数值为"12345678"
if (sqlQuery.exec())
{
int numRowsAffected = sqlQuery.numRowsAffected();
qDebug() << QString("Delete UserInfo,numRowsAffected:%1.")
.arg(numRowsAffected);
}
else
{
QSqlError sqlError = sqlQuery.lastError();
// qDebug() << "nativeErrorCode:" << sqlError.nativeErrorCode() << ",ErrorType:" << (int)(sqlError.type());
if (sqlError.isValid())
{
qWarning() << "Can not delete data,error message is:" << sqlError.text();
}


}
qDebug() << "executedQuery:" << sqlQuery.executedQuery();
db.close();
}


void QtDatabaseDemo::showQuery()
{
QString querySql = QString("SELECT * from tbluserinfo");
UserInfo userInfo;
QSqlDatabase db;
showCreateConnection(db);
//如果无法打开数据库,就输出出错原因
if (!db.open())
{
QSqlError sqlError = db.lastError();
if (sqlError.isValid())//如果错误,则输出错误消息
{
qWarning() << "Can not open database,error message is:" << sqlError.text();
}
return ;
}
QSqlQuery sqlQuery(querySql, db);//因为没有使用参数化SQL语句,所以无需调用prepare()方法
sqlQuery.setForwardOnly(true);//在执行查询前设置ForwardOnly为true在大多数情况下可以大幅度节约内存
// QSqlQuery sqlQuery(db);
// sqlQuery.prepare(querySql);
if (sqlQuery.exec())
{
while (sqlQuery.next())
{
QSqlRecord sqlRecord = sqlQuery.record();
readUserInfo(sqlRecord, userInfo);
qDebug() << "Account Name:" << userInfo.accountName();
}
}
else
{
QSqlError sqlError = sqlQuery.lastError();
if (sqlError.isValid())
{
qWarning() << "Can not query data,error message is:" << sqlError.text();
}
}
db.close();
}


void QtDatabaseDemo::queryUserInfo(int userId, UserInfo& userInfo)
{
QString querySql = QString("SELECT * from tbluserinfo where `Id` = :Id");
QSqlDatabase db;
showCreateConnection(db);
//如果无法打开数据库,就输出出错原因
if (!db.open())
{
QSqlError sqlError = db.lastError();
if (sqlError.isValid())//如果错误,则输出错误消息
{
qWarning() << "Can not open database,error message is:" << sqlError.text();
}
return ;
}
// QSqlQuery sqlQuery(querySql, db);
QSqlQuery sqlQuery(db);
sqlQuery.prepare(querySql);
sqlQuery.bindValue(":Id", userId); //使用命名占位符的方式指定SQL参数值
// sqlQuery.bindValue(0, userId); //使用位置索引的方式指定SQL参数值,这两种是等效的
if (sqlQuery.exec())
{
if (sqlQuery.next())
{
// QSqlRecord sqlRecord = sqlQuery.record();//通过record方式读取可以知道列数,但是有循环设置各字段的过程,因此性能略低
readUserInfo(sqlQuery, userInfo);
}
}
else
{
QSqlError sqlError = sqlQuery.lastError();
if (sqlError.isValid())
{
qWarning() << "Can not query data,error message is:" << sqlError.text();
}
}
db.close();
}


int QtDatabaseDemo::queryMaxUserId()
{
int maxUserId = -1;
QString querySql = QString("SELECT max(Id) from tbluserinfo");
//QString querySql = QString("SELECT Id from tbluserinfo ORDER BY Id DESC limit 0,1");
QSqlDatabase db;
showCreateConnection(db);
//如果无法打开数据库,就输出出错原因
if (!db.open())
{
QSqlError sqlError = db.lastError();
if (sqlError.isValid())//如果错误,则输出错误消息
{
qWarning() << "Can not open database,error message is:" << sqlError.text();
}
return maxUserId;
}
QSqlQuery sqlQuery(querySql, db);
if (sqlQuery.exec())
{
if (sqlQuery.next())
{
maxUserId = sqlQuery.value(0).toInt();
}
}
else
{
QSqlError sqlError = sqlQuery.lastError();
if (sqlError.isValid())
{
qWarning() << "Can not query data,error message is:" << sqlError.text();
}
}
db.close();
return maxUserId;
}


void QtDatabaseDemo::md5Hash(QString& src, QString& dest)
{
if (src.isEmpty()) //如果原始密码为空,则返回空结果
{
dest = src;
}
else
{
QByteArray pwdArray = QCryptographicHash::hash(src.toLatin1(), QCryptographicHash::Md5);
if (!dest.isEmpty())//清空原始信息
{
dest.clear();
}
dest.append(pwdArray.toHex());
}
//
}


void QtDatabaseDemo::readUserInfo(QSqlRecord& sqlRecord, UserInfo& userInfo)
{
if (!sqlRecord.isEmpty())//如果结果集不为空
{
userInfo.setId(sqlRecord.value(0).toInt());
userInfo.setOrgnization(sqlRecord.value(1).toString());
userInfo.setAccountName(sqlRecord.value(2).toString());
userInfo.setRealName(sqlRecord.value(3).toString());
userInfo.setPassword(sqlRecord.value(4).toString());
userInfo.setLoginCount(sqlRecord.value(5).toInt());
userInfo.setEmail(sqlRecord.value(6).toString());
userInfo.setTelephone(sqlRecord.value(7).toString());
userInfo.setCreateAt(sqlRecord.value(8).toDateTime());
userInfo.setLoginAt(sqlRecord.value(9).toDateTime());
userInfo.setIsValid(sqlRecord.value(10).toInt());
//对字段进行非空判断
if (!sqlRecord.isNull(11))
{
userInfo.setJson(sqlRecord.value(11).toString());
}
}
}


void QtDatabaseDemo::readUserInfo(QSqlQuery& sqlQuery, UserInfo& userInfo)
{
if (sqlQuery.isValid())
{
userInfo.setId(sqlQuery.value(0).toInt());
userInfo.setOrgnization(sqlQuery.value(1).toString());
userInfo.setAccountName(sqlQuery.value(2).toString());
userInfo.setRealName(sqlQuery.value(3).toString());
userInfo.setPassword(sqlQuery.value(4).toString());
userInfo.setLoginCount(sqlQuery.value(5).toInt());
userInfo.setEmail(sqlQuery.value(6).toString());
userInfo.setTelephone(sqlQuery.value(7).toString());
userInfo.setCreateAt(sqlQuery.value(8).toDateTime());
userInfo.setLoginAt(sqlQuery.value(9).toDateTime());
userInfo.setIsValid(sqlQuery.value(10).toInt());
//对字段进行非空判断
if (!sqlQuery.isNull(11))
{
userInfo.setJson(sqlQuery.value(11).toString());
}
}
}

个人总结

关于如何获取数据库执行过程中的错误以及判断SQL语句执行是否成功执行

如果想要判断SQL语句是否成功执行,可以通过QSqlQuery::exec()方法的返回值来判断,如果返回值为true则表示成功执行,否则表示执行失败。

通过QSqlQuery::exec()方法的返回值仅仅简单判断SQL语句是否执行成功,如果在SQL执行成功后需要判断受影响的行数,则需要通过QSqlQuery::numRowsAffected()来判断,这一点在执行删除数据操作时极其有用,有可能执行删除操作的SQL语句没有语法错误但是没有符合条件的数据记录,QSqlQuery::numRowsAffected()方法的返回值就为0。此外,在执行SQL语句不成功时,如果需要知道执行不成功的原因,可以通过QSqlQuery::lastError()方法的返回值QSqlError来判断,如果确实发生了错误,那么QSqlError::isValid()会返回true,然后通过QSqlError::text()返回错误描述,用以分析SQL语句为什么会执行出错。

另外,在连接数据库时也有可能会因为服务器信息或者账号信息不对而连接出错,QSqlDatabase::open()会返回false,通过QSqlDatabase::lastError()可以获取错误对象,然后通过QSqlError::text()返回错误描述,用以分析为什么连接数据库出错。

还有一点需要稍微说一下的是,在支持返回最近新增的ID的数据库中,可以通过QSqlQuery::lastInsertId()来返回刚刚新增的记录的主键。早年论坛中新发表帖子或者回帖成功之后,有一个功能就是提供用户查看自己刚刚发表的帖子的查看功能,这个方法就很方便(不过那都是在Web中)。

关于参数化SQL语句

对于没有参数的SQL语句,如"SELECT * from tbluserinfo"可以通过QSqlQuery sqlQuery(querySql, db);来实例化QSqlQuery实例,然后执行QSqlQuery::exec()方法完成对数据库的交互。但是在大多数情况下查询是要带有条件的,而且这些查询条件是由用户输入的,如果不对用户的数据输入进行检查则有可能会发生SQL注入。

因此在QT中支持参数化SQL语句,并且QT支持两种形式的SQL参数化语句:基于命名占位符的和基于索引占位符的。

基于命名的参数化SQL语句

基于命名的在Oracle和MySQL中比较常见,如下面的SQL语句就是基于命名占位符的:

INSERT INTO person (id, forename, surname) VALUES (:id, :forename, :surname)

在上面的SQL语句中” :id”就是参数的名称,在编程中可以直接通过名称给参数赋值。在这种基于命名占位符的SQL语句中给参数赋值有两种方式,一种是基于命名的,如下面的例子:


QSqlQuery query;
query.prepare("INSERT INTO person (id, forename, surname) "
"VALUES (:id, :forename, :surname)");
query.bindValue(":id", 1001);
query.bindValue(":forename", "Bart");
query.bindValue(":surname", "Simpson");
query.exec();

此外还可以基于下标位置,如下面的例子:


QSqlQuery query;
query.prepare("INSERT INTO person (id, forename, surname) "
"VALUES (:id, :forename, :surname)");
query.bindValue(0, 1001);
query.bindValue(1, "Bart");
query.bindValue(2, "Simpson");
query.exec()

我个人比较喜欢基于命名的,因为灵活性强一些,一旦对原来的SQL语句进行修改增加或删除了字段,只需要按照名称删除或增加响应的字段即可,而如果基于参数在SQL语句中的位置,那么只要是改动位置之后的字段序号都会变化,都需要进行响应的修改。

基于位置的参数化SQL语句

通常通过ODBC来访问数据库会采用这种方式,我记得早年开发基于Access的.net应用就是这么弄的,在基于位置的参数化SQL语句中参数都是以”?”来占位,编程时以”?”在SQL语句中出现的位置来给参数赋值,当然可以可以直接按照顺序来赋值。

下面就是直接根据参数位置来赋值的,所以并没有提供位置参数,如下面的例子:


QSqlQuery query;
query.prepare("INSERT INTO person (id, forename, surname) "
"VALUES (?, ?, ?)");
query.addBindValue(1001);
query.addBindValue("Bart");
query.addBindValue("Simpson");
query.exec();

还有一种形式,那就是给参数赋值时还提供参数顺序信息,这样灵活性强一些,下面就是例子:


QSqlQuery query;
query.prepare("CALL AsciiToInt(?, ?)");
query.bindValue(0, "A");
query.bindValue(1, 0, QSql::Out);
query.exec();
int i = query.boundValue(1).toInt(); // i is 65

 在上面的例子中,注意两个方法有所不同,如果不提供参数位置信息就需要按照参数的位置依次调用query.addBindValue()方法,而提供参数信息则是调用query.bindValue()方法。

还有一点需要注意的是,如果不适用参数化SQL语句,则不需要QSqlQuery::prepare()方法,但是如果使用参数化SQL语句,则一定要在给SQL语句中的参数赋值之前调用QSqlQuery::prepare()这个方法。

关于执行效率问题

QSqlQuery类提供了多个关于记录位置的方法,比如previous(), next(), first(), last()分别支持上一条、下一条、第一条和最后一条记录跳转的功能,seek()方法则支持跳转到指定位置的功能,at()方法则是告知当前处在记录结果集的什么位置上。

之前看过网上流传一个段子,有个Java新手程序员写了一个计算24小时后的Date的算法就,就是让Thread.sleep()24小时然后再返回,最终被别人发现差点被祭天的段子。在早年我周围也有真实的类似的事情,有人为了统计符合记录的条数,使用while循环,然后不断next()判断是否有下一条记录,有的话计算器加1,以此来达到统计总记录条数的目的。后来稍微聪明一点了,就利用last()方法定位到最后一条记录,然后在用类似于QT中的at()方法来获取当前位置,当然我相信现在他们都会用select count(1) from table的方式来统计总记录条数了。另外,在数据库支持的情况下,在QT中海可以用QSqlQuery::size()来获取总记录条数。

在执行查询返回多个多条记录时,大多数情况下我们会从第一条开始依次记录直至结束,很少会返回上上一条或第一条。如果是这种情况,可以通过QSqlQuery::setForwardOnly()设置为true(默认为false),这样由于不用缓存一些不再使用的数据,内存占用大为减少,但是这样一来就不能调用previous()方法了。例如在本文的实例代码showQuery()方法就有类似的用法:

QSqlQuery sqlQuery(querySql, db);//因为没有使用参数化SQL语句,所以无需调用prepare()方法

sqlQuery.setForwardOnly(true);//在执行查询前设置ForwardOnly为true在大多数情况下可以大幅度节约内存

此外在执行数据库查询时,有两种方式可以获取一条记录的所有字段值,一种是通过QSqlQuery::value(int index)方法,index为从0开始的字段索引,如果读取完当前记录的所有字段值则可以调用QSqlQuery::next()方法判断是否有下一条记录,如果有下一条记录则自动跳转到下一条记录,否则返回false。还有一种方式是以QSqlRecord的形式读取,一个QSqlRecord的实例代表返回结果集里的一条记录,通过QSqlQuery::record()方法返回当前位置的QSqlRecord,然后按照QSqlRecord中的字段顺序去读取字段的值,不过这种方法比QSqlQuery::value(int index)这种方法效率略低。

上面说的两种读取查询结果的例子,上上面的代码中都有,分别为QtDatabaseDemo::readUserInfo(QSqlRecord& sqlRecord, UserInfo& userInfo)和QtDatabaseDemo::readUserInfo(QSqlQuery& sqlQuery, UserInfo& userInfo)。

在QT中访问数据库的一般流程步骤

增删改操作的一般步骤

1.建立数据库连接,即实例化QSqlDatabase,指定数据库连接信息;

2.通过db.open()返回值判断是否能够打开数据库,如果能打开则进行下一步,如果不能打开则通过db.lastError()来分析是什么原因导致无法打开数据库;

3.通过实例化的QSqlDatabase来实例化QSqlQuery,如果执行参数化SQL语句则需要先执行QSqlQuery::prepare()方法再绑定参数。

4.通过QSqlQuery.exec()的返回值来判断是否执行成功,如果执行成功,可通过QSqlQuery::numRowsAffected()方法可返回收影响的行数(即新增、更新或删除的记录数);如果执行不成功,可通过QSqlQuery::lastError()返回的的QSqlError对象实例查看失败原因,此时该对象的QSqlError::isValid()方法也必定返回true,表示执行SQL语句有错。

5.调用QSqlDatabase的close方法关闭数据库连接。

查询操作

1.建立数据库连接,即实例化QSqlDatabase,指定数据库连接信息;

2.通过db.open()返回值判断是否能够打开数据库,如果能打开则进行下一步,如果不能打开则通过db.lastError()来分析是什么原因导致无法打开数据库;

3.通过实例化的QSqlDatabase来实例化QSqlQuery,如果执行参数化SQL语句则需要先执行QSqlQuery::prepare()方法再绑定参数。

4.通过QSqlQuery.exec()的返回值来判断是否执行成功,如果执行成功,可通QSqlQuery::next()判断是否有可用的结果集(也可以用QSqlQuery::size()来获取结果集数量),对于结果集有多条记录的可以while循环实现对结果集的遍历;如果执行不成功,可通过QSqlQuery::lastError()返回的的QSqlError对象实例查看失败原因,此时该对象的QSqlError::isValid()方法也必定返回true,表示执行SQL语句有错。

5.调用QSqlDatabase的close方法关闭数据库连接。


2021年9月25日