QgsPostgresConn类分析

基本

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;开始事务。

你可能感兴趣的:(QgsPostgresConn类分析)