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

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

.pdf
Скачиваний:
0
Добавлен:
06.09.2026
Размер:
2 Мб
Скачать
Часть 5. Работа с базами данных при помощи Java 271
iterator; а другой метод, isClosed(), проводит проверку закрытия itera­tor, возвращает значение true, если iterator закрыт. Все три метода вы­зывают SQLException исключение.
Совместное использование в итераторе SQL-операторов SELECT и
DELETE уменьшает количество кода, а значит, и увеличивает скорость выполнения. Хорошо составленный SQL-запрос способен значительно облегчить жизнь создателю SQLJ-приложений.
Спецификация SQLJ предоставляет возможность при помощи опера-
тора SET присваивать значения, передаваемые Java-переменным со­здаваемого приложения.
#sql { SET:x = foo1() + foo2() }; java.util. Date _d; #sql {SET :_d=sysdate};
Типы переменных могут быть только OUT, затем с данными пере-
менными можно работать как с обычными полями. В вышеуказанном примере переменной :x присваивается значение суммы функций, а пе­ременной :_d присваивается значение системного времени базы дан­ных.
При определении итератора или соединения их также можно иници-
ализировать одну или больше констант, включенных в определение класса, расширять с помощью оператора with.
#sql public iterator MyIter with (var1=value1, ... , varN=valueN) (type declarations);
Возможно использовать обе конструкции совместно, но конструкция
с implements должна определяться первой.
#sql public iterator MyIter implements mypackage.MyInterface
with (readable=true) (String empname, int empnum);
{ #sql iterator MyIter (String ename, int empno, float sal);
MyIter xy;
#sql xy = {SELECT name, number, salary FROM «Table»}; while (xy. next()) {System.out.println(xy)); } }
Важной частью является расположение SQLJ-деклараций итератора
и контекста соединения. Встроенные объекты можно разместить в лю­бом месте программы, исключая расположение в теле ее методов.
По правилам Java один создаваемый класс должен иметь только
один класс с уровнем доступа public, поэтому при создании SQLJ-фай­лов с итератором и ConnectionContext необходимо создавать различные файлы.
Если созданный файл определен с уровнем доступа public класс
(только один класс должен иметь уровень доступа public), то создавае­мый SQLJ-файл должен именоваться по названию данного класса.
Так, если основной класс МуExample, то файл должен именоваться
как MyExample.sqlj, в противном случае название файла должно соот­ветствовать правилам создания Java идентификаторов. Рекомендуется, чтоб имя файла соответствовало названию основного класса. Для транс-
272 Часть 5. Работа с базами данных при помощи Java
ляции SQLJ-файла можно воспользоваться утилитой командной стро­кой SQLJ,
sqlj MyExample.sqlj — где MyExample.sqlj созданный файл,
так использовать и соответствующие инструменты, например, Stored Procedure Builder DB2 или Visual Age for Java, поставляемые со встро­енным транслятором.
import java.sql.*; import sqlj.runtime.*; import sqlj.runtime.ref.*; #sql iterator App_Cursor1 (String empno, String firstnme); #sql iterator App_Cursor2 (String); class App {static { try { Class.forName("COM.ibm.db2.jdbc.net.DB2Driver").ne­wInstance(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String argv[]) { try { App_Cursor1 cursor1; App_Cursor2 cursor2; String str1 = null; String str2 = null; long count1; String url = "jdbc:db2:sample"; DefaultContext ctx = DefaultContext.getDefaultContext(); if (ctx == null) { try { Connection con = DriverManager.getConnection(url); con. setAutoCommit(false); ctx = new DefaultContext(con); } catch (SQLException e) {System.out.println("Error:"); System.err.println(e); System.exit(1); } DefaultContext.setDefaultContext(ctx); } System.out.println("Successful connected."); #sql cursor1 = {SELECT empno, firstnme FROM employee}; System.out.println("Received results:"); while (cursor1. next()) { str1 = cursor1. empno(); str2 = cursor1.firstnme(); System.out.print ("empno= " + str1); System.out.print ("firstname= " + str2); System.out.print ("");} cursor1. close(); #sql { SELECT count(*) into:count1 FROM employee }; if (1 == count1) System.out.println ("There is 1 row in employee table"); else System.out.println ("Into» + count1 + "rows in employee table"); System.out.println("Обновление."); #sql { UPDATE employee SET firstnme = 'PAT' WHERE empno = '1' }; System.out.println("Обновление прошло успешно."); str1 = "1"; #sql cursor2 = {SELECT firstnme FROM employee WHERE empno = :str1};
Часть 5. Работа с базами данных при помощи Java 273
System.out.println("результат:"); while (true) { #sql { FETCH:cursor2 INTO:str2 }; if (cursor2. endFetch()) break; System.out.print ("empno= " + str1); System.out.print ("firstname= " + str2); System.out.print ("");} cursor2. close(); System.out.println("Rollback the update."); #sql { ROLLBACK work }; System.out.println("Rollback done."); } catch( Exceptione){e.printStackTrace();} }}
Первые три строки указывают импортированные классы, благодаря
которым приложение может работать с SQLJ-конструкциями. В следую­щих двух строках, 4-й и 5-й определяются два итератора, один из кото­рых именованный, другой позиционный. Блок из строк 6, 7, 8 определяет драйвер базы данных, и, если драйвер отсутствует, возбуждается исклю­чительная ситуация. 6-я строка предназначена для инициализации драй­вера базы данных. В 7-й строке определяется драйвер доступа к базе данных. В данном примере — это сетевой драйвер базы данных DB2. Ес­ли система определяет отсутствие такового драйвера (строка 8), то выво­дится значение стека, в котором описана причина и местоположение ошибочной ситуации. 9-я строка описывает основной метод программы. При обнаружении драйвера выполняется метод main(). Строка 11 описы­вает глобальные поля приложения. Два объекта, порожденных от итера­торов, несколько строк и один URL, содержащий имя используемой базы данных. Строка 12 описывает самый важный объект из всех вышеука­занных. В приложении используется объект ctx. Если созданный объект ctx класса DefaultContext имеет пустое значение, т. е. параметры не за­даны ни в командной строке транслятора, ни в других источниках — 14, 15, то создается объект con c указанным адресом (url) используемой базы данных (jdbc:db2:sample). Строки с 20 по 21-ю обрабатывают ошибки, ко­торые могут возникнуть в методе main(), и выводят сообщения о возник­шей ошибочной ситуации. В 24-й строке создается запрос, чей результат присваивается переменной cursor1 класса iterator, указанного в 4-й стро­ке. Используя метод next(), глобальным полям присваиваются результа­ты запроса и выводятся на системную консоль 26, 27, 28, 29. В 30-й строке подсчитывается количество строк в таблице. Полученное значение пере­дается в программу при помощи переменой count1. В SQL-запросе вы­полняется функция count для поиска строк и количество строк присваи­вается переменной count1, строка 32. Последующие операторы проводят проверку и выводят результат проверки. Для освобождения занятых ре­сурсов, используется метод close(), строка 47. В 36-й строке происходит обновление таблицы при помощи SQL-оператора UPDATE. В 39 строке инициализируется еще один объект итератора. По накатанному сцена­рию проходит обработка и этого SQL-запроса.
274 Часть 5. Работа с базами данных при помощи Java
37.3. Совместное использование встроенных объектов
Совместное использование iterator и объекта connection-context ис-
пользуется для получения в одном коде различных вариантов ответов. Можно один запрос указать для разных таблиц или, наоборот, для од­ной таблицы использовать различные запросы различных пользовате­лей.
#sql context MyContextNew;
...
MyContextNew ctx; ctx=new MyContextNew( «Test» ) String name;
// Встроенная функция CREATE FUNCTION FIND_NAME (Name
STRING) RETURN STRING
#sql [ctx] name={VALUES FIND_NAME («user») };
...
#sql iterator myIter(String name); myIter mi;
...
#sql [MyContextNew.getDefaultContext()] mi={SELECT name FROM table WHERE name=’user’} ...
while( mi.next()){System.out.println(mi.name+«Name»);}
Создание объекта класса контекст, уже достаточно хорошо рассмот-
рено выше. Поэтому первые три (1, 2, 3) строки останутся без коммен­тариев.
За счет использования встроенной функции FIND_NAME в базу дан-
ных передается переменная. Самое интересное начинается с 5-й строки, где Java-переменной name присваивается значение встроенной функции для пользователя указанного контекста. На следующем этапе создается именованный итератор, строка 6. И, наконец, пользователю с именем user предоставляется доступ к результатам запроса в 8-й строке.
Глава 38. Обработка SQL-ошибок и сообщений
При выполнении каждого SQL-оператора база данных присваивает
собственной переменной SQLCODE базы данных определенное значе­ние, указывающие, как завершилось выполнение оператора.
Нулевое значение переменной SQLCODE указывает на успешное вы-
полнение SQL-запроса.
Отрицательное значение указывает но возникновение серьезной
проблемы при выполнении SQL оператора.
Положительное значение переменной SQLCODE говорит о ненорма-
льной ситуации, о которой должен быть извещен пользователь.
Для обработки ошибок в SQLJ-приложениях используются объекты
класса s java.sql.SQLException. SQLJ генерирует SQLException, когда
Часть 5. Работа с базами данных при помощи Java 275
SQL-оператор возвращает отрицательное или положительное значение SQLCODE. Можно использовать метод getErrorCode()для получения значения SQLCODE или метод getSQLState() для получения значения SQLSTATE.
Для того чтобы SQLJ-приложение смогло обрабатывать ошибки, в
создаваемый код нужно импортировать класс java.sql.SQLException и использовать Java-блок try/catch.
try {#sql {SELECT LASTNAME INTO:empname FROM EMP WHERE EMPNO='133'}; } catch(SQLException e) { System.out.println("SQLCODE returned:" + e.getErrorCode());}
Так как выполняемый SQL-оператор потенциально может вызвать
ошибку, правильно написанная программа должна после каждого SQL-оператора проверять значение переменной SQLCODE. Поскольку для различных СУБД значения переменной SQLCODE различны то лучше использовать другую переменную — SQLSTATE. Переменная SQLSTATE состоит из двух частей двухсимвольного сообщения о воз­никшей ошибке и трехсимвольного элемента, указывающего конкрет­ный тип ошибки.
Глава 39. Работа с базами данных
Работа любого e-commerсe приложения вплотную связана с базами
данных. СУБД является неотъемлемой частью корпоративных данных. Созданию SQLJ-приложений способствуют две вещи: платформонеза­висимость Java и независимость SQL от используемой СУБД. Java может создавать сетевые приложения, а SQL имеет встроенную под­держку архитектуры клиент\сервер. Любое e-commerсe приложение делится на три логические части: пользовательский интерфейс, бизнес­методы, бизнес-данные. Если посмотреть на количественное соотноше­ние пропорций уровней каждой логики, то наблюдается интересная зависимость, наибольшее количество кода, около 70%, описывает поль­зовательский интерфейс, 20% — бизнес-логика и остальные 10% — дан­ные. Картина меняется в противоположную сторону, если используется хранение объектов пользовательского интерфейса в самой базе данных; тут говорить о числах бесполезно. И все-таки употребление смеси SQL и Java в одном приложении здорово сокращает код пользовательского интерфейса, хранящегося в базе данных. Кроме стандартных операций работы с базами данных, SQL, дополненный мощью Java, имеет пре­красные возможности по работе объектно-ориентированными возмож­ностями, предоставляемыми современными базами данных. Совместное использование SQLJ и UDF значительно упрощает создание Web-при­ложений, работающих с базами данных. А большое количество встроен­ных функций ускоряет разработку Web-приложений. Использование концепции объектного ориентирования помогает созданию распределен­ных Web-приложений.
276 Часть 5. Работа с базами данных при помощи Java
Использование LOB дает возможность не ограничиваться стандарт-
ными представлениями вновь созданного типа, а использовать комплек­сную структуру.
Использование UDT позволяет разработчику создать новый тип дан-
ных. использованием UDF разработчик описывает поведение объекта созданного нового типа.
39.1. LOB с использованием SQLJ
Каждый элемент базы данных хранится в колонке таблицы, а каж-
дая колонка определена типом данных, которые она содержит. В одной колонке могут быть элементы только одного типа. Ограниченное коли­честв типов данных, с которыми работает база данных, не позволяет ис­пользовать неопределенные типы. DB2 содержит множество типов: бук­венные строки, числовые данные, календарные и временные значения, LOB, Nulls, графические строки, бинарные строки и связи.
В современных системах управления базами данных созданы новые
типы. Типы данных Large Objects (LOBs) предназначены для хранения комплексных объектов, таких, как звук, динамические и статические изображения, большие документы.
Большие объекты (LOB). Одной из самой важных характеристик бо-
льших объектов является их полный размер, именно полный размер, а не ссылка на файловый объект, хранящаяся в базе данных. Это особен­но касается мультимедийных приложений, статических изображений видео- и аудио-данных. Одна минута полноэкранного видео занимает около 30 Мбайт дискового пространства. Если учесть, что данный ролик может находиться совершенно в другом месте, а значит, и работать с ним, база данных будет по сети, жизнь системного администратора здо­рово усложнится, а клиент может очень долго ждать получения резуль­тата. Для того чтобы всем было хорошо, в базах данных и стали описы­ваться большие объекты.
У каждой современной базы данных свои собственные типы объявле-
ния больших объектов, но смысл чаще всего один — хранение очень бо­льших структурированных данных.
DB2 представляет три типа данных для хранения LOB-объектов как
строк длиной до двух гигабайт: single-byte Character Large Objects (CLOBs) — для строк-символов и Double-Byte Character Large Objects (DBCLOBs) — для строк, использующих двухбайтовую кодировку, та­кую же, как и все приложения Java, и, наконец, Binary Large Objects (BLOBs) для двоичных объектов.
При размещении большого объекта в базе данных в каждый стол-
бец, содержащий LOB, помещаются дескриптор и описатель LOB, а сами объекты хранятся вне базы данных. При такой политике обеспе­чивается сохранность физических данных в момент краха системы, с другой стороны, ухудшается производительность системы при посто­янном обращении к большим объектам. Представьте, сколько нужно памяти, как физической, так и оперативной, для того чтобы 10 000 клиентов смогли посмотреть новейший популярнейший одноминутный
Часть 5. Работа с базами данных при помощи Java 277
ролик с участием автора книги. Почему 10 000? Это среднестатистиче­ская цифра посещаемости среднего Web-сервера день. DB2 имеет пре­красную способность управлять размещением больших объектов на внешних носителях. Более подробную информацию об этом удивитель­ном свойстве можно почерпнуть из описания оператора CREATE TABLE руководства разработчика СУБД DB2. Опять же, в DB2 есть возможность восстановления больших объектов данных. Для каждого из столбцов, содержащих LOB, в системном журнале создается реги­страционная запись, гарантирующая точное восстановление объекта, в случае необходимости данную возможность можно отключить в целях экономии места в журнале. При отключении регистрации в контроль­ном журнале для столбца согласованность и последовательность тран­закций для этого столбца тем не менее гарантируется, но тогда восста­новление данных этого столбца не смогут быть обновлены корректно базой данных, а будут возвращены данные последней успешной тран­закции.
В связи с большими размерами LOB очень желательно, чтобы коли-
чество операций перемещения и копирования с этими данными было минимально, чем обычно пренебрегают системные архитекторы, сохра­няя клиентскую сессию в едином BLOB стандартного размера. И не важно, сколько шла сессия, десять секунд регистрации или же выбора продуктов, размер BLOB всегда задается в момент создания таблицы. И после всего этого пишется очень большое количество ненужных хра­нимых процедур, выделяющих данные из созданного BLOB. В большин­стве случаев именно незнание архитектором всех возможностей испо­льзуемого инструментария порождает массу дополнительных решений повышения производительности системы в целом. Еще одной ошибкой системных архитекторов можно считать неправильный выбор между хранимой процедурой и внешним приложением для обработки больших объектов.
В DB2 существует специальный объект, называемый LOCATOR-ука-
зателем, содержащий значение большого объекта, но не содержимое са­мого объекта. Указатель содержит описание способа, с помощью кото­рого при необходимости может быть получено значение LOB. Указатель можно использовать в любом SQL-операторе, эффективность оператора основывается на том, что указатель работает со значением BLOB, а не с его содержимым, чем опять же пренебрегают системные архитекторы, предпочитая работать с полным объектом, при этом используя встроен­ные запросы. С использованием указателей прикладная программа мо­жет выполнить манипуляцию над значением большого объекта, отло­жив материализацию результата в виде большого объекта до конечного этапа.
Одной из самых распространенных операций по работе с LOB явля-
ется помещение большого объекта во внешний источник, файл и экст­ракция из внешнего файла и помещение большого объекта в базу дан­ных. Фотография помещающаяся на динамической Web-странице при выборе определенного условия. В обычных приложениях ссылка на файл, содержащий большой объект, оформляется в виде строкового значения, содержащего путь к данном файлу. Затем файловая ссылка
278 Часть 5. Работа с базами данных при помощи Java
будет использоваться в SQL-запросе как в качестве входной, так и в качестве выходной переменной. Использование же хранимой процеду­ры в одном контексте даст преимущества без обращения непосредст­венно к внешнему файлу. С использованием указателей и файловых ссылок приложения очень часто могут обрабатывать большие объекты, даже, собственно, не выбирая сами объекты из внешних носителей. Си­туация меняется на противоположную, когда клиенту действительно необходимо работать с большим объектом, а не со значением этого объ­екта. Чаще всего такая ситуация возникает при использовании ГИС систем, например, для отображения участка поверхности и дальнейше­го моделирования ландшафтного изменения или, например, проектиро­вочных работ связанных с обработкой большого количества графиче­ских изображений.
Именно для решения работы непосредственно с большими объектами
компания IBM расширила свою СУБД различными типами реляцион­ных расширений, предназначенных для обработки LOB. На настоящий момент IBM поставляет шесть различных расширений:
Text Extender — поддерживает очень быстрое извлечение больших
текстовых данных на основе их содержания. Документы могут быть любого формата — Microsoft Word, WordPerfect и AMI PRO. Необходи­мо еще раз отметить: при использовании расширения большие объекты хранятся в самой базе данных. Это значит, что SQL-запрос может со­держать в себе условия, касающиеся содержимого документа, с усло­виями, связанными с другими столбцами таблицы. Обычный SQL-за­прос.
Image Extender — предназначено для сохранения и извлечения раз-
личных изображений в нескольких популярных форматах GIF, BMP и JPEG, а также преобразования изображения из одного формата в дру­гой. Так же как Text Extender, Image Extender хранит объекты в базе данных только в миниатюрном варианте полного изображения для про­смотра.
Video Extender работает, как и два вышеперечисленных, т. е. поме-
щает данные непосредственно в базу данных, но используются только с видео форматами данных MPEG, AVI, и QUICKTime. Video Extender позволяет автоматически разбить запись на отрезки на основе обнару­жения изменения фона, а также извлечь репрезентативный кадр для каждого отрывка.
Audio Extender использует аудио форматы AIFF, MIDI и WAVE, и
работает на основе аудиоданных.
Fingerprint extender — позволяет сохранять и извлекать отпечатки
пальцев в специальном формате, а также осуществлять поиск отпечат­ков пальцев, схожих с представленным образцом.
XML Extender — позволяет хранить XML-документы в столбцах
нового специального типа данных. Предоставляет возможность разло­жить XML-документ на компоненты и хранить их в отдельных столб­цах нескольких таблиц. В обоих случаях для осуществления быстрого поиска можно определить индексы для элементов а атрибутов XML­документа.
Часть 5. Работа с базами данных при помощи Java 279
Каждая таблица DB2 может иметь большое количество LOB-данных.
И любые из этих данных могут быть максимального размера — 2 Гбайт. При этом простая строка может содержать 24 гигабайта LOB-данных, а емкость самой таблицы DB2 составляет 4 терабайта.
При все этой монстрообразности данные объекты обрабатываются
òàê æå, êàê стандартные примитивы è обычными операторами (SELECT, INSERT, DELETE, UPDATE), и встроенными функциями. Проблемы начинаются при использовании больших объемов LOB-дан­ных с маленьким объемом физической памяти сервера. Для устранения больших временных задержек приходится очень активно манипулиро­вать LOB-данными. Одним из решений данной проблемы является со­хранение LOB-данных в файлах или же просто наращивание физи­ческой памяти до нужного размера.
Character Large Objects (CLOBs) — буквенная строка, состоящая
только из текстовых, однобайтных символов, т. е. не UNICODE. Этот тип лучше всего подходит для хранения текста с типом данных VARCHAR. Данные могут быть в любой кодировке, требующей для представления один байт (ASCII).
Double-Byte Character Large Objects (DBCLOBs) — текстовая строка
данных, содержащая двухбайтное представление символов. Все Java­объекты, символы, строки и идентификаторы состоят из 16-разрядных кодов, т. е. двух байт.
Binary Large Objects (BLOBs) — бинарная строка битов, очень по-
лезна для сохранения бинарных данных (человек их обычно не может просто прочесть), таких, как звук, изображение, исполняемый модуль ит.д.
Основная часть приложений в последнее время работает с большими
объектами — Large Objects (LOB), такими, как бинарные объекты (BLOB), буквенные (CLOB), двухбайтные буквенные (DBCLOBs). Для обработки больших объектов данных в SQLJ предусмотрены дополните­льные классы. Следует отметить различия, существующие на настоя­щий момент у различных производителей баз данных в характеристике больших объектов, что никак ни отразилось на принятии стандарта SQLJ. Принцип работы с LOB практически везде сохраняется. Не суще­ствует добавочных операторов по работе с большими объектами, и нет никаких ограничений, связанных с ними.
Пакет sqlj.runtime содержит три класса по работе с потоками. Пото-
ки могут использоваться для работы с большими объектами. Все три класса наследуются от стандартного класса java.io.InputStream.
BinaryStream(InputSteram in, int length)
AsciiStream(InputSteram in, int length)
UnicodeStream(InputStream in, int length)
Эти классы можно использовать для присваивания переменным зна-
чений, сохранении этих значений в базе данных или просто для обра­ботки на получение какого-либо результата. Для получения данных из бинарного файла лучше всего воспользоваться потоком BinarySteram. Два других потока предназначены для работы с текстовыми данными. Один из них, AsciiStream — для текста, представленного в ASCII коди­ровке, другой, UnicodeStream, — для UNICODE. Все три класса имеют
280 Часть 5. Работа с базами данных при помощи Java
похожие конструкторы с двумя параметрами, объектом класса InputSt­ream и указателя длины объекта. Указатель является положительным числом примитивного типа int. Вместо InputStream можно использовать любой класс, порожденный от java.io.InputStream.
java.io.File myFile=new java.io.File("/image/image/profile.gif"); int length=(int) myFile. length(); Java.io.FileInputStream fis=new java.io.FileInputStream(myFile); Sqlj.runtime. BinaryStream bis=new sqlj.runtime. BinaryStream( fis, length); #sql {INSET INTO table (colname) VALUES (:bis) }; bis. close();
Для краткости здесь опуoщены операторы импортирования. Первые
четыре строки — стандартные инициализационные действия Java. 5-я строка — SQLJ-оператор, исполняющий добавление в таблицу файла profile.gif. Далее, строка 6 — поток, и происходит закрытие потока, в котором находится изображение.
Прочитать данные из большого объекта тоже не представляется
сложным:
#sql iterator myIterator (Sting name, sqlj.runtime.BinaryStream image); myIterator iter; String nname; Sqlj.runtime.BinaryStream bis; #sql iter SELECT name, image FROM table}; while (iter.next()) {nname=iter.name; bis=iter.image bis.close();} iter.close();
Вначале создается именованный повторитель с двумя аргументами.
Во 2-й строке инициализируются поля. 3-я строка является SQL-опе­ратором, выполняющим запрос из таблицы изображения и его имени. И, наконец, созданным полям присваиваются значения из таблицы. 7­я и 8-я строки освобождают ресурсы, занятые потоком и повторите­лем.
Немаловажной деталью является работа с большими объектами
при использовании различных версий JDBC-драйверов доступа. Толь­ко в новой версии JDBC 2.0 встроена поддержка работы с большими объектами и графическими типами данных. Например, СУБД DB2 ра­ботает с большими объектами, используя драйвер JDBC 1.2. При этом необходимо учитывать лимит возвращаемых значений больших объек­тов.
39.2. SQLJ и пользовательские типы данных UDT
При использовании только встроенных базовых типов данных разра-
ботчик ограничен количеством этих типов. Для DB2 их порядком 16, представляющих самые распространенные типы, используемые в про­граммировании. Но в каждой компании существуют свои специальные