Соединение с базой данных


Было посчитано, что половина всего разрабатываемого программного обеспечения использует клиент/серверные операции. Огромная перспектива Java была в способности строить платформо-независимые клиент/серверные приложения для баз данных. Это осуществляется с помощью Java DataBase Connectivity (JDBC).

Одна из главных проблем баз данных была в особенностях, которые имеют разные базы данных от разных компаний. Есть "стандартный" язык баз данных, Структурированный Язык Запросов - Structured Query Language (SQL-92), но вы должны обычно знать, с базой данных какого производителя вы работаете, не смотря на стандарт. JDBC разработана так, чтобы быть платформо-независимой, так что вам нет необходимости беспокоиться во время программирования о базе данных. Однако все равно есть возможность делать специфичные для производителя вызовы из JDBC, так что вы не ограничены от выполнения тех задач, которые должны.

Одно место, где программисту может понадобиться использование имен SQL типов - это в SQL выражении TABLE CREATE, когда необходимо создать новую базу данных и определить SQL типы для каждой колонки. К сожалению, есть значительные отличия между SQL типами, поддерживаемыми разными базами данных. Разные базы данных, которые поддерживают SQL типы с одинаковой семантикой и структурой, могут давать этим типам разные имена. Наиболее распространенные базы данных поддерживают SQL типы данных для больших бинарных значений: в Oracle этот тип называется LONG RAW, Sybase называет его IMAGE, а DB2 называет его LONG VARCHAR FOR BIT DATA. Поэтому, если портируемость базы дынных является вашей целью, вы должны попробовать использовать только общие идентификаторы SQL типов.

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

JDBC, как и многое другое API, разработано для упрощения. Вызовы методов, которые вы совершаете, соответствуют логическим операциям, о которых вы думаете при получении данных из базы данных: соединение с базой данных, создание выражения (statement) и выполнение запроса, затем просмотр результирующей выборки.

Чтобы обеспечить независимость от платформы, JDBC предоставляет менеджер драйверов, который динамически поддерживает все объекты-драйверы, необходимые для запроса к вашей базе данных. Так что, если у вас есть три базы данных различных производителей, с которыми вы соединяетесь, вам необходимо три различных объекта драйвера. Объект драйвера регистрирует себя в менеджере драйверов во время загрузки, и вы можете форсировать загрузку, используя Class.forName( ).

Для открытия базы данных вы должны создать "URL базы данных", в котором указывается:

  1. Что вы используете JDBC, с помощью "jdbc".
  2. "Суб протокол": имя драйвера или имя механизма соединения с базой данных. Так как дизайн JDBC инспирирован ODBC, первый из доступных протоколов - это "jdbc-odbc bridge", который указывается, как "odbc".
  3. Идентификатор базы данных. Он различен для различных баз данных, но обычно он предоставляет логическое имя, которое отображается программным обеспечением администрирования базы данных на физический директорий, в котором расположены таблицы базы данных. Для вашей базы данных этом может иметь любое значение, вы должны зарегистрировать имя, используя ваше программное обеспечение администрирования базы данных. (Процесс регистрации различается для разных платформ.)

Вся эта информация комбинируется в одну строку, "URL базы данных". Например, для соединения через ODBC субпроткол к базе данных с идентификатором "people", URL базы данных должен быть таким:

String dbUrl = "jdbc:odbc:people";

Если вы соединяетесь по сети, URL базы данных будет содержать информацию о соединении, идентифицирующую удаленную машину и поэтому может стать немного пугающей. Вот пример базы данных CloudScape, вызываемой из удаленного клиента, использующего RMI:

jdbc:rmi://192.168.170.27:1099/jdbc:cloudscape:db

Этот URL базы данных реально содержит два JDBC вызова в одном. Первая часть "jdbc:rmi://192.168.170.27:1099/" использует RMI для создания соединения с удаленной машиной базы данных, прослушивающей порт 1099 по IP адресу 192.168.170.27. Вторая часть URL, "jdbc:cloudscape:db" выражает более типичные установки используемого субпротокола и имени базы данных, но они обычно проявляются только после первой части, которая устанавливает соединение посредством RMI с удаленной машиной.

Когда вы готовы соединиться с базой данных, вызывайте статический метод DriverManager.getConnection( ) и передавайте ему URL базы данных, имя пользователя и пароль, чтобы получить доступ в базу данных. Назад вы получаете объект Connection, который вы можете затем использовать для запроса и управления базой данных.

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

//: c15:jdbc:Lookup.java
// Поиск email адресов в
// локальной базе данных с использованием JDBC.
// {Broken}
import java.sql.*;

public class Lookup {
  
public static void main(String[] args) throws SQLException,
         ClassNotFoundException
{
     
String dbUrl = "jdbc:odbc:people";
      String user =
"";
      String password =
"";
     
// Загружаем драйвер (регистрирует себя)
     
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
      Connection c = DriverManager.getConnection
(dbUrl, user, password);
      Statement s = c.createStatement
();
     
// SQL код:
     
ResultSet r = s.executeQuery("SELECT FIRST, LAST, EMAIL "
           
+ "FROM people.csv people " + "WHERE " + "(LAST='" + args[0]
           
+ "') " + " AND (EMAIL Is Not Null) " + "ORDER BY FIRST");
     
while (r.next()) {
        
// Регистр не имеет значения:
        
System.out.println(r.getString("Last") + ", "
              
+ r.getString("fIRST") + ": " + r.getString("EMAIL"));
     
}
     
s.close(); // Закрываем ResultSet
  
}
}
// /:~

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

Как только соединение будет установлено с помощью DriverManager.getConnection( ), вы можете использовать полученный объект Connection для создания объекта Statement с помощью метода createStatement( ). С помощью полученного объекта Statement вы можете вызвать метод executeQuery( ), передав в него строку, содержащую SQL выражение в стандарте SQL-92. (Вы увидите, как вы можете сгенерировать это выражение автоматически, так что вам не нужно знать очень много об SQL.)

Метод executeQuery( ) возвращает объект ResultSet, который является итератором: метод next( ) перемещает итератор на следующую запись в выражении, или возвращает false, если достигнут конец результирующего множества. Вы всегда будете получать объект ResultSet из метода executeQuery( ), даже если результатом запроса будет пустое множество (то есть, исключение не выбрасывается). Обратите внимание, что вы должны вызвать метод next( ) один раз, прежде, чем попробуете прочесть любую запись с данными. Если результирующее множество пустое, этот первый вызов next( ) вернет false. Для каждой записи из результирующего множества вы можете выбрать поля, используя (наряду с другими подходами) имя поля, как строку. Также обратите внимание, что регистр букв в имени поля игнорируется - он не имеет значения в базе данных SQL. Вы определяете тип того, что вы получаете назад, вызывая getInt( ), getStrin( ), getFloat( ) и т. п. Таким образом, вы получаете данные из вашей базы данных в родном формате и можете делать с ними все, что хотите, используя обычный код Java.

Получение примера для работы

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

Конечно же, этот процесс может радикально отличаться на разных машинах, но процесс, который использовал я, работает на 32-х битной Windows и может дать вам ключ к пониманию, с какой стороны подойти к вашей собственной ситуации.

Шаг 1: Нахождение JDBC Драйвера

Приведенная ниже программа содержит выражение:

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

Здесь предполагается структура каталогов, которая вводит в заблуждение. В обычной поставке JDK 1.1 не существует файла с названием JdbcOdbcDriver.class, так что если вы заглянете на этот пример и пойдете искать его, вы будите удивлены. Другой опубликованный пример использует псевдо-имя, такое как "myDriver.ClassName", что не намного полезнее. Фактически, вышеприведенная инструкция загрузки для jdbc-odbc драйвера (который реально поставляется с JDK) показана в нескольких местах в онлайн документации (обычно на странице, озаглавленной "JDBC-ODBC Bridge Driver"). Если приведенная выше инструкция загрузки не сработает, то, возможно, изменилось имя, как часть изменений в связи со сменой версии, так что вы должны просмотреть документацию снова.

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

Шаг 2: Конфигурирование базы данных

Опять таки, это специфично для 32-х битной Windows. Вам может понадобиться некоторое исследование, чтобы получить представление о настройках для вашей собственной платформы.

Во-первых, откройте панель управления. Вы можете найти две иконки, которые называются "ODBC". Вы должны использовать одну из них, которая называется "32bit ODBC", так как другая иконка предназначена для обратной совместимости с 16 битным программным обеспечением ODBC и не даст результатов для JDBC. Когда вы откроете иконку "32bit ODBC", вы увидите диалог с несколькими закладками, включая "User DNS", "System DNS", "File DNS" и т. д., в которых "DNS" означает "Data Source Name". Только одно место важно для JDBC-ODBC моста, это установка базы данных в "System DNS", но вы захотите протестировать вашу конфигурацию и создать запросы, а для этого вам также понадобиться установить базу данных в закладке "File DNS". Это позволить инструменту запросов от Microsoft (который поставляется вместе с Microsof Office) находить базу данных. Обратите внимание, что существуют другие инструменты запросов от других производителей.

Наиболее интересная база данных - это так, которую вы уже используете. Стандартный ODBC поддерживает несколько различных форматов файлов, включая такие, как многоуважаемая рабочая лошадь DBase. Однако, он также включает простые форматы, как "разделенные запятыми ASCII". В моем случае, я просто взял мою базу данных "people", которую я поддерживаю многие годы, используя различные инструменты, и экспортировал ее в разделенный запятыми ASCII файл (они обычно имеют расширения .cvs). В разделе "System DNS" я выбрал "Add", выбрал текстовый драйвер для обработки моего разделенного запятыми ASCII файла, а затем убрал пометку с "use current directory", что позволит мне указать директорий, в котором я поместил экспортированный файл с данными.

Когда вы сделаете это, вы увидите, что реально вы не указываете файл, а только директорий. Это происходит потому, что база данных обычно представлена набором файлов в одном единственном директории (хотя база данных может быть также представлена в других форматах). Каждый файл обычно содержит единственную таблицу, а SQL выражения могут выдавать результат, который объединяет данные их нескольких таблиц базы данных (это называется join). База данных, которая содержит только одну таблицу (наподобие моей базы данных "people") обычно называется базой данных из плоского файла. Большинство проблем, которые возникают с простыми хранилищами и получением данных, обычно заключается в том, что требуется множество таблиц, которые должны объединятся для получения желаемых результатов, а это называется реляционной базой данных.

Шаг 3: Проверка конфигурации

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

Connection c = DriverManager.getConnection(dbUrl, user, password);

Если будет выброшено исключение, ваша конфигурация некорректна.

Однако, на этом этапе полезно будет взять инструмент генерации запросов. Я использую Microsoft Query, который поставляется вместе с Microsoft Office, но вы можете выбрать что-то еще. Инструмент запросов должен знать, где располагается база данных, и Microsoft Query требует, чтобы я зашел в ODBC Администратор на закладку "File DNS" и добавить там новую сущность, опять таки указав текстовый драйвер и директорий, в котором живет моя база данных. Вы можете назвать сущность так, как вы хотите, но полезно использовать то же самое имя, которое использовано в "System DNS".

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

Шаг 4: Генерация вашего SQL запроса

Запрос, который я создал с помощью Microsoft Query, не только показывает мне, что моя база данных присутствует и в порядке, но этот инструмент также автоматически создал код SQL запроса, который мне нужно вставить в мою Java программу. Мне нужен запрос, который будет искать записи, содержимое поля фамилии которых совпадает с тем, которое введено в командной строке при запуске Java программы. Так что здесь я ищу определенную фамилию "Eckel". Я также хочу отобразить только те имена, для которых есть ассоциированный с ними EMail. Шаги, которые я предпринял для создания этого запроса, следующие:

  1. Запустил новый запрос и использовал Построитель Запросов. Выбрал базу данных "people". (Это эквивалентно открытию соединения с базой данных с использованием соответствующего URL базы данных.)
  2. Выбрал таблицу "people" из базы данных. Из этой таблицы выбрал колонки FIRST, LAST и EMAIL.
  3. В разделе "File Data" выбрал LAST и выбрал "equals" с аргументом "Eckel". Щелкнул радиокнопку "And".
  4. Выбрал EMAIL и выбрал "Is not Null".
  5. В разделе “Sort By” выбрал FIRST.

В результате этого запроса будет показано то, что вы хотели.

Теперь вы можете нажать кнопку SQL и без каких-либо исследований с вашей стороны будет показан корректный SQL код, готовый для копи-паста. Этот запрос выглядит примерно так:

SELECT people.FIRST, people.LAST, people.EMAIL FROM people.csv people WHERE (people.LAST='Eckel') AND (people.EMAIL Is Not Null) ORDER BY people.FIRST

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

Шаг 5: Изменение и вставка в ваш запрос

Вы заметите, что приведенный выше код на вид отличается от того, что используется в программе. Это происходит потому, что инструмент запроса использует полную квалификацию для всех имен, даже когда используется только одна таблица. (Когда привлечена более, чем одна таблица, квалификация предотвращает коллизию между колонками разных таблиц, которые имеют одно и то же имя.) Так как этот запрос вовлекает только одну таблицу, вы можете, по своему усмотрению, удалить квалификатор "people" из большинства имен, например так:

SELECT FIRST, LAST, EMAIL FROM people.csv people WHERE (LAST='Eckel') AND (EMAIL Is Not Null) ORDER BY FIRST

Кроме того, вы не хотите, чтобы это программа была жестко запрограммирована на поиск только одного имени. Вместо этого, она должна охотится за именем, заданным как аргумент командной строки. Сделав эти изменения и включив SQL выражение в динамически создаваемую строку, получим:

"SELECT FIRST, LAST, EMAIL " +
"FROM people.csv people " +
"WHERE " +
"(LAST='" + args[0] + "') " +
" AND (EMAIL Is Not Null) " +
"ORDER BY FIRST");

В SQL существует другой способ вставки имен в запрос, называемый хранимой процедурой, которая используется для увеличения скорости. Но для большинства ваших экспериментов с базами данных и для первой пробы, построение вашей собственной строки запроса в Java - это великолепно.

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

GUI версия программы поиска

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

//: c15:jdbc:VLookup.java
// GUI version of Lookup.java.
// <applet code=VLookup
// width=500 height=200></applet>
// {Broken}
import javax.swing.*;

import java.awt.*;

import java.awt.event.*;

import javax.swing.event.*;

import java.sql.*;

import com.bruceeckel.swing.*;

public class VLookup extends JApplet {
  
String dbUrl = "jdbc:odbc:people";
   String user =
"";
   String password =
"";
   Statement s;
   JTextField searchFor =
new JTextField(20);
   JLabel completion =
new JLabel("                         ");
   JTextArea results =
new JTextArea(40, 20);
  
  
public void init() {
     
searchFor.getDocument().addDocumentListener(new SearchL());
      JPanel p =
new JPanel();
      p.add
(new Label("Last name to search for:"));
      p.add
(searchFor);
      p.add
(completion);
      Container cp = getContentPane
();
      cp.add
(p, BorderLayout.NORTH);
      cp.add
(results, BorderLayout.CENTER);
     
try {
        
// Загружаем драйвер (регистрирует себя)
        
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
         Connection c = DriverManager.getConnection
(dbUrl, user, password);
         s = c.createStatement
();
     
}
     
catch (Exception e) {
        
results.setText(e.toString());
     
}
   }
  
  
class SearchL implements DocumentListener {
     
public void changedUpdate(DocumentEvent e) {
      }
     
     
public void insertUpdate(DocumentEvent e) {
        
textValueChanged();
     
}
     
     
public void removeUpdate(DocumentEvent e) {
        
textValueChanged();
     
}
   }
  
  
public void textValueChanged() {
     
ResultSet r;
     
if (searchFor.getText().length() == 0) {
        
completion.setText("");
         results.setText
("");
        
return;
     
}
     
try {
        
// Автозавершение:
        
r = s.executeQuery("SELECT LAST FROM people.csv people "
              
+ "WHERE (LAST Like '" + searchFor.getText()
              
+ "%') ORDER BY LAST");
        
if (r.next())
           
completion.setText(r.getString("last"));
         r = s.executeQuery
("SELECT FIRST, LAST, EMAIL "
              
+ "FROM people.csv people " + "WHERE (LAST='"
              
+ completion.getText() + "') AND (EMAIL Is Not Null) "
              
+ "ORDER BY FIRST");
     
}
     
catch (Exception e) {
        
results.setText(searchFor.getText() + "n");
         results.append
(e.toString());
        
return;
     
}
     
results.setText("");
     
try {
        
while (r.next()) {
           
results.append(r.getString("Last") + ", "
                 
+ r.getString("fIRST") + ": " + r.getString("EMAIL")
                 
+ "n");
        
}
      }
     
catch (Exception e) {
        
results.setText(e.toString());
     
}
   }
  
  
public static void main(String[] args) {
     
Console.run(new VLookup(), 500, 200);
  
}
}
// /:~

Большая часть логики работы с базой данных та же самая, но вы можете видеть, что добавлен DocumentListener для отслеживания JTextField (смотрите описание javax.swing.JTextField в HTML документации по Java на java.sun.com, чтобы получить больше информации), так что в то время, когда вы напечатаете новый символ, сначала будет совершена попытка выполнить автозавершение имени путем поиска фамилии в базе данных и отображения первой найденной. (Она помещается в JLabel завершения и используется как текст поиска.) Таким образом, как только вы напечатаете достаточное количество символов для программы для уникального поиска имени, которое вы ищите, вы можете остановиться.

Почему JDBC API выглядит таким сложным

Когда вы просмотрите документацию для JDBC, она может привести в уныние. Обычно в интерфейсте DatabaseMetaData - который просто громоздок, в противовес большинству интерфейсов, которые вы видели в Java - существуют методы, такие как dataDefinitionCausesTransactionCommit( ), getMaxColumnNameLength( ), getMaxStatementLength( ), storesMixedCaseQuotedIdentifiers( ), supportsANSI92IntermediateSQL( ), supportsLimitedOuterJoins( ) и так далее. Что можно сказать об этом?

Как упомянуто раньше, базы данных, по своей сущности, находятся в постоянном состоянии суматохи, прежде всего потому, что это требуется для приложений, работающих с базами данных, и поэтому инструменты работы с базами данных так великолепны. Только в последнее время были достигнуты некоторые договоренности в сторону общего языка SQL (и есть множество других языков работы с базами данных общего использования). Но даже со "стандартным" языком есть много вариаций на эту тему, поэтому JDBC должен предоставлять громоздкий интерфейс DatabaseMetaData, так что ваш код может обнаружить совместимость базы данных, с которой в настоящее время выполнено соединение, с определенным SQL "стандартом". Короче говоря, вы можете написать простой, переносимый SQL, но если вы хотите оптимизировать, скорость вашего кодирования значительно повысится, если вы постигните возможности базы данных определенного поставщика.

Конечно же, это не ошибка Java. Различия между продуктами баз данных является просто тем, что JDBC пробует помочь скомпенсировать. Но имейте в виду, что ваша жизнь будет облегчена, если вы можете либо писать общие запросы и не беспокоиться о производительности, либо, если вы должны настраивать производительности, необходимо знать платформу, для которой вы пишите, так что вам нет необходимости писать весь код для исследования.

Более сложный пример

Более интересный пример [3] вовлекает многотабличную базу данных, которая расположена на сервере. Здесь база данных подразумевает обеспечение ответственности за общую активность и за то, чтобы позволить людям назначать события, так что она называется Community Interests Database (CID). Этот пример предназначен только для обзора базы данных и ее реализации, и не предназначен для подробной инструкции по разработке базы данных. Есть много книг, семинаров и пакетов программного обеспечения, которые помогут вам в дизайне и разработке базы данных.

Кроме того, этот пример предполагает, что у вас уже есть установленная SQL база данных на сервере (хотя она может быть также запущена на локальной машине), а также предполагается, что вами был найден и исследован соответствующий JDBC драйвер для этой базы данных. Существует несколько бесплатных SQL баз данных, и некоторые из них даже устанавливаются автоматически с некоторыми разновидностями Linux. Вы ответственны за выбор базы данных и нахождение JDBC драйвера; приведенных здесь пример основывается на SQL базе данных, называемой "Cloudscape", которая может быть бесплатно скачена для разработки (не развертывания) по адресу http://www.Cloudscape.com. Вам необходимо следовать инструкции, чтобы правильно установить и настроить Cloudscape.

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

//: c15:jdbc:CIDConnect.java
// Информация для соединения с базой данных
// community interests database (CID).
public class CIDConnect {
  
// Вся информация специфична для CloudScape:
  
public static String dbDriver = "COM.cloudscape.core.JDBCDriver";
  
public static String dbURL = "jdbc:cloudscape:d:/docs/_work/JSapienDB";
  
public static String user = "";
  
public static String password = "";
} // /:~

В этом примере нет пароля защиты для базы данных, так что имя пользователя и пароль являются пустыми строками. Для Cloudscape dbURL содержит путь к директории, в которой расположена база данных, но другие JDBC драйверы будут использовать другие способы для передачи этой информации. Этот пример предполагает, что база данных "JSapienDB" уже создана, но чтобы заставить базу данных работать, вам необходимо использовать инструмент cview, который поставляется вместе Cloudscape, чтобы создать новую базу, а затем вы должны изменить указанный выше dbURL в соответствии с путем, где вы создадите базу данных.

База данных состоит из набора таблиц, которые имеют вот такую структуру:

"Member" содержит общую информацию о членах, "Events" и "Locations" содержат информацию об активности и в каком месте она имела место, а "Evtmems" содержит события и членов, которые хотят следить за этими событиями. Вы можете видеть, что данные о членах в одной таблице являются ключами в другой таблицы.

Следующий класс содержит SQL строки, которые создадут эти таблицы базы данных (обратитесь к руководству по SQL, чтобы понять, что делает этот SQL код):

//: c15:jdbc:CIDSQL.java
// SQL строки для создания таблиц для CID.
public class CIDSQL {
  
public static String[] sql = {
        
// Создание таблицы MEMBERS:
        
"drop table MEMBERS",
        
"create table MEMBERS " + "(MEM_ID INTEGER primary key, "
              
+ "MEM_UNAME VARCHAR(12) not null unique, "
              
+ "MEM_LNAME VARCHAR(40), " + "MEM_FNAME VARCHAR(20), "
              
+ "ADDRESS VARCHAR(40), " + "CITY VARCHAR(20), "
              
+ "STATE CHAR(4), " + "ZIP CHAR(5), " + "PHONE CHAR(12), "
              
+ "EMAIL VARCHAR(30))",
        
"create unique index " + "LNAME_IDX on MEMBERS(MEM_LNAME)",
        
// Создание таблицы EVENTS
        
"drop table EVENTS",
        
"create table EVENTS " + "(EVT_ID INTEGER primary key,"
              
+ "EVT_TITLE VARCHAR(30) not null,"
              
+ "EVT_TYPE VARCHAR(20), " + "LOC_ID INTEGER, "
              
+ "PRICE DECIMAL, " + "DATETIME TIMESTAMP)",
        
"create unique index " + "TITLE_IDX on EVENTS(EVT_TITLE)",
        
// Создание таблицы EVTMEMS
        
"drop table EVTMEMS",
        
"create table EVTMEMS " + "(MEM_ID INTEGER not null,"
              
+ "EVT_ID INTEGER not null, " + "MEM_ORD INTEGER)",
        
"create unique index " + "EVTMEM_IDX on EVTMEMS(MEM_ID, EVT_ID)",
        
// Создание таблицы LOCATIONS
        
"drop table LOCATIONS",
        
"create table LOCATIONS " + "(LOC_ID INTEGER primary key,"
              
+ "LOC_NAME VARCHAR(30) not null,"
              
+ "CONTACT VARCHAR(50), " + "ADDRESS VARCHAR(40), "
              
+ "CITY VARCHAR(20), " + "STATE VARCHAR(4), "
              
+ "ZIP VARCHAR(5), " + "PHONE CHAR(12), "
              
+ "DIRECTIONS VARCHAR(4096))",
        
"create unique index " + "NAME_IDX on LOCATIONS(LOC_NAME)", };
} // /:~

Следующая программа использует информацию CIDConnect и CIDSQL для загрузки JDBC драйвера, создания соединения с базой данных и для создания структуры таблиц в соответствии с приведенной выше диаграммой. Для соединения с базой данных вы вызываете статический метод DriverManager.getConnection( ), передаете ему URL базы данных, имя пользователя и пароль, чтобы войти в базу данных. Назад вы получаете объект Connection, который вы можете использовать для запросов и манипуляции с базой данных. Как только соединение будет установлено, вы можете просто передать SQL в базу данных, в данном случае путем передачи через массив CIDSQL. Однако, когда мы запустим эту программу в первый раз, команда "dtop table" не сработает, а станет причиной исключения, которое будет перехвачено, отрапортовано, а затем проигнорировано. Причина использования команды "drop table" состоит в том, что с ней легко экспериментировать: вы можете изменить SQL, затем определить таблицу, а затем запустить программу, которая заменит старые таблицы новыми.

В этом примере имеет смысл выбрасывать исключения на консоль:

//: c15:jdbc:CIDCreateTables.java
// Создание таблиц базы данных для
// community interests database.
// {Broken}
import java.sql.*;

public class CIDCreateTables {
  
public static void main(String[] args) throws SQLException,
         ClassNotFoundException, IllegalAccessException
{
     
// Загружаем драйвер (регистрирует себя)
     
Class.forName(CIDConnect.dbDriver);
      Connection c = DriverManager.getConnection
(CIDConnect.dbURL,
            CIDConnect.user, CIDConnect.password
);
      Statement s = c.createStatement
();
     
for (int i = 0; i < CIDSQL.sql.length; i++) {
        
System.out.println(CIDSQL.sql[i]);
        
try {
           
s.executeUpdate(CIDSQL.sql[i]);
        
}
        
catch (SQLException sqlEx) {
           
System.err.println("Probably a 'drop table' failed");
        
}
      }
     
s.close();
      c.close
();
  
}
}
// /:~

Обратите внимание, что все изменения в базе данных могут быть внесены путем изменения строк в таблице CIDSQL без изменения CIDCreateTables.

executeUpdate( ) обычно будет возвращать количество строк, которые обрабатываются SQL выражением. executeUpdate( ) чаще всего используется для выполнения инструкций INSERT, UPDATE или DELETE, которые изменяют одну или несколько строк. Для таких инструкций, как CREATE TABLE, DROP TABLE и CREATE INDEX метод executeUpdate( ) всегда вернет ноль.

Для тестирования базы данных она загружается некоторыми простыми данными. Для этого необходимо выполнить серию INSERT'ов, за которыми следуют SELECT'ы, производящие результирующее множество. Чтобы сделать добавление и изменение тестовых данных легче, тестовые данные организованы в виде двумерного массива элементов типа Object, а метод executeInsert( ) может затем использовать информацию из одной строки таблицы для создания соответствующей SQL команды.

//: c15:jdbc:LoadDB.java
// Загрузка и тестирование базы данных.
// {Broken}
import java.sql.*;

class TestSet {
  
Object[][] data = {
         {
"MEMBERS", new Integer(1), "dbartlett", "Bartlett", "David",
              
"123 Mockingbird Lane", "Gettysburg", "PA", "19312",
              
"123.456.7890", "bart@you.net" },
        
{ "MEMBERS", new Integer(2), "beckel", "Eckel", "Bruce",
              
"123 Over Rainbow Lane", "Crested Butte", "CO", "81224",
              
"123.456.7890", "beckel@you.net" },
        
{ "MEMBERS", new Integer(3), "rcastaneda", "Castaneda", "Robert",
              
"123 Downunder Lane", "Sydney", "NSW", "12345",
              
"123.456.7890", "rcastaneda@you.net" },
        
{ "LOCATIONS", new Integer(1), "Center for Arts", "Betty Wright",
              
"123 Elk Ave.", "Crested Butte", "CO", "81224",
              
"123.456.7890", "Go this way then that." },
        
{ "LOCATIONS", new Integer(2), "Witts End Conference Center",
              
"John Wittig", "123 Music Drive", "Zoneville", "PA",
              
"19123", "123.456.7890", "Go that way then this." },
        
{ "EVENTS", new Integer(1), "Project Management Myths",
              
"Software Development", new Integer(1), new Float(2.50),
              
"2000-07-17 19:30:00" },
        
{ "EVENTS", new Integer(2), "Life of the Crested Dog",
              
"Archeology", new Integer(2), new Float(0.00),
              
"2000-07-19 19:00:00" },
        
// Match some people with events
        
{ "EVTMEMS", new Integer(1), // Dave is going to
              
new Integer(1), // the Software event.
              
new Integer(0) }, { "EVTMEMS", new Integer(2), // Bruce is
                                                   // going to
              
new Integer(2), // the Archeology event.
              
new Integer(0) }, { "EVTMEMS", new Integer(3), // Robert is
                                                   // going to
              
new Integer(1), // the Software event.
              
new Integer(1) }, { "EVTMEMS", new Integer(3), // ... and
              
new Integer(2), // the Archeology event.
              
new Integer(1) }, };
  
  
// Используем набор данных по умолчанию:
  
public TestSet() {
   }
  
  
// Используем другой набор данных:
  
public TestSet(Object[][] dat) {
     
data = dat;
  
}
}

public class LoadDB {
  
Statement statement;
   Connection connection;
   TestSet tset;
  
  
public LoadDB(TestSet t) throws SQLException {
     
tset = t;
     
try {
        
// Загрузка драйвера (регистрирует себя)
        
Class.forName(CIDConnect.dbDriver);
     
}
     
catch (java.lang.ClassNotFoundException e) {
        
e.printStackTrace(System.err);
     
}
     
connection = DriverManager.getConnection(CIDConnect.dbURL,
            CIDConnect.user, CIDConnect.password
);
      statement = connection.createStatement
();
  
}
  
  
public void dispose() throws SQLException {
     
statement.close();
      connection.close
();
  
}
  
  
public void executeInsert(Object[] data) {
     
String sql = "insert into " + data[0] + " values(";
     
for (int i = 1; i < data.length; i++) {
        
if (data[i] instanceof String)
           
sql += "'" + data[i] + "'";
        
else
           
sql += data[i];
        
if (i < data.length - 1)
           
sql += ", ";
     
}
     
sql += ')';
      System.out.println
(sql);
     
try {
        
statement.executeUpdate(sql);
     
}
     
catch (SQLException sqlEx) {
        
System.err.println("Insert failed.");
        
while (sqlEx != null) {
           
System.err.println(sqlEx.toString());
            sqlEx = sqlEx.getNextException
();
        
}
      }
   }
  
  
public void load() {
     
for (int i = 0; i < tset.data.length; i++)
        
executeInsert(tset.data[i]);
  
}
  
  
// Выбрасывается исключение на консоль:
  
public static void main(String[] args) throws SQLException {
     
LoadDB db = new LoadDB(new TestSet());
      db.load
();
     
try {
        
// Получаем ResultSet из загруженной базы данных:
        
ResultSet rs = db.statement.executeQuery("select "
              
+ "e.EVT_TITLE, m.MEM_LNAME, m.MEM_FNAME "
              
+ "from EVENTS e, MEMBERS m, EVTMEMS em "
              
+ "where em.EVT_ID = 2 " + "and e.EVT_ID = em.EVT_ID "
              
+ "and m.MEM_ID = em.MEM_ID");
        
while (rs.next())
           
System.out.println(rs.getString(1) + " " + rs.getString(2)
                 
+ ", " + rs.getString(3));
     
}
     
finally {
        
db.dispose();
     
}
   }
}
// /:~

Класс TestSet содержит набор данных по умолчанию, который будет воспроизводиться, если вы используете конструктор по умолчанию; однако, вы можете также создать объект TestSet, используя альтернативный набор данных, с помощью второго конструктора. Набор данных располагается в двумерном массиве типа Object, поскольку данные могут быть любого типа, включая String или числовые типы. Метод executeInsert( ) использует RTTI для того, чтобы различать данные типа String (которые должны быть в кавычках) и не строковые данные, которые встраиваются в SQL команды. После распечатки этих не строковых данных на консоль, используется executeUpdate( ) для отправки в базу данных.

Конструктор для LoadDB создает соединение, а load( ) перебирает данные и вызывает метод executeInsert( ) для каждой записи. dispose( ) закрывает выражение и соединение; чтобы гарантировать этот вызов, он помещен внутри выражения finally.

Как только база данных будет загружена, выражения в executeQuery( ) производят простое результирующее множество. Так как запрос комбинирует несколько таблиц, он является примером объединения.

Есть гораздо более обширная информация, доступная в электронной документации, которая поставляется, как часть дистрибутива Java от Sun. Кроме того, вы можете найти больше информации в книге "JDBC Database Access with Java" (Hamilton, Cattel, and Fisher, Addison-Wesley, 1997). Другие книги по JDBC появляются регулярно.

Заключение

Упражнения

  1. Измените CIDCreateTables.java так, чтобы он читал SQL строки из текстового файла вместо CIDSQL.
  2. Сконфигурируйте вашу систему так, чтобы вы могли успешно совершить вызов CIDCreateTables.java и LoadDB.java.
  3. (Более сложное) Возмите программу VLookup.java и измените ее так, чтобы когда вы кликаете на результирующем имени, она автоматически брала это имя и копировала его в буфер обмена (так чтобы вы могли просто вставить его в ваш email). Вам будет необходимо снова просмотреть Главу 13, чтобы вспомнить, как использовать буфер обмена в JFC.