Вот здесь можно узнать больше об базе данных cloudscape. Часть 2

Продолжение. Первая часть здесь

Достаточное количество проблем у неопытных программистов возникает при работе с версиями объектов сохраненных в потоке в одной версии, а восстановление которых производится когда класс уже был модифицирован, перекомпилирован и в общем случае потерял битовую совместимость с предыдущей версией. Дело в том что любой объект который обладает способностью сохранять свое состояние в поток снабжается уникальным номеров версии, который проверяется при восстановлении, и если номер отличиен от того в котором объект был сохранен, то при попытке восстановления объект будет сгенерировано исключение java.io.InvalidClassException. В большинстве случае лучшее решение, в котором мы говорим:”Да, класс изменился, в нем появилось новое поле или новый метода, но я хочу иметь возможность прочитать старый объект, пусть даже некоторые поля получат значения по умолчанию, или будут утеряны, в том случае если мы удаляем поле в новой версии класса”. В этом случае необходимо узнать старую версию поля serialVersionUID и руками прописать его в теле нового класса. Для того чтобы из определения класса извлечь данное значение используют стандартную утилиту командной строки

serialver.exe -classpath E:\jdeveloper_oracle\clouds\proOne\classes mp.UserAddress

При запуске данной утилиты в опциях командной строки следует указать местоположение класса и его имя. Так для класса “UserAddress” используемого в примерах было получено значение

mp.UserAddress:    static final long serialVersionUID = 6102725949207894606L;

после изменения тела данного класса путем добавления нового поля “public int newField”и повторного запуска утилиты получено другое значение номера версии:

mp.UserAddress:    static final long serialVersionUID = -3597872239298434308L;

При попытке выбрать данные из БД было получено как и ожидалось исключение. После вставки в исходный код класса старого значения номера версии все заработало как и ранее. Поле “newField” получило значение по умолчанию, для целых это “0”. Кстати для тех кто использует для экспериментов cview необходимо не просто выгрузить БД, а завершить сеанс виртуальной машины и повторно запустить ее. При работе с разными версиями следует постоянно помнить, что существуют не только совместимые изменения версий классов но и несовместимые где правь или поле версии класса восстановить объект из потока не удастся. Совместимым изменением является добавление или удаление метода или поля. Несовместимыми изменениями являются изменение иерархии объектов или удаление реализации интерфейса Serializable.

Следующей стадией интеграции пользовательских типов данных с БД cloudscape будет создание возможности сортировки и сравнения пользовательских типов данных, чтобы они не смотрелись бедными родственниками стандартных типов sql. Технически для этого следует только поддержать интерфейс Comparable. Без данной поддержки вы лишаетесь такой возможности и будете получать сообщения об ошибке, подобные следующему: “Columns of type 'mp.UserAddress' may not be used in CREATE INDEX, ORDER BY, GROUP BY, UNION, or DISTINCT, because comparisons are not supported for that type.” После того как в классе “UserAddress” был реализован метода “compareTo” все ограничения были сняты.

public class UserAddress implements java.io.Serializable, Comparable {
  
public static final long serialVersionUID = 6102725949207894606L;
  
public String city;
  
public String street;
  
public int home;
  
public int flat;
  
public String zip;
  
public int newField;
  
  
public String toString() {
     
return "User Lives : City " + city + ", " + street;
  
}
  
  
public int compareTo(Object o) {
     
if (!(o instanceof UserAddress))
        
return -1;
     
return zip.compareTo(((UserAddress) o).zip);
  
}
}

При реализации интерфейса сравнения следует следовать следующим правилам:

- sgn (x.compareTo(y)) == -sgn(y.compareTo(x)), где sgn – функция знака;
- требование транзитивности, т.е. если x>y и y > z, следовательно x>z;
- если x.compareTo (y) == 0, то должно выполняться условие, 
что sgn(x.compareTo(z)) == sgn(y.compareTo(z));
- если выполнятся условие: x.compareTo (y) == 0, то x.equals(y) == true.

Важно, никоим образом ваши функции сравнения не должны возвращать случайные величины, или зависящие от них функции. Невозможно отсортировать информацию для которой отношения описанные выше в 4-ех правилах не выполняются. Единственным следствием будут ошибки, неточности, исключения, бесконечные циклы и прочие малоприятные вещи.

Если теперь вам кажется, что можно использовать собственный тип данных как стандартный, то следует учитывать еще один момент. При запуске на выполнение следующего запроса: “select distinct address from clarks order by address” (ключевое слово “distinct” говорит о том что дубликаты следует удалять) выборка данных будет идти на основе значения функции “hashCode”. Ради эксперимента я возвращал из функции случайную величину. И соответственно результаты выборки были не правильными. Так что будьте внимательны. В самом крайнем случае разработчик всегда может определить собственную функцию сравнения и явно вызывать ее, например так:

SELECT * FROM zoo WHERE animal. MoreThan (mp.zoo.Animals::HYPPO)

Определившись с тем, какие типы данных мы можем хранить в таблицах cloudscape. Попробуем кратко иллюстрировать возможности которые дает хранение в БД не только информации в чистом виде но и привязанных методов служащих для обработки этих данных.

public class City {
  
int id;
   String name;
  
int population;
  
   Vector getShortestPath
(City to) {
     
Vector path = new Vector();
     
/**
       * рассчет кратчайшего пути соединяющего два города текущий и город
       * переданный в качестве параметра методу, найденный путь возвращается в
       * виде вектора городов соединяющих первый город с последним
       */
     
return path;
  
}
}

Храня подобные объекты в таблице, возможно делать следующие выборки данных: “select city.getShortestPath (Constants::MOSKOW) as path from Cities where city->population > 100000”. В отдельных ситуациях требуется выполнять явное преобразование хранимого объекта к типу которым он является. Тогда получается следующий фрагмент кода:

SELECT cast (address AS mp.UserAddress)->home FROM clarks

До текущего момента почти весь код который приводился в качестве примера можно было опробовать с помощью идущей в стандартной поставке с cloudscape утилиты просмотра и администрирования БД cview. С ее помощью легко создавать новые таблицы, индексы, условия CHECK, триггеры, сохранять определения используемых в БД классов в виде архивов и подключать их к classpath cloudscape. Можно легко попробовать реализовать идеи с помощью интерактивного построителя запросов. Одним словом рекомендую всем начинающим работать с cludscape попробовать сначала поэкспериментировать именно с cview, и только потом переходить на вызов функций cloudscape из программ java. Для реализации программ java работающих с cloudscape можно воспользоваться либо стандартным JDBC API т.е. множеством классов и интерфейсов таких как Driver, DriverManager, Connection, Statement, PreparedStatement, ResultSet и другие.

Есть и альтернативный подход основанный на использовании технологии sqlj, т.е. средства внедрения инструкций SQL в код программ на java. Первый подход относительно проще и более привычен программистам переходящим на java из мира Delphi/Cbuilder/Vbasic и других языков в которых для доступа к данным используется развитая объектная модель. Второй подход менее распространен не из-за сложности технологии, во многих отношениях она проще чем использование jdbc, по крайней мере более прозрачно к логике приложения. Сдерживающим фактором видится только отсутствие отлаженного IDE. Хотя в jbuilder7 появилась опция настройки IDE на использование внешнего транслятора и даже предлагают выбрать Oracle или IBM. Но на практике эффективно применять данные средства не получается. Так загрузив с сайта oracle транслятор sqlj, правильно настроив classpath, подключив все необходимые библиотеки и попытавшись скомпилировать проект содержащий sqlj файл, были получены ошибки, в основном из-за того что Borland передавал транслятору неподходящие опции командной строки. А переписать командную строку под себя начисто jbuilder не дает.

Выходом стало написание bat-файла которые и запускались. Но ни о каком удобстве разработки уже я и не говорю. Выход jbuilder8 не повлиял сильно на поддержку sqlj. Да, в отличие от 7-ой версии jbuilder8 начал узнавать sqlj-файлы и даже подсвечивать синтаксис (кстати говоря не без ошибок), подсказок не показывал, и запуск на компиляцию выполнялся также плохо. В общем мрак и болото. Компилировать sqlj-файлы из среды jbuilder сплошная мука. Может быть кто-нибудь поделится своим опытом борьбы, буду признателен. Так наверное я и мучился бы с bat-файлами если бы не замечательная ide от oracle jdeveloper, которая кстати в отличие о jbuilder распространяется бесплатно.

Конечно есть и personal версия ключевой файл к которой раздается также бесплатно с сайта Borland, но мы говорим о серьезной разработке приложений и ограничения personal версии не подходят. Jdeveloper сильно ругают за ориентацию на продукты oracle и сложность, например, интегрировать его с tomcat для веб-приложений, но для начинающего разработчика БД и java это лучший выбор в основном благодаря большому количеству мастеров и готовых шаблонов. Впрочем другого от oracle и не ожидалось. Ведь Oracle вместе с другими ведущими игроками на рынке СУБД такими как IBM, Sybase, Tandem организовали процесс принятия стандарта sqlj.

Работа началась с предложения Oracle о возможном подходе к встраиванию операторов SQL в Java. Позднее Sybase внесла предложения о способах использования Java в системах баз данных для обеспечения реализации хранимых подпрограмм и определяемых пользователем типов данных После того, как был предложен начальный проект спецификации, вся группа участвовала в его рецензировании, отмечая имеющиеся проблемы и предлагая некоторые расширения. Проект спецификации включает три части, которые выглядят следующим образом.

- Часть 0. SQL, встроенный в Java (Embedded SQL in Java). 
- Часть 1. Хранимые программы Java (Java Stored Routines). 
- Часть 2. Типы данных Java (Java Data Types). 

Подробная документация может быть найдена на сайте http://www.sqlj.org, однако сразу хочется предостеречь, что многие нормативные документы там местами не согласованы и освещают ранние стадии развития стандарта, на текущий момент который пересмотрен и многие примеры не работают в среде эталонного транслятора sqlj от oracle, который кстати можно легко загрузить с сайта oracle. Говорить о том что данный транслятор бесплатен думаю не приходится. Чтобы разобраться в sqlj и преимуществах, которые он дает разработчику следует сначала отойти немного в сторону и рассмотреть стадии жизненного цикла оператора SQL, который посылается в СУБД. Первый шагом выполнения любого запроса к БД, например следующего “select fio from users where countChildren = 0” будет стадия анализа оператора SQL, после следует стадия проверки запроса на соответствие схеме БД. Третья стадия это оптимизация запроса. На основе оптимизированного запроса создается бинарный код по выборке данных. И наконец идет этап выборки данных.

Каждый из этих шагов требует определенных затрат времени, и если проверка синтаксиса проходит достаточно быстро, то оптимизация – нет. Для сложных запросов существует сотни вариантов оптимизации, над каждым из которых пришлось изрядно подумать разработчикам СУБД. Не зря же продукты Oracle, IBM DB2 и Microsoft SQL Server способны обслуживать тысячи одновременных запросов к БД.

Таким образом хотелось бы, чтобы длительные затраты времени этапа 1-4 были выполнены заранее на этапе разработки приложения, ее компиляции, а вовсе не на этапе выполнения. Мы приходим к идее статического SQL. Статического означает что его операторы постоянны и не изменяются на этапе выполнения программы и компилируются заранее вместе с прикладной программой.

Статический SQL имеет применение в ситуациях когда предъявляются повышенные требования к производительности приложения и если выполняемые операции выборки данных постоянны и могут быть жестко закодированы в программе. Если же приложению требуется выполнять операции над данными которые заранее не детерминированы, то мы вынуждены мигрировать на динамический SQL. JDBC обеспечивает динамическое исполнение операторов SQL. При любых синтаксических или семантических ошибках в операторах SQL будут возникать исключительные ситуации во время исполнения приложения. Разработчики sqlj обещали что идее переносимости java не нанесен никакой ущерб. Таким образом можно интегрировать sql и java, например, для создания компонентов реализующих бизнес-логику и развернутых на серверах приложений и взаимодействующих с серверами БД. В этом отношении java имеет преимущество перед таким языком как pl/sql так как он сочетает безопасность и надежность программирования с широкой поддержкой разработчиками инструментальных средств. Пример программы на sqlj:

double avgPrice (int goodsKind){
  
double r;
   #sql
[контекст соединения] {select avg(price) into :r from goods where kind = :goodsKind};
  
return r;
}

данный пример несколько упрощен и за границей остались некоторый фрагмент кода, в котором должен будет загружаться драйвер к БД, и устанавливаться соединение. Разумеется более подробное объяснение будет следовать далее, сейчас я преследую цель только показать компактность кода sqlj и его простоту. Если написать код выполняющий ту же работу но применяющий объектную модель jdbc api, то пришлось бы написать не одну, а примерно на 5 строчек кода больше. Кроме преимущества в более компактном коде sqlj обладает большей интуитивностью применения. Не стоит также забывать что транслятор sqlj умеет на этапе компиляции проверять соответствие запроса схеме БД. Это дает возможность экономить массу времени при разработке приложений в которых на этапе тестирования приходится совершать массу действий для того чтобы дойти до оператора отправляющего запрос к СУБД и получить сообщение что в операторе sql есть ошибка в имени колонки таблицы.

Особые возможности sqlj проявляются при создании с их помощью хранимых процедур или триггеров, разработку которых в общем случае можно вести либо на специфическом языке конкретной СУБД либо на универсальном языке программирования. Каждый из этих двух подходов имеет свои преимущества: в первом случае можно разрабатывать надежные и компактные надежные и компактные программы. Надежность обеспечивается тем, что конструкции языка и среда выполнения не допустят попытки разрушить СУБД. Второй же подход позволяет не ограничиваться только теми средствами которые были заложены разработчиками СУБД. Например cloudscape поддерживает не полный набор функций стандарта sql92, но необходимости в этом и не возникает, ведь если чего то нет в sql то всегда можно оформить данную возможность как метод java и вызывать его из среды СУБД.

Недостатком использования другого языка программирования является то что исполнимый код должен размещаться в другом адресном пространстве отличном от пространства СУБД, что приводит к затратам ресурсов на взаимодействие между процессами, но является необходимым для защиты СУБД от случайной или преднамеренной попытки ее разрушения. Использование java для создания хранимых процедур позволяет совместить лучшие свойства обоих подходов. Java это не первый язык в котором появилась возможность внедрять инструкции sql в сам код. Существуют стандарты для внедрения sql в С, COBOL.

А раз cloudscape – СУБД ориентированная на java, то создание и вызов триггеров и хранимых процедур реализован очень удобно. Когда я только начинал заниматься java и БД, то очень хотелось поэкспериментировать с хранимыми процедурами. Однако ни одна СУБД среднего уровня: vfoxpro, mysql не говоря о других древних СУБД, таких как paradox, dbase не поддерживала хранимых процедур. Для того чтобы узнать поддерживает конкретная СУБД хранимые процедуры можно прочитать руководство разработчика или воспользоваться средствами получения метаинформации. Пример кода тестирующего СУБД на предмет поддержки хранимых процедур приведен ниже.

package mp;
import java.sql.*;
 
public class TestForStoredProcs
{
  
public static void main(String[] args) throws Exception
  
{
     
Class.forName ("com.ibm.db2j.jdbc.DB2jDriver");
      Connection co = DriverManager.getConnection
("jdbc:db2j:E:\\КУРСЫ\\kolya\\jbuilder_proj\\jdeveloper_oracle\\myBase;" +
     
"create=true");
      DatabaseMetaData dbm = co.getMetaData
();
      System.out.println
("supportsStoredProcedures " + dbm.supportsStoredProcedures());
      System.out.println
("supportsANSI92EntryLevelSQL " + dbm.supportsANSI92EntryLevelSQL());   
      System.out.println
("supportsANSI92FullSQL " + dbm.supportsANSI92FullSQL());       
      System.out.println
("supportsANSI92IntermediateSQL " + dbm.supportsANSI92IntermediateSQL());           
       
  
}
}

В результате запуска данного кода cloudscape честно признался, что он поддерживает хранимые процедуры. Кстати приведенный пример кода может служить основой для более подробного изучения возможностей конкретной СУБД. Полезным будет получение списка всех функций для работы с данными (текстовые, дата и время, статистические и другие) которые поддерживает данная СУБД. При дальнейшем изложении на равных будут присутствовать примеры кода как с использованием традиционного jdbc api, так и с использованием sqlj.

Основные сведения, необходимые для использования sqlj сводятся к понятию контекста соединения, итераторы, соединения по умолчанию, хост-переменных. Основные теоретические познания которые необходимы для создания sqlj кода.

“#sql” – специальный префикс, который говорит что дальше пойдет оператор sql. Затем в фигурных скобках идет сам оператор. Фигурные скобки работают как ограничители этого оператора sql, и они отделяют его от остальной части программы на Java. Когда вы запустите на выполнение траслятор, то он найдет все подобные вставки и заменит их на вызовы методов jdbc. Полученная в результате исходная программа на Java будет компилироваться обычным образом. В качестве иллюстрации можно привести исходный пример кода на sqlj и результат его преобразования транслятором.

try{
  
/*@lineinfo:generated-code*//*@lineinfo:21^5*/
  
 
//   ************************************************************
//   #sql [co] { drop table users };
//   ************************************************************
  
 
  
{
     
sqlj.runtime.ConnectionContext __sJT_connCtx = co;
     
if (__sJT_connCtx == null) sqlj.runtime.error.RuntimeRefErrors.raise_NULL_CONN_CTX();
      sqlj.runtime.ExecutionContext __sJT_execCtx = __sJT_connCtx.getExecutionContext
();
     
if (__sJT_execCtx == null) sqlj.runtime.error.RuntimeRefErrors.raise_NULL_EXEC_CTX();
     
synchronized (__sJT_execCtx) {
        
sqlj.runtime.profile.RTStatement __sJT_stmt = __sJT_execCtx.registerStatement(__sJT_connCtx,
               clo_SJProfileKeys.getKey
(0), 0);
        
try
        
{
           
__sJT_execCtx.executeUpdate();
        
}
        
finally
        
{
           
__sJT_execCtx.releaseStatement();
        
}
      }
   }
//   ************************************************************
  
/*@lineinfo:user-code*//*@lineinfo:21^33*/
}
catch (Exception e){System.out.println ("Table Users doesnt Exist Now");}

И это только малая часть того что сгенерировал транслятор. Не думаю что вам когда-нибудь придется работать напрямую с данным кодом. Но иметь представление о том что происходит на самом деле будет необходимо.

Транслятору SQLJ может быть предписано установить соединение с экземпляром базы данных во время его работы и использовать метаданные, которые он там найдет, для проверки правильности операторов SQL. Так если в вышеприведенном примере таблица “users” отсутствует, то будет получено предупреждение об ошибке.

Для того чтобы код sqlj заработал необходимо выполнить ряд предварительных действий. Прежде всего необходимо помнить о контексте соединения. Для того чтобы оператор sql привязать к конкретной БД, необходимо создать объект контекста соединения указав при его создании параметры, очень похожие на те что передаются DriverManager при запросе соединения с БД. Существует возможность создать объект контекста по умолчанию, который будет использоваться всякий раз, когда явный контекст не будет указан. В примере ниже используется явно созданный контекст.

#sql context CO;
String dburl =
"jdbc:db2j:myBase;create=true”;
CO co  = new  CO (url, "
root", "pass", false);

Параметры конструктора представляют собой URL соединения с БД, имя пользователя, его пароль и последний параметр на который следует внимательно обратить внимание. Это признак того будет ли cloudscape автоматически после выполнения каждого оператора sql выполнять подтверждение транзакции в ходе которой он выполнялся, или же программист должен будет взять эту обязанность на себя. Если забыть подтвердить транзакцию, то очевидно что все внесенные в БД изменения будут утеряны.

#sql [co] { DELETE FROM goods WHERE price < 1000};

В данном примере первой строкой «#sql context…» создается класс контекста соединения, если рассмотреть сгенерированный транслятором код, то можно увидеть, что класс “CO” расширяет класс ConnectionContext. Для того чтобы посылать запросы к БД необходимо создать объект контекста соединения. В последующей строке кода используется ссылка на данный объект, имя которого помещается в квадратных скобках. После определения контекста возможна проверка корректности запросов sql. Разумеется приложение может использовать несколько контекстов одновременно. Если возникнет необходимость использовать контекст соединения в стандартных методахjdbc api, с помощью метода “getConnection” можно добраться до объекта java.sql.Connection, и использовать его для создания и выполнения как обычных “Statement”, так и “PreparedStatement” или же для работы с хранимыми процедурами с использованием “CallableStatement”. Для создания контекста по умолчанию следует воспользоваться следующим вызовом.

DefaultContext.setDefaultContext( Oracle.connect("jdbc:db2j:myBase;create=true" , "user" , "pass" , true));

Кроме контекстов соединения существует понятие контекста выполнения. Данный объект служит для Объект контекста исполнения дает возможность управлять некоторыми аспектами исполнения оператора и позволяет осуществлять выборку информации об исполнении запроса после его завершения. В приведенном ниже примере выводится информация о количестве записей, которые были изменены в результате выполнения запроса sql.

public void doAnalize() throws Exception {
  
#sql [co] {update Clarks  set fio = fio.trim().concat(address->zip)};
   System.out.println
( "Records Has Been Processed " + co.getExecutionContext().getUpdateCount());
}

Статический sql не имел бы и доли своей привлекательности если было бы невозможно ссылаться на переменные java. Это так называемые хост-переменные. Например в следующем коде преполагается наличие базы данных телефонных номеров, и по введенному номеру телефона выводится имя абонента, а также по имени возможно получить номер телефона. Режим работы программы, а также номер телефона или имя пользователя читаются с консоли.

public void phones () throws Exception
{
  
DataInputStream  din = new DataInputStream (System.in);
   System.out.println
("Enter Mode 1- get (p)hone number by user name or 2 - get (u)ser name by phone number");
   String m = din.readLine
();
   System.out.println
("Enter " + (m.indexOf("p")!=-1?" user name":"phone number"));
   String f = din.readLine
(), r;
    
  
if (m.indexOf("p") != -1)
     
#sql {select phone into :r from phones where user = :f};
  
else
     
#sql {select user into :r from phones where phone = :f};     
      System.out.println
("Result : " + r);
}

В данном примере используются хост-переменные как для передачи БД условия отбора записей так и для сохранения результата. Преобразование типов выполняется автоматически. Для переменных и выражений хост-языка java можно указать направление передачи параметров или их роль, так приняты следующие обозначения (in – входной, out – выходной, inout – входной и выходной). Указывать данные параметры не обязательно так как роль может определяться автоматически на основании вида запроса, хотя можно указать ее и явно. Например два приведенных ниже примера идентичны.

String r;
int h = 123456789;
#sql
{select fio into :out (r) from Clarks where address->home = :in(h)};
#sql
{select fio into :r from Clarks where address->home = :h};

При записи хост-переменных можно указывать более сложные выражения, например для передачи параметров как “in” можно использовать вычисляемое выражение.

Признаком серьезности СУБД является поддержка хранимых процедур, которые позволяют автоматизировать и автоматизировать выполнение многих данных.

Для вызова хранимой процедуры используют оператор CALL языка SQL следующим образом:

CALL mp.procs.ProceduresShop::proc1 (:abc)

В данном примере вызывается статическая функция “proc1” принадлежащая классу “mp.procs.ProceduresShop” которой в качестве параметра передается значение хост-переменной “abc”. Ниже приводится не столь абстрактный пример функции которая выводит окно сообщения содержащее список таблиц БД.

package mp;
import java.sql.*;
import com.ibm.db2j.jdbc.*;
import javax.swing.*;
 
public class STOREDPR
{
  
public static void pro (Connection co)throws SQLException{
     
String [] names = new String [] {"TABLE" , "VIEW" , "SYSTEM TABLE"};
     
for (int k= 0; k < names.length; k++)
      {
        
ResultSet rs = co.getMetaData().getTables(null ,
              
"APP" ,null , new String [] {names[k]});
         ResultSetMetaData mdrs = rs.getMetaData
();
        
int cnt = mdrs.getColumnCount();
         StringBuffer buf =
new StringBuffer ();
        
boolean isE = true;
        
while (rs.next()){
           
for (int i = 0; i < cnt; i++)
              
buf.append( mdrs.getColumnName(i+1).toString() + "=" + rs.getObject(i+1)+ "    \t");
            isE =
false
            buf.append
("\n");  
        
}
        
 
         JOptionPane.showMessageDialog
(null ,isE?"Нет данных":buf.toString() , names[k],
               JOptionPane.INFORMATION_MESSAGE
);
     
}
   }
}

Для вызова данной функции можно воспользоваться и следующим запросом с использованием jdbc api.

public void callStored() throws Exception{
  
Statement stmt = co.getConnection().createStatement();
   stmt.executeUpdate
("CALL mp.STOREDPR::pro (getCurrentConnection())");
   stmt.close
();
}

Конечно, более правильным было бы для вызова хранимой процедурой пользоваться не обычным классом “Statement”, а “CallableStatement”, таким образом новая версия функции вызывающей хранимую процедуру будет выглядеть так. Если для вызова процедуры можно использовать не “CallableStatement”, то для того чтобы не просто вызвать некоторую процедуру а использовать возвращаемое значение, то следует вызывать функцию следующим образом: “VALUES ” и использовать только “CallableStatement”.

public void callFoo () throws SQLException{
  
CallableStatement  call = co.getConnection().prepareCall(
        
"? = CALL mp.STOREDPR::foo (getCurrentConnection())");
   call.registerOutParameter
(1 , Types.DOUBLE);
   call.execute
();
  
double d= call.getDouble(1);
   System.out.println
("result :"+ d);
   call.close
();
}

public static double foo (Connection con) throws SQLException{
  
Statement stmt = con.createStatement();
   ResultSet rs = stmt.executeQuery
("select avg(address->flat) as avf from Clarks");
  
if (!rs.next()) return 0;
  
return rs.getDouble(1);
}

А вот те же действия, но запрограммированные с помощью sqlj, неправда ли выглядит логичнее и более прозрачна идея. В приведенном ниже примере стоить обратить внимание на создание псевдонима для метода “Foo”.

public void callFoo2() throws SQLException{
  
double r = 0;
  
try{
     
#sql [co] {create method alias Foo FOR mp.STOREDPR.foo};
  
}
  
catch (Exception e){System.out.println("alias already exists"); e.printStackTrace();}
  
#sql [co] r = { VALUES Foo(getCurrentConnection())};
   System.out.println
("result is :"+ r);
}

Самым простым способом получить некоторый результат выборки будет в конструкции sqlj использование параметра команды “select” “into” за которым должны идти имена хост-переменных куда должны быть помещены данные. В случае если запрос на выборку возвращает несколько строк, при использовании например следующего кода будет сгенерировано исключение, так же как и в случае если не будет найдено ни одной подходящей строки.

public void callSelect() throws SQLException{
  
String zip;
   #sql
[co] {select address->zip into : zip from clarks where address->home > 50};
   System.out.println
("ZIP = " + zip);
}

В подобной ситуации нам может помочь использование итераторов. Итератор результирующего набора строк похож на курсор SQL. Он используется для доступа к строкам результата обработки запроса. Создание итератора проводится следующей командой.

#sql iterator I (int idClark, String fio , java.util.Date birthDay , mp.UserAddress address);

Если посмотреть на результат работы транслятора sqlj, то можно увидеть, что на самом деле итератор это класс java содержащий методы для перемещения по набору записей, и доступа к отдельным полям данного набора.

class I extends sqlj.runtime.ref.ResultSetIterImpl implements
     
sqlj.runtime.NamedIterator {
  
public I(sqlj.runtime.profile.RTResultSet resultSet)
        
throws java.sql.SQLException {
     
super(resultSet);
      idClarkNdx = findColumn
("idClark");
      fioNdx = findColumn
("fio");
      birthDayNdx = findColumn
("birthDay");
      addressNdx = findColumn
("address");
  
}
  
  
public int idClark() throws java.sql.SQLException {
     
return resultSet.getIntNoNull(idClarkNdx);
  
}
  
  
private int idClarkNdx;
  
  
public String fio() throws java.sql.SQLException {
     
return resultSet.getString(fioNdx);
  
}
  
  
private int fioNdx;
  
  
public java.util.Date birthDay() throws java.sql.SQLException {
     
return ((java.util.Date) (resultSet.getObject(birthDayNdx,
            java.util.Date.
class)));
  
}
  
  
private int birthDayNdx;
  
  
public mp.UserAddress address() throws java.sql.SQLException {
     
return ((mp.UserAddress) (resultSet.getObject(addressNdx,
            mp.UserAddress.
class)));
  
}
  
  
private int addressNdx;
}

А раз итератор это класс java то объект данного класса можно передавать и возвращать из функций, сохранять в массивах и т.д. в sqlj существует возможность использовать итератор двумя различными, взаимоисключающими способами. Первый способ это привязка итератора к полям набора данных по именам полей (принципиально важно при определении итератора указать имена полей совпадающие с теми которые содержатся в действительном наборе данных, хотя регистр символов не являюется существенным) . Ранее был приведен именно этот способ. Следующий способ более удобен в том случае если точные имена колонок не известны, но известен их порядок и тип. В таком случае следует использовать позиционный итератор.

public void callSelect2()throws SQLException{
  
#sql iterator I2(String, mp.UserAddress);
   I2 i2;
   #sql i2 =
{SELECT fio, address FROM Clarks WHERE address->zip like '%555%'};
  
System.out.println("=== Here are peoples which zip like 555 ====");
   String myFIO =
"";
   UserAddress myAddress =
null;
  
while (true) {
     
#sql {FETCH :i2 INTO :myFIO, :myAddress};
     
if (i2.endFetch()) break;
      System.out.println
(myFIO + "; " + myAddress.zip);
  
}
  
i2.close();  
}

Продолжение здесь.