SQLJ ЧАСТЬ 0, называемая теперь SQL/OLB

Около полутора лет неформальная и открытая группа компаний организовывала совещания, на которых рассматривалось, каким образом язык программирования Java и реляционные базы данных могли бы использоваться совместно. В этой группе, которая первоначально называлась JSQL, а позднее SQLJ, участвовали Compaq (Tandem), IBM, Informix, Micro Focus, Microsoft, Oracle, Sun и Sybase. Когда группа создавалась, намерение состояло в том, чтобы участники могли предложить и взаимно оценить идеи, в результате достаточно частых встреч выявить, в чем имеется общее понимание и согласие относительно синтаксиса и семантики, и в конечном счете, создать основу для одного или нескольких официальных стандартов. Работа началась с предложения Oracle о возможном подходе к встраиванию операторов SQL в Java. Позднее Sybase внесла предложения о способах использования Java в системах баз данных для обеспечения реализации хранимых подпрограмм и определяемых пользователем типов данных (user-defined data types, UDT).

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

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

Все эти три части быстро прогрессировали. Поскольку работа над Частью 0 началась ранее, чем над другими частями, она была первой представлена для официальной стандартизации. SQLJ Часть 0 рассматривалась Техническим комитетом по базам данных NCITS H2 как «Язык Баз данных SQL - Часть 10, Связывания для объектных языков (SQL/OLB)». Такое название этой части стандарта SQL подразумевает широкую сферу, в рамках которой SQLJ Часть 0 является первым таким связыванием, которое должно быть определено. Хотя в настоящее время нет никаких предложений относительно расширения ее на другие языки, несколько участников проявило интерес к поддержке других объектно-ориентированных языков, таких как C++ или Smalltalk.

Когда писалась эта статья, редактор проекта стандарта только что завершил работу, связанную с учетом критических замечаний, которые были получены в ходе недавно состоявшегося открытого обсуждения в США. Ожидается, что SQL/OLB будет официально принят к концу 1998. Когда это произойдет, спецификация SQL/OLB будет доступна для приобретения в ANSI как стандарт ANSI X3.135.10:1998.

JDBC КОМПАНИИ Javasoft

Интерфейс JDBC, первоначальная версия которого входила в состав JDK 1.1, определяет Java API для доступа к реляционным СУБД. Он упоминается здесь, потому что SQLJ Часть 0 поддерживается его средствами.

JDBC API функционально довольно богат. Он обеспечивает классы и методы для того, чтобы:

  • соединяться с базой данных;
  • получать метаданные из БД;
  • исполнять запрос или оператор ЯОД;
  • подготавливать оператор ЯМД или оператор CALL с параметрами, которые будут заданы во время исполнения оператора;
  • осуществлять выборку как данных, так и метаданных для результирующих наборов, продуцированных исполнением оператора.

JDBC обеспечивает динамическое исполнение операторов SQL. При любых синтаксических или семантических ошибках в операторах SQL будут возникать исключительные ситуации во время исполнения приложения.

Драйвер JDBC должен поддерживать операторы базового уровня (Entry Level) SQL-92 с некоторыми расширениями, определенными в спецификации JDBC. Он может также поддерживать операторы других уровней SQL и операторы, которые являются расширениями поставщика к стандарту SQL.

Очень простая программа для извлечения некоторых данных из таблицы базы данных могла бы выглядеть следующим образом:

try {String url = "jdbc:sybase:
	Tds:localhost:2638";
Connection con = DriverManager.
	getConnection (url, "DBA", "sql"); 
String stmt_source = 
	"SELECT distinct city, 
	state " + "FROM employee"; 
Statement stmt = 
	con.createStatement();    
ResultSet rs = stmt.executeQuery
	(stmt_source); 

while (rs.next()) {System.out.println (rs.getString("city") + " " +

rs.getString(2));} con.close (); } catch (SQLException sqe) {System.out.println (sqe.getMessage());}

ВОЗМОЖНОСТИ SQLJ ЧАСТИ 0

SQLJ Часть 0 позволяет встраивать статические операторы SQL в программы на языке Java в значительной мере таким же образом, как SQL-92 позволяет встраивать операторы SQL в C, КОБОЛ и некоторые другие языки. С динамическими операторами SQL прекрасно справляется JDBC, и поэтому они не были включены в рассматриваемые средства.

Простая программа на SQLJ

Следующий фрагмент кода показывает, как SQLJ Часть 0 может использоваться для доступа к базе данных:

try {#sql { DELETE FROM employee          
WHERE emp_id = 17 ); } 
catch (SQLException sqe) {System.out.println 
(sqe.getMessage());} 

В приведенном примере не показаны некоторые элементы настройки, которые необходимы для использования этого примера. Драйвер JDBC должен быть зарегистрирован менеджером драйверов JDBC, и должно быть осуществлено соединение с базой данных.

«#sql {...};» идентифицирует исполняемый оператор SQL. Фигурные скобки ({}) используются как ограничители этого оператора SQL, и они отделяют его от остальной части программы на Java. Транслятор SQLJ будет искать эти встроенные операторы и заменять их операторами Java, которые вызывают исполнение соответствующих операторов SQL. Полученная в результате исходная программа на Java будет компилироваться обычным образом.

Транслятору SQLJ может быть предписано установить соединение с экземпляром базы данных во время его работы и использовать метаданные, которые он там найдет, для проверки правильности операторов SQL. Если, например, не существовала бы таблица employee (служащие) или столбец emp_id (идентификатор служащего), то транслятор SQLJ сообщил бы пользователю об ошибке.

Возможно также, что поставщик транслятора SQLJ будет обеспечивать средства автономной проверки, которые могли бы выполнять часть проверки оператора, не требующую метаданных. Если бы в указанном выше операторе вместо «=» было бы указано «+», то автономная проверка была бы способна обнаружить такую ошибку.

На стадии исполнения приложение может соединяться с той же самой базой данных, относительно которой оно было проверено, или с другой базой данных с точно такой же схемой.

Приведенный выше пример показывает также, что для обработки исключительных ситуаций в SQL использована модель JDBC. В связываниях для других включающих языков приложение оповещается об исключительной ситуации SQL с помощью переменной SQLSTATE. В SQLJ Части 0 такая исключительная ситуации приведет к тому, что операторы SQLJ будут порождать исключительную ситуацию Java - java.sql.SQLException.

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

Контексты соединений

Объект контекста соединения используется для того, чтобы связывать исполнение оператора SQL с конкретным соединением с базой данных. В приведенном выше примере использовался неявный объект контекста соединения. В следующем примере будет использоваться явный объект контекста соединения.

#sql context EmpContext; 
String url = "jdbc:sybase:Tds:
	localhost:2638"; 
EmpContext empCtxt = new 
	EmpContext(url, "dba", "sql", false); 
#sql [empCtxt] { DELETE FROM 
	employee WHERE emp_id = 17}; 

Пример начинается с «#sql context…» для объявления класса контекста соединения (подкласс ConnectionContext), имеющего имя EmpContext. Далее создается объект empCtxt этого класса. Запись имени объекта в квадратных скобках ([ ]) указывает на его использование в исполнении оператора DELETE.

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

Такой тип явного контекста является в большей мере переносимым, чем неявный контекст. Класс контекста соединения предоставляет методы, которые могут исследовать и изменять некоторые свойства соединения.

Приложение может функционировать на множестве соединений с одной и той же базой данных или множестве соединений с различными базами данных, используя явные объекты контекста соединения. Контексты соединения могут безопасно разделяться между потоками (thread) в многопоточном приложении.

В некоторых средах, например в средах СУБД, приложение SQLJ может вызываться с контекстом соединения, уже предусмотренным для его использования. Использование метода ConnectionContext.getDefaultContext() позволяет определить, имеет ли это место.

Контексты исполнения

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

sqlj.runtime.ExecutionContext exec
	txt = new sqlj.runtime.
	ExecutionContext(); 
#sql [empCtxt, execCtxt] { DELETE                
FROM employee WHERE 
emp_id = 17}; 
	System.out.println ( "Deleted " + 
	execCtxt.getUpdateCount() + 
	" rows."); 

В этом примере явный контекст соединения и контекст исполнения ассоциируются с оператором DELETE. Объект empCtxt контекста исполнения (ExecutionContext) используется для доступа к ряду строк таблицы employee, на которые воздействовал оператор удаления. Любые предупреждения, сгенерированные оператором, могут быть извлечены из контекста исполнения путем использования метода getWarnings().

Подобно контексту соединения, использование контекста исполнения может быть неявным или явным. Контекст соединения имеет связанный с ним контекст исполнения, принимаемый по умолчанию. Этот контекст используется в случае, когда явный контекст исполнения не был определен. Выборка контекста исполнения, принимаемого по умолчанию, может быть осуществлена с помощью метода ConnectionContext.getExecutionContext().

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

Переменные и выражения включающего языка

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

В SQLJ Части 0 допускается использование переменных и выражений включающего языка Java, как показывает следующий пример:

int id; 
#sql { SELECT emp_id INTO :id FROM     
	employee WHERE emp_fname LIKE:(argv[0] + `%`) }; 
System.out.println 
	("Employee id " + id); 

Переменная id включающего языка Java в этом примере используется в операторе SQL для указания необходимости помещения некоторого значения в Java переменную. Шаблон в предложении LIKE - это выражение включающего языка, в котором элемент строкового массива конкатенируется со строковым литералом. SQLJ использует то же самое отображение между типами данных Java и типами данных SQL, что и определяется для JDBC.

Выражение включающего языка имеет следующий синтаксис:

<выражение включающего языка> 		
	::= :[ <роль параметра> ] 
	<выражение> 
<роль параметра> ::= IN | OUT | 
	INOUT 
<выражение> ::= <переменная>| 
	( <сложное выражение> ) 

Для переменных и выражений включающего языка Java указывается роль параметра (IN - входной, OUT - выходной, INOUT - входной и выходной), которая - если она не определена явным образом - неявно определяется использованием. Для переменной включающего языка id определена роль OUT, а для выражения включающего языка во фразе LIKE - роль IN.

При вычислении выражений Java возможны побочные эффекты. Если, например, выражение такого вида, как «count++» появляется в <выражении включающего языка>, значение «count» будет увеличиваться на единицу каждый раз, когда это выражение включающего языка встречается. SQLJ вычисляет все выражения Java, которые появляются в операторе SQL, прежде, чем этот оператор будет исполняться.

Выражения вычисляются в порядке слева направо.

Вызов хранимых программ

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

int count = 0; 
#sql { CALL emp_count 
	(:in (argv[0]),
:in (argv[1]), :out count) }; 
System.out.println ("The result is 
	" + count); 

Хранимая функция может вызываться следующим образом:

int count = 0; 
#sql count = { VALUES (emp_count2                           
	(:in (argv[0]),:in (argv[1])))}; 
System.out.println ("The result is 
	" + count); 
Итераторы результирующих наборов

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

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

Связывание со столбцами по имени

Первый из этих типов, именованный итератор, представлен в следующем примере:

#sql iterator Employee
	(int emp_id, String emp_lname, 	
	java.sql.Date start date); 
Employee emp; 
#sgl emp = { SELECT emp_lname,
	emp_id, start_date FROM employee
	WHERE emp_fname LIKE ?C%?}; 
while (emp.next()) { System.out.
	println emp.start_date() + ", "+ 	
	emp.emp_id() + "," + 					
	emp.emp_lname().trim()); } 
emp.close(); 

Оператор итератора «#sql iterator...» в SQLJ Части 0 определяет класс Employee с методами доступа для каждого из столбцов результата исполнения запроса. Генерируются также методы, подобные next() и close(). Создается и связывается с результатом исполнения запроса объект emp. Далее цикл while повторяется для строк результата запроса и печатает каждую из них. Наконец, результат запроса закрывается.

Именно такое использование пар «имя столбца/тип данных» в описании итератора определяет, что это - именованный итератор. Имена столбцов соответствуют именам в итераторе без учета регистра (case-insensitive). Это означает, что SQLJ Часть 0 требует, чтобы имена столбцов в целевом списке оператора SELECT были уникальными, если их сравнивать таким образом (безотносительно регистра).

Связывание со cтолбцами по позиции

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

#sql iterator Employee 
	(int, String, String); 
int emp_id = 0; String emp_lname = 	
	null; String emp_fname = null; 
Employee emp; 
#sql emp = { SELECT emp_id,
	emp_lname, emp_fname FROM 
	employee WHERE emp_fname 
	LIKE ?C%?}; 
while (true) { 
   #sql {FETCH :emp INTO :emp_id,            
              	:emp_lname, :emp_fname}; 
if (emp.endFetch()) break; 
   System.out.println
(emp_fname.trim() + 
" " + emp_lname.trim() + ", 
" + emp_id ); } 
emp.close(); 

Оператор iterator в этом примере специфицирует только типы данных, а не пары «имя столбца/тип данных», указывая тем самым, что был выбран вариант позиционированного итератора. Во время исполнения оператора FETCH столбцы результата запоминаются в Java-переменных в том порядке, как они были определены. Класс Employee обладает методами такого рода, как endFetch() и close(), сгенерированными для него. Приложение сканирует строки результата исполнения запроса с помощью оператора FETCH языка SQL.

Выбор между именованным и позиционированным итераторами является полностью стилистическим. Для использования именованного итератора все столбцы целевого списка оператора SELECT должны иметь уникальные имена или им должны быть присвоены уникальные имена путем использования имеющейся в SQL возможности переименования столбцов (фраза AS).

Операторы позиционированного обновления и удаления

Использование оператора UPDATE позиционированного обновления можно проиллюстрировать следующим примером:

#sql iterator Employee implements
	sqlj.runtime.ForUpdate (String emp_lname, String emp_fname); 
Employee emp; 

#sql emp = { SELECT emp_lname, emp_fname FROM employee WHERE dept_id = 200};

while (emp.next()) {

System.out.println(emp.emp_fname() + " " + emp.emp_lname());

#sql { UPDATE employee SET dept_id = 100 WHERE CURRENT OF :emp }; }

Здесь «implements sqlj.runtime. ForUpdate» указывает механизмам SQLJ Части 0, что это - итератор обновляемого результирующего набора. Без этого предложения итератор можно было бы использовать только для чтения. Оператор позиционированного обновления SQL был лишь немного изменен в SQLJ Части 0. Вместо спецификации имени курсора в предложении «WHERE CURRENT OF» используется переменная-итератор Java.

Множественные результирующие наборы

Некоторые СУБД позволяют хранимой процедуре возвращать результирующие наборы во время исполнения этой процедуры. Они иногда называются результирующими наборами побочного канала.

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

SQLJ дает возможность приложению иметь дело с этими результирующими наборами, переходя к JDBC, как показано в следующем примере:

ExecutionContext ectxt = 
	new ExecutionContext(); 
#sql [ectxt] { CALL emp_count3 
	(:in (argv[0]), :in (argv[1])) }; 
ResultSet rs; while ((rs = 
	ectxt.getNextResultSet()) != null) {    
 while (rs.next()) {
System.out.println 
(rs.getString(1));} 
System.out.println();} 

ResultSet - это интерфейс JDBC, который позволяет приложению обрабатывать результирующие наборы.

ИНТЕРОПЕРАБЕЛЬНОСТЬ SQLJ/JDBC

Как мы уже не раз утверждали, JDBC был создан для обработки динамических операторов SQL, а SQLJ Часть 0 - для обработки статических операторов SQL.

Ясно, что имеются некоторые приложения, которые нуждаются в обоих видах обработки. SQLJ Часть 0 обеспечивает интероперабельность с JDBC благодаря контекстам соединения и итераторам результирующих наборов.

Контекст соединения может быть создан с помощью использования URL, как было показано ранее, или спецификации соединения JDBC.

Другой вариант состоит в применении метода getConnection() к контексту соединения, что позволяет получить соединение JDBC, которое его использует.

Итератор SQLJ может быть создан из результирующего набора JDBC следующим образом:

ResultSet rs = ... ;     
#sql iterator iter (...); 
#sql iter = { CAST :rs } 

Иной путь - применить метод getResultSet() к итератору SQLJ, получая результирующий набор JDBC, который связан с этим итератором.

ДВОИЧНАЯ ПЕРЕНОСИМОСТЬ

Java-программы не зависят от аппаратной платформы, на которой они исполняются. Поскольку программы SQLJ превращаются в чистый код Java, они могут исполняться всюду, где существует Виртуальная машина Java (JVM).

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

Поскольку SQLJ генерирует обращения к JDBC, одно приложение SQLJ может исполняться со многими СУБД.

SQLJ обеспечивает дополнительный уровень независимости от СУБД. Операторы SQL, которые были обработаны средствами SQLJ, могут далее обрабатываться настройщиками (customizer), ориентированными на продукты различных поставщиков. Этот настройщик может, например, генерировать код для создания и исполнения хранимых процедур, которые содержат исходные операторы приложения SQL.

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

УРОВНИ СООТВЕТСТВИЯ

Реализация SQLJ считается соответствующей спецификации, если она поддерживает синтаксис и семантику, определенные в SQL/OLB, JDBC 1.2 или выше, а также возможности языка SQL, предусмотренные в JDBC 1.2.

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

  • вызовы хранимых процедур и функций;
  • векоторые из атрибутов контекста исполнения ExecutionContext;
  • методы getResultSet() и getJDBCResultSet();
  • операторы begin/end SQL.

ПРЕИМУЩЕСТВА SQLJ ЧАСТИ 0

Мы начали это обсуждение с примера применения JDBC и указали, что JDBC был создан для обработки динамических операторов SQL. В чем же тогда заключаются преимущества использования SQLJ Части 0, по сравнению с JDBC, для статических операторов? Эти преимущества заключаются в следующем:

  • операторы SQLJ Части 0 могут быть подвергнуты проверке правильности во время трансляции. Автономный верификатор может проверять некоторые конструкции синтаксиса SQL. Оперативный верификатор может проверять весь синтаксис SQL и все семантические правила;
  • операторы и программы SQLJ Части 0 являются более короткими и более легкими для чтения, чем их эквиваленты в JDBC;
  • SQLJ Часть 0 позволяет поставщику СУБД предоставлять инструментальные средства для настройки приложения SQLJ, оптимизируя его способами, которые были бы неосуществимы для приложения JDBC.

ЭТАЛОННАЯ РЕАЛИЗАЦИЯ SQLJ ЧАСТИ 0

Когда компания Oracle представила группе SQLJ свои предложения по SQLJ Части 0, она также сделала доступной эталонную реализацию этой технологии, которая может быть найдена на Web-сайте Oracle.

Эталонная реализация сама написана на Java, так что она может работать на любой платформе, поддерживающей JVM. и нейтральна по отношению к поставщикам СУБД.

Оперативный синтаксический верификатор использует соединение с JDBC для проверки правильности операторов SQL. Автономный верификатор может вызываться в ситуации, когда соединение с JDBC не доступно. Автономный верификатор реализуется классом Java, который может быть написан поставщиком СУБД.

SQLJ ЧАСТИ 1 И 2

SQLJ Части 1 и 2 будут вскоре направлены NCITS для принятия их в качестве официальных стандартов (хотя и не как компонентов стандарта SQL). SD-3 (проектное предложение) было представлено на рассмотрение NCITS с просьбой применить к этим спецификациям процедуру ускоренного рассмотрения (Fast-Track processing). Возможно, эти спецификации будут официально приняты в течение 2-ой половины 1999 года.

ПРИЗНАНИЕ РАЗРАБОТЧИКАМ SQLJ

Многие специалисты внесли свой вклад в разработку спецификаций SQLJ Части 0. Рискуя кого-либо пропустить, мы попытаемся тем не менее их перечислить: Julie Basu, Brian Becker, David Birdsall, Jose Blakely, Charles Campbell, Gray Clossman, Curt Cotner, Paul Cotton, Dan Coyle, Stefan Dessloch, Pierre Dufour, Cathy Dwyer, Andrew Eisenberg, John Ellis, Chris Farrar, Mark Hapner, Johannes Klein, Jim Melton, ChongMak Park, Frank Pellow, Richard Pledereder, Ekkehard Rohwedder, David Rosenberg, Jerry Schwartz, Phil Shaw, Yah-Heng Sheng, Ju-Lung Tseng, Michael Ubell и Seth White.


Cсылки на WEB-узлы

Американский национальный институт стандартов (American National Standards Institute) http://web.ansi.org

Национальный комитет по стандартам информационных технологий (National Committee for Information Technology Standards) http://www.ncits.org

Домашняя страница SQLJ http://www.sqlj.org

Страница драйверов JDBC компании Oracle http://www.oracle.com/st/products/jdbc/sqlj/

Спецификации SQL/OLB будут доступны из:

American National Standards Institute Attn:

Customer Service 11 West 42nd Street New York, NY 10036 USA 212-642-4980


Литература

[1] dpANS X3.135.10:1998, draft proposed American National Standard, Information Technology - Database Language - SQL - Part 10: Object Language Bindings (SQL/OLB), June 1998.

[2] SQL Routines using the Java Programming Language, Working Draft, Oct. 14, 1998.

[3] SQL Types using the Java Programming Language, Working Draft, Oct. 14, 1998.