SQLJ

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

SQLJ — подмножество стандарта SQL, направленное на объединение преимуществ синтаксиса языков SQL и Java ради удобства реализации бизнес-логики и работы с данными. Данный стандарт разработан консорциумом, состоящим из компаний IBM, Micro Focus, Microsoft, Compaq (точнее, его подразделение, занимающееся СУБД, которое, скорее, можно отнести к приобретенной компании Tandem), Informix, Oracle, Sun и Sybase.

Предыстория[править | править исходный текст]

На момент появления консорциума JSQL (впоследствии ставшего одноимённым с разрабатываемым им стандартом) в 1997 году идея о взаимодействии реляционных СУБД и программ на Java была не нова. Компанией JavaSoft (дочерним подразделением компании Sun) уже был разработан интерфейс JDBC (англ. Java DataBase Connectivity — «соединение с БД средствами Java»), включённый в стандарт языка, начиная с момента выпуска JDK 1.1. Однако в силу определённых причин (см. «SQLJ и JDBC») возможностей, предоставляемых этим интерфейсом было недостаточно.

Спецификация стандарта SQLJ состоит из трех частей:

  • Уровень 0 регламентирует встраивание SQL-операторов в текст программы на Java;
  • Уровень 1 определяет обратное включение, а именно, реализацию в использующих SQL СУБД хранимых процедур и функций на языке Java;
  • Уровень 2 устанавливает соответствие между типами данных.

К концу 1998 года все три уровня спецификации были завершены и представлены для рассмотрения в ANSI в качестве дополнений к стандарту SQL. Первые две части нового стандарта были включены соответственно в части SQL/OLB и SQL/PSM стандарта SQL:1999; третья часть вошла как отдельный модуль SQL/JRT в стандарт SQL:2003

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

Пример кода[править | править исходный текст]

Приведем простой пример Java-класса, использующего SQLJ для получения результатов запроса из Oracle.

import java.sql.*;
import oracle.sqlj.runtime.Oracle;
public class SingleRowQuery extends Base {
   public static void main(String[] args) {
      try {
         connect();
         singleRowQuery(1);
      } catch (SQLException e) {
         e.printStackTrace();
      }
   }
   public static void singleRowQuery(int id) throws SQLException {
      String fullname = null;
      String street = null;
      #sql {
         SELECT fullname,
            street INTO :OUT fullname,
            :OUT street FROM customer WHERE ID = :IN id};
      System.out.println("Customer with ID = " + id);
      System.out.println();
      System.out.println(fullname + " " + street);
   }
}

Из рассмотрения приведённого кода ясно, что в сам текст процедуры singleRowQuery встраивается SQL-запрос, и встраивание это организовано по определённым правилам:

  • Текст запроса находится внутри директивы #sql {...};
  • Переменные, внешние по отношению к SQL-запросу, задаются внутри него в определенном формате

Подробно все синтаксические конструкции будут рассмотрены далее.

SQLJ и JDBC[править | править исходный текст]

Логично возникновение вопроса о причинах создания двух параллельных стандартов для реализации технологий доступа к СУБД.

Для начала стоит отметить, что SQLJ и JDBC относятся к разным семействам стандартов и концептуально они разные. JDBC является API, входящим в стандарт языка Java и ориентированным на передачу сформированной программой SQL-конструкции в БД, а также обработку результата. SQLJ же является подмножеством стандарта SQL SQL/OLB — для него первичным является понятие базы данных, а язык, в который включаются SQL-конструкции, вторичен. Согласно этому стандарту встраивание SQL-операторов допускается не только в Java, но и в языки программирования Ada, C, COBOL, Fortran, MUMPS, PL/I.

Далее, использование SQLJ на самом деле неявно подразумевает вызов JDBC-методов, так как в данном случае они выполняют роль соответственно высоко- и низкоуровневого API. Если углубиться в подробности реализации технологий SQLJ и JDBC, то можно обнаружить, что любые SQLJ-директивы прозрачно для программиста специальной подсистемой, называемой SQLJ-препроцессором, транслируются в JDBC-вызовы. Благодаря этому можно спокойно сочетать в одном фрагменте кода SQLJ- и JDBC-вызовы, при необходимости используя общий контекст.

На самом деле, в каждом конкретном случае, когда требуется выполнение SQL-оператора, выбор между SQLJ и JDBC стоит делать, исходя из характера предполагаемой операции. Если это сложный поисковый запрос с возможными вариациями по количеству условий на поиск — тогда однозначно более целесообразно будет формирование текстовой строки запроса и последующее его выполнение через JDBC; если же требуется просто подстановка каких-то переменных либо вычислимых выражений — тогда эргономичнее в части длины кода будет написать SQLJ-директиву.

Синтаксис[править | править исходный текст]

Для того, чтобы эффективно использовать синтаксические новшества, вносимые стандартом SQLJ, необходимо предварительно разобраться в их особенностях, связанных с процессом разбора SQLJ-конструкций.

Любые SQLJ-конструкции начинаются с директивы #sql, в частности, блоки, содержащие внутри себя собственно SQL-запросы, задаются как #sql {…}.

Внешние переменные[править | править исходный текст]

В терминологии SQLJ внешней переменной (англ. host variable) называется переменная SQLJ-конструкции, используемая для получения значений или передачи их во внешнюю относительно конструкции программную среду. К примеру:

int i, j;
i = 1;
#sql {	SELECT field INTO :OUT j
            FROM table
            WHERE id = :IN i };
System.out.println(j);

Внешние переменные для избежания неоднозначностей должны задаваться в определённом виде, а именно:

:[IN|OUT|INOUT] <имя переменной>.

Модификаторы IN, OUT, INOUT опциональны и используются для указания переменных, соответственно, передающих значение извне в SQLJ-конструкцию; возвращающих значение вовне и выполняющих обе функции. Данные ключевые слова используются не только для этого — также они задают метод доступа к внешним переменным внутри SQLJ-конструкции: при наличии модификатора IN возможно только чтение значения переменной, при наличии OUT — только запись, при наличии INOUT — полный доступ. По умолчанию (при отсутствии явно заданного модификатора) переменные объявляются с неявным модификатором INOUT.

Внешние выражения[править | править исходный текст]

Вместо просто переменных в SQLJ-конструкциях можно использовать выражения, содержащие внешние переменные, чаще называемые просто внешними выражениями (англ. host expressions). Они имеют определённый синтаксис:

:( <выражение> )

Основной нюанс при использовании внешних выражений заключается в том, что их использование может повлечь за собой определённые последствия, связанные с тем, что разбор SQLJ-конструкции препроцессором при наличии нескольких внешних выражений идёт в определённом порядке, а при использовании в выражениях присваиваний результат присваивания может быть передан в программную среду.

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

int i = 1;
#sql {	SELECT result
	    FROM  table1
	    WHERE field1 = :(x[i++]) AND field2 = :(y[i++]) AND field3 = :(z[i++]) };
System.out.println(i);

Исходя из опыта программирования, можно попытаться предположить, что

  1. Значение переменной i в процессе разбора SQL-выражения не будет изменяться;
  2. Сформированный запрос будет иметь вид
SELECT RESULT
    FROM    table1
    WHERE   field1 = :(x[1]) AND field2 = :(y[1]) AND field3 = :(z[1])

Однако и первое, и второе утверждения — неверны. Для проверки этого составим простую схему, проясняющую порядок разбора данной конструкции SQLJ-препроцессором:

i = 1
x[i++] → x[1], i = 2
y[i++] → y[2], i = 3
z[i++] → z[3], i = 4

Следовательно:

  1. После выполнения SQLJ-директивы будет иметь место i = 4;
  2. Выполняться будет запрос
SELECT RESULT
    FROM    table1
    WHERE   field1 = :(x[1]) AND field2 = :(y[2]) AND field3 = :(z[3])

Контексты[править | править исходный текст]

В терминологии SQLJ и JDBC контекстом подключения называется совокупность из трёх параметров, однозначно ими определяемая:

  1. название базы данных;
  2. идентификатор сессии;
  3. идентификатор активной транзакции.

Для любой SQLJ-конструкции контекст, в котором она будет исполняться, можно определить явно: #sql [<контекст>] {…}.

В рамках директивы #sql можно также создавать новые контексты для последующего использования: #sql context <контекст>. Если контекст явно не задан, то конструкция считается выполняемом в контексте по умолчанию (англ. default context). При необходимости контекст по умолчанию может быть изменён.

Итераторы[править | править исходный текст]

Итератором в терминологии стандарта SQLJ называется объект для хранения результата запроса, возвращающего более одной записи. По своей сути и реализации он представляет собой не просто множество записей, а множество с некоторым упорядочением на нем, позволяющим использовать полученные записи последовательно. В этом плане итератор имеет много общего с курсором.

Стандартом предусмотрены два типа итераторов — разница между ними достаточно интересна: итераторы с привязкой по позиции в использовании требуют более SQL-подобного синтаксиса, в отличие от итераторов с привязкой по столбцам, которые очень близки по использованию к объектам.

Итераторы с привязкой по позиции[править | править исходный текст]

Первым типом итератора является итератор с привязкой по позициям. Он объявляется следующим образом: #sql public iterator ByPos (String, int). Ясно видно, что в данном случае привязка результатов запроса к итератору осуществляется просто по совпадению типов данных между итератором и результатом запроса. Однако для этого требуется, чтобы типы данных у итератора и результата запроса могли быть отображены друг на друга согласно стандарту SQL/JRT.

Создадим простую таблицу:

CREATE TABLE people (
	fullname VARCHAR(50),
	birthyear NUMERIC(4,0))

Теперь с помощью итератора первого типа и конструкции FETCH … INTO … произведем выборку данных из результата запроса:

ByPos positer;
String name = null;
int year = 0;
#sql positer = {SELECT fullname, birthyear FROM people};
for(;;)
{
	#sql {FETCH :positer INTO :name, :year};
	if (positer.endFetch())
		break;
	System.out.println(name + " was born in " + year);
 
}

Первой директивой осуществляется привязка результата запроса к итератору; второй с помощью конструкции FETCH … INTO … из результата последовательно считывается по одной записи.

Итераторы с именованием столбцов[править | править исходный текст]

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

#sql public iterator ByName (
	String fullNAME,
	int birthYEAR);

Используется он как обычный объект, а именно, доступ к полям осуществляется через соответствующие акцессорные методы:

ByName namiter;
#sql namiter = {SELECT fullname, birthyear FROM people};
String s;
int i;
while (namiter.next())
{
	i = namiter.birthYEAR();
	s = namiter.fullNAME();
	System.out.println(s + " was born in "+i);
}

Однако существует правило, которое должно быть соблюдено — имена полей итератора должны совпадать (без учёта регистра) с именами полей в запросе. Это связано с процессом разбора SQLJ-конструкции препроцессором. В случае, если имя столбца в БД имеет название, несовместимое с правилами именования переменных в Java, необходимо использовать в запросе, формирующем итератор, псевдонимы.

Вызовы процедур и функций[править | править исходный текст]

Вызовы процедур очень просто записываются с использованием внешних переменных

#sql {CALL proc (:myarg)};

Функции, в свою очередь, вызываются с использованием конструкции VALUE

int i;
#sql i = {VALUES(func(34))};

Взаимодействие с JDBC[править | править исходный текст]

Так как SQLJ-директивы при своём использовании используют JDBC-вызовы, то представляет интерес возможность использовать эти технологии совместно. Достаточно легко преобразовывать итераторы в объекты ResultSet и наоборот.

Преобразование объекта ResultSet осуществляется очень просто. Для этого сначала нужно определить итератор с именованием столбцов (в нашем примере он будет обозначаться Employees, а затем выполнить операцию CAST:

#sql iterator Employees (String ename, double sal);
 
PreparedStatement stmt = conn.prepareStatement();
String query = "SELECT ename, sal FROM emp WHERE ";
query += whereClause;
ResultSet rs = stmt.executeQuery(query);
Employees emps;
#sql emps = {CAST :rs};
while (emps.next()) {
      System.out.println(emps.ename() + " earns " + emps.sal());
}
emps.close();
stmt.close();

Отдельно стоит обратить внимание, что после привязки результата запроса к итератору отдельно закрывать результат запроса нет надобности — это за программиста сделает сам препроцессор.

Обратный процесс — преобразование итератора в объект ResultSet производится с помощью итераторов особого типа, так называемых слабо типизированных (англ. weakly typed) итераторов.

sqlj.runtime.ResultSetIterator iter;
#sql iter = {SELECT ename FROM emp};
 
ResultSet rs = iter.getResultSet();
while (rs.next()) {
      System.out.println("employee name: " + rs.getString(1));
}
iter.close();

В этом случае связь между итератором и результатом запроса также сохраняется и закрывать следует именно итератор.

Особенности SQLJ[править | править исходный текст]

Как уже упоминалось ранее, сравнивать SQLJ как технологию проще всего с аналогичной Java-ориентированной технологией того же назначения, а именно — с JDBC. Ситуация усложняется тем, что эти технологии не параллельны и не вполне взаимозаменяемы, а находятся друг над другом архитектурно.

  1. Запрос одинакового назначения, записанный в JDBC-вызовах и в SQLJ-директиве, в большинстве случаев будет более компактно записан в тексте программы именно во втором случае, что уменьшает размер листинга и вероятность ошибки, связанной со сборкой итоговой строки запроса из небольших фрагментов;
  2. Любая SQLJ-директива на этапе компиляции разбирается и проверяется препроцессором, следовательно, все ошибки синтаксиса выявляются ещё на этом этапе, в отличие от JDBC, где контролируется правильность конструкций только с точки зрения синтаксиса Java — за разбор и правильность собственно запроса отвечает уже СУБД, что, естественно, приводит к тому, что ошибки такого рода будут выявлены уже на этапе запуска;
  3. Собственно сам препроцессор (обычно имеющий название sqlj) не входит в JDK; он и необходимые для его работы библиотеки обычно предоставляются производителем СУБД. Это закономерно — как показано выше, SQLJ гораздо более близок к СУБД, чем собственно к языку Java; более того, препроцессор должен учитывать особенности SQL-синтаксиса «своей» СУБД;
  4. В большинстве случаев — особенно это касается часто выполняющихся сложных запросов, работающих с большими массивами данных, — SQLJ-директива будет выполняться в среднем быстрее аналогичного набора JDBC-вызовов. Это связано с тем, что план для соответствующего запроса в случае SQLJ-директивы будет строиться только один раз, а затем использоваться повторно, в отличие от JDBC, где построение плана будет осуществляться при каждом вызове;
  5. Создаваемый при трансляции SQLJ-директивы план запроса при необходимости может быть подвергнут настройке со стороны пользователя; в случае JDBC такая возможность по понятным причинам отсутствует;
  6. Если запрос требует значительных изменений в каждом конкретном случае (простой пример: поисковый запрос по набору полей, значения в части которых могут отсутствовать), то проще использовать JDBC, так как преимуществ в использовании SQLJ здесь нет;
  7. Так как при использовании JDBC нет надобности в дополнительном этапе обработки кода — трансляции, то процесс компиляции в этом случае будет быстрее.

Недостатки SQLJ[править | править исходный текст]

  1. SQLJ требует дополнительного шага препроцессирования.
  2. Большинство IDE не имеют поддержки SQLJ.
  3. SQLJ не имеет поддержки в большинстве ORM-фреймворков, таких как Hibernate.

Поддержка программными средствами[править | править исходный текст]

Oracle[править | править исходный текст]

DB/2[править | править исходный текст]

Informix[править | править исходный текст]

http://www-01.ibm.com/software/data/informix/pubs/library/iif.html

см. Embedded SQLJ User’s Guide

Ссылки[править | править исходный текст]

  1. Эндрю Эйзенберг, Джим Мелтон. Связывания для объектных языков. Проверено 12 ноября 2008. Архивировано из первоисточника 25 августа 2011.
  2. Эндрю Эйзенберг, Джим Мелтон. SQLJ – Часть 1. Проверено 12 ноября 2008. Архивировано из первоисточника 25 августа 2011.
  3. IBM Redbooks. DB2 for z/OS and OS/390: Ready for Java. Проверено 12 ноября 2008. Архивировано из первоисточника 25 августа 2011.
  4. Oracle Database 11g. SQLJ Developer's Guide and Reference. Проверено 12 ноября 2008. Архивировано из первоисточника 25 августа 2011.