Java-программы и апплеты с интерфейсом JDBC-ODBC
JDBC (Java Database Connectivity) является не протоколом, а интерфейсом и основан на спецификациях SAG CLI (SQL Access Group Call Level Interface - интерфейс уровня вызова группы доступа SQL).
Сам по себе JDBC работать не может и использует основные абстракции и методы ODBC. Хотя в стандарте JDBC API и предусмотрена возможность работы не только через ODBC, а и через использование прямых линков к базам данных по двух- или трех-звенной схеме (см. Рис.1), эту схему используют гораздо реже, чем повсеместно используемый JDBC-ODBC-Bridge занимающий центральное место в общей схеме взаимодействия интерфейсов (см. Рис. 2)
Рис. 1. Непосредственный доступ к базе данных по 3-х-звенной схеме.
Рис. 2. Схема взаимодействия интерфейсов.
Даже беглого взгляда на Рис. 2 вполне достаточно, чтобы понять - общая схема взаимодействия интерфейсов в Java удивительным образом напоминает столь всем знакомую схему ODBC с ее гениальным изобретением драйвер-менеджера к различным СУБД и единого универсального пользовательского интерфейса. JDBC Driver Manager - это основной ствол JDBC-архитектуры. Его первичные функции очень просты - соединить Java-программу и соответствующий JDBC драйвер и затем выйти из игры. Естественно, что ODBC был взят в качестве основы JDBC из-за его популярности среди независимых поставщиков программного обеспечения и пользователей. Но тогда возникает законный вопрос - а зачем вообще нужен JDBC и не легче ли было организовать интерфейсный доступ к ODBC-драйверам непосредственно из Java? Ответом на этот вопрос может быть только однозначное нет. Путь через JDBC-ODBC-Bridge, как ни странно, может оказаться гораздо короче.
В нем смешаны простые и сложные вещи, причем сложные опции иногда применяются для самых простых запросов.
JDBC API - это естественный Java-интерфейс к базовым SQL абстракциям и, восприняв дух и основные абстракции концепции ODBC, он реализован, все-таки, как настоящий Java-интерфейс, согласующийся с остальными частями системы Java.
В отличие от интерфейса ODBC, JDBC организован намного проще. Главной его частью является драйвер, поставляемый фирмой JavaSoft для доступа из JDBC к источникам данных. Этот драйвер является самым верхним в иерархии классов JDBC и называется DriverManager. Согласно, установившимся правилам Internet, база данных и средства ее обслуживания идентифируются при помощи URL. jdbc::
где под понимается имя конкретного драйвера, или некоего механизма установления соединения с базой данных, например, ODBC. В случае применения ODBC, в URL-строку подставляется именно эта аббревиатура, а в качестве используется обычный DSN (Data Source Name), т.е. имя ODBC-источника из ODBC.INI файла. Например: jdbc:odbc:dBase
В некоторых случаях вместо ODBC может быть использовано имя прямого сетевого сервиса к базе данных, например: jdbc:dcenaming:accounts-payable,
или jdbc:dbnet://ultra1:1789/state
В последнем случае часть URL //ultra1:1789/state представляет собой и описывает имя хоста, порт и соответствующий идентификатор для доступа к соответствующей базе данных.
Однако, как уже говорилось выше, чаще всего, все-таки используется механизм ODBC благодаря его универсальности и доступности. Программа взаимодействия между драйвером JDBC и ODBC разработана фирмой JavaSoft в сотрудничестве с InterSolv и называется JDBC-ODBC-Bridge. Она реализована в виде JdbcOdbc.class (для платформы Windows JdbcOdbc.dll) и входит в поставку JDK1.1.
Помимо JdbcOdbc- библиотек должны существовать специальные драйвера (библиотеки), которые реализуют непосредственный доступ к базам данных через стандартный интерфейс ODBC. Как правило эти библиотеки описываются в файле ODBC.INI. На внутреннем уровне JDBC-ODBC-Bridge отображает медоды Java в вызовы ODBC и тем самым позволяет использовать любые существующие драйверы ODBC, которых к настоящему времени накоплено в изобилии.
Рассмотрим типичное приложение на Java c доступом к типичному реляционному серверу или даже к обычной dBase-таблице.
// Следующий код на Java используется как пример. Простой подстановкой // соответствующих значений url, login, и password, и, затем подстановкой // SQL операторов вы можете посылать их в базу данных. //-------------------------------------- // // Module: SimpleSelect.java // // Описание: Эта программа для ODBC API интерфейса. Java-приложение // будет присоединяться к JDBC драйверу, посылать select оператор // и показывать результаты в таблице // // Продукт: JDBC к ODBC Мост // // Автор: Karl Moss (С.Дунаев модификация для работы с кириллицей) // // Дата: Апрель 1997 // // Copyright: 1990-1996 INTERSOLV, Inc. // This software contains confidential and proprietary // information of INTERSOLV, Inc. //-------------------------------------- import java.net.URL; import java.sql.*; import java.io.*; class SimpleSelect { public static void main (String args[]) { String url = _jdbc:odbc:dBase_; String query = _SELECT * FROM my_table_; try { // Загрузка jdbc-odbc-bridge драйвера Class.forName (_sun.jdbc.odbc.JdbcOdbcDriver_); DriverManager.setLogStream(System.out); // Попытка соединения с драйвером. Каждый из // зарегистрированных драйверов будет загружаться, пока // не будет найден тот, который сможет обработать этот URL Connection con = DriverManager.getConnection ( url, __, __); // Если не можете соединиться, то произойдет exception // (исключительная ситуация). Однако, если вы попадете // в следующую строку программы, значит вы успешно соединились с URL // Проверки и печать сообщения об успешном соединении // checkForWarning (con.getWarnings ()); // Получить DatabaseMetaData объект и показать // информацию о соединении DatabaseMetaData dma = con.getMetaData (); //System.out.println(_\nConnected to _ + dma.getURL()); //System.out.println(_Driver _ + //dma.getDriverName()); //System.out.println(_Version _ + //dma.getDriverVersion()); //System.out.println(__); // Создать Оператор-объект для посылки // SQL операторов в драйвер Statement stmt = con.createStatement (); // Образовать запрос, путем создания ResultSet объекта ResultSet rs = stmt.executeQuery (query); // Показать все колонки и ряды из набора результатов dispResultSet (rs); // Закрыть результирующий набор rs.close(); // Закрыть оператор stmt.close(); // Закрыть соединение con.close(); } catch (SQLException ex) { // Случилось SQLException.
Перехватим и // покажем информацию об ошибке. Заметим, что это // может быть множество ошибок, связанных вместе // //System.out.println (_\n*** SQLException caught ***\n_); while (ex != null) { //System.out.println (_SQLState: _ + // ex.getSQLState ()); //System.out.println (_Message: _ + ex.getMessage ()); //System.out.println (_Vendor: _ + //ex.getErrorCode ()); ex = ex.getNextException (); //System.out.println (__); } } catch (java.lang.Exception ex) { // Получив некоторые другие типы exception, распечатаем их. ex.printStackTrace (); } } //---------------------------------- // checkForWarning // Проверка и распечатка предупреждений. Возврат true если // предупреждение существует //---------------------------------- private static boolean checkForWarning (SQLWarning warn) throws SQLException { boolean rc = false; // Если SQLWarning объект был получен, показать // предупреждающее сообщение. if (warn != null) { System.out.println (_\n *** Warning ***\n_); rc = true; while (warn != null) { //System.out.println (_SQLState: _ + //warn.getSQLState ()); //System.out.println (_Message: _ + //warn.getMessage ()); //System.out.println (_Vendor: _ + //warn.getErrorCode ()); //System.out.println (__); warn = warn.getNextWarning (); } } return rc; } //---------------------------------- // dispResultSet // Показать таблицу полученных результатов //---------------------------------- private static void dispResultSet (ResultSet rs) throws SQLException, IOException { // Объявление необходимых переменных и // константы для желаемой таблицы перекодировки данных int i, length, j; String cp1 = new String(_Cp1251_); // Получить the ResultSetMetaData. Они будут использованы // для печати заголовков ResultSetMetaData rsmd = rs.getMetaData (); // Получить номер столбца в результирующем наборе int numCols = rsmd.getColumnCount (); // Показать заголовок столбца for (i=1; i<=numCols; i++) { if (i > 1) System.out.print(_,_); //System.out.print(rsmd.getColumnLabel(i)); } System.out.println(__); // Показать данные, загружая их до тех пор, пока не исчерпается // результирующий набор boolean more = rs.next (); while (more) { // Цикл по столбцам for (i=1; i<=numCols; i++) { // Следующая группа операторов реализует функции перекодировки // строк из таблицы базы данных в желаемый формат, потому что в // различных базах символы могут быть закодированы произвольным // образом.
Если использовать стандартный метод - getString - на выходе // получается абракадабра. Строки нужно сначала перевести в Unicode, // затем конвертировать в строку Windows и убрать лидирующие нули InputStream str1 = rs.getUnicodeStream(i); byte str2[]; byte str3[]; int sizeCol = rsmd.getColumnDisplaySize(i); str2 = new byte[sizeCol+sizeCol]; str3 = new byte[sizeCol+sizeCol]; length = str1.read(str2); // Здесь нужно убрать нули из строки, которые предваряют каждый // перекодированный символ k=1; for (j=1; j<sizeCol*2; j++) { if (str2[j] != 0) { str3[k]=str2[j]; k=k+1; } } String str = new String(str3,cp1); System.out.print(str); } System.out.println(__); // Загрузка следующего ряда в наборе more = rs.next (); } } }
В этой простой программе, приводимой во множестве руководств, мною произведено одно небольшое изменение, позволяющее использовать ее для работы с различными базами данных, содержащих таблицы с полями в кириллической кодировке. Дело в том, что хотя Java автоматически производит преобразования из Unicode и обратно в соответствии с установленными на вашей машине языковыми спецификациями (так называемые locale), эти преобразования не всегда действуют по отношению к кириллическим фонтам, особенно, когда кириллические строки прописаны не непосредственно в Java-программе, а передаются из внешних источников, например из баз данных через несколько промежуточных слоев. Та же проблема, как мы увидим далее, возникает и при использовании сервлетов, работающих в тесной взаимоувязке с Web-серверами.