在三层架构中实现线程:无法打开数据库

Implement thread in Three-tier architectures: can't open database

本文关键字:线程 数据库 实现 三层      更新时间:2023-10-16

我想分离Db与主线程的交互。连接将在run()函数中对QRunnable进行子类并开始打开连接,连接管理器将保存QthreadPool并在需要查询时启动任务。

但问题是它无法打开数据库,如果我在一个简单的main()中使用相同的代码,它会很好地工作。所以我不知道?

任何想法都值得赞赏:)

这是我的工具:

#include <Qt/QtSql>
#include <QRunnable>
class DbConnection : public QRunnable
{
private:
    QSqlDatabase db;
    bool isConnectToDB;
public:
    DbConnection();
    QSqlDatabase getDb() const;
    void setDb(const QSqlDatabase &value);
    bool getIsConnectToDB() const;
    void setIsConnectToDB(bool value);
    void run();
    void openConnToDB();
};
QSqlDatabase DbConnection::getDb() const
{
    return db;
}
void DbConnection::setDb(const QSqlDatabase &value)
{
    db = value;
}
bool DbConnection::getIsConnectToDB() const
{
    return isConnectToDB;
}
void DbConnection::setIsConnectToDB(bool value)
{
    isConnectToDB = value;
}
void DbConnection::run()
{
    openConnToDB();
    qDebug()<< "Open a connection from thread" << QThread::currentThread();
}
void DbConnection::openConnToDB() //=> work well in a simple test program
{
    db = QSqlDatabase::addDatabase("QPSQL");
    db.setHostName("localhost");
    db.setDatabaseName("test");
    db.setUserName("postgres");
    db.setPassword("1");
    db.setPort(5432);
    isConnectToDB = db.open("postgres","1");;
    //usleep(100000);
}

DbConnection::DbConnection()
{
}

class DBConnManager
{
private:
    DBConnManager();
    static DBConnManager *m_Instance;
    QThreadPool *threadPool;
    QList<DbConnection *> connList;
    DbConnection* conn;
public:
    static DBConnManager *getInstance();
    QList<DbConnection *> getConnList() const;
    void setConnList(const QList<DbConnection *> &value);
    QSqlDatabase acquireDb();
    DbConnection *getConn() const;
    void setConn(DbConnection *value);
    void closeDb();
};

DBConnManager *DBConnManager::m_Instance = 0;
DBConnManager::DBConnManager()
{
    threadPool = QThreadPool::globalInstance();
}
DbConnection *DBConnManager::getConn() const
{
    return conn;
}
void DBConnManager::setConn(DbConnection *value)
{
    conn = value;
}
void DBConnManager::closeDb()
{
    if (conn==NULL) {
        qDebug()<< "NULL connection pointer";
        return;
    }
    conn->getDb().close();
}
QList<DbConnection *> DBConnManager::getConnList() const
{
    return connList;
}
void DBConnManager::setConnList(const QList<DbConnection *> &value)
{
    connList = value;
}
QSqlDatabase DBConnManager::acquireDb()
{
    conn = new DbConnection;
    connList.append(conn);
    threadPool->start(conn);
//    QSqlDatabase tmp;
//    return tmp;
    return conn->getDb();

}

DBConnManager *DBConnManager::getInstance()
{
    if (!m_Instance) {
        m_Instance = new DBConnManager;
    }
    return m_Instance;
}

这就是一切的开始:

QList<arcEntity> arcBL::getAll()
{
    QList <arcEntity> listResult;
    QSqlDatabase db = DBConnManager::getInstance()->acquireDb();
    bool result = m_arcDAL.getAll(&db,listResult);
    if (result==false) {
        qDebug()<<"Query get all fail";
    }
    DBConnManager::getInstance()->closeDb();
    return listResult;

}

您做了很多错误的事情。

首先,如果您希望同时连接多个数据库,则需要为它们提供唯一的名称。

来自文件:

警告:如果添加与现有连接同名的连接,则新连接将替换旧连接。如果您多次调用此函数而未指定connectionName,则默认连接将是被替换的连接。

你可以选择你想要的任何名称,但我用来保证唯一性的一个简单方法是使用从对象的内存地址派生的名称,记住存储连接名称,以便以后不再需要连接时可以删除它。

然后,您可以修改openConnToDB()函数,如下所示:

connectionName = QString("PSQL-%1").arg(reinterpret_cast<int>(this)); // store connection name
db = QSqlDatabase::addDatabase("QPSQL", connectionName);

然后你需要添加一种方法来删除连接,一旦你完成了它

void DbConnection::closeConnToDB() 
{
    if (db.isOpen())
        db.close();
    QSqlDatabase::removeDatabase(connectionName);
}

其次,您还没有完全掌握如何对多个线程进行编程。想象一下阅读以下代码:

int main(int argc, char **argv)
{
    openConnToDB();
    qDebug()<< "Open a connection from thread" << QThread::currentThread();
    return 0;
}

我相信你会看到这个程序做得不多。该程序通过创建数据库连接开始执行,然后创建一条文本消息,然后退出。

这正是您对次要线程所做的操作。您必须像对待入口点函数main()一样对待QRunnable::run()QThread::run()。一旦函数退出,就可以认为线程被破坏了。(脚注:对于QRunnableQThreadPool,这并不是所有实际目的都会发生的事情,可以想象是这样的)。

如果希望线程保持活动状态,则需要防止run()函数退出。有很多方法可以做到这一点:您可以使用forever循环、while循环,或者,如果您想像在主线程中那样处理线程中的信号和槽,您可以使用事件循环。

MyRunnable::run()
{
    QEventLoop loop;
    // ...
    loop.exec();
}

您可以通过连接到QEventLoop::quit()插槽来退出事件循环。(仅供参考:这是QCoreApplication::exec()函数内部发生的情况)


第三,正如@JKSH所指出的,不应该跨线程使用SQL类。

这意味着你应该重新设计你的类,这样你就没有DbConnection类,而是一个DBQuery类。它的接口应该允许您传递需要执行的SQL查询,然后生成结果。QSqlDatabaseQSqlQuery的实例应保持私有和内部,并且只能在run()函数或从run()调用的函数内部创建,以确保它们在工作线程中。

在线程之间移动SQL查询和结果的一种方法是使用QObject:的多重继承

class DBQuery: public QObject, public QRunnable
{
    // ...
public slots:
    void enqueueSQL(QString const &sql);

signals:
    void emitResults(QList<QVariant> const &records);
    // ...
};

官方文档(http://qt-project.org/doc/qt-5/threads-modules.html)关于Qt SQL:"连接只能在创建它的线程内使用。"您必须打开数据库连接,并在同一线程中执行所有查询。

通常,每次运行QRunnable时,QThreadPool都会使用不同的线程。这与Qt中的数据库访问不兼容。

请改用QThread,以确保只使用1个线程。以下是其文档:http://qt-project.org/doc/qt-5/qthread.html