    Глава 1О 

    Программирование баз данных в Java 

    Visual J++ предоставляет огромный набор средств для программирования баз 
данных. Существует не менее трех доступных в Visual J++ функциональных API, 
которые используются в СУБД — системах управления реляционными базами данных 
(Relational Database Management System). 
    Самым простым в использовании является API Data Access Object (DAO). Это 
наБор СОМ-объектов, которые можно использовать с помощью моста Java/COM, 
реализованного в виртуальной машине Java системы Windows. 
    DAO API является программным интерфейсом для Jet-системы управления базами 
данных фирмы Microsoft, который может формировать запросы и редактировать базы 
данных Microsoft Access, файловые базы данных ISAM, а также источники данных 
формата ODBC (Open Database Connectivity). Классы DAO API очень легко использовать. 
    Remote Data Object (RDO) API — это еще один СОМ-интерфейс для баз данных 
ODBC. Использовать RDO труднее^_,чем,^АО, но он является значительно более 
мощным интерфейсом. Объекты RDO предоставляют непосредственный интерфейс 
драйверов базы данных ODBC и источников данных. Приложения и апплеты, 
написанные с использованием RDO-классов, легче распространять, чем 
DAO-объекты, поскольку для них не требуется использовать Jet-систему 
управления базой данных. 
    DAO и RDO платформно-зависимы. В ОАО используется система управления 
базами данных (Jet Engine) фирмы Microsoft, на применение которой надо 
обязательно получить лицензию. И DAO, и RDO основаны на системе ODBC, 
разработанной фирмой Microsoft и доступной исключительно на платформе 
Microsoft Windows. В обоих интерфейсах также используется мост Java/COM, 
который на момент создания этой книги доступен только на платформе Microsoft Windows. 
    Фирмой JavaSoft разработан ряд стандартных API-классов для общего 
программирования СУБД в Java. Основанный на ODBC API стандартный 
платформно-независимый интерфейс Java Database Connectivity (JDBC) API можно 
применять в любой системе, где реализована виртуальная машина Java. Как и в 
ODBC, разработчики баз данных пользуются написанными на 

    289 

    Java JDВС-драйверами (обычно достаточно большая часть кода в них 
представлена в системе команд конкретной платформы), которые управляются 
центральным менеджером драйверов. JDBC API является наиболее распространенным 
и открытым стандартом приложений и апплетов Java. К сожалению, выучить его 
гораздо сложнее, чем DAO. 
    Итак, программист, работающий в Visual J++, имеет возможность выбрать 
любой из трех API. К сожалению, среди огромного количества электронной 
документации по Visual J++ какие-либо сведения об интерфейсах ОАО и RDAO 
отсутствуют. Не слишком много подобной информации можно получить и через 
Internet. Правда, подписчики Microsoft Developer Network (MSDN), каковыми 
являются и многие программисты на Visual C++ и Visual Basic, могут 
ознакомиться с полной документацией по DAO и RDO, обратившись к компакт-дискам 
MSDN. Эти документы созданы с использованием Visual Basic, поскольку именно 
для него ОАО и RDAO предназначались изначально. В настоящей главе об этих API 
рассказывается с точки зрения программирования на Java. 
    Полная документация по JDBC API и загружаемые классы доступны на 
WWW-сервере фирмы JavaSoft. Указанный интерфейс описан и в настоящей главе, с 
тем чтобы читатель мог сравнить его с интерфейсами ОАО и RDAO и выбрать один 
из них. Для более полного ознакомления с JDBC API необходимо обратиться к 
WWW-странице фирмы JavaSoft http://splash.java-soft.com. 

    Объекты доступа к данным (DAO) 

    ОАО объединяет СОМ-объекты, расположенные в одной СОМ-библио-теке, которые 
позволяют приложениям и апплетам взаимодействовать с Jet-системой управления 
базой данных. Эта система является основным компонентом базы данных Microsoft 
Access. Она не только создает базы данных и управляет ими, применяя 
собственный формат файлов Access (MDB-файлы), но и управляет ISAM-файлами, а 
также базами данных ODBC. 
    ISAM-файл — это отформатированный файл базы данных, хранящий одну таблицу 
базы данных, возможно, с добавочными файлами индексов таблицы. Примерами 
систем баз данных ISAM-формата могут служить файлы dBase, Paradox, FoxPro, а 
также простой текстовый файл с разделителями. Jet-система может читать, 
запрашивать и модифицировать эти файлы так же, как и MDB-файлы собственного 
формата. Это позволяет создавать более крупные базы данных, построенные из 
множества различным образом отформатированных таблиц. Формат базы данных не 
должен влиять на программирование, поскольку первоочередное значение имеет 
сама информация, хранящаяся в базе данных. 

    29О	Visual J++: основы программирования 

    Источники данных базы ODBC могут быть базами данных любого типа. ODBC 
представляет собой интерфейс, разработанный фирмой Microsoft для облегчения и 
стандартизации программирования в системе Windows. Один и тот же API может 
использоваться для доступа и модификации баз данных совершенно различных 
систем. Локальные базы данных, имеющие собственный формат файлов, или такие 
сетевые базы данных, как Oracle или SyBase, с помощью интерфейса ODBC 
становятся одинаково доступными. Jet-система знакома и "разговаривает" с 
драйверами ODBC всех типов, поэтому с ее помощью можно запрашивать и 
модифицировать базы данных любых производителей. В Java интерфейс DAO — это 
своего рода панель управления, позволяющая программно взаимодействовать с 
мощной Jet-системой фирмы Microsoft. 
    DAO API является самым легким в применении, наиболее 
объектно-ориентированным и интуитивно понятным из трех интерфейсов баз данных, 
доступных в Java. Классы DAO — Database, DBEngine, Table, Query — просты для 
понимания и намного облегчают задачу программирования. 

    Распространение Jet-системы 

    Учтите, что используемая в Jet-системе СУБД не бесплатна, а следовательно, 
данная система не является свободно распространяемой. Фактически в систему 
разработки Visual J++ включена копия Jet-системы — DLL, СОМ-объекты и прочее, 
что дает возможность создавать приложения и апготеты DAO. 
    Однако вы не имеете права предоставлять библиотеки Jet-системы своим 
пользователям. Требования фирмы Microsoft об условиях распространения 
продуктов, использующих эту систему и DAO-объекты, представлена на 
WWW-странице http://www.micmsoft.com/accessdev/accwhite/qalicen.htm. Согласно 
выдвигаемым требованиям, пользователь, который применяет ОАО-разработки, 
созданные вами в Visual J++, должен инсталлировать один из продуктов фирмы 
Microsoft, содержащий runtime-версию Jet-системы. В документе перечислены 
следующие приложения: Microsoft Access, Microsoft Excel, Microsoft Office, 
Visual Basic, Visual J++ (это не все продукты, содержащие данную версию 
системы). Пользователь, установивший на своей машине одно из этих приложений, 
может использовать Jet-систему в своих приложениях. 
    Заметьте, что для интерфейсов RDO и JDBC подобных ограничений не 
существует. Если вы считаете, что ограничения на использование Jet-системы 
слишком "тяжелы" для вашего проекта, тогда, очевидно, ОАО — неприемлемое для 
вас решение. С другой стороны, если вы разрабатываете локальный проект или 
проект для собственной корпоративной сети и заранее знаете, что в системе, для 
которой он предназначен, уже имеется одно из перечисленных выше приложений 
Microsoft, тогда ОАО — это то, что вам нужна 

    Глава 1О. Программирование 8аз оанных в Java	291 


    Сознание переходных классов Java/COM для интерфейса DAO 

    ОАО является набором СОМ-объектов, которые реализуют интерфейс Jet-системы 
базы данных фирмы Microsoft. В главе 7 подробно описано, как СОМ-объекты с 
помощью виртуальной машины Java используются в системе Windows. Задействовать 
СОМ-объекты в Visual J++ очень просто — достаточно создать "переходные" 
классы, которые осуществляют преобразование из Java в СОМ. 
    Переходные классы Java/COM для ОАО необходимо создать до того, как этот 
интерфейс будет использован в Java. Для этой цели предназначена утилита Java 
Type Library Wizard. При выборе в меню Tools опции Java Type Library Wizard на 
экране появится диалоговое окно Type Library Wizard с вопросом, для_.какой 
СрМ-библиотеки надо создавать переходные классы Java/COM. Выберите пункт 
Microsoft DAO Object Library. 
    В результате утилита Java Type Library Wizard автоматически создаст 
класс-файлы для переходных классов, как это было описано в главе 7. При этом 
переходные классы будут помещены в пакет dao3032, а класс-файлы будут записаны 
в каталог Windows (в подкаталог Java\Trustlib\dao3032). При распространении 
апплетов или приложений Java, в которых используются DAO-объекты, убедитесь, 
что класс-файлы находятся в соответствующем каталоге (т.е. в каталоге пакета dao3032). 
    Теперь можно приступать к созданию ОАО-объектов. 

    Объект DBEngine 

    ОАО-класс, представляющий Jet-систему, называется классом DBEngine. В ВМ 
одновременно существует не более одного экземпляра этого класса. Через этот 
объект можно получить все остальные объекты манипулирования базами данных. 
    Объект DBEngine управляет набором рабочих пространств и имеет ряд 
связанных с ним свойств. Эти свойства относятся к свойствам OLE-автоматизации, 
т.е. в Java для каждого из них имеются соответствующие методы записи и чтения 
(см. главу 7). Рабочее пространство, по сути, представляет пользовательский 
сеанс работы в Jet-системе. Пользователь может открыть одновременно несколько 
сеансов, или рабочих пространств. 
    Ниже приведена программа, создающая экземпляры объекта DBEngine и 
опрашивающая список свойств, возвращенный методом getProperties() класса 
DBEngine. У DAO-классов есть собственное описание создающего DBEngine-объекты 
класса Properties, совершенно независимое от описания класса 
java.util.Properties, представленного в главе 11. 
    Класс Properties взят из DAOSample — одного из поставляющихся с VJ++ 
примеров. Класс DBEngine может быть создан только объектом License Manager, 
поскольку Jet-система является лицензионной программой. License Manager служит 
гарантией того, что система используется только тем, кто имеет на то разрешение. 

    292	Visual J++: основы программирования 


    // Создание объекта DBEngine с использованием схемы лицензирования, import 
dao3032 ^DBEngine/import com.ms.com.*; 
    public class dao_dbengine { 
    // Метод создает объект DBEngine, static public _DBEngine create() { 
    // Возвращаемое значение. _DBEngine result; 
    // Создание объекта License Manager. ILicenseMgr mgr = new LicenseMgr(); 
    // Использование объекта License Manager для создания // объекта DBEngine. 
    result = (_DBEngine)mgr.createWithLic( 
    // Лицензионный ключ для DBEngine, "mjgcqcejfchcijecpdhckcdjqigdejfccjri", 
    // CLSID для DBEngine. "(00025E15-0000-0000-COOO-000000000046)", 
    // Агрегация указателя lUnknown* punkOuter. null, 
    // следующий флаг используется для создания // внутризадачного сервера. 
ComContext.INPROC_SERVER ) ; 
    return result; 
    } } 

    Этот код доступен на компакт-диске Visual J++ в апплете DAOSample окна 
InfoViewer. Для создания DBEngine-объектов этот класс можно использовать во 
всех приложениях и апплетах. 
    В приведенном ниже фрагменте программы перечисляются запущенные в данный 
момент сеансы, а также свойства объекта DBEngine. Для чтения значений двух 
свойств — LoginTimeout и Version — используются соответствующие API-методы: 

    import dao3032.*; 
    public class DBEngineProperties { 
    public static void main(String!] astrArgs) { //Получение объекта DBEngine. 


    Глава 1О. Программирование 8аа данных в Java	293 


    _DBEngine engine = dao_dbengine.create(); 
    // Перечисление рабочих пространств. System.out.println("Workspaces: Name, 
UserName"}; Workspaces workspaces = DBEngine.getWorkspaces(); for(int ii=0; 
ii<Workspaces.getCount() - 1; ii++) { 
    Variant var = new Variant(); 
    var.putlnt(ii); 
    Workspace wrksp = Workspaces.getltem(var) 
    System.out.println(" " + wrksp.getName() = ", " + 
    wrksp.getUserName()); ) 
    // Перечисление встроенных свойств. 
    System.out.println("DBEngine.Version: " +  engine.getVersion()); 
    System.out.println("DBEngine.Timeout: " + 
    engine.getLogTimeout()); 
    // Перечисление других свойств. Properties props = engine.getProperties 
(); ror(int ii=0; ii<props.getCount(); ii++) { 
    Variant var = new Variant)); 
    var.putlnt (ii); 
    Property prop = Properties.getltem(var); 
    System.out.println("DBEngine. " + prop.getName() + 
    ": " + prop.getValue()); } 
    } 

    В этом примере проиллюстрирована важнейшая особенность 
ОАО-программирования — существование огромного количества коллекций объектов. 
В нашем примере приведены только две из них, а именно Workspace и Properties. 
Заглянув же в файл Samples.txt, можно убедиться, что в действительности их 
намного больше. В частности, там содержатся коллекции Tables, Queries, 
TableDefs, QueryDefs, Fields, Users, Groups и многие другие. В двух циклах for 
предыдущего примера показан один из способов использования коллекций: передав 
целое значение (объект типа Variant) методу getltemQ, получаем «-и член коллекции. 
    Обычно объекты в DAO имеют имена. Для получения имени DAO-объекта в 
предыдущем примере был вызван метод getNameQ классов Workspace и Properties. 
Имя объекта можно передать методу getltemQ — в таком случае будет возвращена 
ссылка на объект в коллекции, соответствующей переданному имени. 

    Рабочее пространство, пользователь и пароль по умолчанию 

    При создании объекта DBEngine всегда имеется по крайней мере одно открытое 
рабочее пространство, которое называется рабочим пространством по умолчанию. 
Рабочее пространство по умолчанию открывается при использовании 

    294	Visual J++: основы программирования 

    пользовательского имени по умолчанию и пароля по умолчанию. (В DAO имеется 
средство для разграничения доступа пользователей к базам данных. Имена 
пользователей и групп пользователей хранятся в каждой базе данных DAO.) 
    Для задания имени пользователя и пароля по умолчанию служат методы 
setDefaultUser() и setDefaultPasswordQ объекта DBEngine. Объявления указанных 
методов можно увидеть в файле Summary.txt, созданном с помощью Type Library 
Wizard при образовании переходных классов Java/COM. Этот файл расположен в том 
же каталоге, где содержатся переходные классы DAO (обычно это каталог WINDOWS\JAVA\TRUSTLIB\DAO3032). 
    У объекта DBEngine имеется метод openDatabaseQ, который, по сути, делает 
то же, что и метод openDatabaseQ класса Workspace. Версия объекта DBEngine 
открывает базы данных и создает объект Database в рабочем пространстве, 
заданном по умолчанию. При этом имя пользователя и пароль не запрашиваются, 
поскольку используются данные по умолчанию. 

    Рабочее пространство 

    Рабочее пространство определяет пользовательский сеанс работы с 
Jet-системой. С его помощью открываются базы данных. Происходит это в 
следующем порядке: сначала создается объект DBEngine, потом рабочее 
пространство, а затем в последнем открывается или создается база данных. 

    Сознание рабочего пространства 

    Создавая рабочее пространство, вы фактически регистрируетесь как новый 
пользователь в DBEngine. Объект Workspace определяет ваш сеанс работы с 
DBEngine. Количество открытых рабочих пространств у пользователя не 
ограничено. Если пользователь хочет открыть для работы несколько независимых 
баз данных, то ему надо создать и несколько рабочих пространств — по одному 
для каждой базы. 
    Для создания новых рабочих пространств используется метод 
Create-WorkspaceQ объекта DBEngine. Метод принимает в виде параметров три 
строки: имя нового рабочего пространства, имя пользователя организуемого 
рабочего пространства и пароль пользователя. (Имя пользователя и пароль должны 
быть созданы предварительно с помощью метода CreateUserQ класса Workspace.) В 
приводимом ниже коде создается новое рабочее пространство для конкретного пользователя: 

    public Workspace makeUserWorkspace(String strWorkspaceName) ( // 

Переменные классов m_strUsername и m_strPassword // уже созданы и содержат имя 
пользователя и пароль, return m_dbengine.CreateWorkspace(strWorkspaceName, 

    m_strllsername, m_strPassword) ; 
    } 


    Глава 1О. Программирование Баз оанных в Java	295 


    Создание и открытие баз данных 

    Создание баз данных и доступ к ним происходят в рабочем пространстве. Для 
открытия базы данных предназначен метод OpenDatabase() класса Workspace. Этот 
метод принимает строку с именем базы данных, пару булевых переменных и 
некоторую дополнительную информацию, используемую для открытия источников 
данных ODBC. Синтаксис метода OpenDatabaseQ таков: 

    public  Database OpenDatabase(String  strDBName,Variant  varFExclusive, 
    Variant  varFReadOnly,Variant varStrODBCSource); 

    Параметр strDBName является именем файла базы данных ISAM или MS Access, 
который следует открыть, или именем источника данных ODBC, уже установленным 
административной программой ODBC в Control Panel системы Windows. Этот 
параметр _может быть пустым (""), если параметр varStrODBCSource представляет 
собой просто строку "ODBC"..В.этом случае автоматически появляется окно 
программы-администратора ODBC, давая пользователю возможность выбрать нужный 
пункт из списка доступных источников данных ODBC. Как это делается, показано в 
следующем примере: 

    Workspace wspace  = m_dbengine.CreateWorkspace("UserSpace", 
    m_strUsername,   m_strPassword); 
    Variant varFExclusive =  new Variant (); varFExclusive.putBoolean(false); 
    Variant  varFReadOnly = new Variant(); varFReadOnly.putBoolean(false) ; 
    Variant  varStrODBCSource  =  new Variant(); 
varStrDBCSource.putString("ODBC") ; 
    Database myDB = wspace.OpenDatabase("",varFExclusive, 
    varFReadOnly,   varStrODBCSource); 

    Параметр varFExclusive принимает значение true, если базу данных следует 
открывать в монопольном режиме. Вызов метода OpenDatabaseQ будет неудачным, 
если другой пользователь уже открыл эту базу данных в монопольном режиме, а 
также при попытке открыть ее в монопольном режиме, если она уже открыта другим пользователем. 
    Параметр varFReadOnly принимает значение true, если следует открыть базу 
данных для чтения; если же надо открыть базу данных для чтения и записи, то 
значение этого параметра необходимо установить равным false. 
    При открытии источников данных ODBC используется параметр 
varStrODBCSource. Например, при связи с удаленным источником данных ODBC в 
этом параметре необходимо указать имя и пароль удаленного пользователя. 
Параметр varStrODBCSource должен остаться пустым, если в параметре strDBName 
указано имя^файла базы данных. 

    296	Visual J++: основы программирования 

    DAO-библиотека классов позволяет с помощью объектов DAO создать лишь новую 
базу данных MS Access. С этой целью используется метод CreateDatabaseO класса 
Workspace, у которого имеется три параметра в виде строк. Первый параметр 
является именем того файла, который нужно создать. Второй параметр — это целое 
число, описывающее порядок сортировки текста и строк в базе данных. Третий 
параметр представляет собой набор дополнительных опций, каждая из которых 
представлена константой. Между константами проводится операция побитового ИЛИ. 
Сказанное можно проиллюстрировать простым примером: 

    Variant varStrDBFilename = new Variant (); Variant varNCollatingOrder(); 
Variant varOptions = new Variant(); 
    varStrDBFilename.putString(C:\\MyDB") ; 
    varNCollatingOrder.putint(Constants.dbSortGeneral) ; 
    varOptions.setlnt(Constants.dbEncrypt I Constants.dbVersion30); 
    Database myDB = m_workspace.CreateDatabase(varStrDBFilename, 
    varNCollatingOrder, varOptions); 

    Jet-система может создавать только MDB-файлы баз данных MS Access. 
Указание различных расширений в первом параметре метода CreateDatabaseO не 
изменяет того факта, что файл должен быть файлом MS Access. 
    Существует множество способов сортировки. Возможные значения параметра 
сортировки, называемого locale, определены в интерфейсе Constants. При работе 
с английским текстом используется значение Constants.dbSortGeneral. Другие 
значения позволяют сортировать данные, записанные символами различных 
алфавитов — немецкого, греческого, иврита, кириллицы и прочих. В файле 
summary.txt содержатся разрешенные константы dbSortATA'для параметра locale. 
    Использование параметра varOptions требует более подробных объяснений. В 
пакете dao3032 имеется класс Constants, в котором определены только значения 
констант, объявленных как static final integer. Здесь находятся все константы, 
необходимые для вызова различны"х"методов. В последнем примере параметром 
varOptions были заданы две команды, которые "приказывают" Jet-системе создать 
зашифрованный файл формата MS Access v3.0. В таблице перечислены все значения, 
которые могут быть использованы в этом методе: 

    Константа	Результат 

    dbEncrypt	Зашифрованный файл базы данных 
    dbVersionlO	Новый файл формата MS Access vl.O 
    dbVersion! 1	Новый файл формата MS Access vl.l 
    dbVersionlO	Новый файл формата MS Access v2.0 
    dbVersionSO	Новый файл формата MS Access v3.0 


    Глава 1О. Программирование 8аз данных в Java	297 

    Если опция шифрования не указана, база данных не будет зашифрована. 
Константы dbVersionXX задают, какой из форматов MS Access следует 
использовать. Одновременно можно использовать только одну из четырех констант. 
По умолчанию Jet-система создаст файл формата MS Access 3.0. 

    Список ОТКРЫТЫХ баз данных 

    Каждому рабочему пространству соответствует список открытых баз данных, 
который задается свойством Databases. Как и в случае с любым другим свойством 
автоматизации, для чтения значения этого свойства в Java используется метод 
getXXXQ СОМ-объекта Workspace. Значением этого свойства является ссылка на 
другой СОМ-объект, который реализует интерфейс Databases: 

    dao3032.Databases  dbs  = myWorkspace.getDatabases(); 

    Функциональные возможности интерфейса Databases аналогичны возможностям 
интерфейса Enumeration стандартного пакета java.util: он позволяет получить 
список объектов. Интерфейс Databases предоставляет список баз данных, открытых 
в рабочем пространстве. Свойство Count объекта Databases содержит число баз 
данных, перечисленных в объекте Databases. Метод getltemQ этого объекта 
принимает значение индекса, приведенное к типу Variant, и возвращает открытый 
в рабочем пространстве объект Database, соответствующий указанному индексу. 
Как получить весь список объектов, открытых в рабочем пространстве, показано ниже: 

    //  Предполагаем,   что объект myWorkspace инициализирован  правильно. 
dao3032.Databases  dbs  = myWorkspace.getDatabases() ; 
    for(int  ii=0;   ii<dbs.getCount();   ii++)    { Variant  varlndex =  new 
Vatiant(); varlndex.putlnt(ii); dao3032.Database db = dbs.getltem(varlndex); 
    //  Операции над Database. 


    Коллекипп 

    В объектах ОАО существует несколько коллекций, которые используются так 
же, как описанный выше объект Databases. В каждой из этих коллекций есть 
свойство Count, которое возвращает число элементов коллекции, и метод 
getltem(), используемый для поиска ссылки на один из таких элементов. В каждой 
коллекции реализован метод getCount(), имеющий синтаксис: 
    public int getCount(); 
    Реализован и метод getltemQ, хотя тип возвращаемого значения для каждой 
коллекции свой. Обычно его синтаксис выглядит следующим образом: 
    public <type> getltem(Variant varlndex); 


    298	Visual J+-H основы программирования 

    В каждой из коллекций содержится только определенный тип объектов. В 
частности, объекты Databases включают список объектов Database. Аналогично, в 
коллекции объектов TableDefs содержатся только объекты TableDef, в коллекции 
объектов RecordSets — лишь объекты RecordSet. Для выборки объектов во всех 
этих коллекциях применяются одни и те же методы (getCountQ и getltemQ), но не 
следует забывать, что тип объекта, возвращаемого методом getltemQ, разный для 
разных коллекций. 
    В последующих параграфах данной главы будет рассказано о коллекциях, ранее 
не упоминавшихся. Их интерфейсы аналогичны интерфейсам Databases, которые 
одинаковы в коллекциях всех типов. Имя коллекции говорит также об имени 
содержащихся в ней объектов: в коллекции Databases содержатся объекты 
Database, в коллекции RecordSets — объекты RecordSet и т.д. 

    Объекты Databases и TableDefs 

    Реляционная база данных состоит из ряда таблиц. Каждая таблица включает 
нескольких полей. Таблицу можно представить в виде строк и столбцов. При этом 
каждый столбец является одним полем таблицы, а каждая строка — одной записью. 
    В ОАО каждая таблица открытой базы данных описывается объектом TableDef. 
Этот объект определяет имя и тип каждого поля, следовательно, он описывает тип 
данных и идентифицирующее имя каждого столбца в таблице записей. 
    Все объекты TableDef, которые описывают таблицы, хранятся в коллекции 
объектов, называемой TableDefs и являющейся свойством объекта Database. Объект 
TableDef используется для перечисления всех объектов типа TableDef в базе 
данных таким же образом, как в коллекции Databases объекта Workspace, то есть 
с применением методов getCount() и getltemQ. 

    Поля TableDef 

    Метод getFieldsQ объекта TableDef возвращает коллекцию объектов типа 
Field. Каждый объект Field описывает одно из полей (столбцов), определенных в 
таблице. Поля характерны не только для объектов TableDef. В Jet-системе 
имеется объект QueryDef, который определяет запрос к базе данных. У объектов 
QueryDef, как и у TableDef, есть поля. При выполнении запроса или при 
просмотре отдельных записей в таблице DAO записи предоставляются в виде 
объекта RecordSet. У него также имеются объекты типа Field, по одному на 
каждое из полей набора записей, полученного в результате запроса. 
    Метод getNameQ объекта Field возвращает строку с именем поля. На объект 
Field в его коллекции Fields можно ссылаться по номеру или по имени: 

    //  Предполагается,   что объект myTabbleDef создан корректно... 
    Fields   fids   =  myTableDef.getFields(); 
    //   Поиск  первого  поля  в   коллекции  Fields. 
    Variant  varl  =  new Variant(); 


    Глава Ю. Программирование баз оанных в Java	299 


    varl.putlnt(0); 
    Field fl = flds.getField(O) ; 
    // Получение имени первого поля. String strName = f1.getName(); 
    // Поиск поля с именем "DOB". 
    var2 = new Variant(); 
    var2.putString)"DOB") ; 
    Field f2 = fIds.getField(var2) ; 

    Далее будет рассказано, как с помощью объектов Field получить доступ к 
отдельным полям записи. 
    Поле описывается атрибутами. Различные атрибуты поля являются частью 
интерфейса Constants. Метод getAttributesQ класса Field возвращает целое 
число, которое является результатом операции побитового ИЛИ над значениями 
различных атрибутов. В приведенной ниже таблице указаны все возможные значения 
атрибута Field из интерфейса Constants. 

    Значение атрибута 


    Описание 

    dbFixedField	Фиксированный размер поля (установлен по умолчанию для 
числовых полей). 
    dbVariableField	Размер поля варьируется (только для текстовых полей). 
    dbAutoIncrField	Значение поля для новых записей автоматически 

увеличивается на целое число, которое нельзя изменить (только для таблиц баз 
данных Jet-системы). Такое поле удобно использовать в качестве 
идентификационного номера (ключа) записи в таблице. 

    dbUpdadableField	Значение поля может изменяться. 
    dbDescending	Поле отсортировано в нисходящем порядке (от Z до А 

    или от 100 до 0). Применяется только для объекта Field в коллекции Fields 
объектов типа Index. Если эта константа опущена, то по умолчанию поле 
отсортировано в порядке возрастания (от А до Z или от 0 до 100). 
    dbSystemField	Репликативное поле (в объекте TableDe0, используемое в 
репликативных базах данных и не подлежащее ________________уничтожению._________________________ 
    Использование некоторых из этих атрибутов мы еще не рассматривали. 
Например, атрибут dbDescending применяется только к индексированным полям. 
    Далее приведен пример использования атрибутов поля. Эта программа 
открывает базу данных ОАО, заданную именем файла, и читает все таблицы и поля 
в этой базе данных. Обработка исключений здесь опущена. 

    import dao3032.*; 
    public class DatabaseFieldAttributes { 
    public static void main(String!] astrArgs) throws com.ms.com.ComException { 


    3OO Visual J++: основы программирования 


    // Первый и единственный аргумент — имя файла базы данных, 
if(astrArgs.length !=1) { 
    System.err.println("Usage Error:\n 
    jview DatabaseFieldAttributes <database-filename>"); System.exit(-1); 
    // Создание объекта DBEngine с использованием License Manager. 
    ILicenseM.gr mgr = new LicenseMgr () ; 
    _DBEngine dbeng = (_DBEngine) mgr.createWithLic ( "mjgcqcej 
fchcijecpdhckcdjqigdej fccj ri", "{00025E15-0000-0000-COOO-000000000046}", 
null, ComContext.INPROC_SERVER); 
    // База данных, указанная именем файла в командной // строке, открывается 
для немонопольного доступа. Variant vl = new Variant (); Variant v2 = new 
Variant(); Variant v3 = new Variant(); 
    vl.putBoolean(false); v2.putBoolean (false); v3.putString(""); 
    Database db = dbeng.OpenDatabase(astrArgs[0], vl, v2, v3) ; 
    // Получение списка объектов TableDefs. Для каждого объекта 
    // перечисляются все поля и атрибуты полей. 
    TableDef tds = db.getTableDefs() 
    for(int iTable = 0; iTable<tds.getCount(); iTable++) 
    { 
    TableDef tds = tds.getltem(iTable); System.out.println("Definition for 
table: " + td.getName()); 
    Fields fds = td.getFields(); 
    for(int iField=0; iField<fds.getCount(); iField++) 
    { 
    Field f = fds.getltem(iField); 
    System.out.print("\tField '" + f.getName() + "': "); 
    int attribs = f.getAttributes(); 
    boolean fFirstAttribPrinted = false; 
    if(0 != (attribs & Constants.dbFixedField)) 
    System.out.print("dbFixedField "); if(0 != (attribs & Constants.dbVariableField)) 
    System.out.print("dbVariableField "); if(0 != (attribs & Constants.dbAutoIncrField)) 
    System.out.print C'dbAutoIncrField ") ; if(0 != (attribs & Constants.dbUpdatableField)) 
    System.out.print("dbUpdatableField "); if(0 != (attribs & Constants.dbDescending)) 
    System.out.print("dbDescending ") ; 


    Глава 1О. Программирование ваз оанных в Java	3O1 


    if (0 != (attribs & Constants .dbSystenj) ) 
    System.out.print("dbFixedField "); } System.out.printIn("\n"); 
    } 
    db.Close(); 
    } 
    } 


    Подавление новых полей в таблицу 

    Метод CreateField() объекта TableDef предназначен для добавления новых 
полей в таблицу базы данных DAO. При создании поля в таблице надо указать его 
имя, тип и размер. Для некоторых типов полей размер указывается неявно. 
Синтаксис метода CreateFieldQ объекта TableDef выглядит так: 
    public  Field CreateField(Variant  varStrName,   Variant  varlntType, 
Variant  varlntSize); 
    Допустимые типы полей определены в интерфейсе Constants пакета dao3032. 
Описание каждой из этих констант приведено в таблице. 

    Тип поля     Описание 

    dbBoolean	Булево значение. Параметр VarlntSize игнорируется. 
    dbByte	Байт. Параметр VarlntSize игнорируется, 
    dblnteger	8-битовое целое. Параметр VarlntSize игнорируется. 
    dbLong	32-битовое целое. Параметр VarlntSize игнорируется. 
    dbCurrency       Тип валюты, который описывает число, достаточное для 
хранения 11 цифр до десятичной точки и четырех цифр после десятичной точки. В 
Java метод Variant.currencyVal() возвращает 64-битовое целое. 
    dbSingle	Число с плавающей запятой. Параметр VarlntSize игнорируется 
    dbDouble	Число с плавающей запятой двойной точности. Параметр VarlntSize игнорируется 
    dbDate	Число с плавающей запятой двойной точности, которое показывает 
количество дней, прошедших с полночи 30 декабря 1899 года (например, значение 
1,5 соответствует полдню 1 января 1900 года). 
    dbText	Значение типа String. Параметр VarlntSize показывает максимальную 
длину строки. 
    Существуют и другие типы полей, а именно: dbLongBinaty, dbMemo и dbGUID. 
    В следующем примере показано, как можно создать новое поле в объекте 
TableDef. Имя нового поля — DOB, его тип — dbDate. По умолчанию при создании 
нового поля соответствующим записям в таблице не присваивается никаких значений. 
    public void addDOBField(TableDef td)   throws  com.ms.com.ComException   { 
Variant  varStrName  = new Variant (); Variant varlntType  =  new Variant(); 


    302 Visual J++: основы программирования 


    Variant varlntSize = new Variant(); 
    varStrName.putString("DOB"); varlntType.putlnt(Constants.dbDate); 
varlntSize.putlnt(0) ; 
    td.CreateField(varStrName, varlntType, varlntSize1) ; } 


    Наборы записей: ПРОСМОТР записей таблицы 

    Чтобы просмотреть записи таблицы, с помощью метода OpenRecordSetQ объекта 
TableDef надо создать набор записей. В ОАО имеется несколько способов создания 
наборов записей, о которых будет рассказано в следующих параграфах. Набор 
записей, создаваемый из объекта TableDef, характерен только для баз данных 
Jet-системы (файлы с расширением MDB) или ISAM-файлов. 
    У метода OpenRecordsetQ класса TableDef имеется два параметра типа 
Variant. Первый параметр всегда имеет значение Constants.dbOpenTable. (При 
реализации этого метода другими объектами данный параметр может принимать 
различные значения, однако для метода OpenRecordsetQ класса TableDef может 
быть только значение dbOpenTable.) 
    Второй параметр типа Variant описывает правила доступа к записям таблицы. 
При открытии набора записей существует возможность выбора одного из трех 
значений, указанных в таблице. Над ними можно произвести операцию побитового ИЛИ. 

    Значение onunn      Описание 

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

    Синтаксис метода OpenRecordsetQ класса TableDef выглядит так: 

    public Recordset OpenRecordset(Variant varlntType, 
    Variant  varlntOptions); 


    Работа с наборами записей 

    Объект Recordset представляет набор записей таблицы. При этом он 
"позиционируется" на определенную запись в таблице, указывая тем самым 
выбранный элемент. Объект Recordset при создании позиционируется на первую 
запись. Методы MoveFirstQ, MovePrevQ, MoveNextQ и MoveLastQ предназначены для 
изменения текущей записи в таблице. 

    Глава 1О. Программирование Баз паниых в Java	3O3 

    Включенный в Visual J++ пример использования DAO показывает, как эти 
методы применяются для перемещения по различным записям таблицы. Вот краткое 
объяснение этого примера. 
    При запуске апплета DAOSample открывается база данных Sample Database, 
mdb. Апплет создает набор записей из таблицы Employees этой базы данных. 
(Метод TableDef.OpenRecordsetQ при этом не используется. Для создания набора 
записей из таблицы Employees применяется аналогичный метод объекта Database. 
Но результат точно такой же, как и при использовании метода Tab/eDef.OpenRecordset().j 
    Ниже показан код метода actionQ апплета DAOSample. Пользователь 
перемещается по набору записей с помощью кнопок First, Next, Prev и Last. 
Метод actionQ обрабатывает каждое нажатие этих кнопок. 

    public boolean action(Event evt, Object what) { 
    // Обработка нажатия кнопки First. 
    if (evt.target == m_first) 
    { 
    // Переход к запрашиваемой записи, recordset.MoveFirst(); 
    // Отображение записи. m_form.showData(); 
    //Событие обработано, return true; 
    // Обработка нажатия кнопки Prev. 
    if (evt.target == m_prev) 
    { 
    // Переход к запрашиваемой записи. 
    recordset.MovePrevious(); 
    // Отображение записи. ra_form.showData() ; 
    //Событие обработано. 
    return true; } 
    // Обработка нажатия кнопки Next, if (evt.target == m_next) { 
    // Переход к запрашиваемой записи. 
    recordset.MoveNext (); 
    // Отображение записи. m_form.showData(); 
    // Событие обработано, return true; 


    ЗО4	Visual J++: основы программирования 


    // Обработка нажатия кнопки Last. 
    if (evt.target == m_last) 
    { 
    // Переход к запрашиваемой записи. 
    recordset.MoveLast() ; 
    // Отображение записи. m_form.showData(); 
    // Событие обработано, return true; } 
    // Событие не обработано, return false; } 


    Использование полей пля проверки значения записей 

    Как_|г_у объекта_ТаЫе]Ое:[, у объекта RecordSet имеется свойство Field. 
Ссылка на "свойство Field возникает при использовании методов getFieldsO" 
объекта RecordSet. Значения всех объектов Field в коллекции Fields являются 
значениями полей текущей записи объекта Recordset. 
    Приведенный ниже код проверяет и выводит значение поля DOB в таблице 
Persons базы данных. Заметьте, что значение даты перед отображением на экране 
преобразуется в формат даты, принятый в Java. 

    public void showDOBField(Database db) 
    throws com.ms.comComException { // Доступ к таблице "Persons". Variant 
varStrPersons = new Variant (); varStrPersons.putstring("Persons"); TableDef 
td = db.getTableDefs().getltem(varStrPersons}; 
    // Создание Recordset из TableDef. Variant varlntType = new Variant (); 
Variant varStrOptions = new Variant 
    varlntType.putlnt(Constants.dbOpenTable); VarTrft"6ptidna,putlnt ^ _ 
    Recordset rs = td.OpenRecordse.t(varlntType, varlntOptions); 
    // Для каждой записи в базе данных... while(!rs.getEOF()) { 
    Variant varStrDOB = new Variant varStrDOB.putString("DOB"); double 
dCOMDate = 
    rs.getFields().getltem(varStrDOB).getValue(); // Преобразование даты из 
формата СОМ в формат Java.       * dCOMDate -= (365 * 70)  // Разница в днях между	[ 
    // 1970 и 1900 годами. +17  // Високосные годы. 
    +1   //В СОМ дата отсчитывается от 12/30,    * I/ в Java - от 1/01. 


    Глава 1О. Программирование Ваз оаиных в Java	3O5 


    dCOMDate *= MILLIS_PER_DAY; Date date = new Date(dCOMDate); 
    // Вывод даты на печать. 
    System.out.println("Next record's date: " + date); 
    // Переход к следующей записи Recordset, rs.MoveNext(); 
    } 
    } 


    Изменение записей 

    Объекты Field набора записей можно использовать для изменения значений 
отдельных записей в базе данных. Метод setValueQ объекта Field изменяет 
значение поля в базе данных. Но прежде чем задействовать данный метод, объекту 
Recordset необходимо сообщить о предстоящем изменении значения поля. Для 
предупреждения об изменении значения поля текущей записи служит метод EditQ 
объекта Recordset. 
    Измененные значения полей фиксируются в таблице базы данных с помощью 
метода UpdateQ объекта Recordset. Если этот метод не будет вызван, то все 
произведенные изменения не будут зафиксированы. Модификация значений поля 
записи производится в три этапа: 
    1.   Вызов метода EditQ объекта Recordset, если текущей выбранной записью 
является та, которую необходимо изменить. 
    2.   Изменение значений каждого поля с помощью метода setValue() 
соответствующего объекта Field. Все объекты Field расположены в коллекции 
Fields объекта Recordset (возвращаемой методом getFieldsQ). 
    3.   Фиксация изменений с помощью вызова метода UpdateQ объекта Recordset. 

    Создание наборов записей из запросов 

    Более интересным процессом является создание наборов записей из 
результатов SQL-запросов или из хранимых запросов Jet-СУБД. Наборы записей из 
запросов содержат только те записи, которые пользователь захотел здесь поместить. 
    Объект QueryDef представляет SQL-запрос к базе данных в DAO. Этот объект 
можно хранить в открытой базе данных или путем создания временных объектов 
QueryDef. Сохраненные объекты QueryDef можно многократно использовать в 
различных сеансах работы с базой данных. Запросы, которые будут повторно 
использованы, лучше сохранять в базе данных. Запросы, к которым обращаются 
только один раз или в течение одного сеанса, можно создавать динамически. Это 
временные объекты QueryDef, которые удаляются после каждого сеанса. 
    Для создания нового объекта QueryDef предназначен метод CreateQueryQ. У 
этого метода имеется два параметра, являющихся объектами типа Variant. Первый 
параметр — это имя, под которым следует сохранить запрос. Имя 

    ЗО6	Visual J++: основы программирования 

    дается только тем запросам, которые пользователь собирается сохранять в 
базе данных. В случае временных запросов необходимо передать пустой объект 
типа Variant. Второй параметр является строкой запроса в формате SQL. 
    Если вы еще не знакомы с SQL (Structured Query Language — язык 
структурированных запросов), широко используемым стандартным языком 
формирования запросов к базам данных, обязательно познакомьтесь с ним. Вся 
продукция фирмы Microsoft, идущая в комплекте с Jet-системой базы данных 
(кроме Visual J++ и пакета Office), снабжена документацией по SQL. Информацию 
об этом языке можно также получить из Microsoft Developer Network (MSDN). 
    Ниже приведен фрагмент кода, создающего именованный объект Query-Def с 
простым SQL-запросом: 

    String  strSQL  =   "SELECT   DOB   from  Persons"; 
    //   Предполагается,   что  переменная  db  создана   корректно. Variant 
varStrName =  new Variant(); Variant varStrSQL = new Variant (); 
    varStrName.putString("SimpleSelectQuery"); 
    varStrSQL.putString(strSQL); 
    QueryDef  qd = db.CreateQueryDef(varStrName,   varStrSQL); 

    Имеются следующие типы SQL-запросов: на удаление (delete), добавление 
(append), изменение (update) и выборку (select) данных, а также на создание 
таблицы (table create). Единственный тип запроса, с помощью которого можно 
создать набор записей, — это запрос на выборку данных. Об остальных типах 
запросов в DAO можно прочитать в следующих параграфах, посвященных различным 
типам объектов QueryDef. 
    Создав объект QueryDef (с использованием запроса на выборку данных), можно 
приступать к созданию набора записей, который будет состоять из записей и 
полей, определенных SQL-запросом. Так, в приведенном выше примере 
SimpleSelectQuery набор записей будет представлен полем DOB таблицы Persons. В 
набор записей могут включаться только поля, явно указанные в запросе на 
выборку (или неявно заданные с помощью символа "*"): 

    //  Создание  объекта Recordset  из объекта QueryDef  под названием //   
"SimpleSelectQuery"   из  предыдущего  примера. Variant varlntType =  new 
Variant(}; Variant varlntOptions  =  new Variant(); 
    varlntType.putlnt(Constants.dbOpenSnapshot); 
    varlntOptions.putlnt(0); 
    Recordset  rs  =  qd.OpenRecordset(varlntType,   varlntOptions); 


    Глава 1О. Программирование Ваз оамных в Java	3O7 


    Статические п динамические наборы записей 

    В приведенном выше коде параметр метода OpenRecordsetQ принимал значение 
Constants.dbOpenSnapshot. При создании объекта Recordset из записей таблицы с 
помощью объекта TableDef можно использовать только параметр 
Constant.dbOpenTable, но при создании этого же объекта из результатов 
SQL-запроса можно указать два различных типа наборов записей. 
    Динамический набор записей (Dynaset) обновляется динамически. Пользователь 
может модифицировать значения полей динамического набора записей таким же 
образом, как модифицируются значения набора записей, созданного из объекта 
TableDef. Кроме того, все изменения, осуществляемые другими пользователями, 
будут немедленно появляться в вашем наборе записей. Например, если 
пользователь организует запрос ко всем DOB-no-лям в таблице Persons своей базы 
данных и в это же время другой пользователь модифицирует значения того же 
самого поля той же таблицы, то изменения, производимые вторым пользователем, 
мгновенно отражаются в наборе записей первого пользователя. 
    Изменять же значения в статическом наборе записей (Snapshot) невозможно. 
Изменения, производимые другим пользователем, на статический набор записей не 
влияют. Статический набор записей отражает состояние записей в определенный 
момент времени. 
    Для создания динамического или статического набора записей методу 
QueryDef.OpenRecordsetQ передается соответствующая константа, указываю-щаяГтйп 
объекта Recordset В предыдущем примере статический набор записей был создан 
следующим образом: 

    varlntType.putlnt(Constants.dbOpenSnapshot} ; 
    varlntOptions.putlnt(0) ; 
    Recordset   rs   =  qd.OpenRecordset(varlntType,   varlntOptions); 

    Динамический набор записей можно создать из того же объекта QueryDef, 
используя значение Constants.dbOpenDynaset для первого параметра метода 
QueryDef. OpenRecordsetQ: 

    va:IntType.putlnt(Constants.dbOpenDynaset); 
    varlntOptions.putlnt(0); 
    Recordset rs = qd.OpenRecordset(varlntType, varlntOptions); 
    В динамическом наборе данных значения отдельных полей можно модифицировать 
с помощью методов EditQ и UpdateQ: 
    // Изменение значения текущего поля "DOB" путем увеличения 
    // даты ровно на один год. 
    Field f = rs.getFields().getField("DOB"); 
    Variant varVal = f.getValue(); 
    varVal.putDoubleValue(varVal.getDouble() + 365.0); 
    f.setValue(varVal); 


    3O8	Visual J++: основы программирования 


    Сохранение объекта QueryDef в базе данных 

    Как уже говорилось, часто повторяемые запросы можно поместить в базу 
данных, воспользовавшись DAO API. Чтобы это можно было сделать, созданному 
объекту QueryDef нужно сразу же дать имя. Объект без имени 
aBTOMaTH4ecjCH_cr?yHOjEijjTj^^	и удаляется после завершения "программы. 
    В предыдущем примере объекту QueryDef было присвоено имя "Simple-SelectQuery": 

    varStrName.putstring("SimpleSelectQuery") ; varStrSQL.putSt ring("SELECT 
DOB FROM Persons"); Querydef qd = db.CreateQueryDef(varStrName, varStrSQL); 

    Чтобы сохранить созданный объект QueryDef в базе данных, необходимо просто 
добавить его в коллекцшо QueryDefs: ' " 

    QueryDefs qds = db.getQueryDefs(); qds.Append(qd); 
    Во время последующих сеансов этот же объект QueryDef можно создать, просто 
отыскав его в коллекции QueryDefs: 
    // Получение сохраненного объекта QueryDef. QueryDefs qds = 
db.getQueryDefs(); Variant varQueryName = new Variant(); 
varQueryName.putstring("SimpleSelectQuery") ; QueryDef qdSimpleSQL = qds.getltem(varQueryName); 
    // Создание статического набора данных по результатам запроса на 
    // текущий момент. 
    Variant varlntType = new Variant(); 
    Variant varlntOptions = new Variant(); 
    varlntType.putint(Constants.dbOpenSnapshot); 
    varlntOptions.putlnt(0) ; 
    Recordset rs = qd.OpenRecordset(varlntType, varlntOptions) 


    Выполняемые запросы 

    В предыдущем параграфе рассказывалось о запросе на выборку данных. Такие 
запросы создаются с помощью операторов SQL, которые начинаются с выражения 
"SELECT <fields>...", и только ищут данные в базе данных. Существуют 
SQL-запросы и других типов, которые фактически выполняют действия над базой 
данных. Однако такие выполняемые запросы нельзя использовать для создания 
набораГдаписей. Запросы на добавление данных и запросы на удаление данных не 
возвращают набора записей. Этого не делают и другие типы выполняемых запросов. 
    Так, приведенный ниже оператор SQL уничтожает каждую запись в таблице 
Persons, у которой значение поля DOB меньше 36500 (приблизительно начало 1910 года): 

    DELETE   FROM  Persons   WHERE   (DOB  <   36,500) 

    Глава 1О. Программирование баз данных в Java	3O9 

    Объект QueryDef создается из этого SQL-запроса таким же образом, как и из 
запроса на выборку данных: 

    // Предполагается, что переменная db создана корректно. 
    Variant varStrName = new Variant(); 
    Variant varStrSQL = new Variant(); 
    String strSQL = " DELETE FROM Persons WHERE (DOB < 36,500)"; 
    varStrName.Empty();  // Безымянный, временный запрос. varStrSQL.putString(strSQL); 
    Query Def qdDeleteQuery = db.CreateQueryDef(varStrName, varStrOptions); 

    Для запуска этого выполняемого запроса, конечная цель которого — удаление 
указанных записей из базы данных, используется метод ExecuteQ объекта 
QueryDef. У этого метода имеется необязательный параметр, который описывает, 
как надо выполнить запрос. Ниже перечислены все возможные значения этого 
параметра. Как и остальные значения констант в ОАО API, эти значения 
определены в интерфейсе Constants пакета dao3032. 

    Значение опиип     Описание 

    dbDenyWrite	Отказывает в разрешении на запись данных остальным пользователям. 
    dblnconsistent	Разрешает несовместимые изменения. Например, при 
объединении двух таблиц в одну с отношением "один ко многим" разрешает 
изменять ключевое поле результирующей таблицы на значение, которое не 
встречается в исходных таблицах. Это опция по умолчанию. 
    dbConsistent	Разрешает только совместимые изменения. В частности, при 
объединении двух таблиц в одну с отношением "один ко многим" не разрешает 
изменять ключевое поле результирующей таблицы на значение, которое не 
встречается в исходных таблицах. 
    dbSQLPassThrough      Передает SQL-оператор непосредственно драйверу 
источников данных ODBC. По умолчанию библиотека DAO модифицирует SQL-операторы 
до их передачи основным драйверам ODBC, что в некоторых случаях может привести 
к потере производительности. Эта опция заставляет ОАО-систему не выполнять модификацию. 
    dbFailOnError	Автоматически аннулирует все изменения в базе данных, если 
при выполнении запроса возникла ошибка. Не выполняется по умолчанию. 
    dbSeeChanges	Генерирует ошибку периода выполнения, если другой 
пользователь редактирует данные, которые первый пользователь модифицирует или 
уничтожает в результате выполняемого запроса. 

    310 Visual J++: основы программирования 


    Резюме по DAO 

    В предыдущих параграфах кратко рассказывалось об основных возможностях 
DAO-объектов при вызове их из Java для работы с базами данных. Рассмотрение 
всех функций интерфейса DAO выходит за рамки этой книги, цель которой — 
познакомить читателя лишь с основными его возможностями. Особенно важно знать, 
что Jet-система управления базами данных может соединять таблицы из баз данных 
различных типов. Это означает, что из таблиц FoxPro ISAM, собственных 
MDB-файлов Jet-системы и таблиц, относящихся к другим источникам данных ODBC, 
можно создать единую базу данных. Jet-система может поддерживать целостность 
данных для всех типов таблиц, позволяя создавать общие запросы к таблицам из 
различных баз данных. 
    Необходимо знать о некоторой потере производительности, связанной с 
использованием DAO. Запросы через DAO API выполняются несколько мед-леннее^ 
чем непосредственно через драйвер баз данных ODBC или RDO API. 

    Объекты доступа к удаленным данным (RDO) 

    RDO (Remote Data Objects) является библиотекой объектов OLE-автоматизации. 
С помощью моста Java/COM эту библиотеку можно применять для управления 
сеансами работы с ODBC. RDO представляет собой интерфейс, полностью 
отличающийся от DAO API, используемого в Jet-системе управления базами данных. 
    Наиболее значительные различия между ОАО и RDO: 
    •    DAO является интерфейсом автоматизации для Jet-системы. RDO служит 
лишь "оболочкой" из СОМ-классов и различных API для библиотеки ODBC (Open 
Database Connectivity) в системе Windows. Это означает, что дополнительные 
функциональные возможности, характерные для DAO, в RDO отсутствуют. 
Использование RDO, как правило, обеспечивает более быстрый доступ к информации 
из баз данных. 
    •    RDO представляет собой более сложный по сравнению с DAO интерфейс, 
поскольку воспроизводит большинство функций ODBC API. Основная причина такой 
сложности заключается в том, что ODBC не является объектно-ориентированным 
API. Поэтому необходимо выполнять дополнительные действия для совмещения 
классов RDO с ODBC API. 
    •    Для использования RDO в Java-программах не обязательно иметь 
дополнительную лицензию. Применение же DAO невозможно без лицензии на 
Jet-систему для каждого компьютера, на котором работает программа. В этом 
заключается главное различие между интерфейсами. Следовательно, приложения и 
апплеты, использующие RDO, будут работать на любой платформе Win32, однако 
приложения и апплеты, использующие DAO, смогут функционировать только в 
системах, в которых имеются соответствующие библиотеки. 

    Глава 1О. Программирование баз аанных в Java	311 


    Создание переходных классов Java/COM для RDO 

    Поскольку RDO является набором СОМ-классов, для создания и использования 
RDO-объектов в Java следует создать набор переходных классов Java/COM. (В 
главе 7 описан мост Java/COM, встроенный в виртуальную машину Java для системы 
Windows.) Для создания этих классов воспользуйтесь утилитой Java Type Library Wizard. 
    Для начала запустите утилиту в Visual J++. В списке зарегистрированных 
СОМ-библиотек вы увидите библиотеку Microsoft Remote Data Object 1.0. Выберите 
этот пункт и щелкните на кнопке ОК. В результате в окне вывода появится следующее: 

    import msrdo32.*; 
    С:\WIN95\SYSTEM\MSRD032.DLL(0.0) : warning J5008: method ... (error 
message) C:\WIN95\SYSTEM\MSRD032.DLL(0.0) : warning J5008: method ... (error 
message) C:\WIN95\java\trustlib\msrdo32\summary.txt (1): Class summary 
information created 

    Оба сообщения об ошибке можно проигнорировать. Они сигнализируют, что в 
RDO API есть два метода, тип параметров которых (например, указатели или 
какие-нибудь другие типы, отсутствующие в Java) нельзя преобразовать в 
соответствующий Java-тип. В любом случае эти методы не так уж и необходимы. 
    "Как видим, в первой строке имеется оператор import, который необходимо 
включать в начало любого ссылающегося на RDO-объекты файла исходных текстов 
Java. Пакет msrdo32 находится в каталоге <win-dows>\Java\TrustLib\msrdo32. 
Создание переходных классов позволяет использовать RDO в апплетах и 
приложениях Java. 

    Объект rdoEngine 

    Объект rdoEngine представляет RDO-систему в приложениях и апплетах Java. 
Для приложения необходимо создать только один такой объект. СОМ-интерфейс, к 
типу которого преобразуется объект rdoEngine, называется j-doEngihe. Объект 
rdoEngine можно создать, воспользовавшись кодом: 
    import msrdo32.*; // Этот оператор необходимо включать всегда. 
    // Код метода start () апплета или метода main() приложения... _rdoEngine 
eng = (_rdoEngine)new rdoEngine(); 


    Объект rdoEnvironment 

    В DAO, как уже говорилось, имеется объект Workspace, представляющий каждый 
отдельный сеанс работы с Jet-системой базы данных. Аналогичный объектов RDO, 
называемый rdoEnvironment, позволяет осуществлять все соединения со всеми 
базами данных. 

    312	Visual J++: основы программирования 

    Все транзакции с базами данных так или иначе осуществляются посредством 
объекта rdoEnvironment. Это означает, что при создании шести различных 
соединений с шестью различными источниками данных ODBC образуется^Ьдишобъект 
rdoEnvironment и все шесть транзакций„обрабатываются как одна! Если при 
соединении с двумя базами данных вы занесли в одну из них пять новых записей, 
а затем уничтожили пять записей в другой базе данных, причем до того, как по 
какой-то причине произошел откат транзакции в первой базе, то все десять 
изменений в обеих базах будут отменены. 
    Объекты rdoEnvironment можно также использовать для дифференциации 
различных транзакций в RDO. Если две базы данных открыты в различных объектах 
rdoEnvironment, то откат транзакции в одной базе данных не повлечет за собой 
отката транзакции в другой. 

    Объект rdoEnvironment по умолчанию и коллекипп rdoEnvironments 

    Как и в DAO, в RDO имеется ряд коллекций, принадлежащих различным 
API-классам. Коллекция ^оЕпу)гоптеп|8объекта rdEngine содержит список всех 
открытых объектов rdoEnvironment программы. При создании объекта rdoEngine 
автоматически создается объект rdoEnvironment по умолчанию. Для того чтобы 
получить ссылку на этот объект, надо выбрать нулевой элемент в коллекции 
rdoEnvironments, имя которого — Default_Environment. Как можно создать объект 
rdoEngine и получить ссылку на его объект rdoEnvironment, показано ниже: 

    import msrdo32.*; 
    _rdoEngine eng = (_rdoEngine)new rdoEngine(); rdoEnvironments envs = eng.getrdoEnvironments(); 
    Variant varlndex = new Variant(); 
    varlndex.putlnt(0); 
    rdoEnvironment env = envs.getltem(varlndex); 
    // Объект rdoEnvironment. по умолчанию можно найти по имени, // заменив 
предыдущие три строки следующими: Variant varStrEnvName = new Variant(}; 
varStrEnvName.putString("Dsfault_Environment"); rdoEnvironment env = envs.getltem(varStrEnvName); 


    Свойства объекта rdoEnvironment 

    Объект rdoEnvironment имеет пять различных свойств, которые относятся к 
свойствам автоматизации. Получить доступ к этим свойствам и модифицировать их 
можно при помощи методов getXXXQ и setXXXQ, входящих в переходный 
Java/COM-класс rdoEnvironment. Свойства объекта rdoEnvironment, их значения по 
умолчанию и описание приведены в таблице. 

    Глава 1О. Программирование баз паииых в Java	313 


    Свойство     Значение     по умолчанию     Описание 

    rdoDefaultCursorDriver	rdUselfNeeded          Используемый ODBC-курсор. 
    rdoDefaultPassword	"" Пароль, который следует использовать при соединении 
с новым источником данных ODBC, если он не указан на момент соединения. 
    rdoDefaultName	"" Имя пользователя, которое следует указать при соединении 
с новым источником данных ODBC, если оно не указано на момент соединения. 
    rdoDefaultErrorThreshhold      -1	Номер ошибки, ниже которого 
    ошибки считаются фатальными. Значение по умолчанию -1 свидетельствует о 
том, что не существует ошибки, появление которой может привести к экстренному 
завершению программы. 
    rdoDefaultLoginTimeout	15 (секунд) Время, предшествующее соединению с 
удаленным источником данных 

    Все свойства объекта rdoEnvironment по умолчанию автоматически принимают 
стандартные значения. Во всех оставшихся примерах в этой главе будет 
использован объект rdoEnvironment по умолчанию. 

    Созяанпе пользовательских объектов rdoEnvironment 

    Для создания новых объектов rdoEnvironment предназначен метод 
CreateEnvironmentQ интерфейса _rdoEngine. У данного метода имеется три 
параметра типа String: имя объекта (для поиска объекта rdoEnvironment в 
коллекции rdoEnvironments), имя пользователя по умолчанию и пароль по умолчанию. 
    Даже при удалении ссылки на возвращаемый методом CreateEnviron-mentQ 
объект rdoEnvironment последний можно найти в коллекции rdoEnvironments. Чтобы 
уничтожить указанный объект, следует вызвать его метод CloseQ. Этот метод 
закроет объект rdoEnvironment и все открытые с его помощью соединения. 

    Соединения с базой данных 

    Соединения с источниками данных ODBC создаются с помощью объекта 
rdoEnvironment. В RDO они представлены объектом rdoConnection. Все соединения, 
установленные с зарегистрированными источниками данных ODBC, полностью 
управляются объектом rdoConnection. 

    314 Visual J++: основы программирования 


    Создание соединений с новой базой данных 

    Для того чтобы можно было связаться с источником данных ODBC, он должен 
быть зарегистрирован программой-администратором ODBC в панели управления 
Windows. При этом определяются сетевое местоположение ODBC-сервера (или 
местоположение файла базы данных в локальной файловой системе) и тип драйвера ODBC. 
    Для создания соединения с новой базой данных используется метод 
OpenConnectionQ объекта rdoEnvironment. Первым параметром является объект типа 
String, содержащий имя, которое можно использовать в RDO для ссылки на 
соединение с базой данных. Его можно применить и для поиска объекта 
rdoConnection в коллекции rdoConnections .объекта rdoEnvironment. 
    Второй параметр, являющийся объектом типа Variant, указывает, может ли 
пользователь вносить дополнительную информацию с помощью диалогового окна 
соединения ODBC. Чтобы запретить это делать, для указанного параметра следует 
установить значение rdDriverNoPrompt. Эта константа определена в интерфейсе 
PromptConstants пакета msrdo32. Использование значения rdDriver Prompt дает 
возможность пользователю вводить информацию в диалоговое окно соединения ODBC. 
    Булево значение третьего параметра типа Variant указывает, является ли 
соединение разрешенным только для чтения. Значение false этого параметра 
разрешает чтение и запись во время соединения. 
    Четвертым параметром служит строка параметров ODBC-соединения, также 
передаваемая в виде объекта типа Variant. 
    Приведенный ниже код взят из апплета RDO Sample, включенного в примеры 
Visual J++. В нем показано, как создать соединение с источником данных ODBC с 
именем Sample Database в режиме чтения/записи, запрещая пользователю вводить 
данные в диалоговое окно соединения ODBC. 

    Variant  vl,   v2,   v3; 
    // Установление значений необязательных параметров. 
    vl.putlnt(PromptConstants.rdDriverNoPrompt); 
    v2.putBoolean(false); 
    v3.putString("DSN=Sample Database;UID=;PWD=; DATABASE=Sample Database"); 
    // Создание соединения с исходным файлом данных. 
    m_IConnection = m_IEnvironment.OpenConnection("" , vl, v2, v3) ; 


    Поиск существующих соединений 

    Каждый новый объект rdoConnection автоматически добавляется в коллекцию 
rdoConnections, которая является свойством объекта rdoEnvironment. Объект 
останется в коллекции rdoConnections даже в том случае, если будут удалены все 
ссылки на него. Для обнаружения имеющихся соединений предназначен метод 
getltem() указанной коллекции, для их удаления — метод CloseQ. 

    Глава 1О. Программирование Ваз ааниых в Java	315 


    Наборы результатов в RDO 

    Как и в DAO, в RDO запросы возвращаются в виде набора результатов. Для 
чтения и модификации его значений используется объект rdoResultset, 
возвращаемый методом rdoConnection.OpenResultset(). 
    В RDO можно создавать готовые операторы запроса. Готовый оператор выглядит 
так же, как хранимый запрос QueryDef в DAO, и определяет запрос, который будет 
неоднократно использоваться в одном и том же объекте rdoEnvironment. Созданный 
оператор можно использовать и для генерации набора результатов. О таких 
операторах будет рассказано в следующем параграфе, но они имеют отношение и к 
настоящему параграфу, поскольку получаемый набор результатов используется так 
же, как и набор результатов, создаваемый методом OpenResultQ объекта rdoConnection. 
    У метода OpenResultsetQ четыре параметра. Первым параметром служит объект 
типа String, который является SQL-оператором, используемым для создания 
объекта rdoResultset. В этом параметре можно также указывать имя объекта 
rdoPreparedStatement или rdoTable. В обоих случаях возвращается тот же набор 
результатов, который можно получить с помощью метода OpenRe-sult() объекта 
rdoPreparedStatement или rdoTable. Третьей возможностью для данногд.дараметра 
является передача имени хранимой процедуры, предваряемого строкой "ЕХЕС". 
Имена хранимых процедур, конечно же, "работают" только в том случае, если 
источник данных ODBC поддерживает такие процедуры. 
    Второй параметр задает тип курсора. Курсоры четырех типов определяют 
доступ~к различным записям набора результатов, а также служат для обновления 
этих записей. Для каждого типа курсора в интерфейсе ResultsetType-Constants 
пакета msrdo32 имеются соответствующие константы. В следующей таблице 
перечислены эти константы и описана работа курсоров различных типов. 

    Константа     Описание 

    RdOpenForwardOnly    Доступ к записям rdoResultset осуществляется 
последовательно, начиная с первой записи. Это самый быстрый тип курсора, 
который, однако, налагает ограничение на порядок чтения (только последовательный). 
    RdOpenStatic	Статический курсор аналогичен статическому набору записей в 
ОАО. Записи в наборе не обновляются, пока курсор не будет закрыт и повторно 
открыт. Доступ к записям возможен в произвольном порядке. 
    rdOpenKeyset	Набор записей с ключевыми полями аналогичен динамическому 
набору записей в ОАО. Для каждой записи в наборе хранится уникальный ключ. При 
доступе к определенной записи ее значение считывается с сервера. Это значит, 
что все изменения записи, сделанные после создания набора записей, отражаются 
в значениях ее полей. Записи, удаленные из набора, или записи, чье измененное 
значение не соответствует текущему запросу, не удаляются из набора результатов. 

    316 Visual J++: основы программирования 


    Константа     Описание 

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

    Третьим параметром метода OpenResultsetQ служит объект типа Variant, чье 
целочисленное значение указывает, какой вид одновременного доступа желательно 
использовать для просмотра набора записей. Возможные виды одновременного 
доступа задаются константами интерфейса LockTypeCon-stants пакета msrdo32. 
Значение rdConcurReadOnly говорит о том, что записи объекта rdoResultset 
доступны только для чтения. Значение rdConcurLock указывает на блокирование 
таблицы (или таблиц), следовательно, остальные пользователи не могут в это же 
время изменять в ней записи. 
    Четвертый параметр метода OpenResultsetQ также является объектом типа 
Variant. Его значение указывает на синхронность или асинхронность выполнения 
запроса. Для осуществления асинхронной обработки запроса используется значение 
rdAsynch Enable из интерфейса OptionConstants. По умолчанию запрос 
обрабатывается синхронно. 

    Создание набора результатов из таблицы 

    Как и в DAO, набор записей можно создать из содержимого отдельной 
т^1блиць! базы данных. Коллекция rdoTables, возвращаемая методом 
getrdo-TablesQ объекта rdoConnection, содержит ссылки на все таблицы, 
определенные в базе данных. Метод OpenResultQ объекта rdoTable возвращает 
набор результатов, созданный из всех записей таблицы. 
    Метод OpenResultQ имеет три аргумента, описание которых совпадает с 
описанием трех последних параметров метода OpenResultsetQ объекта 
rdo-Connection. Другими словами, первый параметр определяет тип курсора, 
второй описывает вид одновременного доступа, а последний указывает на 
синхронный или асинхронной способ обработки запроса. 

    Работа с наборами результатов 

    Как и у объекта Recordset в DAO, только одна запись у объекта rdoResultset 
доступна для просмотра или изменения. Для последовательного перемещения по 
набору результатов можно использовать методы MoveAXYQ объекта rdoResultset, а 
именно: MoveFirstQ, MovePrevQ, MoveNextQ и Move-LastQ. Методы getBOFQ и 
getEOFQ выполняют те же функции, что и методы объекта Recordset, имеющие 
аналогичные названия. Они возвращают значение true, если пользователь работает 
с первой (getBOFQ) или последней (getEOFQ) записью. 

    Гааиа 1О. Программирование Баз данных в Java 317 

    Для проверки значений полей текущей записи используется метод 
getrdo-ColumnsQ объекта rdoResultset. Он возвращает объект rdoColumns, который 
является коллекцией, содержащей все объекты rdoColumn, описывающие поля 
записи. Значение поля можно получить с помощью метода getValue() объекта rdoColumn. 
    Для модификации значений определенного поля используется механизм, 
аналогичный применяемому в DAO. Сначала вызывается метод Edit() объекта 
rdoResultset, который готовит текущую запись для модификации, копируя значения 
различных полей во временный буфер. Затем с помощью метода rdoColumn.setValueQ 
изменяются значения нужных полей. Чтобы зафиксировать изменения в базе данных, 
следует вызвать метод UpdateQ объекта Resultset. 

    Краткое описание JDBC 

    Третий из доступных в Java интерфейсов баз данных — это JDBC (Java 
Database Connectivity), созданный фирмой JavaSoft. В отличие от DAO и RDO он 
целиком основан на языке Java и, следовательно, является 
платформно-независимым. Интерфейсы DAO и RDO применяются только в системах Win32. 
    Все коды JDBC написаны на языке Java, поэтому их можно использовать в 
любой системе, где имеется виртуальная машина. Классы JDBC включены в список 
основных API версии Java 1.1, поэтому они доступны на каждой ВМ Java 1.1, в 
том числе и на любом WWW-броузере. 
    JDBC API во многом основывается на ODBC. Внесенные в него некоторые 
второстепенные изменения лишь облегчают использование данного интерфейса в 
языке Java. Этот интерфейс настолько похож на ODBC, что читатели, знакомые с 
последним, могут без подготовки приступать к программированию с использованием JDBC. 
    Структура JDBC API также очень похожа на структуру ODBC, поэтому одним из 
первых драйверов JDBC стал мост JDBC/ODBC. Он дал возможность программировать 
источники данных ODBC прямо на языке Java, без помощи СОМ-объектов. В 
некоторых случаях такое программирование может быть "более быстрым, чем 
программирование с использованием собственных кодов' СОМ-объектов для доступа 
к базе данных из Java. 
    Полная информация о JDBC API опубликована фирмой JavaSoft и доступна по 
адресу http:://splash.javasoft.com 

    Резюме 

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

    318	Visual J++: основы программирования 

    DAO (Data Access Objects) API является СОМ-интерфейсом для Jet-системы 
управления базами данных. Это самый простой из интерфейсов, который содержит 
наиболее полный набор классов. Использование Jet-системы может, однако, внести 
и определенные неудобства, поскольку ее необходимо лицензировать для каждой 
системы, в которой применяется база данных. Некоторые продукты фирмы Microsoft 
содержат runtime-версию Jet-системы, но в системах, где эти продукты не 
инсталлированы, бесполезно пытаться запустить DAO-приложения и апплеты. 
    RDO (Remote Data Objects) API служит оболочкой для ODBC API. Выполняемые в 
RDO запросы обрабатываются не менее быстро, чем в ODBC. Поскольку ODBC API не 
является объектно-ориентированным интерфейсом, то использовать RDO-объекты 
иногда бывает достаточно сложно. Но именно применение RDO API обеспечивает 
эффективность работы приложений и независимость их от лицензий. 
    JDBC API является интерфейсом, полностью написанным на Java. 
Следовательно, рн платформно-независим и доступен в любой виртуальной машине 
Java начиная с версии 1.1. Данный интерфейс очень похож на ODBC API, и 
программисту, знакомому с последним, не составит труда выучить JDBC. 

