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

时间:2014-09-06 18:02:38

标签: c++ qt threadpool qsqlquery

我想将Db交互与主线程分开。连接将子类QRunnable并开始在run()函数中打开连接,连接管理器将保留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;



}

2 个答案:

答案 0 :(得分:2)

你做错了很多事。

首先,如果您想同时连接多个数据库,则需要为它们指定唯一的名称。

来自文档:

  

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

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

然后您可以修改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;
}

我很确定你会发现该程序做得不多。程序通过创建数据库连接开始执行,然后创建一条文本消息然后退出。

这正是您对辅助线程所做的。您必须像对待入口点函数QRunnable::run()一样对待QThread::run()main()。一旦函数退出,线程就可以被认为是被破坏了。 (脚注:对于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);

    // ...
};

答案 1 :(得分:0)

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

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

使用QThread代替,以确保您只使用1个帖子。以下是其文档:http://qt-project.org/doc/qt-5/qthread.html