Qt源码版本:5.13.0
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");