Liber Pontificalis
- 1 year ago
- 0
- 0
SQLJ — подмножество стандарта SQL , направленное на объединение преимуществ синтаксиса языков SQL и Java ради удобства реализации бизнес-логики и работы с данными. Данный стандарт разработан консорциумом, состоящим из компаний IBM , Micro Focus , Microsoft , Compaq (точнее, его подразделение, занимающееся СУБД, которое, скорее, можно отнести к приобретенной компании), Informix , Oracle , Sun и Sybase .
На момент появления консорциума JSQL (впоследствии ставшего одноимённым с разрабатываемым им стандартом) в 1997 году идея о взаимодействии реляционных СУБД и программ на Java была не нова. Компанией (дочерним подразделением компании Sun) уже был разработан интерфейс JDBC ( англ. Java DataBase Connectivity — «соединение с БД средствами Java»), включённый в стандарт языка, начиная с момента выпуска JDK 1.1. Однако в силу определённых причин (см. « ») возможностей, предоставляемых этим интерфейсом было недостаточно.
Спецификация стандарта SQLJ состоит из трех частей:
К концу 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 {...}
;
Подробно все синтаксические конструкции будут рассмотрены далее.
Логично возникновение вопроса о причинах создания двух параллельных стандартов для реализации технологий доступа к СУБД.
Для начала стоит отметить, что 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);
Исходя из опыта программирования, можно попытаться предположить, что
i
в процессе разбора SQL-выражения не будет изменяться;
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
Следовательно:
i = 4
;
SELECT result FROM table1 WHERE field1 = :(x[1]) AND field2 = :(y[2]) AND field3 = :(z[3])
В терминологии SQLJ и JDBC контекстом подключения называется совокупность из трёх параметров, однозначно ими определяемая:
Для любой 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))};
Так как 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 как технологию проще всего с аналогичной Java-ориентированной технологией того же назначения, а именно — с JDBC. Ситуация усложняется тем, что эти технологии не параллельны и не вполне взаимозаменяемы, а находятся друг над другом архитектурно.
sqlj
) не входит в
JDK
; он и необходимые для его работы библиотеки обычно предоставляются производителем СУБД. Это закономерно — как показано выше, SQLJ гораздо более близок к СУБД, чем собственно к языку Java; более того, препроцессор должен учитывать особенности SQL-синтаксиса «своей» СУБД;
см. Embedded SQLJ User’s Guide