Создание СОМ-объектов для работы с базой данных
Создание СОМ-объектов для работы с базой данных
Самым эффективным применением технологии 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