Более изощренный пример
Более интересный пример [73] задействует множественную базу данных, расположенную на сервере. Здесь в качестве базы данных используется хранилище для ведения журнала обращений, чтобы позволить людям регестрировать события, поэтому она называется Community Interests Database (CID). Этот пример будет обеспечивать только просмотр базы данных и ее реализации, и не предназначен быть полным руководством по разработке баз данных. Есть множество книг, семинаров и пакетов программ, которые помогут вам при проектировании и разработке базы данных.
Кроме того, этот пример предполагает, что проведена предварительная установка SQL базы данных на сервере (хотя это может быть запущено и на локальной машине), а так же опрос и обнаружение подходящего JDBC драйвера для базы данных. Существуют несколько бесплатных SQL баз данных, и некоторые из них автоматически устанавливаются с различными версиями Linux. Вы сами отвечаете за выбор базы данных и поиск JDBC драйвера. Приведенный здесь пример основывается на SQL базе данных системы, называемой “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 = ""; } ///:~
В этом примере нет пароля защиты базы данных, поэтому имя пользователя и пароль представлены пустыми строками.
База данных состоит из множества таблиц, структура которых показана ниже:
“Members” содержит информацию о пользователе, “Events” и “Locations” содержат информацию о подключении и откуда оно было сделано, а “Evtmems” объединяет события и пользователей, которые хотят знать о событиях. Можно видеть, что данные в одной таблице являются ключами в другой таблице.
Следующий класс содержит 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 драйвера, создания соединения с базой данных и создания таблиц, структура которых показана на диаграмме. Для соединения с базой данных вы вызываете статический (static) метод DriverManager.getConnection( ), передавая в него URL базы данных, имя пользователя и пароль для доступа к базе данных. Назад вы получаете объект Connection, который вы можете использовать для опроса и манипуляций с базой данных. Как только соединение создано, вы можете просто поместить SQL в базу данных, в этом случае проходя по массиву CIDSQL. Однако при первом запуске этой программы команда “drop table” завершиться неудачей, что станет причиной исключения, которое будет поймано, объявлено и проигнорировано. Необходимость команды “drop table” легко понять из экспериментов: вы можете измнить SQL, который определяет таблицы, а затем вернуться в программу. При этом возникнет необходимость заменить старые таблицы новыми.
В этом примере есть смысл позволить исключениям отображаться на консоли:
//: c15:jdbc:CIDCreateTables.java
// Создание таблиц базы данных для
// community interests database.
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(); } } ///:~
Обратите внимание, что все изменения в базе данных могут управляться путем изменения строк (String) в таблице CIDSQL, при этом CIDCreateTables не меняется.
executeUpdate( ) обычно возвращает число строк, которые были получены при воздействии SQL инструкции. executeUpdate( ) чаще всего используется для выполнения таких инструкций, как INSERT, UPDATE или DELETE, чтобы изменить одну или более строк. Для таких инструкций, как CREATE TABLE, DROP TABLE и CREATE INDEX, executeUpdate( ) всегда возвращает ноль.
Для проверки базы данных она загружается некоторыми простыми данными. Это требует нескольких INSERT'ов, за которыми следует SELECT для получения результирующего множества. Чтобы облегчить проверку добавления и изменения данных, тестовые данные представлены в виде двумерного массива типа Object, а метод executeInsert( ) затем может использовать информацию из одной строки для создания соответствующей SQL команды.
//: c15:jdbc:LoadDB.java
// Loads and tests the database.
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" }, // Сопоставление людей и событий
{ "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) }, }; // Use the default data set:
public TestSet() {} // Use a different data set:
public TestSet(Object[][] dat) { data = dat; } }
public class LoadDB { Statement statement; Connection connection; TestSet tset; public LoadDB(TestSet t) throws SQLException { tset = t; try { // Load the driver (registers itself)
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 cleanup() 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.cleanup(); } } } ///:~
Класс TestSet содержит множество данных по умолчанию, которое производится, если вы используете конструктор по умолчанию. Однако вы можете создать объект TestSet, используя альтернативный набор данных со вторым конструкторомo. Набор данных хранится в двумерном массиве типа Object, поскольку он может быть любого типа, включая String или числовые типы. Метод executeInsert( ) использует RTTI того, чтобы различать данные типа String (которые должны быть в кавычках) и данные не типа String, так как SQL команда строится из данных. После печати этой команды на консоль используется executeUpdate( ) для отсылки ее в базу данных.
Конструктор для LoadDB создает соединение и пошагово с помощью load( ) проходит по данным и вызывает executeInsert( ) для каждой записи. cleanup( ) закрывает инструкцию и соединение. Чтобы гарантировать этот вызов, он помещен в предложение finally.
Как только база данных будет загружена, инструкция executeQuery( ) производит простое результирующее множество. Так как запрос комбинирует несколько таблиц, он является примером объединения.
Более подробно о JDBC можно узнать в электронной документации, которая распространяется как часть пакета Java от Sun. Кроме того, вы можете найти дополнительную информацию в книге JDBC Database Access with Java (Hamilton, Cattel, and Fisher, Addison-Wesley, 1997). Другие книги, посвященные JDBC, появляются регулярно.