基本
QgsPostgresConn是Qgis对Postgres数据库连接的封装类,位于头文件qgspostgresconn.h
中。
connectDb()
static QgsPostgresConn *connectDb( const QString &connInfo, bool readOnly, bool shared = true, bool transaction = false );
首先查看静态函数connectDb()
,该接口返回一个数据库连接。
QgsPostgresConn *QgsPostgresConn::connectDb( const QString &conninfo, bool readonly, bool shared, bool transaction )
{
QMap &connections =
readonly ? QgsPostgresConn::sConnectionsRO : QgsPostgresConn::sConnectionsRW;
函数开始,根据readonly
标志,获取到其中一个Map引用,两个Map都是QgsPostgresConn的静态成员变量,其中sConnectionsRO存放只读数据库连接,sConnectionsRW存放可读写的数据库连接。
根据Map的key、value类型可知,Map的values存放了数据库连接的指针,指向实际的数据库连接对象。
if ( QApplication::instance()->thread() != QThread::currentThread() )
{
shared = false;
}
if ( conn->mRef == 0 )
{
delete conn;
return nullptr;
}
接下来判断调用该函数的线程是不是应用主线程,如果不是,则shared
值被设为false,代表不可共享数据库连接。由此可见,数据库连接的共享仅限于主线程使用。
if ( shared )
{
// sharing connection between threads is not safe
// See https://github.com/qgis/QGIS/issues/21205
Q_ASSERT( QApplication::instance()->thread() == QThread::currentThread() );
if ( connections.contains( conninfo ) )
{
QgsDebugMsgLevel( QStringLiteral( "Using cached connection for %1" ).arg( conninfo ), 2 );
connections[conninfo]->mRef++;
return connections[conninfo];
}
}
如果允许共享连接,则根据conninfo
连接信息从Map中查找是否有相同的连接,如果连接信息一致,则将连接的引用加1,并返回该连接。此时一个数据库连接对象可能被多个调用者使用。
QgsPostgresConn *conn = new QgsPostgresConn( conninfo, readonly, shared, transaction );
如果连接数据库为不可共享,或从缓存的Map中没有找到相同连接信息的连接对象,则新建QgsPostgresConn
对象。
if ( shared )
{
connections.insert( conninfo, conn );
}
对象新建成功后,如果可共享,则将该对象插入到Map缓存中。
QgsPostgresConn()
public:
QgsPostgresConn( const QString &conninfo, bool readOnly, bool shared, bool transaction );
QgsPostgresConn::QgsPostgresConn( const QString &conninfo, bool readOnly, bool shared, bool transaction )
: mRef( 1 )
, mOpenCursors( 0 )
, mConnInfo( conninfo )
, mGeosAvailable( false )
, mProjAvailable( false )
, mTopologyAvailable( false )
, mGotPostgisVersion( false )
, mPostgresqlVersion( 0 )
, mPostgisVersionMajor( 0 )
, mPostgisVersionMinor( 0 )
, mPointcloudAvailable( false )
, mRasterAvailable( false )
, mUseWkbHex( false )
, mReadOnly( readOnly )
, mSwapEndian( false )
, mNextCursorId( 0 )
, mShared( shared )
, mTransaction( transaction )
在构造函数中,对成员变量进行初始化,其中mRef
初始化为1,代表该对象有一个引用,即它自己。
mConn = PQconnectdb( expandedConnectionInfo.toUtf8() );
之后通过libpq库的接口建立一个数据库连接。
if ( PQstatus() != CONNECTION_OK )
{
QString username = uri.username();
QString password = uri.password();
QgsCredentials::instance()->lock();
int i = 0;
while ( PQstatus() != CONNECTION_OK && i < 5 )
{
++i;
bool ok = QgsCredentials::instance()->get( conninfo, username, password, PQerrorMessage() );
if ( !ok )
{
break;
}
PQfinish();
if ( !username.isEmpty() )
uri.setUsername( username );
if ( !password.isEmpty() )
uri.setPassword( password );
QgsDebugMsgLevel( "Connecting to " + uri.connectionInfo( false ), 2 );
QString connectString = uri.connectionInfo();
addDefaultTimeoutAndClientEncoding( connectString );
mConn = PQconnectdb( connectString.toUtf8() );
}
if ( PQstatus() == CONNECTION_OK )
QgsCredentials::instance()->put( conninfo, username, password );
QgsCredentials::instance()->unlock();
}
根据while循环可知,如果连接失败,则尝试进行多次重连操作。
if ( PQstatus() != CONNECTION_OK )
{
QString errorMsg = PQerrorMessage();
PQfinish();
QgsMessageLog::logMessage( tr( "Connection to database failed" ) + '\n' + errorMsg, tr( "PostGIS" ) );
mRef = 0;
return;
}
多次重连后,如果仍然连接失败,则将引用mRef
置0,即无人引用,代表当前连接对象无效。外部调用者在得到新建的对象后可通过该值是否为0判断连接状态。如果为0,外部调用者应能知晓该连接对象无效,并delete该对象。上面的connectDb()
接口内就是这么做的。
~QgsPostgresConn()
~QgsPostgresConn() override
QgsPostgresConn::~QgsPostgresConn()
{
Q_ASSERT( mRef == 0 );
if ( mConn )
::PQfinish( mConn );
mConn = nullptr;
}
析构函数内释放数据库连接。
unref()
void QgsPostgresConn::unref()
{
QMutexLocker locker( &mLock );
if ( --mRef > 0 )
return;
if ( mShared )
{
QMap &connections = mReadOnly ? sConnectionsRO : sConnectionsRW;
QString key = connections.key( this, QString() );
Q_ASSERT( !key.isNull() );
connections.remove( key );
}
// to avoid destroying locked mutex
locker.unlock();
delete this;
}
unref()接口对该对象进行减引用。当减少到0时,判断该连接对象是否共享,如果是,因为该对象引用为0了,所有从Map缓存中移除。并在后面删除该对象。
begin()、commit()、rollback()
bool QgsPostgresConn::begin()
{
QMutexLocker locker( &mLock );
if ( mTransaction )
{
return PQexecNR( QStringLiteral( "SAVEPOINT transaction_savepoint" ) );
}
else
{
return PQexecNR( QStringLiteral( "BEGIN" ) );
}
}
三个函数实现逻辑一样。如果该连接对象是mTransaction,则基于SAVEPOINT操作事务,否则基于事务。在查找mTransaction
可能被改写的地方时,发现其只在构造函数中被初始化赋值,也就是说连接对象在被创建的时候就决定了是否属于事务对象。
openCursor()、closeCursor()
bool openCursor( const QString &cursorName, const QString &declare );
bool closeCursor( const QString &cursorName );
bool QgsPostgresConn::openCursor( const QString &cursorName, const QString &sql )
{
QMutexLocker locker( &mLock ); // to protect access to mOpenCursors
QString preStr;
if ( mOpenCursors++ == 0 && !mTransaction )
{
QgsDebugMsgLevel( QStringLiteral( "Starting read-only transaction: %1" ).arg( mPostgresqlVersion ), 4 );
if ( mPostgresqlVersion >= 80000 )
preStr = QStringLiteral( "BEGIN READ ONLY;" );
else
preStr = QStringLiteral( "BEGIN;" );
}
QgsDebugMsgLevel( QStringLiteral( "Binary cursor %1 for %2" ).arg( cursorName, sql ), 3 );
return PQexecNR( QStringLiteral( "%1DECLARE %2 BINARY CURSOR%3 FOR %4" ).
arg( preStr, cursorName, !mTransaction ? QString() : QStringLiteral( " WITH HOLD" ), sql ) );
}
在控制连接对象打开游标时,通过成员变量mOpenCursors
判断打开的游标数量。根据 if 条件,如果当前没有游标打开且为非事务连接时,则预先执行BEGIN;
语句才可开启游标。
在closeCursos()
接口中,则相应的关闭游标并减少mOpenCursors的值。
事务连接
QgsPostgresConn通过成员变量mTransaction
决定该连接是否为事务连接。而该成员变量只在构造函数的时候被赋值,也就是说在创建QgsPostgresConn对象时,就要决定该对象是否为事务连接,无法通过接口修改该属性。
在上面对QgsPostgresConn的事务接口begin()
的分析中可知,事务连接不会执行BEGIN;
语句,而在查看其他接口时也没有找到能执行BEGIN;
语句的地方。由此分析事务连接的BEGIN;
语句应由外部调用者执行。同时在查看QgsPostgresTransaction
类的源码时验证了这一猜想。
QgsPostgresTransaction类持有成员变量QgsPostgresConn *mConn = nullptr;
bool QgsPostgresTransaction::beginTransaction( QString &error, int statementTimeout )
{
mConn = QgsPostgresConn::connectDb( mConnString, false /*readonly*/, false /*shared*/, true /*transaction*/ );
return executeSql( QStringLiteral( "SET statement_timeout = %1" ).arg( statementTimeout * 1000 ), error )
&& executeSql( QStringLiteral( "BEGIN TRANSACTION" ), error );
}
当开启事务时,调用接口QgsPostgresTransaction::beginTransaction()
,它首先通过QgsPostgresConn::connectDb()
创建连接对象,并传入transaction为true,之后执行sql语句BEGIN TRANSACTION;
开始事务。