Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

JAVA. Серверные приложения

.pdf
Скачиваний:
0
Добавлен:
06.09.2026
Размер:
2 Мб
Скачать
Часть 5. Работа с базами данных при помощи Java 261
commit()
getAutoCommit()
getMetaData()
getWarnings()
isClosed()
isReadOnly()
rollback()
setAutoCommit()
Остальные методы данного класса используются в SQLJ-приложени-
ях и при попытке доступа к ним возвращают ошибку. По умолчанию значением метода autoCommit() является off для всех соединений. В SQLJ-приложениях, предназначенных для работы с DB2, SQL-опера­тор COMMIT не работает. Для установки возможности использования оператора COMMIT в SQLJ-приложении, обращающегося к DB2, испо­льзуется метод setAutoCommit() с параметром on.
В SQLJ-коде можно использовать кооментарии обоих типов: SQL-
комментарии и Java-комментарии. Использование комментариев в SQLJ-коде позволительно для всех конструкций, но SQL-комментарии ставятся только после метки #sql, в то время как Java-комментарии мо­гут указываться в любом месте программы.
/* Java комментарии */ #sql c1={—SQLкомментарии /* Java комментарии */ // Java-операторы — это тоже коментарий SELECT name FROM table }; /* Java-комvентарии */
Правильно оформленные комментарии позволяют без участия про-
граммиста создать документацию на созданное приложение. Есть очень полезная утилитка, поставляемая в стандартном JDK. JavaDoc пред­назначена для генерации сопроводительной документации на основе комментариев, используемых при создании приложения. После созда­ния SQLJ-приложения необходимо определить роль созданного SQLJ­приложения. Будет ли SQLJ-приложение самостоятельным, или же ему необходим инициатор, как, например, апплет, от этого выбора зависит, каким способом SQLJ-приложение будет доступно другим приложени­ям. Также обязательно следует учесть, каким способом осуществляется передача пользовательских параметров соединения с базой данных по­средством командной строки, при помощи использования файла свойств илb же параметры будут передаваться посредством форм графического интерфейса.
Глава 37. Встроенные объекты
В SQLJ-приложениях, так же как и в любом другом предназначен-
ном для взаимодействия с базой данных, при соединении с СУБД необ­ходимо в вести параметры, идентифицирующие пользователя, сделав­шего запрос, пароль, логин, а также может потребоваться ввод другой
262 Часть 5. Работа с базами данных при помощи Java
служебной информации, имя базы данных и тип драйвера. Можно каж­дый раз воспользоваться интерактивным SQL, но в таком случае отпа­дает необходимость в SQLJ-приложении. Спецификация SQLJ описыва­ет специальные встроенные объекты, облегчающие задачи соединения клиента с базой данных. Два класса ConnectionContext и ExecutionCon­text предназначены для получения информации о пользовательском со­единении и пользовательском SQL-запросе. При использовании СУБД часто встречаются задачи, выполняемые SQL-операторами, требующие многократного повторения или повторения одних и тех же действий для разных SQL-операторов. SQLJ-приложения содержат встроенный объ­ект Iterator, упрощающий выполнение однородных SQL-операторов.
37.1. Context
Встроенный объект, определяющий внутренние значения работы
SQL-оператора — это context. Существуют два типа Context-объектов: ConnectionContext и ExecutionContext. Синтаксис обоих идентичен, и оба могут не указываться в SQLJ-приложении, поскольку значения присваиваются в момент трансляции SQLJ-приложения, а значит, уста­навливаются по умолчанию. Если трансляция SQLJ-приложения вы­полняется из командной строки, то аргументы командной строки явля­ются параметрами объекта ConnectionContext, в противоположном ва­рианте значения берутся из других источников.
Для работы со встроенным объектом context указывается дополните-
льная синтаксическая конструкция SQLJ. В теле конструкции содер­жится определение соединения с базой данных. Обычно конструкция используется для организации доступа к самой базе данных с использо­ванием объектов класса ConnectionContext. Эти объекты содержат слу­жебную информацию, такую, как тип драйвера, имя пользователя, па­роль и т. д. Создание объекта ConectionContext является стандартной операцией инициализации объекта, затем созданные СonnectionContext объекты можно заставлять работать. В SQLJ-коде, ConnectionContext объекты следуют непосредственно после #sql метки, в квадратных скобках [ ] указывается имя созданного объекта контекста соединения, желательно не путать данное определение с определением Java-мас­сива.
import java.sql.*; import sqlj.runtime.*; import sqlj.runtime.ref.*;
#sql public context MyContext;
Class. forName("COM.ibm.db2.jdbc.app.DB2Driver"). newInstance();
MyContext ctx1=DriverManager.getConnection ("jdbc:db2:sample"); MyContext ctx2=DriverManager.getConnection("jdbc:db2:sample", "user", pwd");
...
#sql [ctx1] { SQL operation }; MyContext.setDefaultContext(ctx2);
...
Часть 5. Работа с базами данных при помощи Java 263
#sql { SQL operation }; #sql { SQL operation }; MyContext. getDefaultContext();
В 1-й строке создается объект MyContext, рожденный от встроенного
объекта context, в результате чего будет создан класс MyContext с не­сколькими конструкторами. Генерируемой код сделает более понятным описанное ниже.
class MyContext extends sqlj.runtime.ConnectionContext{ public MyContext(String url, Properties info)throws SQLException; public MyContext(String url)throws SQLException; public MyContext(String url, String user, String password) throws SQLException; public MyContext(ConnectionContext conctx) throws SQLException; public static MyContext getDefaultConnection(); public static void setDefaultConnection(MyContext myCtx); public java.sql. DriverManager getConnection ();}
2-я и 3-я строки создают два разных объекта соединения с базой
данных для различных пользователей. У одного из подключаемых поль­зователей отсутствуют значения логина и пароля. Это указывает на ис­пользование значений данных атрибутов по умолчанию.
При создании нескольких разных соединений с базой данных воз-
можно использование единственного определенного объекта connection­context, который указывается в квадратных скобках после метки #sql — строка 4, выполняющая для пользователя с отсутствующими парамет­рами соединения, присущие только этому пользователю SQL-операторы после выполнения операций пользователя с параметрами, заданными по умолчанию. Выполняются конкретные SQL-операторы (строки 6, 7) для пользователя с именем «user» (строка 5). При помощи метода setDefault­Connection объекта MyContext в 8-й строке вызывается последний ме­тод данного класса getDefaultConnection(), возвращающий значение те­кущего соединения. Перед использованием конструкторов класса необ­ходимо указать драйвер базы данных непосредственно в коде, или через файл свойств, или при трансляции указанием специальных аргументов в командной строке. 1-ю строку обычно опускают и используют встроен­ный класс DefaultContext, без инициализации. Интерфейс Connection­Context содержит ряд абстрактных методов, полезных при определении соединения. Полезным здесь можно назвать метод освобождения ресур­сов close(). Для получения информации о SQL-запросе используется ме­тод getExecutionContext(). Получение информации о самом объекте Con­nectionContext происходит при вызове метода getConnectionContext().
Каждый SQLJ-объект класса ConnectionContext включает конструктор,
использующий как аргумент объект класса Connection, описанный в JDBC. Конструктор используется для создания объекта Connection. Доступ к объ­екту Connection можно получить, используя метод getConnection(). Другой метод close() предназначен для освобождения системных ресурсов.
Константа CLOSE_CONNECTION указывается, если соединения с ба-
зой данных должны освободить ресурсы и соединение должно быть за-
264 Часть 5. Работа с базами данных при помощи Java
крыто. Другая константа, KEEP_CONNECTION, используется при обя­зательном сохранении соединения после выполнения части кода. Метод isClosed() объекта connection context возвращает значение true, если вы­зывается любой вариант метода close().
37.1.1. Использование объекта Connection Context
Организация соединения с базой данных SQLJ-приложения может
осуществляться несколькими способами. Первый способ не использует API JDBC для работы с базой.
1. Выполняется подпрограмма инициализации, описанная в создан-
ном классе #sql context ctx;
2. Вызывается конструктор класса Connection Context с параметрами
проинициализированными на первом этапе, имя драйвера, URL базы данных и т. д.
ctx myconn=new ctx( jdbc:db2:SABPLEDB); В другом способе доступ к базе данных осуществляется при помощи
объектов класса Connection, из пакета java.sql.Connection. SQLJ-прило­жение использует следующие шаги при встроенном объекте Connection Context.
1. Выполняется подпрограмма инициализации, описанная в создан-
ном классе #sql context ctx;
2. Затем вызывается метод getConnection() класса java.sql.DriverMa-
nager с определенными параметрами, обеспечивающими доступ к базе:
Connection jdbccon= DriverManager. getConnection(jdbc:db2:SAMPLE);
3. Следующим шагом вызывается конструктор уже класса connection
context.
4. Наконец, вызывается конструктор для класса ctx, использующий
JDBC-объект connection как аргумент соединения:
ctx myconn=new ctx(jdbccon);
Каждый экземпляр Connection Contexts содержит объект класса
ExecutionContext, имеющий метод getExecutionContext(), используемый для получения сведений о выполняемом операторе.
37.1.2. Модификация исполняемого SQL-оператора, используя объект Execution Context
Для изменения SQL-запроса в стандарте SQLJ описан класс Executi-
onContext, имеющий методы по обработке SQL-запроса. Используя ме­тоды данного класса, приложение может получить сведения об испол­няемом запросе, количестве строк выполненного запроса, о времени вы­полнения запроса и конструкции самого SQL-запроса.
ExecutionContext myExecCtx = new ExecutionContext(); #sql [myConnCtx, myExecCtx] { DELETE FROM emp WHERE sal > 3000}; #sql [myExecCtx] { DELETE FROM emp WHERE sal > 3000};
Часть 5. Работа с базами данных при помощи Java 265
Чаще всего работа ExecutionContext связана с работой потоков при вы-
полнении одного потока по обработке запроса, другой поток, пытающийся обратиться к используемому оператору, блокируется. Нередко возникает ситуация, когда вызов одной функции осуществляется посредством дру­гой. И если оба ConnectionContext и ExecutionContext не определены, т. е. используются значения, принятые по умолчанию, что является обычной ситуацией, тогда SQLJ-операторы вызываемой функции используют объекты Context, идентичные объектами другой функции, что может привести к ошибке. После того как объект Execution Context привязали к определенному SQL-оператору, можно выполнять методы объекта Exe­cutionContext. Например, используя метод getUpdateCount(), возвращаю­щий количество обновленных строк данным SQL-оператором:
#sql TconnCtx, execCtxR {DELETE FROM EMP WHERE SALARY >
100000};
System.out.println(«Deleted» + execCtx. getUpdateCount() + «rows»); Методы объекта ExecutionContext полезны, если необходимо полу-
чить исчерпывающую информацию о выполняемом запросе.
Метод getWarnings() — возвращает java.sql.SQLWarning объект, со-
держащий предупреждения выполнения SQL-операторов.
Метод getUpdateCount() — указывает количество строк, обработанных
последним SQL-оператором. Константа QUERY_COUNT возвращает ко­личество обновленных строк. Другая константа, EXCEPTION_COUNT, указывает на возникновение исключительной ситуации в момент испол­нения последнего SQL-оператора.
Метод getNextResultSet() — указывает на следующую строчку сфор-
мированного ответа.
Метод getMaxFieldSize() — возвращает объект типа int, указываю-
щий максимальное число (в байтах), возвращаемое SQL-операторами объекта. Значение по умолчанию ноль (0) говорит об отсутствии ограни­чений объема возвращаемого объекта.
Метод setMaxFieldSize() — устанавливает значение типа int, только
для колонок, содержащих объекты SQL типов BINARY, VARBINARY, LONGVARBINARY, CHAR, VARCHAR или LONGVARCHAR.
Метод getMaxRows() — максимальное количество строк, обрабатыва-
емое итератором, как число типа int. Значение ноль (0) по умолчанию указывает на отсутствие ограничений количества строк.
Метод setMaxRows() — указывает количество строк для модификации. Метод getQueryTimeout() — определяет время в секундах как тип
int, по прохождении которого будет выполнен SQL-оператор, использу­емый в ExecutionContext. Значение по умолчанию ноль (0) снимает вре­менной лимит.
Метод setQueryTimeout() — устанавливает значение выполнения
SQL-оператора как тип int.
Метод сancel() — прерывает поток существующего SQL-запроса. В нижеуказанном примере показано использование некоторых мето-
дов класса ExecutionContext:
ExecutionContext execCtx=DefaultContext. getDefaultContext(). getExecuti onContext();
-
266 Часть 5. Работа с базами данных при помощи Java
execCtx. setQueryTimeout(3); #sql { DELETE FROM emp WHERE sal > 10000 }; System.out.println («removed» + execCtx. getUpdateCount() + «employees»);
В 1-й строке создается объект execCtx класса ExecutionContext, и
данному объекту присваивается значение выполняемого SQL-операто­ра. 2-й строка указывает промежуток времени, через который испол­нится запрос, указанный в 3-й строке. Задержка по времени хороша при многопотоковом запросе. И напоследок, 4-я строка выводит количе­ство строк выполненного запроса.
Можно использовать в SQLJ-приложениях потоки, при этом может
возникнуть ситуация неправильной работы драйвера базы данных или обращение к уже используемым данным.
Для предотвращения этой и подобных ситуаций лучше для объекта
ConnectionContext использовать различные объекты ExecutionContext. Это позволит избежать ненужных тупиковых ситуаций, поскольку для каждого объекта ConnectionContext будет создаваться свой поток. Если поток выполнения SQL-оператора использует тот же ExecutionContext, что и другой, в таком случае поток будет заблокирован, пока первый поток не закончится. В результате достигается точность выполнения SQL-операторов, а соответственно верность и ответов.
При использовании объекта класса ExecutionContext в SQLJ-прило-
жении делаются следующие шаги:
Создается объект ExecutionContext execCtx=new ExecutionContext(); Следом созданный объект execCtx будет ассоциирован с определен-
ным SQL-оператором для вызова объекта класса ExecutionContext. Имя вызываемого объекта заключается в квадратные скобки, и затем будет выполнен оператор. Можно ассоциировать различные объекты Execu­toionContext с определенным SQL-оператором
#sql execCtx {DELETE FROM EMP WHERE SALARY > 100};
При использовании в коде объектов двух классов ConnectionContext
и ExecutionContext объект класса ConnectionContext должен указыва­ться первым.
Если в SQLJ-коде не указаны параметры доступа, то DB2 будет ис-
пользовать значение системы, на которой проинсталлирована база дан­ных. Если необходимо выполнить SQL-оператор в другой базе данных, необходимо создать объект ConnectionContext и передать ему парамет­ры, необходимые для подключения к другой базе.
37.2. Iterator
SQLJ позволяет использовать повторитель (iterator). Итератор созда-
ется как Java-объект, т. е. имеет все атрибуты Java-классов, со своими параметрами, указывающими типы объектов, а также, возможно, сами объекты с которыми будет работать SQL-запрос. Итератор очень похож на стандартный курсор в базе данных. Возвращаемое значение пред­ставляет набор строк из таблицы как Java-объектов. Курсор в базе дан­ных используется для того, чтобы дать возможность пользователю про-
Часть 5. Работа с базами данных при помощи Java 267
сматривать результирующую таблицу строка за строкой. В отличие от курсора, значения, полученные при помощи итератора, можно передать методу для дальнейшей обработки. Существует два типа итераторов — именованные и позиционные.
Именованный итератор указывает тип и имя колонки, а позицион-
ный итератор указывает только тип. Инициализация итератора всегда должна быть раньше, чем его использование. Входящие переменные за­писываются в круглых скобках. Созданный объект класса Interator мо­жет также расширяться внешними интерфейсами при использовании ключевого слова языка Java-implements. При этом обязательно следует или импортировать пакет, содержащий данные интерфейсы, или же указывать полное имя интерфейса — com.myPackage.myInterface. При создании итератора необходимо соблюдать условие любви: сначала ого­вариваем, затем используем. Сначала объявляется итератор, затем со­здается объект созданного класса итератора.
#sql public iterator iterName (String name, date birthdate); {iterName nameFromDB; #sql nameFormDB {SELECT name, date FROM table}; String _s; Date _d; while (nameFromDB.next()) {s=nameFromDB.name(); i=nameFormDB.birthdate();}} В 1-й строке создается именованный итератор с двумя аргументами,
указывающими имя колонок, name и birthdate, и типы данных в этих столбцах, String и date. Затем в строке 2 создается объект класса ите­ратора iterName. В 3-й строке данному объекту присваивается значение запроса из таблицы table. Используя метод netx(), строки 6 и 7, двум переменным, 4, присваивается значение из таблицы.
37.2.1. Использование именованного итератора
Использование именованного итератора предназначено для выбора
определенных строк из результирующей таблицы. При создании имено­ванного итератора, в SQL-запросе указываются имена колонок резуль­тирующей таблицы. Имена колонок, используемые в SQL-запросе и имена колонок, используемые в именованном итераторе, должны быть идентичны.
При определении именованного итератора в SQLJ-приложении в мо-
мент трансляции генерируется класс, содержащий специальный метод, ассоциированный с каждой колонкой результирующего запроса. Метод имеет эквивалентное имя колонки, с которой ассоциирован.
В момент выполнения SQL-запроса, указанного в именованном ите-
раторе, SQLJ-проверяет соответствие указанных имен метода и резуль­тирующей таблицы. Большой плюс именованного итератора состоит в том, что последовательность указания колонок в итераторе может не совпадать с последовательностью в результирующей таблице.
{#sql public iterator ByName(String LastName, Date HireDate); ByName namiter;
268 Часть 5. Работа с базами данных при помощи Java
#sql nameiter={SELECT LASTNAME, HIREDATE FROM EMP}; String name; Date hrdate; while (namiter. next()) { hrdate = namiter. HireDate(); name = namiter. LastName(); System.out.println(name + «was hired on» + hrdate); } }
В 1-й строке определяется именованный итератор с двумя перемен-
ными, имена которых являются именами колонок результирующей таб­лицы. Во 2-й строке создается объект класса итератор. На следующем этапе созданному объекту присваивается значение SQL-запроса. Опять же, имена колонок, из которых выбираются значения, такие же как и в созданном итераторе. Java-переменные (поля), определенные в строках 4, 5, используются в дальнейшем для присвоения табличных значений. 9-я строка выводит результат на системную консоль.
Если оператор SELECT, используемый в именованном итераторе,
выбирает данные из таблицы, имена колонок не определены в Java, не­обходимо использовать псевдоним, зарезервированное слово AS. Такая ситуация может возникнуть, например, при объединении таблиц.
Обычный SQL-оператор
SELECT «bad colname» FROM table,
но итератор не знает как работать с колонкой «bad colname», поэтому в SQL запросе необходимо использовать псевдоним
SELECT «bad colname» AS GOODCOLNAME FROM GOODTABLE
Затем в приложении можно создавать итератор, с правильным име-
нем, будет использоваться псевдоним.
#sql public iterator ByName(String GoodColName); ByName namiter; #sql nameiter={SELECT «bad colname» AS GOODCOLNAME FROM GOODTABLE};
Именованный повторитель работает только с именами колонок, по-
этому при объединенном запросе или при создании колонки, хранящей данные вычислений из других колонок, необходимо использовать псев­донимы.
#sql emps = {SELECT maximum(comm, 0) AS maxComm FROM Emp};
В вышеуказанном SQL-операторе для этого используется ключевое
слово AS.
Классы, содержащие iterator c именованными значениями использу-
ют интерфейс NamedIterator, порожденный от ResultSetIterator, с од­ним методом next() который возвращает значение true, при условии су­ществования следующей строки в результирующей таблице.
Часть 5. Работа с базами данных при помощи Java 269
37.2.2. Использование позиционного итератора
Другой тип итератора, описанный в спецификации, называется пози-
ционным итератором.
В отличие от именованного итератора, позиционный работает не с
именами колонок результирующей таблицы, а позициями колонок в таблице. Позиции колонок перечисляются в определенном порядке сле­ва направо. Тип объектов в колонке, указанной первым параметром итератора, является первой колонкой с таким же типом в таблице. То есть, если в таблице используется несколько колонок с различными ти­пами данных, что вполне естественно, то первой колонке с определен­ным типом будет соответствовать переменная итератора того же типа. Позиционный итератор используется для работы с SQL-оператором FETCH.
#sql public iterator ByPos(String, Date);
Созданный позиционный итератор работает с колонками, содержа-
щими объекты типа string и Date.
Пример, рассмотренный выше с именованным итератором, с позици-
онным итератором выглядит несколько иначе.
{#sql public iterator ByPos(String, Date); ByPos positer; String name = null; Date hrdate; #sql positer = {SELECT LASTNAME, HIREDATE FROM EMP}; #sql { FETCH:positer INTO:name,:hrdate }; while (!positer. endFetch()): {System.out.println(name + «was hired in»+hrdate);}
В первых строках создается позиционный итератор. Как можно заме-
тить, в нем отсутствуют имена колонок. До 5-й строки ничего важного не происходит. Итератору присваивается значение результирующей табли­цы, строка за номером 5. Дальше хуже. Используя SQL-оператор FETCH, в программу передается следующее значение SQL-запроса, описанного в 6-й строке. Итератор, указанный в операторе FETCH, определяет, какая строка результатов запроса должна быть считана. И ПОКА (while) дан­ные в таблице существуют, о чем сообщает метод endFetch(), в 7-й стро­ке, на экране можно увидеть имена всех, у кого дата.
Позиционный итератор очень полезен для получения данных одного
òèïà.
#sql iterator iterPos (String, double); String u, v, w; double x, y, z; iterPos iter; #sql iter={SELECT param1,aram2,param3 FROM table}; #sql {FETCH:iter INTO:u,:v,:w}; #sql {FETCH:iter INTO:x,:y,:z};
270 Часть 5. Работа с базами данных при помощи Java
Без учета уже известных строк 1—4 работа позиционного итератора
описана в коде, начинающемся с 5-й строки, в 6-й строке выбираются все три значения типа String результирующей таблицы, а в 7-й все три значения double той же таблицы. Используя один итератор с одним SQL-оператором, можно очень гибко организовать работу с различными запросами.
Каждый итератор использует системные ресурсы, используя метод
close(), можно добиться освобождения ресурсов без ожидания включе­ния «сборщика мусора» Виртуальной Машиной Java(JVM).
Классы, содержащие inerator с позиционными значениями, реализу-
ют интерфейс PositionalIterator, расширенный от ResultSetIterator и до­бавочный методом endFetch(), прерывающим цикл FETCH... INTO.
37.2.3. Использование итератора для UPDATE и DELETE SQL-операторов
Использование итераторов возможно не только для получения дан-
ных из таблицы, но и для обновления и удаления данных.
SQL-операторы по изменению структуры таблицы — UPDATE и
DELETE — по-разному работают с таблицей. Использование данных операторов в SQLJ-приложении несет свои ограничения. Для этого обычно создается два различных SQLJ-файла. В первом файле создает­ся Iterator, реализующий интерфейс sqlj.runtime.forUpdate:
#sql public iterator DelByName implements sqlj.runtime. ForUpdate(String EmpNo);
Каждый создаваемый итератор должен иметь уровень доступа pub-
lic. Именно по этой причине необходимо создание двух различных клас­сов. Созданным классом можно пользоваться для создания различных объектов, основанных на обозначенном итераторе.
В нижеследующем примере в файле создается «удалитель», на осно-
ве вышеуказанного класса:
{ DelByName deliter;
String enum; #sql deliter = { SELECT empno FROM emp WHERE workdept='D11'}; while (deliter. next()) { enum = deliter. EmpNo(); #sql { DELETE WHERE CURRENT OF :deliter }; } }
1-я строка определяет класс deliter — своеобразный iterator. Создан-
ному объекту присваивается значение запроса. 3-я — проводится про­верка на нахождение всех подобных объектов, и в 6-й, удаляются все элементы с заданным значением. Весь фокус в том, что здесь отсутст­вует надобность вложенных запросов. «Делитеру» этот же самый ите­ратор присваивает SQL-запрос, а затем SQL-оператор DELETE все присвоенное удаляет.
Все классы, содержащие iterator, наследуют интерфейс ResultSetIte-
rator, в котором содержатся три метода: rowCount(), возвращающий ко­личество строк, обработанных повторителем; метод close() — закрывает