Мир InterBase


Создание СОМ-объектов для работы с базой данных


Самым эффективным применением технологии OLEDB является создание и использование специализированных компонентов для работы с базой данных. Этот подход изначально начал применяться для серверов приложений (application servers). Однако ничто не мешает реализовывать re же самые принципы при создании обычного приложения базы данных. В этом случае, помимо обычных преимуществ компонентной технологии, исчезают типичные проблемы, связанные с передачей и разделением ресурсов сервера баз данных между несколькими модулями клиентского приложения. Для малосвязанных модулей достаточно разделять одно подключение, в случае использования сервисных компонентов может потребоваться совместная работа в контексте одной транзакции. Поскольку эти ресурсы представлены в виде СОМ-объектов, то для корректной работы требуется только правильно управлять их счетчиком ссылок. Однако для опытного программиста, использующего СОМ-технологии в реальной работе, это не проблема.

Исходя из накопленного опыта создания таких компонентов и их использования из программ, написанных на C++, VBA и VBScript, можно порекомендовать следующую структуру СОМ-объектов:

  • Дуальный (dual) интерфейс автоматизации, через который выполняется основное взаимодействие с объектом. Этот же интерфейс предоставляет свойство Connection для того, чтобы устанавливать и получать подключение, используя ADODB-компоненты. Как уже было сказано ранее, ADODB.Connection одновременно является и источником данных и сессией.
  • Обычный интерфейс (наследующий ILJnknown) для инициализации компонента посредством указателя на ITJnknown сессии.
  • Внутренняя работа с базой данных осуществляется через низкоуровневые интерфейсы OLEDB посредством классов C++. Таким образом, компонент изолируется от ADODB и обеспечивает более производительное функционирование собственных алгоритмов.
  • Принцип наиболее эффективной работы также не очень сложный - компонент должен свести к минимуму число создающихся и подготавливающихся команд. Так же имеет смысл загрузить в память содержимое таблиц, небольших по размеру и хранящих фиксированные данные. В основном под эту категорию попадают таблицы справочников. Тогда можно исключить из запросов все возможные обращения для выборки этой информации, что в конечном итоге, уменьшает нагрузку на сервер базы данных.


    В качестве поддержки совместного использования ADODB и OLEDB в одном проекте инструментальная библиотека представляет две утилиты:

  • construct_adodb_connection - создание ADODB подключения на базе существующего источника данных и сессии;


  • get_adodb_session - получение OLEDB-сессии, обслуживаемой ADODB-подключением.


  • Несмотря на открывающиеся в связи с использованием IBProvider перспективы, связанные с дроблением ваших приложений для InterBase на модули, главное не переусердствовать. Не стоит делать компоненты, предназначенные для коллекций, с собственным механизмом чтения и записи. Помните, что любой использующий команды объект делает как минимум 4-5 обращений к серверу:

  • Создание


  • Подготовка.




  • Выполнение.


  • Выборка результата.


  • Разрушение


  • Поэтому для групповых операций больше всего приемлем классический подход, когда реализуется групповая загрузка и запись, отделенная от самих данных.

    Еще одной хорошей идеей является создание в приложении обычных классов с реализацией той логики, которая скорее всего не потребуется вне границ вашего приложения. Согласитесь, что написание класса и СОМ-компонента требует несравнимых усилий. Кроме того, не забывайте, что создание СОМ-объектов производиться через СОМ-инфраструктуру, поэтому накладные расходы распространяются и на время выполнения приложения. Поэтому обычные классы все равно остаются основным "тактическим средством" больших приложений, разработанных в объектно-ориентированном стиле

    Ниже приводиться пример СОМ-объекта, который подключается к сессии и используется для того, чтобы получить значение генератора (см. главу "Таблицы Первичные ключи и генераторы" (ч. 1)). Здесь мы ограничимся лишь IDL-описанием двух интерфейсов и реализацией их методов. Помимо этого, используются возможности инструментальной библиотеки из дистрибутива IBProvider I.6.2. В реальном случае этот код, конечно же, лучше оформить в виде обычного класса. Тогда можно исключить одновременную поддержку ADODB и OLEDB. Кроме того, в данном примере не оптимизирована работа метода GenID для случая повторного использования подготовленной команды, для случая многократного вызова метода с идентичными аргументами.



    IDL-описание интерфейсов:

    ////////////////////////////////////////////////////////////

    //interface IDBSessionObject

    // уcтановка/получение рабочей OLEDB-сессии объекта

    [

    object,

    uuid(98E5AB40-333E-llD6-AC8F-OOAOC907DB93),

    pointer_default(unique)

    ]

    interface IDBSessionObject:IUnknown

    {

    HRESULT SetDBSession([in] lUnknown* pSession);

    HRESULT GetDBSession([out]lUnknown** ppSession);

    };//interface IDBSessionObject

    /////////////////////////////////////////////

    //interface IDBGenID

    // интерфейс получения значения генератора

    [

    object,

    uuid(98E5AB41-333E-llD6-AC8F-OOAOC907DB93),

    dual,

    oleautomation,

    pointer_default(unique),

    nonextensible

    ]

    interface IDBGenID:IDispatch

    {

    [propput]

    HRESULT Connection([in]IDispatch* pConnection);

    [propget]

    HRESULT Connection([out,retval]IDispatch** ppConnection);

    HRESULT Convert([in]BSTR GenName,

    [in]LONG Count,

    [out,retval]LONG* pResult);

    };//interface IDBGenID

    Реализация методов установки сессии:

    //m_spADODBConnection - член класса,

    // содержащий указатель на ADODB-подключение

    //m_spSession - член класса,

    // содержащий указатель на используемую OLEDB-сессию

    //m_Cmd - команда (t_db_command) получения значения генератора

    //IDBSessionObject interface ------------------------------

    HRESULT _stdcall TDBGenID::SetDBSession(lUnknown* pSession)

    {

    ::SetErrorlnfo(0,NULL);

    HRESULT hr=S_OK;

    _OLE_TRY_

    {

    //освобождаем ADODB connection

    m_spADODBConnection.Release();

    m_spSession=pSession;

    //инициализируем объекты взаимодействия с базой данных

    m_Cmd destroy();

    }

    _OLE_DIS P_CATCHES_

    return hr;

    }//SetDBSession

    HRESULT _stdcall TDBGenID::GetDBSession(lUnknown** ppSession)

    {

    ::SetErrorlnfo(0,NULL);

    return m_spSession.CopyTo(ppSession);

    }//GetDBSession

    //IOC2_ObjectLoader interface -----------

    HRESULT _stdcall TDBGenID::put_Connection

    (IDispatch* pConnection)

    {

    ::SetErrorInf0(0,NULL);

    HRESULT hr=NOERROR;

    _OLE_TRY_

    {

    IDispatchPtr spConnection(pConnection) ; //блокируем в памяти

    //освобождаем текущие подключения



    SetDBSession(NULL);

    if(pConnection) {

    IUnknownPtr spDBSession;

    get_adodb_session(pConnection,spDBSession); //throw

    if(SUCCEEDED(hr=SetDBSession(spDBSession)))

    m_spADODBConnection=pConnection;

    }//pConnection!=NULL

    }

    _OLE_DISP_CATCHES_

    return hr;

    }//put_Connection

    HRESULT _stdcall TDBGenID::get_Connection

    (IDispatch** ppConnection)

    {

    ::SetErrorlnfо(0,NULL);

    if(ppConnection==NULL)

    return E_POINTER;

    *ppConnection=NULL;

    HRESULT hr=S_OK;

    _OLE_TRY_

    {

    if(!m_spADODBConnection && (bool)m_spSession)

    {

    IGetDataSourcePtr spGetDataSource(m_spSession);

    if(i spGetDataSource)

    t_ole_error::throw_error

    ("query IGetDataSource interface",spGetDataSource.m_hr);

    IUnknownPtr spDataSource;

    if(FAILED(hr=get_data_source(spGetDataSource,spDataSource)))

    t_ole_error::throw_error("Получение источника данных",hr);

    IDBPropertiesPtr spDBProperties(spDataSource);

    if(!spDBProperties)

    t_ole_error::throw_error

    ("query IDBProperties interface",spDBProperties.m_hr);

    construct_adodb_connection(spDBProperties,m_spSession,

    m_spADODBConnection);//throw

    }//if - создание ADODB-объекта

    hr=m_spADODBConnection.CopyTo(ppConnection);

    }

    _OLE_DISP_CATCHES_

    return hr;

    }//get_Connection

    Реализация метода получения значения генератора:

    HRESULT _stdcall TDBGenID::GenID(BSTR GenName,LONG Count,

    LONG* pResult)

    {

    ::SetErrorlnfо(0,NULL);

    if(pResult==NULL)

    return E_POINTER;

    HRESULT hr=S_OK;

    _OLE_TRY_

    {

    if(!m_spSession)

    throw runtime_error("Объект неинициализирован");

    if(!m_Cmd.is_created())

    _THROW_OLEDB_FAILED(m_Cmd,create(m_spSession));

    structure::str_formatter stmt

    ("select gen_id(%l,%2) from rdb$database");

    t_db_row row(1);

    _THROW_OLEDB_FAILED (m_Cmd, prepare ( stmt«GenName«Count, &row) )

    _THROW_OLEDB_FAILED(m_Cmd,execute(NULL));

    if(m_Cmd.fetch(row)==S_OK)

    *pResult=row[0].as_integer;

    else

    {

    //проверим причину сбоя получения данных

    _THROW_OLEDB_FAILED(m_Cmd,m_last_result)

    throw runtime_error("Получено пустое множество");

    }

    }

    _OLE_DIS P_CATCHES_

    return hr;

    }//GenID


    Содержание раздела