美文网首页
QgsPostgresConn类分析

QgsPostgresConn类分析

作者: NullUser | 来源:发表于2022-11-01 18:50 被阅读0次

    基本

    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<QString, QgsPostgresConn *> &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<QString, QgsPostgresConn *> &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类分析

          本文链接:https://www.haomeiwen.com/subject/tzijtdtx.html