Основы проектирования приложений баз данных
Соотношение стандарта ODBC и стандарта интерфейса уровня вызовов (CLI)
Как уже отмечалось выше, открытый интерфейс доступа к базам данных фирмы Microsoft основан на следующих стандартах:
В настоящее время фирма Microsoft поддерживает версию 3.x ODBC API. Приложения, написанные на основе спецификации X/Open и ISO CLI, будут правильно работать с ODBC-драйверами версии 3.x или драйверами "согласованного стандарта" в том случае, если они компилируются с заголовочными файлами ODBC версии 3.x и линкуются с ODBC 3.x библиотеками, а доступ к ODBC-драйверу получают через менеджер драйверов ODBC 3.x. Аналогично, что и сами драйверы 3.x, написанные на основе спецификации X/Open и ISO CLI, будут правильно работать с приложениями при соблюдении этих же условий.
Драйвер ODBC 3.x всегда поддерживает все возможности, используемые приложением "согласованного стандарта", а приложение ODBC 3, которое использует только возможности, предоставляемые ISO CLI, и обязательные средства, описываемые X/Open CLI, всегда будет работать с драйвером "согласованного стандарта".
В дополнение к интерфейсу, специфицированному в стандартах ISO/IEC и X/Open CLI, ODBC реализует следующие возможности:
Aey niaeaniaaiiinoe ni noaiaa?oii ISO CLI e X/Open CLI a caaieiai?iuo oaeeao niaa??aony inaaaiieiu cia?aiee, eniieucoaiuo a ODBC API. Oae, a inaaaiieiao "MAX" ?anoe?aii ai "MAXIMUM", "LEN" ai "LENGTH", "MULT" ai "MULTIPLE", "OJ" ai "OUTER_JOIN", a "TXN" ai "TRANSACTION". Iai?eia?:
| SQL_MAX_CATALOG_NAME_LEN | SQL_MAXIMUM_CATALOG_NAME_LENGTH |
| SQL_MAX_COLUMN_NAME_LEN | SQL_MAXIMUM_COLUMN_NAME_LENGTH |
| SQL_MAX_COLUMNS_IN_GROUP_BY | SQL_MAXIMUM_COLUMNS_IN_GROUP_BY |
| SQL_MAX_COLUMNS_IN_ORDER_BY | SQL_MAXIMUM_COLUMNS_IN_ORDER_BY |
| SQL_MAX_COLUMNS_IN_SELECT | SQL_MAXIMUM_COLUMNS_IN_SELECT |
| SQL_MAX_COLUMNS_IN_TABLE | SQL_MAXIMUM_COLUMNS_IN_TABLE |
| SQL_MAX_CONCURRENT_ACTIVITIES | SQL_MAXIMUM_CONCURRENT_ACTIVITIES |
| SQL_MAX_CURSOR_NAME_LEN | SQL_MAXIMUM_CURSOR_NAME_LENGTH |
| SQL_MAX_DRIVER_CONNECTIONS | SQL_MAXIMUM_DRIVER_CONNECTIONS |
| SQL_MAX_IDENTIFIER_LEN | SQL_MAXIMUM_IDENTIFIER_LENGTH |
| SQL_MAX_SCHEMA_NAME_LEN | SQL_MAXIMUM_SCHEMA_NAME_LENGTH |
| SQL_MAX_STATEMENT_LEN | SQL_MAXIMUM_STATEMENT_LENGTH |
| SQL_MAX_TABLE_NAME_LEN | SQL_MAXIMUM_TABLE_NAME_LENGTH |
| SQL_MAX_TABLES_IN_SELECT | SQL_MAXIMUM_TABLES_IN_SELECT |
| SQL_MAX_USER_NAME_LEN | SQL_MAXIMUM_USER_NAME_LENGTH |
| SQL_MULT_RESULT_SETS | SQL_MULTIPLE_RESULT_SETS |
| SQL_OJ_CAPABILITIES | SQL_OUTER_JOIN_CAPABILITIES |
| SQL_TXN_CAPABLE | SQL_TRANSACTION_CAPABLE |
| SQL_TXN_ISOLATION_OPTION | SQL_TRANSACTION_ISOLATION_OPTION |
Aey niaeaniaaiiinoe ni noaiaa?oii ISO CLI e X/Open CLI a caaieiai?iuo oaeeao niaa??aony inaaaiieiu cia?aiee, eniieucoaiuo a ODBC API. Oae, a inaaaiieiao "MAX" ?anoe?aii ai "MAXIMUM", "LEN" ai "LENGTH", "MULT" ai "MULTIPLE", "OJ" ai "OUTER_JOIN", a "TXN" ai "TRANSACTION". Iai?eia?:
| SQL_MAX_CATALOG_NAME_LEN | SQL_MAXIMUM_CATALOG_NAME_LENGTH |
| SQL_MAX_COLUMN_NAME_LEN | SQL_MAXIMUM_COLUMN_NAME_LENGTH |
| SQL_MAX_COLUMNS_IN_GROUP_BY | SQL_MAXIMUM_COLUMNS_IN_GROUP_BY |
| SQL_MAX_COLUMNS_IN_ORDER_BY | SQL_MAXIMUM_COLUMNS_IN_ORDER_BY |
| SQL_MAX_COLUMNS_IN_SELECT | SQL_MAXIMUM_COLUMNS_IN_SELECT |
| SQL_MAX_COLUMNS_IN_TABLE | SQL_MAXIMUM_COLUMNS_IN_TABLE |
| SQL_MAX_CONCURRENT_ACTIVITIES | SQL_MAXIMUM_CONCURRENT_ACTIVITIES |
| SQL_MAX_CURSOR_NAME_LEN | SQL_MAXIMUM_CURSOR_NAME_LENGTH |
| SQL_MAX_DRIVER_CONNECTIONS | SQL_MAXIMUM_DRIVER_CONNECTIONS |
| SQL_MAX_IDENTIFIER_LEN | SQL_MAXIMUM_IDENTIFIER_LENGTH |
| SQL_MAX_SCHEMA_NAME_LEN | SQL_MAXIMUM_SCHEMA_NAME_LENGTH |
| SQL_MAX_STATEMENT_LEN | SQL_MAXIMUM_STATEMENT_LENGTH |
| SQL_MAX_TABLE_NAME_LEN | SQL_MAXIMUM_TABLE_NAME_LENGTH |
| SQL_MAX_TABLES_IN_SELECT | SQL_MAXIMUM_TABLES_IN_SELECT |
| SQL_MAX_USER_NAME_LEN | SQL_MAXIMUM_USER_NAME_LENGTH |
| SQL_MULT_RESULT_SETS | SQL_MULTIPLE_RESULT_SETS |
| SQL_OJ_CAPABILITIES | SQL_OUTER_JOIN_CAPABILITIES |
| SQL_TXN_CAPABLE | SQL_TRANSACTION_CAPABLE |
| SQL_TXN_ISOLATION_OPTION | SQL_TRANSACTION_ISOLATION_OPTION |
Создание источника данных с использованием ODBC API
DLL-библиотека ODBCCP32.DLL предоставляет функции ODBC API ConfigDSN и SQLConfigDataSource, позволяющие выполнять регистрацию новых источников данных или удалять информацию об источниках данных из реестра Windows (и из файла ODBC.ini).
Функция ConfigDSN позволяет добавлять, изменять или удалять источники данных и имеет следующее формальное описание:
BOOL ConfigDSN( HWND hwndParent, WORD fRequest, LPCSTR lpszDriver, LPCSTR lpszAttributes);
Для использования в среде Visual Studio функций ConfigDSN и SQLConfigDataSource следует подключить заголовочный файл odbcinst.h.
Параметр hwndParent определяет дескриптор окна или NULL. Если дескриптор не указан, то при выполнении данной функции окно с предложением уточнить параметры не отображается. Параметр fRequest указывает тип запроса, который задается одной из следующих констант:
Параметр lpszDriver содержит описание драйвера, а параметр lpszAttributes - список атрибутов в форме "ключевое слово=значение" (например: DSN=MyDB\0UID=U1\0PWD=P1\0DATABASE=DB1\0\0). Список атрибутов завершается двумя null-байтами.
При успешном завершении функция возвращает значение TRUE, в противном случае вызовом функции SQLInstallerError можно получить один из следующих кодов ошибки:
Для записи информации об источнике данных в секцию [ODBC Data Sources] секции ODBC.INI реестра Windows функция ConfigDSN вызывает функцию SQLWriteDSNToIni, а для удаления - функцию SQLRemoveDSNFromIni.
Функция SQLConfigDataSource имеет следующее формальное описание:
BOOL SQLConfigDataSource( HWND hwndParent, WORD fRequest, LPCSTR lpszDriver, LPCSTR lpszAttributes);
Параметры функции SQLConfigDataSource аналогичны параметрам функции ConfigDSN, при этом параметр fRequest может принимать следующие значения:
Функция ConfigDSN относится к группе функций установки DLL (setup DLL), а функция SQLConfigDataSource - к группе функций инсталляции DLL (Installer DLL).
При выполнении функция SQLConfigDataSource использует значение параметра lpszDriver для получения из системной информации полного пути к Setup DLL конкретного драйвера, загружает эту DLL и вызывает функцию ConfigDSN, передавая ей свой список параметров (значение параметра fRequest преобразуется к значению, принимаемому функцией ConfigDSN). Перед вызовом ConfigDSN, в зависимости от типа обрабатываемого DSN, устанавливается режим USERDSN_ONLY (пользовательский DSN) или SYSTEMDSN_ONLY (системный DSN), а перед завершением выполнения функция SQLConfigDataSource возвращает режим BOTHDSN.
Функция SQLConfigDataSource имеет следующее формальное описание:
BOOL SQLConfigDataSource( HWND hwndParent, WORD fRequest, LPCSTR lpszDriver, LPCSTR lpszAttributes);
Параметры функции SQLConfigDataSource аналогичны параметрам функции ConfigDSN, при этом параметр fRequest может принимать следующие значения:
Функция ConfigDSN относится к группе функций установки DLL (setup DLL), а функция SQLConfigDataSource - к группе функций инсталляции DLL (Installer DLL).
При выполнении функция SQLConfigDataSource использует значение параметра lpszDriver для получения из системной информации полного пути к Setup DLL конкретного драйвера, загружает эту DLL и вызывает функцию ConfigDSN, передавая ей свой список параметров (значение параметра fRequest преобразуется к значению, принимаемому функцией ConfigDSN). Перед вызовом ConfigDSN, в зависимости от типа обрабатываемого DSN, устанавливается режим USERDSN_ONLY (пользовательский DSN) или SYSTEMDSN_ONLY (системный DSN), а перед завершением выполнения функция SQLConfigDataSource возвращает режим BOTHDSN.
Основные функции ODBC
Как уже отмечалось в предыдущей лекции, все функции ODBC API условно можно разделить на четыре группы:
В следующей таблице представлен список основных функций ODBC API и их уровень соответствия стандартам (в столбце "Соответствие" показано соответствие стандартам и указана версия ODBC, начиная с которой данная функция доступна):
| SQLAllocHandle | ISO 92 ODBC 3.0 | Получает идентификатор (дескриптор) среды, соединения или оператора, или дескриптор приложения | |
| SQLConnect | ISO 92 ODBC 1.0 | Соединение с источником данных по DSN, имени и паролю пользователя | |
| SQLDriverConnect | ODBC ODBC 1.0 | Соединение с источником данных по указанной строке соединения или при помощи отображаемого диалога для интерактивного ввода параметров соединения | |
| SQLBrowseConnect | ODBC ODBC 1.0 | Последовательно запрашивает атрибуты соединения и устанавливает допустимые значения атрибута. После спецификации значения для каждого требуемого атрибута соединения функция выполняет соединение с источником данных | |
| SQLDataSources | ISO 92 ODBC1.0 | Возвращает список доступных источников данных | |
| SQLDrivers | ODBC ODBC2.0 | Возвращает список установленных драйверов и их атрибуты | |
| SQLGetInfo | ISO 92 ODBC1.0 | Возвращает информацию об указанных драйвере и источнике данных | |
| SQLGetFunctions | ISO 92 ODBC1.0 | Возвращает функции, которые поддерживаются используемым драйвером | |
| SQLGetTypeInfo | ISO 92 ODBC1.0 | Возвращает информацию о поддерживаемых типах данных | |
| SQLSetConnectAttr | ISO 92 ODBC3.0 | Устанавливает атрибуты соединения | |
| SQLGetConnectAttr | ISO 92 ODBC3.0 | Возвращает значение атрибута соединения | |
| SQLSetEnvAttr | ISO 92 ODBC3.0 | Устанавливает атрибуты среды | |
| SQLGetEnvAttr | ISO 92 ODBC3.0 | Возвращает значение атрибута среды | |
| SQLSetStmtAttr | ISO 92 ODBC3.0 | Устанавливает атрибуты оператора | |
| SQLGetStmtAttr | ISO 92 ODBC3.0 | Возвращает значение атрибута оператора | |
| SQLGetDescField | ISO 92 ODBC3.0 | Возвращает значение дескриптора для одного поля | |
| SQLGetDescRec | ISO 92 ODBC3.0 | Возвращает значения дескриптора для нескольких полей | |
| SQLSetDescField | ISO 92 ODBC3.0 | Устанавливает значение дескриптора для одного поля | |
| SQLSetDescRec | ISO 92 ODBC3.0 | Устанавливает значение дескриптора для нескольких полей | |
| SQLPrepare | ISO 92 ODBC1.0 | Компилирует SQL-оператор для последующего выполнения | |
| SQLBindParameter | ODBC ODBC2.0 | Связывает буфер с параметрами, используемыми в SQL-операторе | |
| SQLGetCursorName | ISO 92 ODBC1.0 | Возвращает имя курсора, которое ассоциировано с дескриптором оператора | |
| SQLSetCursorName | ISO 92 ODBC1.0 | Определяет имя курсора | |
| SQLSetScrollOptions | ODBC1.0 | Устанавливает опции, которые управляют поведением курсора. В версиях ODBC 2.x и 3.x эта функция заменена функцией SQLSetStmtAttr | |
| SQLExecute | ISO 92 ODBC1.0 | Выполняет откомпилированный SQL-оператор | |
| SQLExecDirect | ISO 92 ODBC1.0 | Выполняет SQL-оператор | |
| SQLNativeSql | ODBC ODBC1.0 | Возвращает текст SQL-оператора, преобразованного конкретным драйвером, но не выполняет его | |
| SQLDescribeParam | ODBC ODBC1.0 | Возвращает описание параметров, используемых в откомпилированном SQL-операторе | |
| SQLNumParams | ISO 92 ODBC1.0 | Возвращает число параметров в откомпилированном SQL-операторе | |
| SQLParamData | ISO 92 ODBC1.0 | Используется совместно с функцией SQLPutData для передачи во время выполнения значений параметров | |
| SQLPutData | ISO 92 ODBC1.0 | Передает часть или все значения параметров | |
| SQLRowCount | ISO 92 ODBC1.0 | Возвращает число строк, на которые воздействовал SQL-оператор insert, update или delete | |
| SQLNumResultCols | ISO 92 ODBC1.0 | Возвращает число столбцов в результирующем наборе | |
| SQLDescribeCol | ISO 92 ODBC1.0 | Описывает столбец результирующего набора, возвращая имя поля, его тип, размер и т.п | |
| SQLColAttribute | ISO 92 ODBC3.0 | Возвращает информацию о столбце результирующего набора. В отличие от функции SQLColAttribute, позволяет получить более обширную информацию о столбце, включая информацию, определяемую конкретным драйвером | |
| SQLBindCol | ISO 92 ODBC1.0 | Выполняет связывание буфера приложения-клиента со столбцами результирующего набора | |
| SQLFetch | ISO 92 ODBC1.0 | Извлекает данные одной следующей строки из результирующего набора, возвращая данные для всех связанных столбцов | |
| SQLFetchScroll | ISO 92 ODBC3.0 | Извлекает данные одной или нескольких строк из результирующего набора, возвращая данные для всех связанных столбцов. Функция позволяет явно указать, какую строку следует извлечь. Данная функция заменила функцию SQLExtendedFetch из ODBC 2.x | |
| SQLGetData | ISO 92 ODBC1.0 | Извлекает из результирующего набора значение одного столбца одной текущей строки. Для использования этой функции не требуется предварительное связывание столбцов | |
| SQLSetPos | ODBC ODBC1.0 | Позиционирует курсор в извлеченном блоке данных и позволяет приложению-клиенту: обновлять данные в строке, модифицировать или удалять данные в результирующем наборе | |
| SQLBulkOperations | ODBC ODBC3.0 | Выполняет несколько вставок или несколько помеченных операций, включая изменение, удаление и выборку по установленному маркеру | |
| SQLMoreResults | ODBC ODBC1.0 | Определяет, есть ли еще следующий результирующий набор, и при его наличии выполняет переход на него | |
| SQLGetDiagField | ISO 92 ODBC3.0 | Возвращает значение поля записи из структуры диагностической информации, ассоциированной с конкретным дескриптором (среды, соединения, оператора) | |
| SQLGetDiagRec | ISO 92 ODBC3.0 | Возвращает значения нескольких предопределенных полей записи из структуры диагностической информации, ассоциированной с конкретным дескриптором (среды, соединения, оператора) | |
| SQLColumnPrivileges | ODBC ODBC1.0 | Возвращает список полей и имеющиеся привилегии для одной или нескольких таблиц | |
| SQLColumns | X/Open ODBC1.0 | Возвращает список имен полей в указанной таблице | |
| SQLForeignKeys | ODBC ODBC1.0 | Возвращает список полей, которые составляют внешние ключи таблицы, если они созданы | |
| SQLPrimaryKeys | ODBC ODBC1.0 | Возвращает список полей, которые составляют первичный ключ таблицы | |
| SQLProcedureColumns | ODBC ODBC1.0 | Возвращает в виде результирующего набора список входных и выходных параметров указанной процедуры | |
| SQLProcedures | ODBCODBC1.0 | Возвращает список хранимых процедур для подключенного источника данных | |
| SQLSpecialColumns | X/Open ODBC1.0 | Получает информацию об оптимальном наборе полей, уникально идентифицирующих строку в указанной таблице, или имя поля, которое автоматически обновляется при изменении какого-либо поля в строке | |
| SQLStatistics | ISO 92 ODBC1.0 | Возвращает информацию о таблице и список индексов, ассоциированных с ней | |
| SQLTablePrivileges | ODBC ODBC1.0 | Возвращает в виде результирующего набора список таблиц и привилегии, назначенные для каждой таблицы | |
| SQLTables | X/Open ODBC1.0 | Возвращает в виде результирующего набора список таблиц, хранимых в указанном источнике данных | |
| SQLFreeStmt | ISO 92 ODBC1.0 | Завершает обработку оператора, удаляет результирующий набор и освобождает все ресурсы, ассоциированные с данным дескриптором оператора | |
| SQLCloseCursor | ISO 92 ODBC3.0 | Закрывает курсор, открытый с данным дескриптором оператора, и удаляет результирующий набор | |
| SQLCancel | ISO 92 ODBC1.0 | Завершает выполнение SQL-оператора, прекращая асинхронное выполнение функции, выполнение функции, требующей данные, или функции, выполняемой в другом потоке. В отличие от версии 2.x, данная функция не может выполнить освобождение дескриптора оператора, и требуется явный вызов функции SQLFreeStmt | |
| SQLEndTran | ISO 92 ODBC3.0 | Выполняет завершение или откат транзакции | |
| SQLDisconnect | ISO 92 ODBC1.0 | Закрывает соединение с источником данных | |
| SQLFreeHandle | ISO 92 ODBC3.0 | Освобождает ресурсы, ассоциированные с указанным дескриптором (среды, соединения, оператора, приложения) |
Создание дескрипторов
Перед использованием функций ODBC API приложение-клиент создает дескриптор (идентификатор) окружения, определяющий глобальный контекст для доступа к источникам данных. Дескриптор окружения предоставляет доступ к различной информации, включая текущие установки всех атрибутов окружения, дескрипторы соединений, созданные для данного окружения, диагностику уровня окружения.
Дескриптор окружения определяет некоторую структуру, содержащую данную информацию. Непосредственно дескриптор окружения обычно используется при вызове функций SQLDataSources и SQLDrivers и при создании дескрипторов соединения.
Для приложения-клиента, реализующего с использованием функций ODBC API доступ к источнику данных, достаточно иметь один дескриптор окружения.
Создание дескриптора окружения выполняется функцией SQLAllocHandle, а освобождение - функцией SQLFreeHandle.
Функция SQLAllocHandle введена в версии ODBC 3.x вместо существовавших в версии ODBC 2.0 функций SQLAllocConnect, SQLAllocEnv и SQLAllocStmt. Для того чтобы приложение, использующее функцию SQLAllocHandle, могло работать через драйверы ODBC 2.x, менеджер драйверов версии 3.x заменяет вызовы функций третьей версии на их аналоги второй версии и передает такой "откорректированный" вызов ODBC-драйверу.
Для подключения к базе данных следует создать дескриптор (идентификатор) соединения. Для одного дескриптора окружения может быть создано несколько дескрипторов соединения.
Для выполнения SQL-оператора создается дескриптор (идентификатор) оператора.
Для одного дескриптора соединения может быть создано несколько дескрипторов оператора.
По спецификации ODBC для каждого приложения драйверы могут поддерживать неограниченное число дескрипторов каждого типа. Однако конкретный драйвер может накладывать некоторые ограничения на количество дескрипторов.
Функция, используемая для создания дескриптора окружения, соединения, оператора или приложения, имеет следующее формальное описание:
SQLRETURN SQLAllocHandle( SQLSMALLINT HandleType, SQLHANDLE InputHandle, SQLHANDLE * OutputHandlePtr);
Параметр HandleType ([Input]) указывает одной из следующих констант тип создаваемого дескриптора:
SQL_HANDLE_ENV SQL_HANDLE_DBC SQL_HANDLE_STMT SQL_HANDLE_DESC
Параметр InputHandle ([Input]) определяет контекст, в который добавляется создаваемый дескриптор. Если тип дескриптора SQL_HANDLE_ENV, то параметр InputHandle указывается константой SQL_NULL_HANDLE. При создании дескриптора среды параметр InputHandle задает дескриптор окружения, а для создания дескриптора оператора (SQL_HANDLE_STMT) и дескриптора приложения (SQL_HANDLE_DESC) - дескриптор соединения.
Идентификаторы, определяющие тип дескриптора и сам дескриптор, описаны в заголовочных файлах sql.h и sqltypes.h следующим образом:
/* sql.h */ #if (ODBCVER >= 0x0300) #define SQL_HANDLE_ENV 1 #define SQL_HANDLE_DBC 2 #define SQL_HANDLE_STMT 3 #define SQL_HANDLE_DESC 4 #endif /* sqltypes.h */ #if (ODBCVER >= 0x0300) #if defined(WIN32) || defined(_WIN64) typedef void* SQLHANDLE; #else typedef SQLINTEGER SQLHANDLE; #endif /* defined(WIN32) || defined(_WIN64) */ typedef SQLHANDLE SQLHENV; typedef SQLHANDLE SQLHDBC; typedef SQLHANDLE SQLHSTMT; typedef SQLHANDLE SQLHDESC; #else //ODBCVER < 0x0300 #if defined(WIN32) || defined(_WIN64) typedef void* SQLHENV; typedef void* SQLHDBC; typedef void* SQLHSTMT; #elsetypedef SQLINTEGER SQLHENV; typedef SQLINTEGER SQLHDBC; typedef SQLINTEGER SQLHSTMT; #endif /* defined(WIN32) || defined(_WIN64) */ #endif /* ODBCVER >= 0x0300 */
Параметр OutputHandlePtr ([Output]) - это указатель на буфер, в который помещается создаваемая для дескриптора структура данных.
Функция SQLAllocHandle может возвращать следующие значения:
Для получения дополнительной информации об ошибке выполнения функции приложение-клиент может использовать данные из дескриптора, указанного параметром InputHandle.
Если при выполнении функции произошла ошибка (код возврата SQL_ERROR) или функция выполнена, но с уведомительным сообщением (код возврата SQL_SUCCESS_WITH_INFO), то значение SQLSTATE можно получить при вызове функции SQLGetDiagRec.
После создания дескриптора окружения следует установить атрибут SQL_ATTR_ODBC_VERSION. В противном случае при попытке создать дескриптор соединения произойдет ошибка.
Для приложений "согласованного стандарта" во время компиляции функция SQLAllocHandle заменяется на SQLAllocHandleStd. Основное отличие последней состоит в том, что при вызове этой функции с значением параметра HandleType, равным SQL_HANDLE_ENV, происходит установка атрибута окружения SQL_ATTR_ODBC_VERSION, равным SQL_OV_ODBC3 (так как приложения "согласованного стандарта" всегда являются приложениями ODBC 3.x и не требуют регистрации версии приложения).
Пул соединений
Организация пула соединений позволяет приложению выбирать соединения из пула без необходимости переустанавливать их для каждого использования. После того как соединение создано и помещено в пул, приложение может использовать это соединение, не повторяя опять процесс установки соединения.
Использование соединений, помещенных в пул, может значительно увеличить производительность приложений, многократно устанавливающих и разрывающих соединения. Примером таких приложений могут служить серверные Интернет-приложения среднего звена в трехзвенной архитектуре, постоянно повторно устанавливающие и разрывающие соединение.
Соединения из пула могут быть использованы несколькими компонентами в одном процессе. Это означает, что автономные компоненты в одном процессе могут взаимодействовать друг с другом без уведомления друг друга. Соединение из пула может быть использовано повторно несколькими компонентами.
При работе с пулом соединений используемый драйвер ODBC должен быть полностью потокозащищенным, что позволит одновременно выполнять различные вызовы из разных потоков (например, выполнить подсоединение в одном потоке, использовать соединение в другом потоке, а отсоединение выполнить в третьем потоке).
Пул соединений управляется менеджером драйверов. Соединение выбирается из пула при вызове приложением функции SQLConnect или функции SQLDriverConnect, а возвращается в пул при выполнении функции SQLDisconnect. Размер пула изменяется динамически: если соединение не было использовано в течение определенного периода времени, то оно удаляется из пула.
Используя атрибут SQL_ATTR_CONNECTION_DEAD в версии ODBC API 3.х, менеджер драйверов может определить состояние соединения из пула: значение SQL_CD_TRUE определяет, что соединение разорвано, а значение SQL_CD_FALSE означает, что соединение пока еще активно. При этом обычно для получения значения данного атрибута драйвер не обращается к серверу, а возвращает результат, основываясь на состоянии соединения при его последнем использовании.
Информация о том, будут ли предотвращены попытки драйвера менеджера установить соединение при включенном пуле соединений, сохраняется в реестре Windows в разделе:
HKEY_LOCAL_MACHINE\ Software\Odbc\Odbcinst.ini\ ODBC Connection Pooling\Retry Wait
Для использования приложением пула соединений следует:
Для отключения режима пула соединений следует установить значение атрибута SQL_ATTR_CONNECTION_POOLING равным SQL_CP_OFF.
Соединение с источником данных
Для непосредственного подключения к базе данных ODBC API предоставляет следующие три функции:
Функция SQLConnect имеет следующее формальное описание:
SQLRETURN SQLConnect( SQLHDBC ConnectionHandle, SQLCHAR * ServerName, SQLSMALLINT NameLength1, SQLCHAR * UserName, SQLSMALLINT NameLength2, SQLCHAR * Authentication, SQLSMALLINT NameLength3);
Параметр ConnectionHandle ([Input]) указывает используемый дескриптор соединения, параметр ServerName ([Input]) - имя источника данных. Параметры UserName ([Input]) и Authentication ([Input]) описывают имя пользователя и пароль, а параметры NameLength1 ([Input]), NameLength2 ([Input]) и NameLength3 ([Input]) определяют длину параметров *ServerName, *UserName и *Authentication соответственно.
Например:
SQLConnect(hdbc, (SQLCHAR*) "MySQLDB", SQL_NTS, (SQLCHAR*) "", SQL_NTS, (SQLCHAR*) "", SQL_NTS);
Для выполнения соединения с источкиком данных, требующим для подключения дополнительной информации, или отображения перед подключением диалога с уточнением значения параметров используется функция SQLDriverConnect, которая имеет следующее формальное описание:
SQLRETURN SQLDriverConnect( SQLHDBC ConnectionHandle, SQLHWND WindowHandle, SQLCHAR * InConnectionString, SQLSMALLINT StringLength1, SQLCHAR * OutConnectionString, SQLSMALLINT BufferLength, SQLSMALLINT * StringLength2Ptr, SQLUSMALLINT DriverCompletion);
Параметр ConnectionHandle ([Input]) указывает дескриптор соединения; параметр WindowHandle ([Input]) - это указатель родительского окна или NULL.
Параметр InConnectionString ([Input]) задает полностью или частично строку соединения или пустую строку, а параметр StringLength1 ([Input]) - длину в байтах строки *InConnectionString.
Строка соединения позволяет описывать атрибуты соединения в текстовом формате. Пары атрибут=значение разделяются между собой символом "точка с запятой".
Параметр OutConnectionString ([Output]) - это указатель на буфер, в котором после успешного подключения к источнику данных возвращается полная строка соединения. Размер этого буфера задается параметром BufferLength ([Input]) и должен быть не менее 1024 байт. Если вызывается функция SQLDriverConnectW и строка соединения указывается в символах Unicode, то размер буфера должен содержать четное число байтов.
Параметр StringLength2Ptr ([Output]) возвращает указатель на буфер, в котором размещается общее число символов полной строки соединения. Если размер буфера *OutConnectionString, указанный параметром BufferLength, меньше, чем требуется, то возвращаемая строка соединения усекается до размера буфера минус длина null-символа.
Параметр DriverCompletion ([Input]) - это флажок, указывающий, будет ли менеджер драйверов и драйвер предлагать диалоги для формирования завершенной строки соединения. Этот параметр определяется следующими значениями:
Строка соединения, передаваемая в качестве параметра, имеет следующее формальное описание:
строка_соединения ::= пустая_строка[;] | атрибут[;] | атрибут; строка_соединения атрибут ::= ключевое_слово =значение | DRIVER=[{]значение [}] ключевое слово ::= DSN | UID | PWD | идентификатор_используемый_драйвером
Набор ключевых слов, указываемых в строке соединения, частично зависит от используемого драйвера. К общепринятым ключевым словам относятся следующие:
Ключевые слова DSN и FILEDSN являются в строке соединения взаимоисключающими: будет использовано первое из указанных. С остальными ключевыми словами FILEDSN не является взаимоисключающим: приоритет имеет значение, указанное непосредственно в строке состояния. Очевидно, что значение ключевого слова PWD не сохраняется в .dsn файле.
По умолчанию, каталогом для сохранения и загрузки .dsn файла будет комбинация пути, указанного как CommonFileDir в разделе реестра Windows HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ Windows\CurrentVersion, и пути ODBC\DataSources.
На рис. 3.1 отображен соответствующий раздел реестра Windows (в данном примере .dsn файлы будут сохраняться в каталоге C:\Program Files\Common Files\ODBC\Data Sources).

Рис. 3.1. Параметр CommonFileDir из реестра Windows
Функция SQLBrowseConnect реализует итерационный метод запроса значений атрибутов, требуемых для подключения к базе данных, возвращая каждый раз код ответа SQL_NEED_DATA и идентификатор очередного запрашиваемого атрибута. После определения значений всех необходимых атрибутов функция устанавливает соединение с базой данных и при успешном завершении операции возвращает код ответа, равный SQL_SUCCESS или SQL_SUCCESS_WITH_INFO.
Функция SQLBrowseConnect имеет следующее формальное описание:
SQLRETURN SQLBrowseConnect( SQLHDBC ConnectionHandle, SQLCHAR * InConnectionString, SQLSMALLINT StringLength1, SQLCHAR * OutConnectionString, SQLSMALLINT BufferLength, SQLSMALLINT * StringLength2Ptr);
Параметр ConnectionHandle ([Input]) определяет дескриптор соединения, параметр InConnectionString ([Input]) описывает строку подключения или ее часть, указанную при предыдущем вызове функции.
Параметр StringLength1 ([Input]) задает длину буфера*InConnectionString.
Параметр OutConnectionString ([Output]) определяет указатель на буфер, содержащий информацию о недостающем атрибуте строки соединения (например, в буфере может быть возвращено следующее значение: "HOST:Server={MySR1,S2,S3};UID:ID=?;PWD:Password=?").
Параметр BufferLength ([Input]) задает длину буфера*OutConnectionString.
Параметр StringLength2Ptr ([Output]) указывает общее число байтов, которое должно быть возвращено в буфере *OutConnectionString. Если размер этого буфера меньше, чем требуется, то возвращаемое значение будет усечено.
Например:
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv) if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) { retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3, 0); if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) { /* Создание дескриптора соединения */ retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) { /* Вызов SQLBrowseConnect до тех пор пока*/ /*код ответа будет SQL_NEED_DATA */
lstrcpy(szConnStrIn, "DSN=MeDB"); do { retcode = SQLBrowseConnect(hdbc, szConnStrIn, SQL_NTS, szConnStrOut, BRWS_LEN, &cbConnStrOut); if (retcode == SQL_NEED_DATA) /* Вызов функции пользователя, возвращающей запрашиваемое значение атрибута*/ GetValueAttr(szConnStrOut, szConnStrIn); } while (retcode == SQL_NEED_DATA);
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){
/* Соединение установлено и можно формировать дескриптор оператора. */ retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt); } // ... SQLFreeHandle(SQL_HANDLE_STMT, hstmt); } SQLDisconnect(hdbc); } } SQLFreeHandle(SQL_HANDLE_DBC, hdbc); } } SQLFreeHandle(SQL_HANDLE_ENV, henv);
Обработка результирующего набора
Результирующий набор формируется в выделяемой области памяти. Для того чтобы использовать данные, записанные в результирующий набор, их следует извлечь из заданной области памяти в переменные используемого языка программирования.
Извлечение данных из результирующего набора в переменные может быть выполнено:
Функции SQLFetch или SQLFetchScroll всегда выполняют продвижение курсора на следующую запись. При этом функция SQLFetch, реализует механизм "однонаправленного курсора", а функция SQLFetchScroll позволяет, в зависимости от используемого источника данных, реализовывать механизм "двунаправленного курсора" и механизм "прямой выборки".
Функции SQLFetch или SQLFetchScroll выполняют одновременное извлечение данных только в том случае, если поля результирующего набора предварительно были связаны с переменными вызовом функции SQLBindCol для каждого связываемого поля.
Функция SQLFetch имеет следующее формальное описание:
SQLRETURN SQLFetch( SQLHSTMT StatementHandle);
Параметр StatementHandle ([Input]) указывает дескриптор оператора.
Функция SQLGetData имеет следующее формальное описание:
SQLRETURN SQLGetData( SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, SQLPOINTER TargetValuePtr, SQLINTEGER BufferLength, SQLINTEGER * StrLen_or_IndPtr);
Параметр StatementHandle ([Input]) указывает дескриптор оператора.
Параметр ColumnNumber ([Input]) указывает номер связываемого столбца результирующего набора (начиная с 1). По умолчанию столбец номер 0 является столбцом маркера строки, в том случае, если маркеры доступны.
Параметр TargetType ([Input]) определяет C-тип данных для буфера*TargetValuePtr в соответствии со следующей таблицей.
| SQL_C_CHAR | SQLCHAR * | unsigned char * |
| SQL_C_SSHORT | SQLSMALLINT | Short int |
| SQL_C_USHORT | SQLUSMALLINT | unsigned short int |
| SQL_C_SLONG | SQLINTEGER | long int |
| SQL_C_ULONG | SQLUINTEGER | unsigned long int |
| SQL_C_FLOAT | SQLREAL | float |
| SQL_C_DOUBLE | SQLDOUBLE, SQLFLOAT | double |
| SQL_C_BIT | SQLCHAR | unsigned char |
| SQL_C_STINYINT | SQLSCHAR | Signed char |
| SQL_C_UTINYINT | SQLCHAR | unsigned char |
| SQL_C_SBIGINT | SQLBIGINT | _int64 |
| SQL_C_UBIGINT | SQLUBIGINT | unsigned _int64 |
| SQL_C_BINARY | SQLCHAR * | unsigned char * |
| SQL_C_BOOKMARK | BOOKMARK | unsigned long int |
| SQL_C_VARBOOKMARK | SQLCHAR * | unsigned char * |
| SQL_C_TYPE_DATE | SQL_DATE_STRUCT | struct tagDATE_STRUCT { SQLSMALLINT year; SQLUSMALLINT month; SQLUSMALLINT day; } DATE_STRUCT; |
| SQL_C_TYPE_TIME | SQL_TIME_STRUCT | struct tagTIME_STRUCT { SQLUSMALLINT hour; SQLUSMALLINT minute; SQLUSMALLINT second; } TIME_STRUCT; |
| SQL_C_TYPE_TIMESTAMP | SQL_TIMESTAMP_STRUCT | struct tagTIMESTAMP_STRUCT { SQLSMALLINT year; SQLUSMALLINT month; SQLUSMALLINT day; SQLUSMALLINT hour; SQLUSMALLINT minute; SQLUSMALLINT second; SQLUINTEGER fraction; } TIMESTAMP_STRUCT; |
| SQL_C_NUMERIC | SQL_NUMERIC_STRUCT | Struct tagSQL_NUMERIC_STRUCT { SQLCHAR precision; SQLSCHAR scale; SQLCHAR sign[g]; SQLCHAR val[SQL_MAX_NUMERIC_LEN]; } SQL_NUMERIC_STRUCT; |
| SQL_C_GUID | SQLGUID | struct tagSQLGUID { DWORD Data1; WORD Data2; WORD Data3; BYTE Data4[8]; } SQLGUID; |
| Все интервальные C типы данных | SSQL_INTERVAL_STRUCT | typedef struct tagSQL_INTERVAL_STRUCT { SQLINTERVAL interval_type; SQLSMALLINT interval_sign; union { SQL_YEAR_MONTH_STRUCT year_month; SQL_DAY_SECOND_STRUCT day_second; } intval; } SQL_INTERVAL_STRUCT; typedef enum { SQL_IS_YEAR = 1, SQL_IS_MONTH = 2, SQL_IS_DAY = 3, SQL_IS_HOUR = 4, SQL_IS_MINUTE = 5, SQL_IS_SECOND = 6, SQL_IS_YEAR_TO_MONTH = 7, SQL_IS_DAY_TO_HOUR = 8, SQL_IS_DAY_TO_MINUTE = 9, SQL_IS_DAY_TO_SECOND = 10, SQL_IS_HOUR_TO_MINUTE = 11, SQL_IS_HOUR_TO_SECOND = 12, SQL_IS_MINUTE_TO_SECOND = 13 } SQLINTERVAL; typedef struct tagSQL_YEAR_MONTH { SQLUINTEGER year; SQLUINTEGER month; } SQL_YEAR_MONTH_STRUCT; typedef struct tagSQL_DAY_SECOND { SQLUINTEGER day; SQLUINTEGER hour; SQLUINTEGER minute; SQLUINTEGER second; SQLUINTEGER fraction; } SQL_DAY_SECOND_STRUCT; |
Например:
SQLCHAR ValuePtr[50]; SQLINTEGER ValueLenOrInd; SQLGetData(hstmt, 1, SQL_C_CHAR, ValuePtr, sizeof(ValuePtr), &ValueLenOrInd);
В параметре TargetType также можно указывать идентификатор типа SQL_C_DEFAULT: в этом случае драйвер самостоятельно выбирает тип данных С, основываясь на типе данных поля источника данных.
Параметр TargetValuePtr ([Output]) определяет буфер, в который выполняется извлечение данных, а параметр ([Input]) определяет размер этого буфера в байтах. Для данных, имеющих фиксированную длину, таких как целочисленные значения, драйвер игнорирует значение параметра BufferLength.
Параметр StrLen_or_IndPtr ([Output]) определяет буфер, в котором возвращается размер данных или индикатор и может содержать следующие значения:
Следующий пример иллюстрирует применение механизма извлечения данных при помощи функции SQLGetData.
//OBDC_Connect.cpp #include "stdafx.h" #include "Test_ODBC_connect.h" #include
#ifdef _DEBUG #define new DEBUG_NEW #endif CWinApp theApp; // Объявление объекта приложения using namespace std; int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) { _tprintf(_T("Ошибка инициализации MFC\n")); nRetCode = 1; } else { std::cout<<"Begin"<
//OBDC_Connect.h #pragma once #include "resource.h" #ifndef _AFX_NOFORCE_LIBS //////////////////////////////////////////////////////////////// // Win32 библиотеки #pragma comment(lib, "odbc32.lib") #pragma comment(lib, "odbccp32.lib") #endif //!_AFX_NOFORCE_LIBS #ifndef __SQL #include
Применение ESCAPE-последовательностей
Escape-последовательности позволяют передавать значения даты и времени, скалярных функций и функций даты/времени, строки в предикате LIKE, внешних соединений, процедурных вызовов, одинаково определяемых в стандарте, в различные источники данных. ODBC-драйвер в зависимости от используемого источника данных приводит передаваемый SQL-оператор в соответствующую форму, заменяя значение escape-последовательности. Преобразованный ODBC-драйвером текст SQL-оператора можно посмотреть, вызвав метод SQLNativeSql.
Escape-последовательность указывается в фигурных скобках.
Для определения значений даты и времени используется следующая форма escape-последовательности:
{тип_значения 'значение'}.
Тип значения указывается следующими символами:
Следующий пример иллюстрирует два способа определения даты: в первом случае с применением escape-последовательности, а во втором - с использованием естественного синтаксиса Oracle. При этом код, использующий естественный синтаксис Oracle, не является интероперабельным.
Пример:
UPDATE tbl1 SET OpenDate= {d '2004-01-24'} WHERE FieldID=1010 UPDATE tbl1 SET OpenDate= '24-Jan-2004' WHERE FieldID=1010
Escape-последовательность может передаваться как значение параметра SQL-оператора. Применение параметра позволяет в последующих реализациях быстро переходить от значения в виде escape-последовательности ("{d '2004-01-24'}") к значению, использующему естественное обозначение в соответствии с конкретным драйвером ("24-Jan-2004").
Например:
SQLCHAR Date1[56]; // Размер даты равен 55 SQLINTEGER Date1LenOrInd = SQL_NTS; // Определение параметров SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_TYPE_DATE, 0, 0, Date1, sizeof(Date1), &Date1LenOrInd); // Задание значения переменной Date1 как // escape-последовательности. strcpy(Date1, "{d '2004-01-24'}"); // Выполнение SQL-оператора SQLExecDirect(hstmt, "UPDATE tbl1 SET Date1=? WHERE FieldID = 1010", SQL_NTS);
Другим способом определения значения даты посредством параметра является применение структуры SQL_DATE_STRUCT. В большинстве случаев этот способ бывает более эффективен.
Например:
SQL_DATE_STRUCT Date1; SQLINTEGER Date1Ind = 0; // Определение параметра SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_TYPE_DATE, SQL_TYPE_DATE, 0, 0, &Date1, 0, &Date1Len); // Определение полей структуры Date1 Date1.year = 2004; Date1.month = 1; Date1.day = 24; // Выполнение SQL-оператора SQLExecDirect(hstmt, "UPDATE tbl2 SET Date1=? WHERE FieldID = 1010", SQL_NTS);
Для определения того, поддерживает ли конкретный ODBC-драйвер escape-последовательности для представления значений типа даты и времени, применяется функция SQLGetTypeInfo (если источник данных поддерживает типы данных для даты и времени, то он также должен поддерживать и соответствующие escape-последовательности).
Для определения того, поддерживает ли конкретный ODBC-драйвер представление значений даты и времени в виде, определяемом спецификацией ANSI SQL-92, применяется функция SQLGetInfo с опцией SQL_ANSI_SQL_DATETIME_LITERALS.
Связывание данных
Функция, выполняющая предварительное связывание данных, имеет следующее формальное описание:
SQLRETURN SQLBindCol( SQLHSTMT StatementHandle, SQLUSMALLINT ColumnNumber, SQLSMALLINT TargetType, SQLPOINTER TargetValuePtr, SQLINTEGER BufferLength, SQLLEN * StrLen_or_Ind);
Значения параметров функции SQLBindCol аналогичны значениям параметров функции SQLGetData. Но функция SQLBindCol указывается только один раз для каждого поля, а затем выборка данных выполняется автоматически при вызове функции SQLFetch. А при отсутствии связывания функция SQLGetData должна вызываться каждый раз для каждого поля после выполнения функции SQLFetch.
Функция SQLBindCol может выполнять предварительное связывание для последующего извлечения данных функциями SQLFetch, SQLFetchScroll, SQLBulkOperations и SQLSetPos.
Например:
#define NAME_LEN 50 #define PHONE_LEN 10
SQLCHAR szName[20], szPhone[15]; SQLINTEGER sID, cbName, cbID, cbPhone; SQLHSTMT hstmt; SQLRETURN retcode;
retcode = SQLExecDirect(hstmt, "SELECT CID, NAME, PHONE FROM TBL1 ORDER BY 2, 1, 3", SQL_NTS); if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) { /* Связывание столбцов 1, 2 и 3 */ SQLBindCol(hstmt, 1, SQL_C_ULONG, &sCID, 0, &cbCID); SQLBindCol(hstmt, 2, SQL_C_CHAR, szName, 20, &cbName); SQLBindCol(hstmt, 3, SQL_C_CHAR, szPhone, 15, &cbPhone); while (TRUE) { retcode = SQLFetch(hstmt); if (retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO) { show_error(); } if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){ // Значения полей успешно извлечены из // результирующего набора } else { break; } } }
Выполнение SQL-оператора
Результирующий набор создается при выполнении SQL-оператора SELECT. Для выполнения любого SQL-оператора первоначально должен быть создан дескриптор оператора.
Например:
SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
Второй параметр указывает дескриптор соединения, для которого создается дескриптор оператора, а третий параметр определяет область памяти, выделяемую под дескриптор оператора.
Для выполнения запроса ODBC API предоставляет следующие две функции:
Функция SQLExecDirect реализует одношаговый интерфейс, при котором процесс компиляции SQL-оператора и его выполнение осуществляется единожды при вызове данной функции.
Функции SQLPrepare и SQLExecute реализуют многошаговый интерфейс: сначала выполняется компиляция оператора и строится план выполнения, а затем возможно многократное выполнение подготовленного оператора (например, с различными значениями параметров).
Функция SQLExecDirect имеет следующее формальное описание:
SQLRETURN SQLExecDirect( SQLHSTMT StatementHandle, SQLCHAR * StatementText, SQLINTEGER TextLength);
Параметр StatementHandle ([Input]) указывает дескриптор оператора, параметр StatementText ([Input]) определяет выполняемый SQL-оператор, TextLength (Input]) - длина строки *StatementText.
Функция SQLExecDirect возвращает одно из следующих значений: SQL_SUCCESS, SQL_SUCCESS_WITH_INFO, SQL_NEED_DATA, SQL_STILL_EXECUTING, SQL_ERROR, SQL_NO_DATA, SQL_INVALID_HANDLE.
Функция SQLExecDirect через ODBC-драйвер передает SQL-оператор источнику данных. При этом драйвер может изменить передаваемый SQL-оператор, приводя его к форме, соответствующей используемому источнику данных - например, изменяя передаваемые escape-последовательности (при использовании значений даты и времени, скалярных функций и функций даты/времени, предиката LIKE, внешних соединений, процедурных вызовов).
При выполнении SQL-оператора SELECT с вызовом функции SQLSetCursorName драйвер использует заданное этой функцией имя курсора.
Если имя курсора явно не указывается, то драйвер самостоятельно формирует имя курсора для оператора SELECT.
Если режим работы с источником данных не установлен как автокоммит, а перед выполнением SQL-оператора транзакция не была инициализирована явным образом, то драйвер самостоятельно выполняет открытие транзакции.
Если приложение использует функцию SQLExecDirect для выполнения SQL-операторов COMMIT или ROLLBACK, то оно не будет интероперабельным между различными СУБД. Для достижения полной переносимости разрабатываемого приложения работы с базами данных следует для завершения или отката транзакции вызывать функцию ODBC API SQLEndTran.
При выполнении SQL-оператора, содержащего параметры, их значения предварительно должны быть определены вызовом функций SQLParamData и SQLPutData или SQLBindParameter. В противном случае при выполнении функции SQLExecDirect будет возвращено значение SQL_NO_DATA.
При многошаговом интерфейсе первой должна быть выполнена функция SQLPrepare, инициирующая компиляцию SQL-оператора.
Функция SQLPrepare имеет следующее формальное описание:
SQLRETURN SQLPrepare( SQLHSTMT StatementHandle, SQLCHAR * StatementText, SQLINTEGER TextLength);
Параметр StatementHandle ([Input]) указывает дескриптор оператора, параметр StatementText ([Input]) определяет текст компилируемого SQL-оператора, TextLength ([Input]) - это длина строки *StatementText.
Функция SQLExecute выполняет откомпилированный SQL-оператор. Если выполняемым SQL-оператором был оператор SELECT, то в результате выполнения SQLExecute создается результирующий набор.
Функция SQLExecute имеет следующее формальное описание:
SQLRETURN SQLExecute( SQLHSTMT StatementHandle);
Параметр StatementHandle ([Input]) указывает дескриптор оператора
Например:
SQLHSTMT hstmtS, hstmtU; SQLExecDirect(hstmtS, "SELECT F1, F2 FROM TBL1 ", SQL_NTS); SQLPrepare(hstmtU, "UPDATE TBL1 SET F2=F2*1.3 WHERE F1=1010", SQL_NTS); SQLExecute(hstmtU);
Передача параметров
Если перед выполнением функции SQLExecDirect значения параметров, используемых в запросе, не переданы на сервер, то функция возвращает код ответа SQL_NEED_DATA. Для передачи параметров в приложении могут использоваться функции SQLParamData и SQLPutData. Функция SQLParamData используется совместно с функцией SQLPutData для передачи значений параметров во время выполнения. Если функция SQLParamData возвращает значение SQL_NEED_DATA, то она также возвращает и номер параметра, для которого следует ввести значение. Передача значения параметра выполняется функцией SQLPutData.
Функция SQLParamData имеет следующее формальное описание:
SQLRETURN SQLParamData( SQLHSTMT StatementHandle, SQLPOINTER * ValuePtrPtr);
Параметр StatementHandle ([Input]) указывает дескриптор оператора, а параметр ValuePtrPtr ([Output]) указывает буфер, который был предназначен для хранения параметра функцией SQLBindParameter (указывается как значение параметра ParameterValuePtr).
Функция SQLPutData позволяет приложению передавать данные параметра или столбца во время выполнения. При этом данные могут передаваться по частям.
Функция SQLPutData имеет следующее формальное описание:
SQLRETURN SQLPutData( SQLHSTMT StatementHandle, SQLPOINTER DataPtr, SQLINTEGER StrLen_or_Ind);
Параметр StatementHandle ([Input]) указывает дескриптор оператора, параметр DataPtr ([Input]) определяет указатель буфера, в котором размещается значение параметра или столбец (значение соответствующего C-типа, указанного параметром ValueType функции SQLBindParameter или параметром TargetType функции SQLBindCol), а параметр StrLen_or_Ind ([Input]) определяет длину передаваемых данных *DataPtr.
Следующий пример иллюстрирует применение функций OBDC API для передачи параметров, используемых оператором INSERT. Этот оператор содержит два параметра - для полей F1_ID и F2_PIC. Для каждого параметра приложение вызывает метод SQLBindParameter, определяющий С-тип данных и SQL-тип поля.
#define MAX_DATA_LEN 1024 SQLINTEGER cbF1_ID = 0, cbF2_PICParam, cbData; SQLUINTEGER sF1_ID; szPhotoFile; SQLPOINTER pToken, InitValue; SQLCHAR Data[MAX_DATA_LEN]; SQLRETURN retcode; SQLHSTMT hstmt; // Компиляция параметрического запроса retcode = SQLPrepare(hstmt, "INSERT INTO TBL1 (F1_ID, F2_PIC) VALUES (?, ?)", SQL_NTS); if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
// Выполнение связывания параметров. // Для 2 параметра передаем в ParameterValuePtr // вместо адреса буфера номер параметра SQLBindParameter(hstmt, 1, // Для 1 параметра SQL_PARAM_INPUT, SQL_C_ULONG, SQL_INTEGER, 0, 0, &sF1_ID, 0, &cbF1_ID); SQLBindParameter(hstmt, 2, // Для 2 параметра SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, 0, 0, (SQLPOINTER) 2, // Передаваемый номер 0, &cbF2_PICParam);
// Значения второго параметра будут передаваться // во время выполнения. // Длина параметра в макросе SQL_LEN_DATA_AT_EXEC // равна 0. Это предполагает, что драйвер вернет // значение "N" для типа SQL_NEED_LONG_DATA_LEN, // указанного в функции SQLGetInfo.
cbF2_PICParam = SQL_LEN_DATA_AT_EXEC(0);
sF1_ID = MyGetNextID(); /* Запрос следующего значения поля F1_ID таблицы TBL1 */
retcode = SQLExecute(hstmt);
/* Для параметров времени выполнения (data-at-execution parameters) вызывается функция SQLParamData для получения номера параметра, установленного функцией SQLBindParameter. */ /* Вспомогательные функции MyInitData и MyGetData формируют значение параметра*/ /* Функция SQLParamData завершает процесс обработки параметра */
while (retcode == SQL_NEED_DATA) { retcode = SQLParamData(hstmt, &pToken); // Запрос // значений параметров во время выполнения if (retcode == SQL_NEED_DATA) { MyInitData((SQLSMALLINT)pToken, InitValue); while (MyGetData(InitValue, (SQLSMALLINT)pToken, Data, &cbData)) SQLPutData(hstmt, Data, cbData); } } }
VOID MyInitData (sParam, InitValue) SQLPOINTER InitValue; { SQLCHAR szPhotoFile[MAX_FILE_NAME_LEN];
/* Запрос у пользователя имени используемого BMP-файла, открытие этого файла и возвращения дескриптора файла */
MyPromptPhotoFileName(szPhotoFile); MyOpenBMPFile(szPhotoFile, (FILE *)InitValue); break; }
BOOL MyGetData (InitValue, sParam, Data, cbData) SQLPOINTER InitValue; SQLCHAR * Data; SQLINTEGER * cbData; BOOL Done;
{ /* Функция MyGetNextBMPData возвращает следующую часть данных и количество передаваемых байтов (не более, чем MAX_DATA_LEN). */ Done = MyGetNextBMPData((FILE *)InitValue, Data, MAX_DATA_LEN, &cbData); if (Done) { MyCloseBMPFile((FILE *)InitValue); return (TRUE); } return (FALSE); }
Изменение позиции курсора
При использовании перемещаемого курсора после создания результирующего набора позицию курсора можно перемещать. Это можно выполнять непосредственно функцией выборки данных SQLFetchScroll или функцией перемещения курсора SQLSetPos.
Функция SQLSetPos, кроме изменения позиции курсора в результирующем наборе и обновления данных, также позволяет приложению определять набор строк результирующего набора для изменения или удаления данных.
Для изменения или удаления любой строки из набора строк можно использовать функцию SQLSetPos. Применение функции SQLSetPos является удобной альтернативой выполнения отдельного SQL-оператора. Так, это позволяет ODBC-драйверу поддерживать позиционированное изменение даже в том случае, если сам источник данных не поддерживает позиционированный UPDATE в SQL-операторе.
Функция SQLSetPos оперирует с текущим набором строк (rowset), который создается после вызова функции SQLFetchScroll, выполняющей извлечение этого набора строк из результирующего набора, сформированного, в свою очередь, при выполнении SQL-оператора.
Функция SQLSetPos имеет следующее формальное описание:
SQLSetPos( SQLHSTMT StatementHandle, SQLUSMALLINT RowNumber, SQLUSMALLINT Operation, SQLUSMALLINT LockType);
Параметр StatementHandle ([Input]) указывает дескриптор оператора. Параметр RowNumber ([Input]) определяет позицию строки в наборе строк, над которой выполняется операция, указываемая параметром Operation. Если значение параметра RowNumber равно 0, то операция будет выполняться над каждой строкой набора строк (которая отмечена соответствующим образом в массиве операций над строками).
Параметр Operation ([Input]) определяет тип выполняемой операции и указывается следующими значениями:
SQL_POSITION SQL_REFRESH SQL_UPDATE SQL_DELETE
Отметим, что значение параметра Operation, равное SQL_ADD, начиная с версии ODBC 3.x, отменено. Однако драйверы ODBC 3.x в целях обратной совместимости поддерживают эту возможность, заменяя вызов функции SQLSetPos с данным значением параметра на вызов функции SQLBulkOperations со значением параметра Operation, равным SQL_ADD.
Обратно, если приложение ODBC 3.x использует драйвер ODBC 2.x, то менеджер драйверов заменяет для параметра, равного SQL_ADD, вызов SQLBulkOperations на вызов SQLSetPos.
Параметр LockType ([Input]) определяет уровень блокировки строки при выполнении операции, указываемой параметром Operation, и может принимать следующие значения:
Строка, заблокированная при выполнении функции SQLSetPos, остается заблокированной до тех пор, пока приложение не вызовет эту же функцию со значением параметра LockType, равным SQL_LOCK_UNLOCK, или функцию SQLFreeHandle для дескриптора оператора, или функцию SQLFreeStmt с опцией SQL_CLOSE. Если драйвер поддерживает транзакции, то строка, заблокированная при выполнении функции SQLSetPos, освобождается при завершении транзакции функцией SQLEndTran (как при коммите, так и при откате транзакции) в том случае, если при завершении транзакции установлено закрытие курсора.
При выполнении операций SQL_DELETE и SQL_UPDATE изменяется состояние строки. Так, в массиве состояния строк, указатель на который определяется атрибутом оператора SQL_ATTR_ROW_STATUS, все удаленные строки будут отмечены как SQL_ROW_DELETED.
Для определения во время выполнения операций, поддерживаемых используемым источником данных, следует вызвать функцию SQLGetInfo с одним из следующих значений параметра в зависимости от типа используемого курсора:
SQL_DYNAMIC_CURSOR_ATTRIBUTES1, SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1, SQL_KEYSET_CURSOR_ATTRIBUTES1, SQL_STATIC_CURSOR_ATTRIBUTES1.
В следующей таблице приведено описание операций, которые могут быть выполнены функцией SQLSetPos
| SQL_POSITION | Позиция курсора устанавливается на строку с номером, указанным параметром RowNumber (нумерация начинается с 1) |
| SQL_REFRESH | Позиция курсора устанавливается на строку с номером, указанным параметром RowNumber, и драйвер обновляет данные для этой строки в буфере результирующего набора. При этом функция SQLSetPos выполняет обновление содержания и состояния строк в текущем сформированном результирующем наборе: данные в буфере обновляются, а не повторно извлекаются. В отличие от функции SQLSetPos функция SQLFetchScroll с значениями параметров FetchOrientation, равным SQL_FETCH_RELATIVE, и RowNumber, равным 0, выполняет повторное извлечение строк из результирующего набора (при этом, если драйвер допускает, то для обновляемого курсора будут отображаться добавленные строки и не отображаться - удаленные). Успешное выполнение функцией SQLSetPos обновления строки не изменяет состояния строки, равного SQL_ROW_DELETED, а состояние строки, равное SQL_ROW_ADDED, заменяет на SQL_ROW_SUCCESS (в том случае, если массив состояний строк существует). В том случае, если курсор был открыт с атрибутом оператора SQL_ATTR_CONCURRENCY SQL_CONCUR_ROWVER или SQL_CONCUR_VALUES, то в случае операции обновления, выполняемой функцией SQLSetPos, может быть определено, что строки были изменены: при этом буфер результирующего набора обновляется при извлечении строки с сервера |
| SQL_UPDATE | Устанавливает позицию курсора на строку, указанную параметром RowNumber, и изменяет значения строки, беря их из буфера (буферов результирующего набора), определенного параметром TargetValuePtr функции SQLBindCol |
| SQL_DELETE | Устанавливает позицию курсора на строку, определенную параметром RowNumber, и удаляет указанную строку |
Управление поведением курсора
Приложение, использующее функции ODBC API, управляет поведением курсора, устанавливая атрибуты оператора. Можно использовать два различных способа определения характеристик курсора:
Для определения типа курсора вызывается функция SQLSetStmtAttr со значением атрибута SQL_ATTR_CURSOR_TYPE, который имеет тип SQLUINTEGER и может задаваться следующими значениями:
На используемый тип курсора накладывает ограничение применяемый ODBC-драйвер. Так, многие драйверы не поддерживают возможность применения динамического курсора. В том случае, если при вызове функции SQLSetStmtAttr был указан недопустимый тип курсора, то драйвер заменит его на наиболее подходящий из поддерживаемых типов, а функция вернет код ответа SQLSTATE 01S02 (Опция была изменена).
Для определения поведения курсора вызывается функция SQLSetStmtAttr со значениями атрибутов SQL_ATTR_CURSOR_SCROLLABLE и SQL_ATTR_CURSOR_SENSITIVITY. Смысл этих атрибутов соответствует применению ключевых слов SCROLL и SENSITIVE в SQL-операторе DECLARE CURSOR в стандарте SQL-92.
Атрибут SQL_ATTR_CURSOR_SCROLLABLE управляет перемещаемым курсором, имеет тип SQLUINTEGER и может задаваться следующими двумя значениями:
Перемещаемые курсоры для извлечения данных используют функцию SQLFetchScroll (в версии ODBC 2.x использовалась функция SQLExtendedFetched). Если простой однонаправленный курсор, используемый функцией SQLFetch, позволяет перемещаться только в одном прямом направлении и выполнять выборку только одной строки за один вызов функции, то перемещаемый курсор позволяет:
Атрибут SQL_ATTR_CURSOR_SENSITIVITY определяет чувствительность курсора к изменениям, выполняемым другими курсорами, имеет тип SQLUINTEGER и может определять режимы, задаваемые следующими значениями:
Приложение может определять характеристики курсора двояко: или как тип курсора, или как поведение курсора. При этом ODBC-драйвер неявным образом приводит в соответствие значение остальных атрибутов оператора. Следующая таблица иллюстрирует соответствие атрибутов оператора.
| SQL_ATTR_CONCURRENCY установлен как SQL_CONCUR_READ_ONLY | SQL_ATTR_CURSOR_SENSITIVITY получает значение SQL_INSENSITIVE |
| SQL_ATTR_CONCURRENCY установлен как SQL_CONCUR_LOCK, SQL_CONCUR_ROWVER или SQL_CONCUR_VALUES | SQL_ATTR_CURSOR_SENSITIVITY получает значение SQL_UNSPECIFIED или SQL_SENSITIVE, что определяется драйвером. Значение SQL_INSENSITIVE никогда не будет назначено данному атрибуту, так как оно предусматривает режим "только чтение" |
| SQL_ATTR_CURSOR_SCROLLABLE установлен как SQL_NONSCROLLABLE | SQL_ATTR_CURSOR_TYPE получает значение SQL_CURSOR_FORWARD_ONLY |
| SQL_ATTR_CURSOR_SCROLLABLE установлен как SQL_SCROLLABLE | SQL_ATTR_CURSOR_TYPE получает значение SQL_CURSOR_STATIC, SQL_CURSOR_KEYSET_DRIVEN или SQL_CURSOR_DYNAMIC, что определяется драйвером. Значение SQL_CURSOR_FORWARD_ONLY никогда не будет назначено данному атрибуту |
| SQL_ATTR_CURSOR_SENSITIVITY установлен как SQL_INSENSITIVE | SQL_ATTR_CONCURRENCY получает значение SQL_CONCUR_READ_ONLY. SQL_ATTR_CURSOR_TYPE получает значение SQL_CURSOR_STATIC |
| SQL_ATTR_CURSOR_SENSITIVITY установлен как SQL_SENSITIVE | SQL_ATTR_CONCURRENCY получает значение SQL_CONCUR_LOCK, SQL_CONCUR_ROWVER или SQL_CONCUR_VALUES ( как определено драйвером). Атрибут SQL_ATTR_CONCURRENCY никогда не получает значение SQL_CONCUR_READ_ONLY. SQL_ATTR_CURSOR_TYPE получает значение SQL_CURSOR_FORWARD_ONLY, SQL_CURSOR_STATIC, SQL_CURSOR_KEYSET_DRIVEN или SQL_CURSOR_DYNAMIC (как определяется драйвером) SQL_ATTR_CURSOR_SENSITIVITY установлен как SQL_UNSPECIFIED SQL_ATTR_CONCURRENCY получает значение SQL_CONCUR_READ_ONLY, SQL_CONCUR_LOCK, SQL_CONCUR_ROWVER или SQL_CONCUR_VALUES ( как определено драйвером). SQL_ATTR_CURSOR_TYPE получает значение SQL_CURSOR_FORWARD_ONLY, SQL_CURSOR_STATIC, SQL_CURSOR_KEYSET_DRIVEN или SQL_CURSOR_DYNAMIC ( как определено драйвером) |
| SQL_ATTR_CURSOR_TYPE установлен как SQL_CURSOR_DYNAMIC | SQL_ATTR_SCROLLABLE получает значение SQL_SCROLLABLE. SQL_ATTR_CURSOR_SENSITIVITY получает значение SQL_SENSITIVE. (Только в том случае, если атрибут SQL_ATTR_CONCURRENCY не равен SQL_CONCUR_READ_ONLY. Обновляемые динамические курсоры всегда "видят" все изменения, сделанные в их собственной транзакции |
| SQL_ATTR_CURSOR_TYPE установлен как SQL_CURSOR_FORWARD_ONLY | SQL_ATTR_CURSOR_SCROLLABLE получает значение SQL_NONSCROLLABLE |
| SQL_ATTR_CURSOR_TYPE установлен как SQL_CURSOR_KEYSET_DRIVEN | SQL_ATTR_SCROLLABLE получает значение SQL_SCROLLABLE. SQL_ATTR_SENSITIVITY получает значение SQL_UNSPECIFIED или SQL_SENSITIVE (в соответствии с тем, как определяется драйвером, но если атрибут SQL_ATTR_CONCURRENCY не равен SQL_CONCUR_READ_ONLY) |
| SQL_ATTR_CURSOR_TYPE установлен как SQL_CURSOR_STATIC | SQL_ATTR_SCROLLABLE получает значение SQL_SCROLLABLE. SQL_ATTR_SENSITIVITY получает значение SQL_INSENSITIVE (в том случае, если атрибут SQL_ATTR_CONCURRENCY равен SQL_CONCUR_READ_ONLY). SQL_ATTR_SENSITIVITY получает значение SQL_UNSPECIFIED или SQL_SENSITIVE (в том случае, если атрибут SQL_ATTR_CONCURRENCY не равен SQL_CONCUR_READ_ONLY) |
SQLRETURN SQLSetStmtAttr( SQLHSTMT StatementHandle, SQLINTEGER Attribute, SQLPOINTER ValuePtr, SQLINTEGER StringLength);
Параметр StatementHandle ([Input]) указывает дескриптор оператора.
Параметр Attribute ([Input]) определяет атрибут, значение которого устанавливается, а параметр ValuePtr ([Input]) является указателем на значение, назначаемое атрибуту Attribute.
Параметр StringLength ([Input]) зависит от типа значения, передаваемого параметром ValuePtr, и от вида атрибута (ODBC-определяемый или определяемый драйвером) и может содержать длину строки, игнорироваться или указываться константами, такими как SQL_NTS, SQL_IS_POINTER.
Выполнение множественных операций
Множественная операция выполняется функцией SQLSetPos в том случае, если параметр RowNumber равен 0. Она выполняется для всех строк, у которых в массиве операций над строками установлено значение SQL_ROW_PROCEED.
По умолчанию должны обрабатываться все строки результирующего набора. Для того чтобы драйвер игнорировал одну или несколько строк, им в массиве операций над строками надо назначить значение SQL_ROW_IGNORE.
Это может быть выполнено вызовом функции SQLSetStmtAttr для установки атрибута оператора SQL_ATTR_ROW_OPERATION_PTR, указывающего на массив элементов типа SQLUSMALLINT. Это поле также может быть установлено вызовом функции SQLSetDescField для определения SQL_DESC_ARRAY_STATUS_PTR.
При множественных операциях игнорировать можно не только обработку строк, но и обработку столбцов (например, столбцов "только для чтения"). Для этого в функции SQLBindCol игнорируемый столбец следует пометить как SQL_COLUMN_IGNORE.
Следующий пример иллюстрирует применение функции SQLSetPos.
#define ROWS 20 // Число строк // в результирующем наборе #define STATUS_LEN 6
SQLCHAR szStatus[ROWS][STATUS_LEN], szReply[3]; SQLINTEGER cbStatus[ROWS], cbID; SQLUSMALLINT rgfRowStatus[ROWS]; SQLUINTEGER sID, crow = ROWS, irow; SQLHSTMT hstmtS, hstmtU;
SQLSetStmtAttr(hstmtS, SQL_ATTR_CONCURRENCY, (SQLPOINTER) SQL_CONCUR_ROWVER, 0); // Определение типа курсора SQLSetStmtAttr(hstmtS, SQL_ATTR_CURSOR_TYPE, (SQLPOINTER) SQL_CURSOR_KEYSET_DRIVEN, 0); // Определение размера результирующего набора SQLSetStmtAttr(hstmtS, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER) ROWS, 0); // Определение массива состояния строк SQLSetStmtAttr(hstmtS, SQL_ATTR_ROW_STATUS_PTR, (SQLPOINTER) rgfRowStatus, 0); SQLSetCursorName(hstmtS, "C1", SQL_NTS); // Выполнение SQL-оператора SQLExecDirect(hstmtS, "SELECT ID1, STATUS FROM TBL1", SQL_NTS); // Выполнение "связывания" данных SQLBindCol(hstmtS, 1, SQL_C_ULONG, &sID, 0, &cbID); SQLBindCol(hstmtS, 2, SQL_C_CHAR, szStatus, STATUS_LEN, &cbStatus);
while ((retcode == SQLFetchScroll(hstmtS, SQL_FETCH_NEXT, 0)) != SQL_ERROR) { if (retcode == SQL_NO_DATA_FOUND) break; // Отображение 20-ти извлеченных строк // результирующего набора for (irow = 0; irow < crow; irow++) { if (rgfRowStatus[irow] != SQL_ROW_DELETED) // Отображение данных printf("%2d %5d %*s\n", irow+1, sID, NAME_LEN-1, szStatus[irow]); } while (TRUE) { printf("\ nУкажите номер изменяемой строки или 0?"); gets(szReply); // Получаем номер строки irow = atoi(szReply); if (irow > 0 && irow <= crow) { printf("\nНовое состояние?"); gets(szStatus[irow-1]); // Получаем новое // значение для поля STATUS // Изменяем текущую позицию курсора SQLSetPos(hstmtS, irow, SQL_POSITION, SQL_LOCK_NO_CHANGE); SQLPrepare(hstmtU, "UPDATE TBL1 SET STATUS=? WHERE CURRENT OF C1", SQL_NTS); // Выполняем "связывание" параметра SQLBindParameter(hstmtU, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, STATUS_LEN, 0, szStatus[irow], 0, NULL); // Выполняем изменение данных SQLExecute(hstmtU); } else if (irow == 0) { break; } } }
Реализация блочной выборки строк
При использовании перемещаемого курсора для изменения текущей позиции курсора и выборки строк используется функция SQLFetchScroll. Эта функция позволяет реализовывать:
Функция SQLFetchScroll выполняет выборку набора строк из сформированного результирующего набора и возвращает данные для всех связанных столбцов. Наборы строк (rowset) могут быть указаны как через абсолютное или относительное позиционирование, так и посредством закладок (bookmark). В версии ODBC 2.x для этих целей использовалась функция SQLExtendedFetch.
Функция SQLFetchScroll имеет следующее формальное описание:
SQLRETURN SQLFetchScroll( SQLHSTMT StatementHandle, SQLSMALLINT FetchOrientation, SQLINTEGER FetchOffset);
Параметр StatementHandle ([Input]) указывает дескриптор оператора.
Перемещение курсора определяется типом выборки, указывается параметром FetchOrientation ([Input]) и может принимать следующие значения:
Количество строк, на которые выполняется перемещение курсора и номер абсолютной позиции, указывается параметром FetchOffset ([Input]).
Параметры FetchOrientation и FetchOffset функции SQLFetchScroll совместно определяют набор строк, который будет извлечен из результирующего набора. На следующей схеме показан механизм выборки строк при позиционировании на следующую, предыдущую, первую или последнюю строку.

Механизм выборки строк при помощи функции SQLFetchScroll также позволяет реализовывать позиционирование по абсолютному указанному номеру, позиционирование со смещением на заданное число строк или позиционирование посредством закладки. Эти механизмы отображены на следующей схеме.

Функция SQLFetchScroll выполняет позиционирование курсора на указанную строку результирующего набора и возвращает набор строк, начиная с установленной позиции курсора. Если требуемый набор строк выходит за нижнюю границу результирующего набора, то возвращается существующая часть требуемого набора строк. А при выходе за верхнюю границу, как правило, возвращается набор строк требуемого размера, начиная с первой строки результирующего набора.
Иногда требуется выполнить позиционирование курсора без извлечения данных - например, для проверки существования строки или для получения закладки строки. В этом случае следует установить режим выполнения SQL_RD_OFF (без чтения). Такой режим устанавливается назначением атрибуту оператора SQL_ATTR_RETRIEVE_DATA значения SQL_RD_OFF. Отметим, что если существует переменная, связанная со столбцом закладок, то она всегда обновляется вне зависимости от того, какой режим установлен.
После того как набор строк будет извлечен, для позиционирования в нем конкретной строки или извлечения строки можно использовать функцию SQLSetPos.
Связывание по столбцу
При использовании связывания по столбцу с каждым столбцом может быть связано от одного до трех массивов: первый массив - для извлекаемых значений, второй - для длины / индикатора буферов, третий - для индикатора буферов (если индикаторы и длина хранятся по отдельности). Каждый массив должен содержать число элементов, равное числу строк в извлекаемом наборе строк.
Хранятся ли по отдельности индикаторы и значения длины, определяется установкой дескрипторов полей SQL_DESC_INDICATOR_PTR и SQL_DESC_OCTET_LENGTH_PTR.
На следующем рисунке приведена схема связывания по столбцам.

Следующий пример иллюстрирует применение связывания по столбцам для набора строк, состоящего из трех столбцов:
#define ROW_ARRAY_SIZE 10 // Кол-во строк // в наборе строк
SQLUINTEGER ID1Array[ROW_ARRAY_SIZE], NumRowsFetched; SQLCHAR SalArray[ROW_ARRAY_SIZE][11], StatusArray[ROW_ARRAY_SIZE][7]; SQLINTEGER ID1IndArray[ROW_ARRAY_SIZE], SalLenOrIndArray[ROW_ARRAY_SIZE], StatusLenOrIndArray[ROW_ARRAY_SIZE]; SQLUSMALLINT RowStatusArray[ROW_ARRAY_SIZE], i; SQLRETURN rc; SQLHSTMT hstmt;
// Устанавливаем атрибут оператора // SQL_ATTR_ROW_BIND_TYPE для использования связывания // по столбцам SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_TYPE, SQL_BIND_BY_COLUMN, 0); // Размер набора строк задаем атрибутом оператора // SQL_ATTR_ROW_ARRAY_SIZE SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, ROW_ARRAY_SIZE, 0); // Устанавливаем атрибут оператора // SQL_ATTR_ROW_STATUS_PTR для определения массива // состояний строк SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_STATUS_PTR, RowStatusArray, 0); // Устанавливаем атрибут оператора // SQL_ATTR_ROWS_FETCHED_PTR для указания на // cRowsFetched SQLSetStmtAttr(hstmt, SQL_ATTR_ROWS_FETCHED_PTR, &NumRowsFetched, 0);
// Связываем массивы со столбцами ID1, Sal и Status SQLBindCol(hstmt, 1, SQL_C_ULONG, ID1Array, 0, ID1IndArray); SQLBindCol(hstmt, 2, SQL_C_CHAR, SalArray, sizeof(SalArray[0]), SalLenOrIndArray); SQLBindCol(hstmt, 3, SQL_C_CHAR, StatusArray, sizeof(StatusArray[0]), StatusLenOrIndArray);
// Выполняем SQL-оператор SELECT для создания // результирующего набора SQLExecDirect(hstmt, " SELECT ID1, Sal, Status FROM TBL1", SQL_NTS);
// Выполняем блочную выборку из результирующего набора // В переменной NumRowsFetched возвращается число // в действительности выбранных строк while ((rc = SQLFetchScroll(hstmt,SQL_FETCH_NEXT,0)) != SQL_NO_DATA) { for (i = 0; i < NumRowsFetched; i++) {
// Отображаем только успешно извлеченные строки (если // код ответа (rc) равен SQL_SUCCESS_WITH_INFO или // SQL_ERROR, то строку не выводим.)
if ((RowStatusArray[i] == SQL_ROW_SUCCESS) || (RowStatusArray[i] == SQL_ROW_SUCCESS_WITH_INFO)) { if (ID1IndArray[i] == SQL_NULL_DATA) printf(" NULL "); else printf("%d\t", ID1Array[i]); if (SalLenOrIndArray[i] == SQL_NULL_DATA) printf(" NULL "); else printf("%s\t", SalArray[i]); if (StatusLenOrIndArray[i] == SQL_NULL_DATA) printf(" NULL\n"); else printf("%s\n", StatusArray[i]); } } } // Закрываем курсор SQLCloseCursor(hstmt);
Связывание по строкам
При использовании связывания по строкам определяется структура, содержащая от одного до трех элементов для каждого столбца извлекаемых данных. Первый элемент предназначается для извлекаемых данных, второй - для длины / индикатора буфера, третий - для индикатора буфера при раздельном сохранении значений длины и индикатора (что определяется дескрипторами полей SQL_DESC_INDICATOR_PTR и SQL_DESC_OCTET_LENGTH_PTR).
Массив таких структур должен содержать количество элементов, равное числу строк в извлекаемом наборе строк.
Размер структуры (число строк) указывается атрибутом оператора SQL_ATTR_ROW_BIND_TYPE. При связывании используются адреса каждого члена первого элемента массива. Так, адрес конкретного значения из набора строк может быть вычислен следующим образом (нумерация строк с 1):
Адрес = Адрес_связывания + ((Номер_строки - 1) * Размер_структуры)
На следующем рисунке приведена схема связывания по строкам.

Следующий пример иллюстрирует применение связывания по строкам для набора строк, состоящего из трех столбцов.
#define ROW_ARRAY_SIZE 10
// Определяем структуру TBL_INFO и создаем массив // структур, содержащий 10 элементов typedef struct { SQLUINTEGER ID1; // Для значения 1 столбца SQLINTEGER ID1Ind; // Для длины/индикатора SQLCHAR Sal[11]; // Для значения 2 столбца SQLINTEGER SalLenOrInd; SQLCHAR Status[7]; // Для значения 3 столбца SQLINTEGER StatusLenOrInd; } ORDERINFO; TBL_INFO TBL_Array[ROW_ARRAY_SIZE]; // Массив структур
SQLUINTEGER NumRowsFetched; SQLUSMALLINT RowStatusArray[ROW_ARRAY_SIZE], i; SQLRETURN rc; SQLHSTMT hstmt;
// Определяем размер структуры, используя атрибут //оператора SQL_ATTR_ROW_BIND_TYPE, и одновременно // устанавливаем тип связывания по строкам SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_TYPE, sizeof(TBL_INFO), 0);
// Используя атрибут оператора SQL_ATTR_ROW_ARRAY_SIZE // устанавливаем количество строк в извлекаемом наборе // строк SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE, ROW_ARRAY_SIZE, 0);
// Используя атрибут оператора SQL_ATTR_ROW_STATUS_PTR // определяем массив состояния строк SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_STATUS_PTR, RowStatusArray, 0); // Устанавливаем атрибут оператора // SQL_ATTR_ROWS_FETCHED_PTR для указания на // NumRowsFetched SQLSetStmtAttr(hstmt, SQL_ATTR_ROWS_FETCHED_PTR, &NumRowsFetched, 0);
// Связываем поля структуры первого элемента массива // со столбцами ID1, Sal и Status SQLBindCol(hstmt, 1, SQL_C_ULONG, &TBL_Array[0].ID1, 0, &TBL_Array[0].ID1Ind); SQLBindCol(hstmt, 2, SQL_C_CHAR, TBL_Array[0].Sal, sizeof(TBL_Array[0].Sal), &TBL_Array[0].SalLenOrInd); SQLBindCol(hstmt, 3, SQL_C_CHAR, TBL_Array[0].Status, sizeof(TBL_Array[0].Status), &TBL_Array[0].StatusLenOrInd);
// Выполняем SQL-оператор SELECT для формирования // результирующего набора SQLExecDirect(hstmt, "SELECT ID1, Sal, Status FROM TBL1", SQL_NTS);
// Используя блочный курсор, извлекаем установленное //число строк while ((rc = SQLFetchScroll(hstmt,SQL_FETCH_NEXT,0)) != SQL_NO_DATA) { // Переменная NumRowsFetched содержит число // в действительности извлеченных строк for (i = 0; i < NumRowsFetched; i++) { if (RowStatusArray[i] == SQL_ROW_SUCCESS|| RowStatusArray[i] == SQL_ROW_SUCCESS_WITH_INFO) { if (TBL_Array[i].ID1Ind == SQL_NULL_DATA) std::cout<<" NULL "; else std::cout<< TBL_Array[i].ID1; if (TBL_Array[i].SalLenOrInd == SQL_NULL_DATA) std::cout<< " NULL "; else std::cout<< TBL_Array[i].Sal; if (TBL_Array[i].StatusLenOrInd == SQL_NULL_DATA) std::cout<< " NULL\n"; else std::cout<< TBL_Array[i].Status; } } } // Закрываем курсор SQLCloseCursor(hstmt);
Асинхронное выполнение функций
По умолчанию ODBC-драйверы применяют синхронное выполнение функций, когда приложение ожидает завершения выполнения функции. При асинхронном выполнении функций ODBC-драйвер возвращает управление приложению до окончательного завершения выполнения функции. В этом случае приложение продолжает выполняться дальше и имеет возможность синхронного или асинхронного вызова других функций.
В зависимости от используемого источника данных асинхронное выполнение осуществляется на основе оператора или соединения. Режим асинхронного выполнения можно определить при вызове функции SQLGetInfo с параметром, равным SQL_ASYNC_MODE: для асинхронного выполнения уровня соединения возвращается значение SQL_AM_CONNECTION, а для асинхронного выполнения уровня оператора - значение SQL_AM_STATEMENT.
Для определения того, что функция будет выполняться асинхронно на уровне оператора, следует вызовом функции SQLSetStmtAttr установить значение атрибута SQL_ATTR_ASYNC_ENABLE равным SQL_ASYNC_ENABLE_ON. Для определения асинхронного выполнения на уровне соединения следует вызовом функции SQLSetConnectAttr устанавливать значение атрибута соединения SQL_ATTR_ASYNC_ENABLE равным SQL_ASYNC_ENABLE_ON. При использовании асинхронного режима уровня соединения все операторы, ассоциируемые с данным соединением, могут выполняться в асинхронном режиме.
Количество максимально допустимых параллельно и асинхронно выполняемых операторов для конкретного драйвера определяется вызовом функции SQLGetInfo с параметром, равным SQL_MAX_ASYNC_CONCURRENT_STATEMENTS.
При асинхронном выполнении функции до завершения ее выполнения каждый повторный вызов этой функции с теми же параметрами возвращает код ответа SQL_STILL_EXECUTING. Так, повторяя вызовы через некоторый интервал времени, можно определять момент, когда будет завершено асинхронное выполнение функции.
Например:
SQLHSTMT hstmt1; // Дескриптор оператора SQLRETURN rc;
// Устанавливаем режим асинхронного выполнения SQLSetStmtAttr(hstmt1, SQL_ATTR_ASYNC_ENABLE, SQL_ASYNC_ENABLE_ON, 0); // Выполняем асинхронно оператор SELECT while ((rc=SQLExecDirect(hstmt1, "SELECT * FROM TBL1",SQL_NTS)) == SQL_STILL_EXECUTING) { // Действия, осуществляемые во время асинхронного // выполнения оператора SELECT (нельзя использовать // дескриптор hstmt1) } // Асинхронное выполнение завершено
Во время асинхронного выполнения оператора приложение может осуществлять синхронный или асинхронный вызов функций для других дескрипторов оператора. Для самого дескриптора оператора в период асинхронного выполнения может осуществляться только повторный вызов выполняемой функции или функций SQLCancel (прерывание вызова), SQLGetDiagField и SQLGetDiagRec (но только заголовки полей).
Для соединения, ассоциированного с асинхронно выполняемым оператором, возможен вызов следующих функций: SQLAllocHandle (для размещения дескриптора оператора), SQLGetDiagField, SQLGetDiagRec, SQLGetFunctions.
Пример:
SQLHDBC hdbc1, hdbc2; // Дескрипторы соединения SQLHSTMT hstmt1, hstmt2, hstmt3; SQLCHAR * SQLStatement = "SELECT * FROM TBL1"; SQLUINTEGER InfoValue; SQLRETURN rc; // Создание дескрипторов операторов SQLAllocHandle(SQL_HANDLE_STMT, hdbc1, &hstmt1); SQLAllocHandle(SQL_HANDLE_STMT, hdbc1, &hstmt2); SQLAllocHandle(SQL_HANDLE_STMT, hdbc2, &hstmt3); // Устанавливаем для hstmt1 режим асинхронного // выполнения SQLSetStmtAttr(hstmt1, SQL_ATTR_ASYNC_ENABLE, SQL_ASYNC_ENABLE_ON, 0); // Асинхронное выполнение для дескриптора hstmt1 while ((rc = SQLExecDirect(hstmt1, SQLStatement, SQL_NTS)) == SQL_STILL_EXECUTING) { // Следующий вызов вернет код ответа HY010, // т.к. дескриптор оператора hstmt1 пока " занят", // а второй вызов использует дескриптор hdbc1, в // котором размещен дескриптор оператора hstmt1 SQLExecDirect(hstmt1, SQLStatement, SQL_NTS); SQLGetInfo(hdbc1, SQL_UNION, (SQLPOINTER) &InfoValue, 0, NULL); // Ошибка с кодом HY010 // Следующие операторы будут выполнены без ошибки, // т.к для hdbc1 применяются дескрипторы оператора, //не используемые асинхронно в настоящий момент SQLExecDirect(hstmt2, SQLStatement, SQL_NTS); SQLTables(hstmt3,NULL,0,NULL,0,NULL,0,NULL,0); SQLGetInfo(hdbc2, SQL_UNION, (SQLPOINTER) &InfoValue, 0, NULL); }
Для отключения режима асинхронного выполнения операторов следует вызвать функцию SQLSetStmtAttr со значением атрибута SQL_ATTR_ASYNC_ENABLE, равным SQL_ASYNC_ENABLE_OFF (для уровня оператора) или функцию SQLSetConnectAttr со значением атрибута SQL_ATTR_ASYNC_ENABLE, равным SQL_ASYNC_ENABLE_OFF (для уровня соединения).
При вызове функции SQLGetDiagField, применяемой для получения диагностической информации, с дескриптором оператора, используемого асинхронно, возвращаются следующие значения:
Следующий пример иллюстрирует процесс получения диагностической информации:
SQLCHAR SqlState[6], SQLStmt[100], Msg[SQL_MAX_MESSAGE_LENGTH]; SQLINTEGER NativeError; SQLSMALLINT i, MsgLen; SQLRETURN rc1, rc2; SQLHSTMT hstmt; // В SQLStmt содержится SQL-оператор // Выполнение оператора и получение информации // о возникающих ошибках или предупреждениях rc1 = SQLExecDirect(hstmt, SQLStmt, SQL_NTS); if ((rc1 == SQL_SUCCESS_WITH_INFO) || (rc1 == SQL_ERROR)) { // Получение записей об ошибках i = 1; while ((rc2 = SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, i, SqlState, &NativeError, Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) { cout<< SqlState<<" " <
if ((rc1 == SQL_SUCCESS) || (rc1 == SQL_SUCCESS_WITH_INFO)) { // Оператор выполнен успешно }
Создание именованного курсора
Курсор, для которого определено имя, называется именованным курсором.
Ассоциировать имя курсора с активным дескриптором оператора можно вызовом функции SQLSetCursorName. В том случае, если эта функция не вызывается явным образом, драйвер при необходимости может генерировать имя курсора при выполнении SQL-оператора.
Функция SQLSetCursorName имеет следующее формальное описание:
SQLRETURN SQLSetCursorName( SQLHSTMT StatementHandle, SQLCHAR * CursorName, SQLSMALLINT NameLength);
Параметр StatementHandle ([Input]) указывает дескриптор оператора, а параметр CursorName ([Input]) задает назначаемое имя курсора. Отметим, что для эффективной обработки имя курсора не должно иметь лидирующих или завершающих пробелов.
Параметр NameLength ([Input]) определяет длину буфера *CursorName.
Следующий пример иллюстрирует применение функции SQLSetCursorName для получения имени курсора и выполнения затем позиционированного SQL-оператора UPDATE:
#define NAME_LEN 40 #define PHONE_LEN 12
SQLHSTMT hstmtSelect; // Дескриптор оператора // (для оператора SELECT) SQLHSTMT hstmtUpdate; // Дескриптор оператора // (для оператора UPDATE) SQLRETURN retcode; SQLHDBC hdbc; SQLCHAR szName[NAME_LEN], szPhone[PHONE_LEN]; SQLINTEGER cbName, cbPhone;
/* Создаем дескриптор оператора и определяем имя курсора */ SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmtSelect); SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmtUpdate); SQLSetCursorName(hstmtSelect, "C1", SQL_NTS);
/* Выполняем SQL-оператор SELECT для формирования результирующего набора и связываем его столбцы с локальными буферами для извлечения данных */ SQLExecDirect(hstmtSelect, "SELECT NAME, PHONE FROM Tbl1", SQL_NTS); SQLBindCol(hstmtSelect, 1, SQL_C_CHAR, szName, NAME_LEN, &cbName); SQLBindCol(hstmtSelect, 2, SQL_C_CHAR, szPhone, PHONE_LEN, &cbPhone);
/* Выбираем строки результирующего набора до тех пор, пока не найдем строку "Иванов А." */ do retcode = SQLFetch(hstmtSelect); while ((retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) && (strcmp(szName, "Иванов А.") != 0));
/* Выполняем позиционированный UPDATE для текущей выбранной строки именованного курсора */ if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) { SQLExecDirect(hstmtUpdate, "UPDATE Tbl2 SET PHONE=\"1373737\" WHERE CURRENT OF C1", SQL_NTS); }
Для получения имени курсора, ассоциированного с конкретным дескриптором оператора, используется функция SQLGetCursorName, которая имеет следующее формальное описание:
SQLRETURN SQLGetCursorName( SQLHSTMT StatementHandle, SQLCHAR * CursorName, SQLSMALLINT BufferLength, SQLSMALLINT * NameLengthPtr);
Параметр StatementHandle ([Input]) указывает дескриптор оператора.
Имя курсора возвращается в буфере, определяемом параметром CursorName ([Output]).
Параметр BufferLength ([Input]) задает длину буфера *CursorName в байтах. Параметр NameLengthPtr ([Output]) определяет число байтов (включая ограничивающий 0-символ), которые занимает имя курсора. Если размер выделенного буфера меньше, чем требуется под имя курсора, то возвращаемое имя усекается.
В том случае, если в приложении функция SQLSetCursorName для задания имени курсора не вызывается, то драйвер автоматически генерирует имя курсора, начинающееся с префикса SQL_CUR.
Имя курсора в основном используется для позиционированного изменения или удаления, которое указывается в SQL-операторе фразой WHERE CURRENT OF имя_курсора.
Отметим, что в ODBC 2.x в том случае, если курсор не был открыт и ему не было назначено имя при вызове функции SQLSetCursorName, то инициировалась ошибка с кодом ответа SQLSTATE HY015 (Нет имени курсора). В версии ODBC 3.x вне зависимости от того, вызывалась ли функция SQLSetCursorName, драйвер всегда возвращает имя курсора.
Пока курсор не открыт, разрешено его переименование. Для этого достаточно вызвать функцию SQLSetCursorName.
Явно или неявно созданное имя курсора остается установленным до тех пор, пока не будет разрушен дескриптор оператора при вызове функции SQLFreeHandle с параметром HandleType, равным SQL_HANDLE_STMT.
Объектная модель OLE DB
Спецификация OLE DB описывает набор интерфейсов, реализуемых объектами OLE DB. Каждый объектный тип определен как набор интерфейсов. Спецификация OLE DB определяет набор интерфейсов базового уровня, которые должны реализовываться любыми OLE DB провайдерами.
В базовую модель OLE DB входят следующие объекты:
На следующей схеме приведен пример использования интерфейсов базового уровня для создания результирующего набора.
Создание объекта: CoCreateInstance | ![]() | Объект DataSource | |
![]() | |||
| pIDBCreateSession->CreateSession | |||
![]() | |||
| Объект Session | |||
![]() | |||
| pIOpenRowset->OpenRowset | |||
![]() | |||
| Объект Rowset |
Спецификация OLE DB определяет объект Command (команда), предназначенный для выполнения текстовой команды. В качестве такой команды может выступать и SQL-оператор. При этом выполнение команды может создавать результирующий набор (в случае SQL-оператора - это оператор SELECT).
Некоторые OLE DB провайдеры поддерживают работу со схемой (Schema), которая предоставляет метаданные по базе данных. Метаданные становятся доступны как обычные результирующие наборы. В заголовочном файле oledb.h содержатся уникальные идентификаторы всех доступных типов результирующих наборов схемы данных (например, для получения информации по таблицам базы данных следует указать уникальный идентификатор DBSCHEMA_TABLES). Столбец результирующего набора с именем TABLE_NAME содержит имя таблицы, столбец TABLE_TYPE указывает один из следующих типов таблицы: ALIAS, TABLE, SYNONYM, SYSTEM TABLE, VIEW, GLOBAL TEMPORARY, LOCAL TEMPORARY, SYSTEM VIEW.
Представление (View) определяет подмножество строк и столбцов из набора данных, но само не содержит их. Представления не могут объединять данные из нескольких наборов данных.
Для обеспечения расширенных возможностей управления транзакциями объектная модель OLE DB включает объект Transaction.
OLE DB провайдеры, как и все COM-компоненты, регистрируются в реестре Windows. Для поиска информации о зарегистрированных источниках данных используются специальные объекты, называемые нумераторами. Нумератор - это обычный СОМ-сервер, позволяющий получить информацию об источниках данных в виде результирующего набора. Для создания такого результирующего набора в объектном типе DataSource специфицирован интерфейс IDBEnumerateSources.
Для каждого объектного типа спецификация OLE DB определяет набор интерфейсов, который должен обязательно быть реализован для данного объекта. Такие интерфейсы отмечаются как [mandatory]. Интерфейсы, которые могут отсутствовать, отмечаются как [optional].
Для объекта "источник данных" специфицирован следующий набор интерфейсов:
CoType TDataSource { [mandatory] interface IDBCreateSession; [mandatory] interface IDBInitialize; [mandatory] interface IDBProperties; [mandatory] interface IPersist; [optional] interface IConnectionPointContainer; [optional] interface IDBAsynchStatus; [optional] interface IDBDataSourceAdmin; [optional] interface IDBInfo; [optional] interface IPersistFile; [optional] interface ISupportErrorInfo; }
Для объекта "сеанс" специфицирован следующий набор интерфейсов:
CoType TSession { [mandatory] interface IGetDataSource; [mandatory] interface IOpenRowset; // Создание набора данных [mandatory] interface ISessionProperties; [optional] interface IAlterIndex; [optional] interface IAlterTable; [optional] interface IBindResource; [optional] interface ICreateRow; [optional] interface IDBCreateCommand; [optional] interface IDBSchemaRowset; [optional] interface IIndexDefinition; [optional] interface ISupportErrorInfo; [optional] interface ITableCreation; [optional] interface ITableDefinition; // Для создания таблицы [optional] interface ITableDefinitionWithConstraints; [optional] interface ITransaction; [optional] interface ITransactionJoin; [optional] interface ITransactionLocal; [optional] interface ITransactionObject; }
Для объекта " результирующий набор" специфицирован следующий набор интерфейсов:
CoType TRowset { [mandatory] interface IAccessor; [mandatory] interface IColumnsInfo; [mandatory] interface IConvertType; [mandatory] interface IRowset;// Последовательное // чтение таблицы [mandatory] interface IRowsetInfo; [optional] interface IChapteredRowset; [optional] interface IColumnsInfo2; [optional] interface IColumnsRowset; [optional] interface IConnectionPointContainer; [optional] interface IDBAsynchStatus; [optional] interface IGetRow; [optional] interface IRowsetChange; // Для удаления, изменения и добавления // строк в набор данных [optional] interface IRowsetChapterMember; [optional] interface IRowsetCurrentIndex; [optional] interface IRowsetFind; [optional] interface IRowsetIdentity; [optional] interface IRowsetIndex; [optional] interface IRowsetLocate; // Прямое // позиционирование на запись набора данных [optional] interface IRowsetRefresh; // Для // обновления данных в созданном наборе данных [optional] interface IRowsetScroll; // Поддержка // скроллинга по набору данных [optional] interface IRowsetUpdate; [optional] interface IRowsetView; [optional] interface ISupportErrorInfo; [optional] interface IRowsetBookmark; }
Все объекты объектного типа Rowset должны реализовывать следующие интерфейсы:
Объекты COMMAND
Перед использованием объекта Command следует определить, поддерживается ли данный объект. Для этого с помощью метода QueryInterface следует запросить интерфейс IDBCreateCommand объекта "сеанс".
Объект Command должен реализовывать следующие интерфейсы:
Для создания команды вызывается метод IDBCreateCommand::CreateCommand объекта "сеанс".
Например:
ICommandText pICommandText; HRESULT hr= pIDBCreateCommand-> CreateCommand( NULL, // если есть агрегирование, то указатель // на управляющий IUnknown IID_ICommandText, // Запрашиваемый интерфейс (IUnknown**)& pICommandText); // Указатель // на запрашиваемый интерфейс
Текст, выполняемый командой, устанавливается при вызове метода ICommandText::SetCommandText. При этом указывается уникальный идентификатор GUID синтаксиса команды (например, DBGUID_SQL (только для версий OLE DB начиная с 1.5)).
Например:
pICommandText->SetCommandText(DBGUID_SQL, "SELECT * FROM TBL1");
Для выполнения команды вызывается метод ICommand::Execute (этот метод наследуется интерфейсом ICommandText).
Например:
ULONG ulRs=0; IRowset** ppRowsets=NULL; HRESULT hr= pICommandText->Execute ( NULL, // если есть агрегирование, то указатель // на управляющий IUnknown IID_IRowset, // Запрашиваемый интерфейс NULL, // Указатель на структуру типа // struct DBPARAMS { // void *pData; // DB_UPARAMS cParamSets; // HACCESSOR hAccessor; //}; ulRs, // Количество строк, на которые // воздействовала команда INSERT, UPDATE //или DELETE (IUnknown**)& ppRowsets); // Указатель на // указатели наборов данных
Алгоритм выполнения команды приведен на следующей схеме:

До выполнения команды можно определить поведение создаваемого результирующего набора вызовом метода ICommandProperties::SetProperties.
Для многократного выполнения запроса и при использовании параметров следует вызвать метод ICommandPrepare::Prepare, а затем определить параметры вызовом метода ICommandWithParameters::SetParameterInfo.
Если в результате выполнения команды возвращается несколько результирующих наборов, то используется метод IMultipleResults::GetResult.
Создание результирующего набора
При реализации доступа к БД посредством OLE DB провайдера сначала следует создать объект данных и установить соединение с базой данных. Далее необходимо создать объект "сеанс". И только потом можно создавать результирующий набор.
Механизм создания объекта "сеанс" приведен на следующей схеме.

Результирующий набор может быть создан одним из следующих способов:
Чтобы результирующий набор, хранимый на сервере, можно было использовать, необходимо выполнить связывание и извлечение данных. Для этого следует определить структуры типа DBBINDING, описывающие столбцы, и создать аксессор. Далее для получения строк результирующего набора можно использовать один из следующих методов:
В заключение для записи данных в структуру, определенную аксессором, вызывается метод IRowset::GetData.
После получения и обработки строк их следует освободить, вызвав метод IRowset::ReleaseRows.
После просмотра всего результирующего набора следует также освободить аксессор, вызвав метод IRowset::ReleaseAccessor, и освободить сам результирующий набор, вызвав метод IRowset::Release.
Интерфейс IAccessor определяет следующие методы:
Для создания аксессора следует запросить интерфейс IAccessor и выполнить следующий код:
HRESULT hr=pIAccessor-> CreateAccessor();
Метод CreateAccessor имеет следующее формальное описание:
HRESULT CreateAccessor ( DBACCESSORFLAGS dwAccessorFlags, // Свойства // аксессора и как он используется DBCOUNTITEM cBindings, // Число связей // в аксессоре const DBBINDING rgBindings[], // Описание // столбца или параметра DBLENGTH cbRowSize, // Число байтов, // используемых для одного набора параметров HACCESSOR *phAccessor, // Указатель //на созданный аксессор DBBINDSTATUS rgStatus[]); // Массив значений, // определяющий статус // каждого связывания
Каждый столбец формируемого результирующего набора или параметр описывается структурой DBBINDING, которая имеет следующее формальное описание:
typedef struct tagDBBINDING { DBORDINAL iOrdinal; // Порядковый номер // столбца или параметра (начиная с 1) DBBYTEOFFSET obValue; // Сдвиг в байтах для // значения столбца или параметра в буфере // (указатель на буфер задается при // создании аксессора) DBBYTEOFFSET obLength; DBBYTEOFFSET obStatus; ITypeInfo *pTypeInfo; DBOBJECT *pObject; DBBINDEXT *pBindExt; DBPART dwPart; DBMEMOWNER dwMemOwner; DBPARAMIO eParamIO; DBLENGTH cbMaxLen; DWORD dwFlags; DBTYPE wType; BYTE bPrecision; BYTE bScale; } DBBINDING;
Поле wType определяет тип столбца или параметра, который описывается следующим образом:
typedef WORD DBTYPE; enum DBTYPEENUM { // Следующие значения точно соответствуют VARENUM // при автоматизации и не могут быть использованы // как VARIANT. DBTYPE_EMPTY = 0, // Значение отсутствует, // соответствующего типа С нет DBTYPE_NULL = 1, // Значение равно NULL, // соответствующего типа С нет DBTYPE_I2 = 2, // Двухбайтовое целое со знаком, // соответствует С типу short DBTYPE_I4 = 3, // Четырехбайтовое целое со знаком, // соответствует С типу long DBTYPE_R4 = 4, DBTYPE_R8 = 5, // Вещественное двойной точности, // соответствует С типу Double
DBTYPE_CY = 6, // Тип для значения Cyrrency DBTYPE_DATE = 7, // Тип для значения даты // (дата хранится в виде вещественного числа: // целочисленная часть определяет дату, // а дробная - время) DBTYPE_BSTR = 8, // Указатель на строку BSTR DBTYPE_IDISPATCH = 9, // Указатель на интерфейс // IDispatch DBTYPE_ERROR = 10, // 32-битовый код ошибки DBTYPE_BOOL = 11, // Для логического значения DBTYPE_VARIANT = 12, // Для значения VARIANT DBTYPE_IUNKNOWN = 13, // Указатель на интерфейс // IUnknown DBTYPE_DECIMAL = 14, DBTYPE_UI1 = 17, // Однобайтовое беззнаковое целое, // соответствует С типу byte DBTYPE_ARRAY = 0x2000, DBTYPE_BYREF = 0x4000, DBTYPE_I1 = 16, DBTYPE_UI2 = 18, DBTYPE_UI4 = 19,
// Следующие значения точно соответствуют VARENUM // при автоматизации, но не могут быть использованы // как VARIANT. DBTYPE_I8 = 20, DBTYPE_UI8 = 21, DBTYPE_GUID = 72, // Для уникального идентификатора GUID DBTYPE_VECTOR = 0x1000, DBTYPE_FILETIME = 64, DBTYPE_RESERVED = 0x8000,
// Следующие значения недопустимы в VARENUM для OLE. DBTYPE_BYTES = 128, DBTYPE_STR = 129, DBTYPE_WSTR = 130, DBTYPE_NUMERIC = 131, DBTYPE_UDT = 132, DBTYPE_DBDATE = 133,// Для даты, определяемой // как структура // Typedef struct tagDBDATE { // SHORT year; // USHORT month; // USHORT day; // } DBDATE; DBTYPE_DBTIME = 134, DBTYPE_DBTIMESTAMP = 135 // Для даты и времени, // определяемых как структура // Typedef struct tagDBTIMESTAMP { // SHORT year; // USHORT month; // USHORT day; // USHORT hour; // USHORT minute; // USHORT second; // ULONG fraction; } DBTIMESTAMP;
DBTYPE_HCHAPTER = 136 DBTYPE_PROPVARIANT = 138, DBTYPE_VARNUMERIC = 139 };
Библиотека MFC
Среда Visual Studio.NET предоставляет различные подходы для реализации работы с базами данных:
Библиотека MFC реализует поддержку доступа к базам данных, основанную на двух механизмах:
Приложения, использующие доступ к базам данных с применением ODBC-драйверов, в обязательном порядке используют класс CDatabase для создания объекта "база данных" и класс CRecordset для создания объекта "результирующий набор".
Библиотека MFC предоставляет для работы с базами данных через ODBC-драйверы следующие классы:
Библиотека MFC предоставляет для работы с базами данных с применением провайдеров OLE DB класс COleDBRecordView, используемый для отображения данных из результирующего набора (представляемого классом CRowset) в элементах управления шаблона диалога. Этот класс объявляется в заголовочном файле afxoledb.h.
Для реализации клиентов и серверов OLE DB можно использовать ATL, предоставляющую OLE DB шаблоны как С++ шаблоны. Более подробно шаблоны ATL будут рассмотрены в следующей лекции.
Библиотека MFC предоставляет следующие два класса, упрощающие обмен данными:
Для доступа к конкретной базе данных требуется, чтобы был установлен соответствующий драйвер ODBC или OLE DB провайдер.
Приложения, работающие с базами данных, могут быть реализованы как консольные приложения, приложения с архитектурой документ-отображение (SDI- и MDI-приложения), приложения-диалоги, серверные приложения.
Класс CDATABASE
Класс CDatabase предоставляет средства подключения к источнику данных. В приложении может использоваться несколько активных объектов CDatabase.
Для использования класса CDatabase следует подключить заголовочный файл afxdb.h.
Член класса CDatabase::m_hdbc является указателем на подсоединенный ODBC источник данных. Он может использоваться как дескриптор соединения при непосредственном вызове функций ODBC.
Например:
// Использование m_hdbc для непосредственного вызова // ODBC API. // m_db – объект типа CDatabase, // m_hdbc – член класса CDatabase nRetcode = ::SQLGetInfo( m_db.m_hdbc, SQL_ODBC_SQL_CONFORMANCE, &nValue, sizeof( nValue ), &cbValue );
Класс CDatabase имеет один конструктор без параметров и набор методов, включая следующие:
OpenEx (LPCTSTR lpszConnectString, DWORD dwOptions = 0);throw (CDBException, CMemoryException ); - метод, выполняющий открытие базы данных. Первый параметр определяет ODBC строку подключения, описывающую источник данных, и некоторую дополнительную информацию, такую как идентификатор пользователя и пароль.
Например: "DSN=SQLServer_Source;UID=Us1;PWD=pwd1".
Второй параметр по умолчанию равен нулю, что предполагает открытие базы данных с разделяемым доступом и с правами записи, без загрузки ODBC Cursor Library DLL и без принудительного отображения диалога ODBC, определяющего информацию для подключения к базе данных. Этот параметр может быть задан как битовая маска, определяемая комбинацией следующих значений:
CDatabase::forceOdbcDialog – обеспечивает отображение диалога с целью определения информации для ODBC-соединения.
Например:
// Объявление объекта типа CDatabase CDatabase m_db1; // Объект CDatabase: открытие источника данных // "только на чтение" m_db1.OpenEx( _T( "DSN=MYDATASOURCE;UID=U1" ), CDatabase::openReadOnly | CDatabase::noOdbcDialog ) );
После закрытия соединения объект типа CDatabase можно использовать для открытия другого источника данных;
ExecuteSQL – метод, выполняющий SQL-оператор без возвращения результирующего набора.
Например:
CString strCmd = "UPDATE TBL1 SET F1 = 124"; TRY { m_db1.ExecuteSQL( strCmd );} CATCH(CDBException, e) { // Код ошибки в e->m_nRetCode } END_CATCH
Класс CRECORDSET
Класс CRecordset реализует операции над результирующим набором, получаемым из источника данных при выполнении SQL-оператора SELECT.
Результирующий набор может использоваться в двух режимах:
Класс CRecordset позволяет:
Для использования набора записей из базы данных следует:
Метод Open создает (открывает) результирующий набор.
Член класса CRecordset::m_hstmt содержит указатель на структуру данных типа HSTMT (дескриптор оператора).
Класс CRecordset содержит переменные и методы для работы с результирующим набором, включая следующие:
Например:
CMySet rsMySet( NULL ); // Класс CMySet наследуется от CRecordset // Определение фильтра (условия во фразе WHERE) rsMySet.m_strFilter = "field2 > 123"; // Выполнение запроса – открытие результирующего набора rsMySet.Open( CRecordset::snapshot, "MyTbl1" );
Например:
rsSet.Open( ); // Результирующий набор открыт: // текущая запись - первая if( rsSet.IsBOF( ) ) return; // Результирующий набор пуст while ( !rsSet.IsEOF( ) ) // Просмотр всех // записей от начала до конца rsSet.MoveNext( ); rsSet.MoveLast( ); // Переход к последней записи while( !rsSet.IsBOF( ) ) // Просмотр всех // записей от конца до начала rsSet.MovePrev( ); rsSet.MoveFirst( ); // Переход к первой записи
Например:
rsSet.Edit( ); // Начало операций изменения записи rsSet.m_dwF1 = 123; rsSet.m_strF1 = "abc"; if( !rsCustSet.Update( ) ) // Внесение изменений
Например:
void CSet::DoFieldExchange(CFieldExchange* pFX) { pFX->SetFieldType(CFieldExchange::outputColumn); RFX_Int(pFX, "F1", m_wF1); // Вызов RFX-метода RFX_Text(pFX, "F2", m_strF2); }
Создание приложений баз данных
Средства MFC Application Wizard позволяют практически без всякого программирования разрабатывать приложения, выполняющие подключение к таблице базы данных и отображение содержимого ее полей. При этом для реализации доступа к базе данных используется или механизм ODBC, или OLE DB.
Для того чтобы использовать ODBC-драйверы, первоначально следует создать источник данных DSN (Data Source Name). Это можно сделать как в момент формирования проекта с помощью MFC Application Wizard, так и используя ODBC32 панели управления Windows.
При создании источника данных определяется имя источника данных, используемое приложениями, выбирается требуемый для подключения к базе данных ODBC-драйвер и указывается местоположение самой базы данных. Определение имени источника данных выполняется только один раз и затем может быть многократно применяться для создания всех приложений, использующих этот же ODBC-драйвер для этой же базы данных.
Для того, чтобы создать приложение, реализующее доступ к базе данных, выполните следующие действия:

Рис. 10.1. Страница Database Support мастера MFC Application Wizard
Если для Database Support была установлена опция Database view without file support или Database view with file support, то для механизма ODBC создаваемый класс отображения будет наследоваться от CRecordView и будет ассоциирован с классом результирующего набора, наследуемого от CRecordset. Для механизма OLE DB класс отображения наследуется от COleDBRecordView и ассоциируется с классом, наследуемым от CTable или CCommand.

Рис. 10.2. Диалог для выбора источника данных (для ODBC)

Рис. 10.3. Диалог для выбора источника данных (для OLE DB)

Рис. 10.4. Диалог Select Database Object
Созданное AppWizard приложение будет содержать панель инструментов с кнопками для перехода между записями таблицы базы данных, но не будет содержать элементов управления для отображения полей таблицы базы данных.
Мастер MFC Application Wizard создает в классе, наследуемом от Crecordset, переменные члены класса для каждого поля таблицы подключаемой базы данных.
Для того чтобы отобразить в форме поля базы данных, следует:
Вставить в метод DoDataExchange класса отображения вызовы методов DDX_FieldText (или DDX_FieldCheck, DDX_FieldRadio, DDX_FieldSlider и т.п.), выполняющие связь между идентификатором ресурса и переменной – членом класса результирующего набора.
Например:
void CP2View::DoDataExchange(CDataExchange* pDX) { CRecordView::DoDataExchange(pDX); DDX_FieldText(pDX, IDC_EDIT1, // Идентификатор ресурса m_pSet->m_f1, // Переменная — член // класса, наследуемого // от CRecordset m_pSet); // Член класса, наследуемого // от CRecordView: CP2Set* m_pSet; // Член класса, наследуемого от // CDocument: CP2Set m_P2Set; // Метод OnInitialUpdate класса // наследуемого от CRecordView : // CDocument: CP2Set m_P2Set; // m_pSet = &GetDocument()->m_P2Set; DDX_FieldText(pDX, IDC_EDIT2, m_pSet->m_f2, m_pSet); }
В результате выполненных действий записи базы данных будут отображаться в окне документа в соответствующих элементах управления.
Добавление строк
Метод CRowset::Insert создает новую строку, используя данные из аксессора, и вставляет ее после текущей строки.
Метод Insert имеет следующее формальное описание:
HRESULT Insert(int nAccessor = 0, bool bGetRow = false)
Параметр bGetRow определяет, какая строка станет текущей: значение false (по умолчанию) указывает, что текущей будет новая вставленная строка; значение true указывает, что текущая строка не изменится.
В следующем примере иллюстрируется добавление новой строки после 10-й строки результирующего набора:
CTable
// Открываем результирующий набор tbl1.Open(session, "Product", &ps, 1); // ps - набор // свойств tbl1.MoveToBookmark(&bookmark, 0); // Переход // на 10-ю строку // Устанавливаем значение столбцов tbl1.m_F1 = 2020; _tcscpy( tbl1.m_F2, _T( "ABCD" ) ); tbl1.m_F3 = 137137; tbl1.m_F4 = 456; _tcscpy( tbl1.m_F5, _T( "123456789" ) ); tbl1.m_F6 = 3; tbl1.m_F7 = false;
// До добавления строки следует инициализировать поля // состояния и длины // Состояние столбцов m_dwF1Status = DBSTATUS_S_ISNULL; m_dwF2Status = DBSTATUS_S_ISNULL; m_dwF3Status = DBSTATUS_S_ISNULL; m_dwF4Status = DBSTATUS_S_ISNULL; m_dwF5Status = DBSTATUS_S_ISNULL; m_dwF6Status = DBSTATUS_S_ISNULL; m_dwF7Status = DBSTATUS_S_ISNULL; // Длина строк m_dwF2Length = 4; // "ABCD" - это 6 символов m_dwF5Length = 10; // "123456789" - это 9 символов // Добавление строки HRESULT hr = tbl1.Insert( );
Поля статуса и длины для каждого поля записываются в таблице столбцов, указываемой после BEGIN_COLUMN_MAP (при использовании мастера ATL OLE DB Consumer эта таблица формируется автоматически).
Например:
[db_source("insert connection string")] [db_command(" SELECT \ Au_ID, \ Author, \ ` Year Born`, \ FROM TblAuthors")]
class CAuthors { public: DWORD m_dwAuIDStatus; // Поля статуса DWORD m_dwAuthorStatus; DWORD m_dwYearBornStatus;
DWORD m_dwAuIDLength; // Поля длины DWORD m_dwAuthorLength; DWORD m_dwYearBornLength;
BEGIN_COLUMN_MAP(CTblAuthorsAccessor) // Таблица // столбцов COLUMN_ENTRY_LENGTH_STATUS(1, m_AuID, dwAuIDLength, dwAuIDStatus) COLUMN_ENTRY_LENGTH_STATUS(2, m_Author, dwAuthorLength, dwAuthorStatus) COLUMN_ENTRY_LENGTH_STATUS(3, m_YearBorn, dwYearBornLength, dwYearBornStatus) END_COLUMN_MAP()
[ db_column(1, status=m_dwAuIDStatus, length=m_dwAuIDLength) ] LONG m_AuID; [ db_column(2, status=m_dwAuthorStatus, length=m_dwAuthorLength) ] TCHAR m_Author[51]; [ db_column(3, status=m_dwYearBornStatus, length=m_dwYearBornLength) ] SHORT m_YearBorn;
void GetRowsetProperties(CDBPropSet* pPropSet) { pPropSet->AddProperty(DBPROP_IRowsetChange, true); pPropSet->AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE); } };
Для добавления входа в таблицу столбцов могут использоваться следующие макросы:
Подключение к БД
Для подключения к базе данных следует создать объект типа CDataSource, затем объект типа CSession. Далее, уже используя объект "сеанс", можно реализовывать работу с таблицами базы данных.
В следующем примере иллюстрируется подключение к базе данных с использованием OLE DB провайдера, создание сеанса и получение доступа к таблице TBL1:
#include
CDataSource con; CSession session;
CTable
// Устанавливаем соединение с базой данных con.Open(CLSID_MSDASQL,"MyDB1","user1","psw1"); // Создаем сеанс session.Open(connection); // Открываем таблицу c именем TBL1 (создаем результирующий набор). // Структура таблицы должна соответствовать классу записи CTBL1 Tbl1.Open(session, "TBL1"); // Извлекаем данные из результирующего набора while (Tbl1.MoveNext() == S_OK) { cout << Tbl1.m_szF1; // Записываем в стандартный // поток вывода значение // первого поля (класс CTBL1) cout << Tbl1.m_szF2; } // Класс записи CTBL1 class CTBL1 { public: // Поля записи CHAR m_szF1[10]; CHAR m_szF2[12]; short m_nF3; // Таблица связывания столбцов // Каждый вход таблицы сопоставляет номер столбца // с полем в классе записи. BEGIN_COLUMN_MAP(CTBL1) COLUMN_ENTRY(1, m_szF1) COLUMN_ENTRY(2, m_szF2) COLUMN_ENTRY(3, m_nF3) END_COLUMN_MAP()
Редактирование результирующего набора
Технология OLE DB позволяет выполнять редактирование результирующего набора, добавляя, удаляя или изменяя записи.
Для выполнения этих действий в классе CRowset реализован интерфейс IRowsetChange, предоставляющий следующие методы:
Для редактирования записей должно быть соответствующим образом установлено значение свойства DBPROP_UPDATABILITY результирующего набора. Это можно выполнить как в мастере ATL OLE DB Consumer, так и впоследствии вызовом метода AddProperty.
Тип поддерживаемого редактирования указывается следующими константами:
Например:
CDBPropSet ps(DBPROPSET_ROWSET); ps.AddProperty(DBPROP_IRowsetChange, true) ps.AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE)
На следующей схеме приведена иерархия классов результирующих наборов:

Выборка данных
Для того чтобы приступить к выборке данных, предварительно должны быть открыты источник данных и сеанс. В зависимости от используемого типа аксессора, может потребоваться связывание данных.
На следующей схеме приведена иерархия классов аксессоров.

Класс записи должен содержать переменные члены класса, соответствующие (по типам) полям создаваемого результирующего набора.
Для извлечения данных достаточно выполнить следующие действия:
Например:
// Получение информации о столбцах ULONG ulColumns = 0; DBCOLUMNINFO* pColumnInfo = NULL; LPOLESTR pStrings = NULL; // rc - результирующий набор if (rs.GetColumnInfo(&ulColumns, &pColumnInfo, &pStrings) != S_OK) ThrowMyOLEDBException(rs.m_pRowset, IID_IColumnsInfo); // Ошибка выполнения struct MYBIND* pBind = new MYBIND[ulColumns]; // Создание аксессора rs.CreateAccessor(ulColumns, &pBind[0], sizeof(MYBIND)*ulColumns); // Цикл по числу столбцов for (ULONG l=0; l
При использовании класса типа CAccessor обязательно должен быть класс записи, переменные члены класса которого соответствуют полям таблицы базы данных. Эти члены класса используются для доступа к данным результирующего набора.
Например:
while (rs.MoveNext() == S_OK) { cout<
При использовании классов типа CDynamicAccessor или CDynamicParameterAccessor данные извлекаются вызовом функций GetValue и GetColumn. При этом тип данных может быть определен вызовом функции GetType.
Например:
while (rs.MoveNext() == S_OK) { // Определение числа столбцов ULONG ulColumns = rs.GetColumnCount(); for (ULONG i=0; i
При использовании класса типа CManualAccessor следует определить переменные для извлечения значений столбцов, связать эти переменные со столбцами, и извлечение данных будет выполняться в эти переменные.
Например:
while (rs.MoveNext() == S_OK) { // использование переменных, определенных // при вызове AddBindEntry. cout<
Класс TDATABASE
Класс TDatabase реализует работу с объектом "база данных" и предоставляет средства контроля над соединением с базой данных.
Компонент типа TDatabase позволяет управлять транзакциями, использовать настраиваемое подключение к серверу, определять в приложении свои псевдонимы для базы данных.
Для работы с компонентом TDatabase необходимо установить значение свойств AliasName и DatabaseName.
Если значением свойства AliasName указан DSN существующего источника данных, то разработчик может сам определить любой внутренний (для приложения) псевдоним базы данных и задать его в свойстве DatabaseName. В этом случае для любого набора данных в списке значений свойства DatabaseName будет отображаться наряду со всеми доступными DSN источниками данных и внутренний псевдоним, заданный свойством DatabaseName компонента TDatabase.
В том случае, если DSN не определен, то свойство DatabaseName должно содержать полное имя файла базы данных, а свойство DriverName - указывать используемый ODBC-драйвер.
Компонент типа TDatabase позволяет управлять режимами работы с наборами данных и транзакциями, используя следующие свойства и методы.
TransIsolation - метод, задающий уровень изоляции при управлении транзакциями. Уровень изоляции определяет, как данная транзакция будет взаимодействовать с другими транзакциями, работающими с одними и теми же таблицами.
Свойство TransIsolation может быть указано одним из следующих значений:
tiDirtyRead - транзакция может читать данные, которые были изменены другой транзакцией, но для которых не был выполнен вызов Commit (фиксация изменений);
tiReadCommitted - позволяет в одной транзакции читать фиксированные изменения, сделанные в базе данных другой транзакцией;
tiRepeatableRead - истинность данных гарантируется на все время чтения, и транзакция не видит никаких изменений, сделанных другой транзакцией. Прочитанная запись остается постоянной, пока в ней не будут сделаны изменения внутри самой транзакции;
Класс TDATASET
Класс TDataSet является базовым для всех классов наборов данных, наследующих общие свойства и методы этого класса, включая следующие:
Fields - свойство, представляющее собой массив полей набора данных и используемое для доступа к этим полям.
Например:
Table1.Edit; // Включение режима редактирования Table1.Fields.Fields[6].AsString := Edit1.Text; Table1.Post; // Присвоение изменений
Свойство Fields позволяет:
Например:
var S: String; begin S := Fields[0].FieldName; // Имя первого поля S := Fields[1].FieldName; // Имя второго поля ... end;
Например:
var s: String; i: Integer; d: TDateTime; s := Fields[0].AsString; i := Fields[0].AsInteger; d := Fields[0].AsDate;
Вместо Fields[n] можно использовать функцию FieldsByName('имя_поля').
Например:
s := Fields[0].AsString; s := FieldsByName('F1').AsString;
FieldByName - метод, определяющий поле набора данных по его имени;
Например:
Table1.FieldByName('F1').AsInteger:=StrToInt(Edit1.Text);
Filter - свойство, в которое заносится строка, определяющая фильтр для набора данных. Фильтр определяет условие, которому должны удовлетворять доступные записи.
Определение фильтра должно удовлетворять следующим правилам:
Например: F2 > 10 AND F2 <50;
State - свойство, указывающее текущее состояние набора данных. Это свойство может принимать следующие значения:
Класс TDATASOURCE
Класс TDataSource реализует связь между компонентами - наборами данных и элементами управления, используемыми для отображения данных.
При построении отношений между таблицами "родительская-дочерняя" компонент "источник данных" служит для связывания наборов данных, указывая родительский набор данных.
Класс TDataSource содержит набор свойств и методов, используемых для доступа к набору данных, включая следующие:
DataSet - свойство, указывающее используемый набор данных.
Изменяя значение свойства DataSet во время выполнения, можно эффективно переключаться на работу с различными наборами данных, отображая разные наборы данных в одних и тех же элементах управления.
Например: DataSource.DataSet := Table1;.
State - свойство, позволяющее определить состояние используемого набора данных.
Например:
if DataSource1.Dataset <> nil then //Кнопка доступна только в том случае, если набор //данных находится в состоянии редактирования //или вставки новой записи BtnPost1.Enabled := DataSource1.State in [dsEdit, dsInsert];
Класс TQUERY
Компонент типа TQuery позволяет выполнять любой SQL-оператор, допустимый по синтаксису ODBC-драйвером. Если в качестве выполнимого оператора используется SQL-оператор SELECT, то компонент возвращает набор данных (результирующий набор).
В отличие от класса Ttable, класс TQuery позволяет создавать наборы данных из нескольких таблиц, а также ограничивать получаемый набор данных определенными условиями. Это отменяет необходимость извлечения всех записей таблицы в набор данных, что, в свою очередь, экономит память, сокращает сетевой трафик для удаленных баз данных и уменьшает время доступа.
Для определения набора данных TQuery следует установить значение свойства SQL и, возможно, свойства DatabaseName (свойство DatabaseName определяет имя источника данных, но для некоторых баз данных можно задать полное имя таблицы, включающее месторасположение файла, в тексте SQL-оператора, - в этом случае свойство DatabaseName не используется). Наиболее правильным подходом все же следует считать тот, при котором имя DSN источника данных указывается свойством DatabaseName, а в SQL-операторе определяется только имя таблицы без определения ее местоположения.
По умолчанию, набор данных, формируемый компонентом типа TQuery, не является редактируемым.
Для того чтобы значения в созданном наборе данных можно было редактировать, необходимо выполнить одно из следующих действий:
Класс TQuery содержит свойства и методы, используемые для работы с набором данных, включая следующие:
DataSource - свойство, позволяющее указать родительский набор данных (для отношения "родительский-дочерний").
Например, если свойство SQL содержит значение 'SELECT * FROM Tbl1 t WHERE (t.FNo = :FNo)', то значение переменной связи :FNo будет определяться из источника данных, указанного свойством DataSource.
Params - свойство, содержащее список параметров для SQL-оператора.
Например:
Query3.SQL.Clear; // Очищаем значение свойств // Динамически формируем код SQL-оператора INSERT Query3.SQL.Add('INSERT INTO ADDRESS (F1, F2)'); // Имена параметров указываются после символа : Query3.SQL.Add('VALUES (:F1, :F2)'); // Устанавливаем значение параметров Query3.Params[0].AsString := 'Abc'; Query3.Params[1].AsInteger := 123; Query3.ExecSQL; // Выполнение SQL-оператора
ExecSQL - метод, выполняющий SQL-оператор, указанный свойством SQL (для SQL-оператора, создающего набор данных, вместо ExecSQL используется метод Open).
ExecSQL можно вызывать для таких SQL-операторов как INSERT, UPDATE, DELETE, CREATE TABLE и т. п.
Если перед вызовом ExecSQL не был вызван метод Prepare, то SQL-оператор будет одновременно и откомпилирован, и выполнен.
Prepare - метод, выполняющий компиляцию SQL-оператора.
Вызов этого метода перед ExecSQL увеличивает скорость выполнения запроса при многократном повторении вызовов ExecSQL для одного и того же оператора (например, параметризированного запроса). Это позволяет откомпилировать SQL-оператор только один раз, а затем многократно его выполнять.
Класс TTABLE
Компонент типа TTable используется для доступа к базам данных посредством определения источника данных DSN и имени таблицы базы данных. При этом допускается выбор всех полей таблицы или только части полей, а также задание фильтра, определяющего, какие строки таблицы будут доступны.
Компоненты типа TTable могут использовать все свойства и методы, наследуемые от класса TDataSet, а также свойства и методы класса TTable для набора данных, включая следующие:
FindNearest - метод, перемещающий курсор на запись, содержащую значение, наиболее близкое к указанному значению ключевого поля (поиск может выполняться как по одному значению, так и по нескольким, если используется составной индекс).
Например:
{Изменение значения в компоненте Edit1 автоматически перемещает позицию курсора в наборе данных Table1} procedure TForm1.FormActivate(Sender: TObject); begin Table1.DatabaseName := 'DBDemos'; Table1.TableName := 'Customer.db'; Table1.Active := True; Table1.IndexName := 'ByCompany'; {Ключевое поле} end; {Обработчик события OnChange (изменение значения) для компонента Edit1} procedure TForm1.Edit1Change(Sender: TObject); begin Table1.FindNearest([Edit1.Text]); {Выполнение поиска} end;
Locate - метод, используемый для поиска первого вхождения значения указанного поля или набора полей (если запись найдена, то она становится текущей).
Например:
with Table1 do Locate('F1;F2', VarArrayOf(['ABC', 123]), [loPartialKey]);
Классы компонентов управления данными
Компоненты управления данными расположены на странице Data Controls палитры компонентов. Многие из этих компонентов аналогичны элементам управления страницы Standard, с тем лишь отличием, что связаны через источник данных (компонент типа TDataSource) с определенным полем (или полями) из набора данных (компонентов типа TTable или TQuery).
Библиотека VCL предоставляет следующие классы компонентов управления данными:
TDBNavigator - класс, предоставляющий средства навигации по набору данных, а также возможности добавления новых записей, включения режима редактирования, присвоения и отмены сделанных изменений. Для того чтобы программно инициировать действие, выполняемое по щелчку на кнопке навигатора, следует вызвать метод BtnClick. Например:
DBNavigator1.BtnClick(nbPost); // Присвоение сделанных изменений.
Компонент TDBNavigator может отображать кнопки, указываемые следующими константами:
nbFirst - переход к первой записи;
nbPrior - переход к предыдущей записи;
nbNext - переход к следующей записи;
nbLast - переход к последней записи;
nbInsert - вставка перед текущей записью новой записи и переход на нее;
nbDelete - удаление текущей записи;
nbEdit - переход в режим редактирования текущей записи;
nbPost - внесение изменений текущей записи в базу данных;
nbCancel - отмена изменений, сделанных в текущей записи;
nbRefresh - повторное считывание значений полей из источника данных.
Применение такого объекта предоставляет пользователю удобную возможность устанавливать значение поля базы данных, выбирая его из предлагаемых опций.
Механизмы доступа к БД
VCL-библиотека классов среды проектирования Delphi предоставляет ряд классов, позволяющих быстро и эффективно разрабатывать различные приложения баз данных.
Эти классы представлены следующими группами:
Основными механизмами доступа к данным, поддерживаемым в Delphi, являются:
Самый простой механизм управления данными, использующий ODBC-драйверы, может быть реализован по следующей схеме:
Свойство DataSet компонента типа TDataSourse указывает набор данных, формируемый компонентами таких классов как TTable или TQuery. Если компоненты набора данных и источника данных расположены в модуле данных, то их следует добавить в проект (команда меню File | Use unit).
Графически схему работы с базами данных для двухзвенных архитектур в среде Delphi можно представить следующим образом:
![]() ![]() | ![]() ![]() | ![]() ![]() | ![]() ![]() | |||
![]() ![]() | ![]() ![]() | ![]() ![]() | ![]() ![]() | |||
![]() ![]() | ![]() ![]() | ![]() ![]() | ![]() ![]() | |||
| TADODataSet, TADOTable, TADOQuery | TSQLDataSet, TSQLQuery, TSQLTable, TSQLStoreProc, TSQLClientDataSet | TIBDataSet, TIBTable, TIBQury | ||||
![]() ![]() | ![]() ![]() | ![]() ![]() | ![]() ![]() | |||
![]() ![]() | ||||||
Графически схема сохранения данных из БД в XML-формате приведена на следующей схеме:
![]() ![]() | |||
(свойство DataSet) | |||
(свойство ProviderName) | Провайдер данных TDataSetProvider|||
(метод SaveToFile) | (метод LoadFromFile) | ||
Наборы данных
Предком всех классов наборов данных является класс TDataSet. Он определяет основу структуры всех наборов данных - массив компонентов типа TField (каждый элемент массива соответствует столбцу таблицы).
Набор данных - это упорядоченная последовательность строк, извлеченных из источника данных. Каждая строка набора данных состоит из полей, указываемых в свойствах класса.
В зависимости от механизма доступа, используемого приложением, базовыми классами набора данных могут быть:
На следующей схеме приведена иерархия классов наборов данных библиотеки VCL:

Для определения набора данных необходимо задать следующие свойства:
Для того чтобы читать данные из таблиц или записывать их в таблицы, набор данных предварительно должен быть открыт.
Открыть набор данных можно одним из следующих способов:
Аналогично, закрыть набор данных можно вызовом метода Close или установив значение свойства Active равным False. Для компонента типа TQuery метод Open может быть выполнен только для закрытого набора данных: попытка открыть уже открытый набор данных инициирует ошибку.
Открытие набора данных влечет за собой:
Если в момент открытия набора данных произошла ошибка, то состояние набора данных устанавливается в dsInactive, а курсор закрывается.
При работе с компонентами наборов данных можно обойтись без явного использования компонентов, реализующих соединение с базой данных. Однако некоторые возможности, такие как управление транзакциями или кэшированные обновления, невозможны без компонентов типа TDatabase или TADOConnection. Компонент "база данных" TDatabase применяется для соединения с источником данных через драйверы BDE или внешние ODBC-драйверы. Компонент TADOConnection используется для создания объекта "соединение" при доступе через OLE DB, который инкапсулируется посредством ADO-объектов VCL-библиотеки.
По умолчанию при переходе от одной записи набора данных к другой происходит запись всех сделанных изменений в базу данных. Для того чтобы можно было отменять сделанные изменения или выполнять обновление нескольких записей, применяют кэшированные обновления. Они позволяют значительно снизить сетевой трафик за счет того, что все сделанные изменения хранятся во внутреннем кэше и при переходе от одной записи к другой информация в базу данных не передается. Чтобы включить режим кэшированного обновления, следует установить значение свойства CachedUpdates равным True для компонента набора данных. Для присвоения кэшированного обновления вызывается метод ApplyUpdates, а для отмены - CancelUpdates.
Применение многозвенных архитектур
Применение многозвенной архитектуры позволяет вынести бизнес-логику работы с данными в приложение-сервер.
При многозвенной архитектуре приложение разбивается на ряд компонентов, которые могут выполняться на различных компьютерах.
Применение трехзвенной архитектуры приведено на следующей схеме.
| приложение-клиент | - | приложение-сервер Приложение Remote Data Module | |||
| Компонент TDCOMConnection | (свойство ServerName или ServerGUID) | ||||
(свойство RemoteServer) | |||||
| Компонент TClientDataSet | (свойство ProviderName) | Удаленный модуль данных - компонент TDataSetProvider | |||
(свойство Dataset) | (свойство DataSet) | ||||
| Компонент TDataSource | Набор данных - компонент TTable или Tquery | - | ![]() | ||
Такая архитектура позволяет реализовывать доступ к серверу БД из приложения-сервера, не имея на клиентских машинах никаких драйверов доступа к базе данных.
Большое преимущество использования такой архитектуры заключается также в возможности изменения серверной части без необходимости перетрансляции клиентского приложения.
Приложение-сервер получает набор данных стандартным способом - через один из компонентов набора данных, таких как TTable или Tquery, и пересылает его с помощью компонента TDataSetProvider компоненту TClientDataSet в приложение-клиент. Приложение-сервер реализуется как удаленный модуль данных, представляющий из себя СОМ-объект. Доступ к такому компоненту может быть выполнен посредством DCOM с любого удаленного компьютера.
Использование JDBC-драйверов
Язык Java позволяет реализовывать доступ к таблице базы данных как через JDBC-драйверы, так и через мост JDBC-ODBC.
В любом случае для доступа к базе данных выполняется следующая последовательность действий:
Загружать и регистрировать драйверы можно как методом Class.forName, так и методом DriverManager.registerDriver. Но в первом случае экземпляр драйвера создается неявно, а во втором при регистрации драйвера явным образом создается экземпляр драйвера.
При создании объекта типа Connection метод getConnection определяет параметр, содержащий JDBC-URL и передаваемый менеджером драйверов последовательно всем зарегистрированным драйверам: если драйвер распознает URL, то он возвращает экземпляр класса, реализующий интерфейс Connection.
Этот экземпляр класса и возвращает менеджер драйверов в результате вызова метода getConnection.
Далее для извлечения данных из результирующего набора в переменные языка Java выполняются:
Метод executeQuery используется для создания результирующего набора, а для выполнения SQL-оператора, изменяющего информацию в базе данных, вызывается метод executeUpdate.
После завершения работы с данными необходимо последовательно освободить результирующий набор, оператор и соединение.
Например:
rs.close(); stmt.close(); con.close();
Возвращать результирующий набор может не только выполнение оператора SELECT, но и выполнение хранимой процедуры. Вызов хранимых процедур определяется интерфейсом java.sql.CallableStatement.
Например:
import oracle.jdbc.driver.*; // Для работы с JDBC-драйвером Oracle import java.sql.*; : CallableStatement cstmt; // Вызываемый операторный объект cstmt= con.prepareCall("{packeg1.metod1(?)}") // Оператор вызова // хранимой процедуры packeg1.metod1 cstmt.registerOutParameter(1,OracleTypes.CURSOR); // Регистрация // выходного параметра cstmt.execute(); // Выполнение операторного объекта OracleResultSet ors= (OracleResultSet) ((OracleCallableStatement) cstmt).getCursor(1); // Получение // результирующего набора для БД Oracle // из выходного параметра хранимой процедуры
По умолчанию при подключении через JDBC установлен режим автокоммита. Для применения транзакций этот режим следует отключить, а фиксацию изменений указывать явным вызовом метода Commit.
Например:
Connection con = DriverManager.getConnection ("jdbc:oracle:oci:@datai_com", "scott", "tiger"); con.setAutoCommit(false); : con.commit(); // Фиксация транзакции : con.rollback(); // Откат транзакции
Следующий пример иллюстрирует код приложения, выполняющего создание таблицы базы данных, доступ к которой выполняется через мост JDBC-ODBC.
import java.sql.*; // Импорт пакета из JDK
public class CreateMyTable { public static void main(String args[]) { String url = "jdbc:odbc:myDataSource"; // myDataSource - // имя источника данных DSN Connection con; String createString = "create table TEST (P1 VARCHAR(32)," + " P2 INTEGER, " + " P3 FLOAT, " + " P4 INTEGER)" ; Statement stmt; try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); // Загрузка драйвера
} catch(java.lang.ClassNotFoundException e) { System.err.print("ClassNotFoundException: "); System.err.println(e.getMessage()); } try { // Соединение с источником данных с использованием объекта типа Connection con = DriverManager.getConnection(url, "Login", "Psw"); stmt = con.createStatement(); // Создание объекта оператор класса Statement stmt.executeUpdate(createString); // Выполнение SQL-оператора stmt.close(); // Освобождение объекта "оператор" con.close(); // Закрытие соединения } catch(SQLException ex) { System.err.println("SQLException: " + ex.getMessage()); } } }
Компонент DATASTORE
Компонент DataStore представляет JDS-файл базы данных DataStore.
JBuilder предоставляет набор классов, поддерживающих работу с базой данных DataStore. Основные из них расположены на станице DataExpress палитры компонентов окна дизайнера.
Для того чтобы создать файл базы данных DataStore, надо выполнить следующие действия:
Для открытия и закрытия базы данных используются методы open и close объекта типа DataStore. Следующий пример иллюстрирует открытие базы данных типа DataStore.
import com.borland.datastore.*; public class CreateNew_DataStore { public static void main(String[] args) { DataStore ds1 = new DataStore(); try { ds1.setFileName("DS1.jds"); // Определяем имя БД java.io.File f = new java.io.File(ds1.getFileName()); ds1.setUserName("CreateTx"); ds1.setTxManager(new TxManager()); if(!f.exists()) ds1.create(); // Создание БД else ds1.open(); ds1.close(); } catch(Exception ex) { System.out.println(ex); } } }
Открыть базу данных DataStore можно несколькими способами:
Механизм доступа к базе данных DataStore через JDBC-драйверы не отличается от механизма доступа к любой другой базе данных. В качестве загружаемого драйвера можно использовать com.borland.datastore.jdbc.DataStoreDriver.
Например: Class.forName("com.borland.datastore.jdbc.DataStoreDriver");
Если для открытия соединения используется статический метод getConnection, то в качестве первого параметра ему следует передать URL-адрес расположения файла базы данных, который формируется из двух частей: адреса ПК и полного имени файла.
Адрес ПК указывается следующим образом:
Например:
Connection con = DriverManager.getConnection(URL + FILE, "user", "")
Построение SQLJ-приложений
Для создания SQLJ-приложения следует сформировать пустой проект, а затем добавить в него SQLJ-файл (команда меню File | New, выбрать пиктограмму SQLJFile).
SQLJ-файл - это файл с кодом программы на языке Java, в который включены SQLJ-операторы.
SQLJ-операторы могут быть двух видов:
SQLJ-оператор может иметь следующее формальное описание:
#sql {sql-оператор }; // Оператор без возвращения результирующего набора
или
#sql выражение={sql-оператор}; // Оператор, возвращающий // результирующий набор
Для поддержки возможности подключения во время компиляции SQLJ-транслятора к базе данных, с целью проверки синтаксиса введенного SQL-кода, следует создать объект соединение (команда меню Project | Properties, вкладка SQLJ), установить флажок Check SQL semantics against database shcema, выбрать New Connection и определить параметры подключения к базе данных, введя ее URL-адрес (localhost на локальном компьютере), имя пользователя и пароль).
При формировании SQLJ-приложения среда JDeveloper автоматически вставляет в начало приложения код, выполняющий подключение необходимых библиотек.
Например:
import sqlj.runtime.*; import sqlj.runtime.ref.*; import java.sql.*; import java.sql.Date; import oracle.sql.*; import oracle.sqlj.runtime.Oracle; public class MySQLJ1 { public MySQLJ1() {} // Конструктор public static void main (String[] args) throws SQLException { } }
Для подключения к базе данных из SQLJ-приложения первоначально следует создать объект типа данного класса, зарегистрировать драйвер и установить контекст по умолчанию.
Например:
MySQLJ1 sqlapp= new MySQLJ1(); DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver()); DefaultContext.setDefaultContext( new DefaultContext( "jdbc:oracle:thin:@db.com:1521:ORCL","scott","tiger",false)); if (DefaultContext.getDefaultContext==null){ // Ошибка подключения к базе данных) }
Для доступа к таблице базы данных следует объявить класс итератора (вставить в код класса до метода main).
Например:
#sql public iterator Tbl1Iter ( int f1, string f2, float f3);
Итераторы могут быть именованные и позиционные. SQLJ-транслятор в зависимости от типа итератора генерирует класс итератора, реализующий интерфейс sqlj.runtime.NamedIterator или sqlj.runtime.PositionedIterator. Оба этих интерфейса наследуют интерфейс ResultSetIterator, предоставляющий следующие методы:
Для использования итератора следует объявить объект типа итератора, описать выполняемый итератор, а затем, используя метод next, последовательно выбирать строки результирующего набора.
Например:
Tbl1Iter t1=null; // Объект типа итератора #sql t1= { SELECT //Выполняемый итератор Fl1 as a1, Fl2 as a2, Fl3 as a3 FROM Tbl1}; while (t1.next()){ System.out.println( t1.a1()); // Вывод значения столбца // результирующего набора System.out.println( t1.a2()); System.out.println( t1.a3()); } t1.close(); // Закрытие итератора
Работа с текстовыми файлами
JBUILDER предоставляет компонент "текстовый файл" - TextDataFile, который используется для импортирования данных из текстового файла и экспортирования их обратно.
Компонент набора данных реализуется классом TableDataSet. Он используется для хранения данных, импортируемых из текстового файла. Если для текстового файла существует одноименный файл с расширением SHEMA, то структуру столбцов описывать нет необходимости. Такой файл автоматически создается компонентом TableDataSet при записи в текстовый файл и содержит описание столбцов и другую информацию по набору данных. Для каждого столбца набора данных создается объект типа Column.
В следующем примере иллюстрируется чтение данных из текстового файла и их отображение в компоненте типа JdbTable:
import java.awt.*; import java.awt.event.*; import javax.swing.*; import com.borland.dx.dataset.*; import com.borland.dbswing.*; public class Frame1 extends JFrame { // contentPane - контейнер для размещения // визуальных компонентов JPanel contentPane; BorderLayout borderLayout1 = new BorderLayout(); // Компонент TextDataFile со страницы DataExpress // для работы с текстовым файлом - источником данных TextDataFile textDataFile1 = new TextDataFile(); // Компонент TableDataSet со страницы DataExpress, // реализующий набор данных TableDataSet tableDataSet1 = new TableDataSet(); Column column1 = new Column(); // Создается для // каждого столбца в наборе данных Column column2 = new Column(); Column column3 = new Column(); TableScrollPane tableScrollPane1 = new TableScrollPane(); // Компонент "таблица для набора данных" JdbTable jdbTable1 = new JdbTable(); public Frame1() { // Конструктор // Определение прослушиваемых событий enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { jbInit(); } catch(Exception e) { e.printStackTrace(); } } private void jbInit() throws Exception { column2.setCaption("Столбец2"); //Отображаемый // заголовок столбца column2.setColumnName("NewColumn2"); //Имя столбца column3.setCaption("Столбец3"); column3.setColumnName("NewColumn3"); column1.setCaption("Столбец1"); column1.setColumnName("NewColumn1");
// Тип поля указывается классами из пакета com.borland.dx // Тип данных в столбце column2.setDataType(com.borland.dx.dataset.Variant.STRING); column2.setServerColumnName("NewColumn2"); column2.setSqlType(0); // Тип данных в столбце column3.setDataType(com.borland.dx.dataset.Variant.SHORT); column3.setServerColumnName("NewColumn3"); column3.setSqlType(0); // Тип данных в столбце column1.setDataType(com.borland.dx.dataset.Variant.SHORT); column1.setServerColumnName("NewColumn1"); column1.setSqlType(0); // Определение связи набора данных с текстовым файлом tableDataSet1.setDataFile(textDataFile1); textDataFile1.setFileName("D:\\J8\\ImportText.txt"); textDataFile1.setSeparator(","); // Разделитель между // полями contentPane = (JPanel) this.getContentPane(); // Текущая // панель для размещения компонентов contentPane.setLayout(borderLayout1); // Определяем // компоновку this.setSize(new Dimension(500, 400)); this.setTitle("Чтение данных из текстового файла"); tableDataSet1.setColumns(new Column[] {column1, column2, column3}); // Устанавливаем связь визуальной таблицы с набором данных jdbTable1.setDataSet(tableData Set1); tableScrollPane1.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); tableScrollPane1.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); contentPane.add(tableScrollPane1, BorderLayout.CENTER); tableScrollPane1.getViewport().add(jdbTable1, null); } protected void processWindowEvent(WindowEvent e) { super.processWindowEvent(e); if (e.getID() == WindowEvent.WINDOW_CLOSING) { System.exit(0); // Завершение приложения } } }
Сохранение данных из открытого набора данных в текстовом файле выполняется методом Save.
Следующий пример иллюстрирует сохранение набора данных, выполняемое в обработчике события actionPerformed.
void button1_actionPerformed(ActionEvent e) { try { tableDataSet1.getDataFile().save(tableDataSet1); System.out.println ("Изменения успешно сохранены в файле"); } catch (Exception ex) { System.out.print("При сохранении файла произошла "); System.err.println(" ошибка: " + ex); } }
Реализация доступа к базам данных в среде JBUILDER
При создании программ на языке Java можно придерживаться "чистого" Java, что ограничивает возможности программиста использованием только классов и интерфейсов пакетов JDK. Однако, различные существующие среды программирования, включая и JBuilder, значительно расширяют возможности программиста, предоставляя дополнительные библиотеки классов. Далее будут рассмотрены классы и интерфейсы, применяемые для реализации доступа к базам данных как из пакетов JDK, так и предоставляемые средой JBuilder.
JBUILDER предоставляет средства для доступа к различным форматам данных из источников данных. Это могут быть текстовые файлы, содержащие табличную информацию, или таблицы базы данных.
JBUILDER позволяет реализовывать доступ к базам данных с применением драйверов JDBC и с использованием моста JDBC-ODBC, реализующего непосредственный доступ к базе данных через ODBC-драйверы. Мост JDBC-ODBC может быть использован и в том случае, если JDBC-драйвер для базы данных отсутствует.
На следующей схеме представлен механизм взаимодействия компонентов, реализующих работу с базой данных.

Преимущество доступа с использованием JDBC-драйвера заключается в том, что нет необходимости устанавливать эти драйверы на каждой клиентской машине (в отличие от ODBC-драйверов, которые предварительно должны быть зарегистрированы на каждом клиенте).
Для реализации доступа к любому источнику данных в приложение должен быть добавлен компонент, представляющий связь с конкретным источником данных. К таким компонентам относятся:
Компоненты, представляющие в приложении структуру извлекаемых данных, называются наборами данных. К таким компонентам относятся:
Приложение может содержать компоненты, предназначенные для отображения информации из набора данных. Страница dbSwing палитры компонентов окна дизайнера содержит визуальные компоненты, специально предназначенные для отображения набора данных (эти классы не входят в JDK). К таким компонентам относятся:
Для навигации по полям и записям набора данных можно использовать компонент JdbNavToolBar,

который предоставляет стандартный интерфейс, включающий возможность перехода от записи к записи, переход к первой и последней записи, добавление и удаление записи, отмена изменений, выполненных над текущей записью, обновление набора данных и фиксация сделанных изменений. Для изменения предусмотренного по умолчанию поведения кнопок компонента JdbNavToolBar можно:
Для определения текущего набора данных, используемого компонентом, следует вызвать метод getFocusedDataSet().
Дополнительно пакет com.borland.dbswing предоставляет компоненты, которые также могут использоваться для навигации по набору данных, включая:
Приложение может содержать компоненты, предназначенные для отображения информации из набора данных. Страница dbSwing палитры компонентов окна дизайнера содержит визуальные компоненты, специально предназначенные для отображения набора данных (эти классы не входят в JDK). К таким компонентам относятся:
Для навигации по полям и записям набора данных можно использовать компонент JdbNavToolBar,

который предоставляет стандартный интерфейс, включающий возможность перехода от записи к записи, переход к первой и последней записи, добавление и удаление записи, отмена изменений, выполненных над текущей записью, обновление набора данных и фиксация сделанных изменений. Для изменения предусмотренного по умолчанию поведения кнопок компонента JdbNavToolBar можно:
Для определения текущего набора данных, используемого компонентом, следует вызвать метод getFocusedDataSet().
Дополнительно пакет com.borland.dbswing предоставляет компоненты, которые также могут использоваться для навигации по набору данных, включая:
Методы DOPOST и DOGET
Методы doPost и doGet кода сервлета используются для обработки HTTP-запроса и формирования возвращаемой HTML-страницы.
При использовании компонента IxPageProducer в тело метода doGet следует вставить только одну строку:
ixPageProducer1.servletGet(this, request, response);
Метод servletGet класса IxPageProducer выполняет следующие начальные действия по обработке GET HTTP-запроса:
При использовании компонента IxPageProducer в тело метода doPost могут быть вставлены следующие строки:
// Получение объекта модуля данных: DataModule1 dm = (DataModule1) ixPageProducer1.getSessionDataModule(request.getSession()); ixPageProducer1.servletPost(this, request, response); // Отображение данных, переданных из формы doGet(request, response);
Метод doPost в данном примере используется для обработки параметров запроса. Для динамического формирования HTML-документа вызывается метод doGet.
Метод servletPost класса IxPageProducer выполняет начальные действия по обработке POST HTTP-запроса и, если была нажата кнопка submit, то вызывает обработчик события submitPerformed.
Модуль данных
Модуль данных - это Java-класс, реализующий интерфейс DataModule. Модуль данных можно рассматривать как централизованный контейнер для всех компонентов доступа к данным. Модуль данных предоставляет методы, позволяющие работать с наборами данных.
Для создания модуля данных выполните следующие действия:

Рис. 14.1. Диалог Data Modeler - страница Columns
Диалог Data Modeler позволяет определить соединение с источником данных и автоматически сформировать SQL-оператор, выполняющий запрос данных.
Для формирования запроса на панели Available columns следует выбрать имена столбцов и скопировать их на панель Selected columns. Страница Where диалога Data Modeler позволяет сформировать условие, а страницы Order By и Group By служат для задания порядка сортировки и группирования данных, получаемых в результирующем наборе.
Для того чтобы просмотреть результат сформированного запроса, следует перейти на страницу Test диалога Data Modeler и щелкнуть по кнопке Execute Query.
При создании модуля данных JBuilder автоматически формирует код класса модуля данных, реализующего интерфейс DataModule.
Например:
package myservlet_database1;
import java.awt.*; import java.awt.event.*; import com.borland.dx.dataset.*; import com.borland.dx.sql.dataset.*;
public class DataModule1 implements DataModule { static private DataModule1 myDM; private Database database1 = new Database(); private QueryDataSet customer = new QueryDataSet(); // Переменная customer будет применяться как ссылка // на используемый набор данных public static DataModule1 getDataModule() { // Статической метод getDataModule используется для // создания объекта типа DataModule1 if (myDM == null) { myDM = new DataModule1(); } return myDM; } public DataModule1() { // конструктор try { jbInit(); } catch(Exception e) { e.printStackTrace(); } } private void jbInit() throws Exception { customer.setQuery( new com.borland.dx.sql.dataset.QueryDescriptor( database1, "SELECT CUSTOMER.CUST_NO, CUSTOMER.CUSTOMER, CUSTOMER.PHONE_NO, CUSTOMER.CITY " + "FROM CUSTOMER", null, true, Load.ALL)); // Метод setQuery устанавливает значение свойства query // компонента типа QueryDataSet database1.setConnection( new com.borland.dx.sql.dataset.ConnectionDescriptor "jdbc:borland:dslocal:C:\\JBuilder8\\samples\\JDataStore\\ datastores\\employee.jds", "Sample", "", false, "com.borland.datastore.jdbc.DataStoreDriver")); } // Метод setConnection определяет соединение с источником данных public Database getDatabase1() { return database1; } public QueryDataSet getCustomer() { return customer; } }
Для создания HTML-файла, отображающего форму, используемую при публикации данных, выполните следующие действия:
Например:
Таблица Customer
| Поле1 |
|---|
| - |
Для того чтобы определить используемый модуль данных, следует:
Опция Create New Instance of Data Module диалога Use DataModule Wizard используется в том случае, если предполагается использовать модуль данных для одной формы.
Опция Share (Static) Instance of DataModule позволяет использовать модуль данных несколькими формами приложения, разделяя один экземпляр модуля.
Опция Caller sets Inctance with setModule() позволяет использовать для одной формы несколько модулей данных, выполняя соответствующую настройку в окне дизайнера.
В результате добавления модуля данных в файле Servlet1 появится объявление переменной dataModule11 типа DataModule1 (например: DataModule1 dataModule11;), а в метод jbInit будет добавлен вызов метода getDataModule, используемого для создания объекта модуля данных (например, dataModule11 = myservlet_database1.DataModule1.getDataModule();).
Обработка событий
Компонент IxControl может выполнять обработку событий. Так, если данный компонент связан с командной кнопкой Submit, то для обработки события следует перейти на вкладку Events и создать обработчик события submitPerformed.
Например:
ixControl2.addSubmitListener(new com.borland.internetbeans.SubmitListener() { public void submitPerformed(SubmitEvent e) { ixControl2_submitPerformed(e); } }); : void ixControl2_submitPerformed(SubmitEvent e) {
}
Вставьте в тело метода обработчика события submitPerformed следующий код:
// Запрос текущего модуля данных: DataModule1 dm = (DataModule1) ixPageProducer1.getSessionDataModule(e.getSession()); // передача и сохранение данных, введенных пользователем, // в источнике данных, определенным модулем данных: dm.getCustomer().post(); dm.getCustomer().saveChanges(); // Метод getCustomer - это автоматически сформированный метод модуля //данных DataModule1.java, возвращающий объект типа QueryDataSet
Методы post и saveChanges класса QueryDataSet используются для передачи и сохранения изменений в базе данных.
Применение ASP-страниц
Серверные ASP-страницы реализуются как текстовые HTML-файлы с расширением asp, которые содержат сценарии на языке JScript или VBScript. ASP-сценарии записываются между тегами <% и %>.
Доступ к базам данных выполняется из серверных ASP-сценариев посредством вызова методов интерфейса ADO (ActiveX Data Object).
При создании ASP-сценариев можно использовать следующие объекты:
Для работы с базами данных в ASP-файлах удобно использовать объектный интерфейс ADO, который создан на базе OLE DB. Объектная модель ADO представляется набором последовательно используемых объектов, включая следующие:
При применении ADO для соединения с базой данных в ASP-сценарии следует использовать объект ADODB.Connection, а для работы с результирующим набором - объект ADODB.Recordset.
Следующий пример иллюстрирует ASP-сценарий, выполняющий отображение записей из таблицы базы данных:
<% var con,rs; con=Server.CreateObject("ADODB.Connection"); con.Open ("MyDB", "User1", ""); srtSQL="Select * from t1"; rs= Server.CreateObject("ADODB.Recordset"); rs.Open (strSQL, con); %>
| <%= rs.Fields("field1")%> | <%= rs.Fields("field2")%> |
Метод CreateObject ASP- объекта Server создает объект, указываемый параметром. Метод Open ADO-объекта Connection устанавливает соединение с базой данных. Метод MoveNext объекта Recordset выполняет переход к следующей записи.
Объект Recordset позволяет выполнять модификацию записей, используя методы:
Например:
<% rs.AddNew; rs("field1")="123"; rs.Update; %>
Для реализации поиска указываемого значения в столбце результирующего набора следует использовать методы объекта Recordset:
Для наложения на открытый результирующий набор некоторого фильтра вызывается метод Filter объекта Recordset.
Для создания результирующего набора можно использовать метод Execute объекта Command.
Например:
<%@LANGUAGE=VBSCRIPT%> <% dim con, rs, sql1 sql1="SELECT * FROM Tbl1;" set con = Server.CreateObject("ADODB.Connection") con.Open "DSN = MyDB" ' Устанавливаем соединение set rs = con.execute(sql1) ' Открываем результирующий набор if rs.BOF and rs.EOF then ' Результирующий набор пустой Response.Write("Нет строк") else rs.MoveFirst Do While Not rs.EOF ' Доступ по имени столбца Response.Write(rs("F1") & " " & rs("F2") & "
") rs.MoveNext Loop end if rs.close set rs = nothing %>
Доступ к значениям столбцов может быть реализован через объект типа Recordset как по имени столбца, так и по его номеру. Отображаемые значения в ASP-сценариях указываются после оператора =.
Например:
| <% = rs(Index)%> | ' Доступ по номеру ' столбца <% Next %> ' Цикл по столбцам
Применение бинов JDBCBEAN и HTTPJDBCBEAN для реализации доступа к базе данных
В состав JBuilder Enterprise входит сервер IAS (версия сервера зависит от версии среды JBuilder), по умолчанию инсталлируемый в каталог \Inprise\AppServer.
Данный WEB-сервер содержит в каталоге Inprise\AppServer\examples\beans\inpriseexamples\beans файлы JDBCBean.java и HttpJDBCBean.java, которые можно использовать для упрощения процесса публикации данных на JSP-страницах. Файл JDBCBean.java представляет собой бин, содержащий свойства, определяющие подключение к базе данных, и выполняемый SQL-оператор. Файл HttpJDBCBean.java применяется для определения параметров, используемых бином JDBCBean.
Для того, чтобы выполнить публикацию данных на JSP-странице с использованием существующих файлов с классами бинов, следует:
Перейти на вкладку Bean окна содержания и в редакторе бинов перейти на вкладку Properties.
Эта вкладка содержит следующие свойства бина, позволяющие выполнить SQL-запрос:
classname - имя используемого JDBC-драйвера;
url - расположение источника данных;
username - имя пользователя, подключаемого к базе данных;
password - пароль пользователя;
query - выполняемый SQL-оператор.
Для каждого из перечисленных свойств существует get-метод и set-метод.
Свойства cols и rows (число столбцов и строк в результирующем наборе) используются как простые переменные только внутри бина. Для доступа к их значениям служат свойства columnCount и rowCount, имеющие get-методы.
Свойство result используется для формирования отображаемого результата.
Создать второй компонент JavaBean и аналогичным образом скопировать в него файл HttpJDBCBean.java.
Класс HttpJDBCBean наследует классу JDBCBean.
Бин HttpJDBCBean использует свой метод processRequest для обработки запроса. Этот метод должен быть непосредственно вызван из JSP-файла для определения значений всех свойств бина JDBCBean.
В файле JDBCBean.java выполнение SQL-запроса осуществляется методом go на основе значений, установленных для свойств данного бина. Так, этот метод реализует следующие основные действия:
В файле HttpJDBCBean.java. метод processRequest выполняет присвоение значений всем свойствам бина JDBCBean, получая эти значения из параметров запроса.
Метод processRequest определяет значения параметров запроса через объект request типа HttpServletRequest:
if ((_p = request.getParameter("classname")) != null) { classname = _p; }
Так, если параметр classname определен, то значение одноименного свойства устанавливается равным его значению.
Если все необходимые параметры заданы, то вызывается метод go бина JDBCBean: this.go();
Класс HttpJDBCBean наследует классу JDBCBean.
Бин HttpJDBCBean использует свой метод processRequest для обработки запроса. Этот метод должен быть непосредственно вызван из JSP-файла для определения значений всех свойств бина JDBCBean.
В файле JDBCBean.java выполнение SQL-запроса осуществляется методом go на основе значений, установленных для свойств данного бина. Так, этот метод реализует следующие основные действия:
В файле HttpJDBCBean.java. метод processRequest выполняет присвоение значений всем свойствам бина JDBCBean, получая эти значения из параметров запроса.
Метод processRequest определяет значения параметров запроса через объект request типа HttpServletRequest:
if ((_p = request.getParameter("classname")) != null) { classname = _p; }
Так, если параметр classname определен, то значение одноименного свойства устанавливается равным его значению.
Если все необходимые параметры заданы, то вызывается метод go бина JDBCBean: this.go();
Публикация данных с использованием классов TQUERYTABLEPRODUCER и TDATASETTABLEPRODUCER
Для публикации данных из базы данных на HTML-страницы можно использовать следующие классы:
Компонент типа TDataSetTableProducer при формировании HTML-страницы, как правило, обрабатывает следующие события:
Для того чтобы создать серверное приложение, отображающее на HTML-странице поля таблицы базы данных, следует:
Тег BODY открывает область, в которой размещается отображаемое содержимое HTML-страницы.
Далее можно вызывать редактор свойства Column. Для этого или в инспекторе объектов выполните двойной щелчок мышью на значении свойства Column компонента DataSetTableProducer1, или в web-модуле выполните двойной щелчок мышью на компоненте DataSetTableProducer1.
Для добавления всех столбцов в диалоге Editing.DataSetTableProducer1.Columns выполните команду контекстного меню Add All Fields. Каждый добавленный столбец может быть доступен как DataSetTableProducer1.Columns[номер_столбца_начиная_с_0].
Сформированная HTML-страница будет отображать таблицу с набором данных, который определен компонентами Table и DataSetTableProducer.
Тег BODY открывает область, в которой размещается отображаемое содержимое HTML-страницы.





