记录一些在QT中使用QSqlDatabase操作数据库时,需要注意的地方
创建数据库
cpp
bool CDBOperatorAbstract::_openDBConn(CDatabaseConfig config)
{
QWriteLocker locker(&m_locker);
QSqlDatabase db;
if(QSqlDatabase::contains(m_connectionName))
{
db = QSqlDatabase::database(m_connectionName);
QStringList tables = db.tables();
if(db.isOpen() && tables.contains(config.m_databaseName))
{
m_isConnected = true;
return true;
}
}
else
{
// 此时目标数据库不一定存在,不指定数据库名,会默认连接到mysql的主数据库(mysql数据库)
db = QSqlDatabase::addDatabase("QMYSQL",m_connectionName);
db.setHostName(config.m_hostAddress); // 数据库服务器IP
db.setPort(config.m_port); // 端口号
db.setUserName(config.m_userName); // 用户名
db.setPassword(config.m_userPwd); // 密码
}
// 连接数据库
if(!db.open())
{
qDebug() << db.lastError().text();
m_isConnected = false;
return false;
}
QSqlQuery query(db);
QString strSql = QString("CREATE DATABASE IF NOT EXISTS `%1`").arg(config.m_databaseName);
//执行建表语句,若执行成功
if(query.exec(strSql))
{
//若查询语句有错
if(query.lastError().isValid())
{
qDebug()<< query.lastError().text();
m_isConnected = false;
return false;
}
}
else
{
m_isConnected = false;
return false;
}
// 确认目标数据库创建成功,关闭之前打开的默认数据库
db.close();
// 重新设置连接的数据库名
db.setDatabaseName(config.m_databaseName);
// 重新打开数据库
if(!db.open())
{
m_isConnected = false;
return false;
}
else
{
m_isConnected = true;
return true;
}
}
在上述创建数据库的代码中有几个需要注意的地方:
1、一个QSqlDatabase对象表示的是一个数据库连接,而不是字面上的数据库(数据库连接与数据库的关系是:一个数据库连接可以通过修改配置连接到多个数据库,一个数据库也可以有多个数据库连接)
2、addDatabase()方法需要指定数据库驱动类型,如MySQL、SQLite......,如果没有指定连接名,将默认使用defaultConnection作为连接名。对于同一个连接,重复调用addDatabase(),会提示:
QSqlDatabasePrivate::addDatabase: duplicate connection name
'qt_sql_default_connection', old connection removed.
3、removeDatabase()删除数据库连接,需要在确保所有QSqlQuery查询操作执行完毕并且销毁,否则会提示:
QSqlDatabasePrivate::removeDatabase: connection 'qt_sql_default_connection' is still in use, all queries will cease to work.
4、在进行数据库连接时,目标数据库可能还不存在,这时候可以不指定数据库名进行连接,此会将连接到数据库服务器的主数据库,例如我使用的是MySQL数据库服务器,就会连接到mysql数据库
5、数据库连接建立成功后,创建目标数据库(不存在时才创建),确保目标数据库创建成功后,先关闭之前的数据库连接,设置数据库名称后再重新打开。(只有重新打开数据库连接,数据库配置才会生效)
多线程使用数据库
QSqlDatabase是非线程安全的,意味着在多线程环境下同时使用同一个数据库连接对象可能会导致竞态条件和数据一致性问题。因此,每个线程最好都维护一个独立的数据库连接,并且在同一个线程中创建、使用和关闭数据库连接。如果数据库连接有父对象,它和父对象也必须在同一个线程。
一般用QMap来管理线程和对应的数据库连接,可以用QMap<QThread*,QSqlDatabase*>来存储,也可以用QMap<QThread*,QString>来存储(存连接名)
由于程序可能在各个地方操作数据库,为了让数据库连接的创建和使用保持在同一个线程中,可以用QMetaObject::invokeMethod()将数据库的创建、使用、关闭都移动到数据库连接所在的线程中
cpp
bool CDBOperatorAbstract::checkIsInWorkThread()
{
if(QThread::currentThread() == this->m_pThread)
return true;
else
return false;
}
bool CDBOperatorAbstract::openDBConn(CDatabaseConfig config)
{
if(checkIsInWorkThread())
{
return _openDBConn(config);
}
else
{
bool isOk = false;
QMetaObject::invokeMethod(this,"_openDBConn",Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool,isOk),
Q_ARG(CDatabaseConfig,config));
return isOk;
}
}
用BlockingQueuedConnection的连接方式,会将目标函数放在接收者的生存线程中执行,并且阻塞发送者所在的线程,直到目标函数执行完毕。
增删改查
numRowsAffected()
这个函数返回最后一个执行的 SQL 查询语句所影响的行数。通常用于 INSERT、UPDATE 或 DELETE 查询之后,用来确定有多少行受到了影响。返回值是一个整数。
lastInsertId()
这个函数用于在主键自增的情况下,获取最后一次 INSERT 操作生成的主键,返回值为QVariant类型,需要根据主键实际的类型进行转换。
size()
这个函数用于获取当前查询结果集中的记录数。例如执行 SELECT 查询后需要确定返回的记录数量。返回值是一个整数。
isValid()
这个函数用于检查 QSqlQuery 对象是否有效,即它是否正常地初始化以执行 SQL 查询。