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);
Исходя из опыта программирования, можно попытаться предположить, что
- Значение переменной
i
не изменится; - Сформированный запрос будет иметь вид
SELECT result
FROM table1
WHERE field1 = :(x[2]) AND field2 = :(y[2]) AND field3 = :(z[2])
Однако и первое, и второе утверждения — неверны. Для проверки этого составим простую схему, проясняющую порядок разбора данной конструкции SQLJ-препроцессором:
i = 1
x[i++] → x[1], i = 2
y[i++] → y[2], i = 3
z[i++] → z[3], i = 4
Следовательно:
- После выполнения SQLJ-директивы будет иметь место
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).
При необходимости контекст по умолчанию может быть изменён.