Схема взаимодействия интерфейсов.
Рисунок 2. Схема взаимодействия интерфейсов.
Даже беглого взгляда на Рисунок 2 вполне достаточно, чтобы понять - общая схема взаимодействия интерфейсов в Java удивительным образом напоминает столь всем знакомую схему ODBC с ее гениальным изобретением драйвер-менеджера к различным СУБД и единого универсального пользовательского интерфейса. JDBC Driver Manager - это основной ствол JDBC-архитектуры. Его первичные функции очень просты - соединить Java-программу и соответствующий JDBC драйвер и затем выйти из игры. Естественно, что ODBC был взят в качестве основы JDBC из-за его популярности среди независимых поставщиков программного обеспечения и пользователей. Но тогда возникает законный вопрос - а зачем вообще нужен JDBC и не легче ли было организовать интерфейсный доступ к ODBC-драйверам непосредственно из Java? Ответом на этот вопрос может быть только однозначное нет. Путь через JDBC-ODBC-Bridge, как ни странно, может оказаться гораздо короче.
1. ODBC нельзя использовать непосредственно из Java, поскольку он основан на C-интерфейсе. Вызов из Java C-кода нарушает целостную концепцию Java, пробивает брешь в защите и делает программу трудно-переносимой.
2. Перенос ODBC C-API в Java-API нежелателен. К примеру, Java не имеет указателей, в то время как в ODBC они используются.
3. ODBC слишком сложен для понимания. В нем смешаны простые и сложные вещи, причем сложные опции иногда применяются для самых простых запросов.
4. Java-API необходим, чтобы добиться абсолютно чистых Java решений. Когда ODBC используется, то ODBC-драйвер и ODBC менеджер должны быть инсталлированы на каждой клиентской машине. В то же время, JDBC драйвер написан полностью на Java и может быть легко переносим на любые платформы от сетевых компьютеров до мэйнфреймов.
JDBC API - это естественный Java-интерфейс к базовым SQL абстракциям и, восприняв дух и основные абстракции концепции ODBC, он реализован, все-таки, как настоящий Java-интерфейс, согласующийся с остальными частями системы Java.
В отличие от интерфейса ODBC, JDBC организован намного проще. Главной его частью является драйвер, поставляемый фирмой JavaSoft для доступа из JDBC к источникам данных. Этот драйвер является самым верхним в иерархии классов JDBC и называется DriverManager. Согласно, установившимся правилам Internet, база данных и средства ее обслуживания идентифируются при помощи URL.
jdbc::
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-серверами.