QSqlDatabase是数值类,一个QSqlDatabase实例代表一个数据库连接,但它的创建不是依靠自身的构造函数(QSqlDatabase()只会创建一个空的,无效的对象),而是依靠它的静态成员函数addDatabase来构建,它的返回值就是一个有效的QSqlDatabase对象(也就是一个数据库连接)。

1、通过addDatabase函数创建QSqlDatabase对象

QSqlDatabase addDatabase(const QString &type, 
		const QString &connectionName = QLatin1String(defaultConnection))
QSqlDatabase database(const QString &connectionName = QLatin1String(defaultConnection),
                bool open = true)
void removeDatabase(const QString &connectionName)

QSqlQuery(QSqlDatabase db)
QSqlQuery(const QString &query = QString(), QSqlDatabase db = QSqlDatabase())

其中,(1)参数type指的是数据库驱动的类型,它提供对数据库的访问,目前Qt支持的类型大概有以下几种。它们都是从QSqlDriver类派生而来(实际访问数据库的也是QSqlDriver对象),我们可以自定义数据库驱动。

QDB2		IBM DB2 (version 7.1 and above)
QIBASE		Borland InterBase
QMYSQL		MySQL
QOCI		Oracle Call Interface Driver
QODBC		Open Database Connectivity (ODBC) - Microsoft SQL Server and other ODBC-compliant databases
QPSQL		PostgreSQL (versions 7.3 and above)
QSQLITE2	SQLite version 2
QSQLITE		SQLite version 3

(2)、参数connectionName指的是连接名称,它代表的是这个数据库连接本身,而不是我们将要连接的数据库的名字。这个参数非常重要,要知道,当一个连接通过addDatabase成员函数创建以后,它会一直存在,直到它所在的应用程序退出,或者通过removeDatabase函数显式删除为止,不然database函数就可以通过该连接名称来获得这个连接。

我们不须要将QSqlDatabase对象的副本作为类的成员保留,因为通过静态成员函数database即可获得该连接(将它的参数connectionName指定为相同的连接名称)。如果将QSqlDatabase对象作为成员变量,则必须在QCoreApplication销毁之前先期销毁,否则会导致未定义的行为。

如果多次调用addDatabase函数,而参数connectionName指定的值相同,那么新的数据库连接将替换掉旧的连接。参数connectionName有个默认值defaultConnection,如果不显式指定,则使用这个默认的连接名称。

通过addDatabase函数创建QSqlDatabase对象之后,就可以根据情况选择性地调用该对象的成员函数setDatabaseName,setUserName,setPassword,setHostName,setPort和setConnectOptions等等,然后调用它的成员函数open来激活与数据库的连接。连接成功(即open成功)之后,就可以使用QSqlQuery类来访问数据库了。QSqlQuery类的构造函数需要指定QSqlDatabase对象,如果不显式指定,则使用参数connectionName为默认值的那个对象所代表的连接(该连接不是默认存在的,也是须要手动创建)。代码片段如下所示:

QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "test");
db.setDatabaseName("test.db3");
db.setConnectOptions("QSQLITE_OPEN_READONLY");
if (db.open())
{
	QSqlQuery query("SELECT * FROM testTable", db);
	......
}

//在这里,database获得的对象与上一步addDatabase函数创建的相同,
//并且如果上一步尚未激活连接,这个函数还会尝试激活连接(如果参数open的值为true)
QSqlDatabase db = QSqlDatabase::database("test"); 


//在不同的函数内,仍然可以使用上一步所创建的QSqlDatabase对象
QSqlQuery query("SELECT * FROM testTable", QSqlDatabase::database("test"));


//删除QSqlDatabase对象
QSqlDatabase::removeDatabase("test");

2、如何实现QSqlDatabase类

本文只说明QSqlDatabase类的数据结构及一些比较重要的函数。每个QSqlDatabase对象只包含一个数据成员,那就是指向它的私有对象(QSqlDatabasePrivate类)的指针,它的数据都保存在私有对象中。这样的设计就类似于Java中的对象,对象不拥有数据,只拥有对数据的引用。

(1)、数据结构

简单地说,就是使用宏Q_GLOBAL_STATIC定义了一个局限于当前编译单元的全局变量dbDict,它的数据类型为QConnectionDict(实际由QGlobalStatic类封装,间接访问),一个哈希链表QHash<QString, QSqlDatabase>的派生类(键为连接名称,值为QSqlDatabase对象)。也就是说,我们所创建的QSqlDatabase对象会一直存在,除非程序退出,dbDict销毁,或者调用removeDatabase函数手动删除。源代码如下:

//Qt5.13.0\5.13.0\Src\qtbase\src\sql\kernel\qsqldatabase.cpp
class QConnectionDict: public QHash<QString, QSqlDatabase>
{
public:
    inline bool contains_ts(const QString &key)
    {
        QReadLocker locker(&lock);
        return contains(key);
    }
    inline QStringList keys_ts() const
    {
        QReadLocker locker(&lock);
        return keys();
    }

    mutable QReadWriteLock lock;
};
Q_GLOBAL_STATIC(QConnectionDict, dbDict)

通过Q_GLOBAL_STATIC宏定义的静态全局对象,可以像指针一样使用,并且能保证只初始化一次。所定义对象的数据类型为QGlobalStatic类。该类的成员函数exists,如果返回真则表示该对象已经初始化完成,而且之后该函数会一直返回真,直到重新启动程序或重新加载动态库。成员函数isDestroyed返回真则表示该对象已经析构完成。Q_GLOBAL_STATIC宏展开之后的源代码如下所示:

namespace QtGlobalStatic {
enum GuardValues {
    Destroyed = -2,		//已销毁
    Initialized = -1,	//已初始化
    Uninitialized = 0,	//未初始化
    Initializing = 1	//初始化中
};
}

template <typename T, T *(&innerFunction)(), QBasicAtomicInt &guard>
struct QGlobalStatic
{
    typedef T Type;

    bool isDestroyed() const { return guard.load() <= QtGlobalStatic::Destroyed; }
    bool exists() const { return guard.load() == QtGlobalStatic::Initialized; }
    operator Type *() { if (isDestroyed()) return 0; return innerFunction(); }	//类型转换运算符
    Type *operator()() { if (isDestroyed()) return 0; return innerFunction(); }	//函数调用运算符
    Type *operator->()	//成员访问运算符
    {
        Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC", "The global static was used after being destroyed");
        return innerFunction();
    }
    Type &operator*()	//解引用运算符
    {
        Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC", "The global static was used after being destroyed");
        return *innerFunction();
    }
};

namespace { namespace Q_QGS_dbDict {
    typedef QConnectionDict Type;

    QBasicAtomicInt guard = { QtGlobalStatic::Uninitialized }; //整数原子操作

    inline Type *innerFunction()
    {
        struct HolderBase
        {
            ~HolderBase() noexcept
            {
                if (guard.load() == QtGlobalStatic::Initialized) //加载
                    guard.store(QtGlobalStatic::Destroyed);
            }
        };

        static struct Holder : public HolderBase
        {
            Type value;

            Holder() noexcept(noexcept(Type ())) : value () //使用默认构造函数初始化Type类型的对象
            {
                guard.store(QtGlobalStatic::Initialized); //存储
            }
        } holder;

        return &holder.value;
    }
} }

static QGlobalStatic<QConnectionDict, Q_QGS_dbDict::innerFunction, Q_QGS_dbDict::guard> dbDict;

Q_GLOBAL_STATIC宏是通过另一个宏Q_GLOBAL_STATIC_WITH_ARGS来实现的,其最后一个宏参数里的括号是必须的(括号内为空表示使用TYPE的默认构造函数),如下所示:

#define Q_GLOBAL_STATIC(TYPE, NAME) Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ())

(2)、重要函数的实现

静态成员函数addDatabase将连接名称和QSqlDatabase对象的键值对插入哈希链表,并返回其中的QSqlDatabase对象,如下所示:

QSqlDatabase QSqlDatabase::addDatabase(const QString &type, const QString &connectionName)
{
    QSqlDatabase db(type);  //①
    QSqlDatabasePrivate::addDatabase(db, connectionName);  //②
    return db;	//③
}

//①构建QSqlDatabase对象
QSqlDatabase::QSqlDatabase(const QString &type)
{
	//构建QSqlDatabasePrivate对象
	//按照Qt的风格,私有数据(其他访问域一般不会存在数据成员)都保存在它的私有类里
    d = new QSqlDatabasePrivate(this); 
    d->init(type); //初始化QSqlDatabasePrivate对象
}

//②将连接名称和QSqlDatabase对象添加到哈希链表dbDict中
void QSqlDatabasePrivate::addDatabase(const QSqlDatabase &db, const QString &name)
{
    QConnectionDict *dict = dbDict();
    Q_ASSERT(dict);
    QWriteLocker locker(&dict->lock);

    if (dict->contains(name)) {  //该连接名称已经存在,则使旧的无效
        //哈希链表的take函数删除name对应的项并返回对应的QSqlDatabase对象
        invalidateDb(dict->take(name), name); 
        qWarning("QSqlDatabasePrivate::addDatabase: duplicate connection name '%s', old "
                 "connection removed.", name.toLocal8Bit().data());
    }
    dict->insert(name, db);  //插入哈希链表
    db.d->connName = name;
}

//③拷贝构造函数
QSqlDatabase::QSqlDatabase(const QSqlDatabase &other)
{
    d = other.d; //d是私有数据对象的指针
    d->ref.ref(); //递增引用计数
}

//③拷贝赋值运算符
QSqlDatabase &QSqlDatabase::operator=(const QSqlDatabase &other)
{
    qAtomicAssign(d, other.d); //④
    return *this;
}

//Qt5.13.0/5.13.0/Src/qtbase/src/corelib/thread/qatomic.h
//④针对指针的赋值操作
template <typename T>
inline void qAtomicAssign(T *&d, T *x) //*&放在一起表示参数d是一个指针的引用
{
    if (d == x) //地址相同
        return;
    x->ref.ref(); //递增引用计数
    if (!d->ref.deref()) //递减原有所指向的对象的引用计数(为0则删除)
        delete d;
    d = x; //赋值
}

静态成员函数database从哈希链表中查找连接名称相同的键值对,若找到则返回该键值对中QSqlDatabase对象的一个副本(特别注意,同一连接不能跨线程使用,否则database函数会返回一个空的、无效的对象),如下所示:

QSqlDatabase QSqlDatabase::database(const QString& connectionName, bool open)
{
    return QSqlDatabasePrivate::database(connectionName, open);
}

QSqlDatabase QSqlDatabasePrivate::database(const QString& name, bool open)
{
    const QConnectionDict *dict = dbDict();
    Q_ASSERT(dict);

    dict->lock.lockForRead();
    QSqlDatabase db = dict->value(name); //根据连接名称从哈希链表中获得QSqlDatabase对象
    dict->lock.unlock();
    if (!db.isValid()) //①无效的QSqlDatabase对象
        return db;
    if (db.driver()->thread() != QThread::currentThread()) { //对象所在的线程与当前线程不一致
        qWarning("QSqlDatabasePrivate::database: requested database does not belong to the calling thread.");
        return QSqlDatabase(); //返回一个空的、无效的对象
    }

    if (open && !db.isOpen()) { //当参数open为true且对象db尚未打开
        if (!db.open()) //尝试打开
            qWarning() << "QSqlDatabasePrivate::database: unable to open database:" << db.lastError().text();
    }
    return db;
}

//①QSqlDatabase对象具有有效的驱动程序则返回真
bool QSqlDatabase::isValid() const
{
    return d->driver && d->driver != d->shared_null()->driver;
}

静态成员函数removeDatabase从哈希链表中删除连接名称对应的键值对,如下所示:

void QSqlDatabase::removeDatabase(const QString& connectionName)
{
    QSqlDatabasePrivate::removeDatabase(connectionName); //①
}

//①从哈希链表中删除连接名称name所在的项
void QSqlDatabasePrivate::removeDatabase(const QString &name)
{
    QConnectionDict *dict = dbDict();
    Q_ASSERT(dict);
    QWriteLocker locker(&dict->lock);

    if (!dict->contains(name)) //哈希链表中没有name所在的项则直接退出
        return;

    invalidateDb(dict->take(name), name); //②哈希链表中的take函数不仅会删除它所在的项,还会返回该项中的值
}

//②主要用于销毁它的成员对象QSqlDriver
void QSqlDatabasePrivate::invalidateDb(const QSqlDatabase &db, const QString &name, bool doWarn)
{
    //引用计数ref不等于1,表明还有地方在引用该QSqlDatabase对象
    //出现这种情况,多半是因为QSqlDatabase的定义不恰当
    if (db.d->ref.load() != 1 && doWarn) { 
        qWarning("QSqlDatabasePrivate::removeDatabase: connection '%s' is still in use, "
                 "all queries will cease to work.", name.toLocal8Bit().constData()); 
        db.d->disable(); //销毁QSqlDriver对象
        db.d->connName.clear(); //清除保存连接名称的空间
    }
}

//每次定义的QSqlDatabase变量,都应该能够让它自动销毁,不须将它保存下来
QSqlDatabase::~QSqlDatabase()
{
    if (!d->ref.deref()) { //判断引用计数是否为0,如果不为0,则表示该QSqlDatabase对象会一直存在
        close();
        delete d; //删除私有数据对象
    }
}

(3)、QSqlQuery与QSqlDatabase对象的关联

QSqlQuery类通过其构造函数中的参数db建立与QSqlDatabase类的关联,如下所示:

//Qt5.13.0\5.13.0\Src\qtbase\src\sql\kernel\qsqlquery.cpp(.h)
//QSqlDatabase类的默认构造函数会创建一个空的、无效的对象
explicit QSqlQuery(const QString& query = QString(), QSqlDatabase db = QSqlDatabase());
explicit QSqlQuery(QSqlDatabase db);

QSqlQuery::QSqlQuery(const QString& query, QSqlDatabase db)
{
    d = QSqlQueryPrivate::shared_null();
    qInit(this, query, db); //①
}

QSqlQuery::QSqlQuery(QSqlDatabase db)
{
    d = QSqlQueryPrivate::shared_null();
    qInit(this, QString(), db); //①
}

//①根据QSqlDatabase对象来构建qlQuery对象
static void qInit(QSqlQuery *q, const QString& query, QSqlDatabase db)
{
    QSqlDatabase database = db;
    if (!database.isValid()) //无效的QSqlDatabase对象
        //②使用默认的连接名称defaultConnection
        database = QSqlDatabase::database(QLatin1String(QSqlDatabase::defaultConnection), false); 
    if (database.isValid()) {
        *q = QSqlQuery(database.driver()->createResult()); //构建QSqlQuery对象
    }
    if (!query.isEmpty())
        q->exec(query); //执行sql语句
}

//②默认的连接名称
const char *QSqlDatabase::defaultConnection = const_cast<char *>("qt_sql_default_connection");