КаталогИндекс раздела


©IBM developerWorks
© А.С.Деревянко (перевод)

Использование JDBC для выборки данных в XML

Nicholas Chase

Содержание

  1. Введение
  2. Доступ к базе данных через JDBC
  3. Выборка информации из базы данных через JDBC
  4. Установка отображения
  5. Создание XML-документа с использованием результатов SQL
  6. Итоги выборки данных при помощи JDBC

Раздел 1. Введение

Нужен ли мне этот учебник?

Этот учебник создан для того, чтобы помочь тем Java-разработчикам, которым нужно выделить информацию из базы данных и поместить ее в XML-документ.

Учебник предполагает, что вы уже знакомы с Java и XML в общем и с Объектной Моделью Документа (DOM), в частности. Вы должны быть знакомы с языком программирования Java, но предварительные знания о соединении с базами данных при помощи JDBC для основных приемов, описанных в этом учебнике, не требуются. Это учебник кратко рассматривает основы SQL. Знания в программировании GUI не являются необходимыми, поскольку ввод-вывод приложения работает через командную строку. Ссылки в Ресурсах включают в себя указания на учебники по основам XML и DOM и на подробные ресурсы по SQL.

О чем этот учебник?

XML так хорошо работает на хранение данных, что неизбежно в какой-то момент кто-то попросит вас вытянуть информацию из базы данных и манипулировать ею через XML. JDBC является независимым от производителя методом для доступа к базам данных из Java. Этот учебник объясняет, как создать экземпляр драйвера JDBC и использовать его для соединения с базой данных для выборки информации. Он также объясняет основы SQL и как создать и использовать результат запроса JDBC.

Целью учебника является выделение данных из база данных и создание документа DOM. Структура документа DOM определяется файлом отображения XML, который демонстрирует один из способов, каким XML-файлы могут использоваться для этой цели.

Инструменты

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

Соглашения, используемые в данном учебнике

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

Об авторе

Nicholas Chase участвовал в разработке Web-сайтов для таких компаний, как Lucent Technologies, Sun Microsystems, Oracle и Tampa Bay Buccaneers. Nick был преподавателем физики в высшей школе, менеджером низшего звена по использованию радиоактивных отходов, редактором онлайнового журнала научной фантастики, инженером по мультимедиа и инструктором по Oracle. В последнее время он - руководитель технического отдела фирмы Site Dynamics Interactive Communications в Clearwater, Florida, USA и автор четырех книг по Web-разработке, включая Java and XML From Scratch Он любит слышать отзывы читателей, с ним можно связаться по адресу nicholas@nicholaschase.com.

Раздел 2. Доступ к базе данных через JDBC

Что такое JDBC?

Если вы уже прошли через учебник "Использование JDBC для вставки XML в базу данных", вы можете перейти дальше.

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

JDBC похоЖе на ODBC (Open Database Connectivity), которое предоставляет стандартный API, являющийся посредником при доступе к базе данных. Как видно из рисунка, могут использоваться стандартные команды JDBC, и драйвер JDBC переводит их в естественный API для базы данных.

Нигде в данном учебнике не упоминается конкретная база данных, потому что выбор ее не имеет значения. Все команды являются стандартными командами JDBC, которые переводятся драйвером в естественные команды для той базы данных, которую вы выбрали. Благодаря этой независимости API, вы можете легко использовать другую базу данных, не изменяя ничего в своем приложении, кроме имени драйвера и, возможно, URL соединения, используемого при Создании соединения.

Информацию о выгрузке соответствующих драйверов JDBC для вашей базы данных см. в Ресурсах. Доступно более 150 драйверов JDBC для виртуально любой базы данных.

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

Мост JBDC-ODBC

Нет необходимости иметь специфический драйвер JDBC для базы данных, если для нее доступен драйвер ODBC; вы можете использовать вместо этого мост JDBC-ODBC. Приложение вызывает мост, который переводит команды в ODBC, а драйвер ODBC переводит их в естественный API.

Мост JDBC-ODBC не является рекомендуемым способом обращения к базе данных по разным причинам, включая конфигурацию и производительность, - команды должны проходить через два API, и на каждом клиенте должен быть установлен и сконфигурирован драйвер ODBC - но он применим для тестирования и разработки, если нет чистого драйвера JDBC.

Если вы выбрали использование моста, создайте System Data Source Name (DSN) в системе Windows, выбрав Start>Settings>Control>ODBC Data Sources. Запишите имя DSN, так как вы будете на него ссылаться при Создании соединения.

Установка базы данных и драйвера

Прежде всего, убедитесь, что база данных, которую вы выбрали, установлена и работает, и что драйвер доступен. Драйверы JDBC могут быть выгружены из http://industry.java.sun.com/products/jdbc/drivers.

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

create table products (
  product_id   numeric primary key,
  product_name varchar(50),
  base_price   numeric,
  size         numeric,
  unit         varchar(10),
  lower        numeric,
  upper        numeric,
  unit_price   numeric )

Замечание: В нормальной обстановке эти данные должны быть разнесены, как минимум, по двум таблицам; для простоты в нашем примере есть только одна.

Процесс обращения к базе данных

Взаимодействие с базой данных при помощи Java обычно состоит из следующих шагов:

  1. Загрузка драйвера базы данных. Это может быть драйвер JDBC или мост JDBC-ODBC.
  2. Создание Connection с базой данных.
  3. Создание объекта Statement. Этот объект реально выполняет SQL или хранимые процедуры.
  4. Создание ResultSet и заполнение его результатами выполнения запроса (если целью является получение или непосредственное изменение данных).
  5. Получение или изменение данных из ResultSet.

Пакет java.sql содержит JDBC 2.0 Core API для доступа к базе данных, как часть поставки JavaTM 2 SDK, Standard Edition. Пакет javax.sql, который поставляется, как часть Java 2 SDK, Enterprise Edition, содержит JDBC 2.0 Optional Package API.

В данном учебнике используются только классы из JDBC 2.0 Core API.

Создание экземпляра драйвера

Для обращения к базе данных сначала загрузите драйвер JDBC. В любой момент могут быть доступны много разных драйверов; имеется DriverManager, который знает о каждом драйвере и решает, какой из них будет использоваться для создания соединения. Первый, способный успешно создать соединение, будет использоваться приложением. Есть два способа, которыми DriverManager может узнать о существующих драйверах.

Первый способ - непосредственно загрузить его, используя Class.forName(), как показано в данном примере. Когда класс драйвера загружен, он регистрируется при помощи DriverManager, как показано здесь:

public class Pricing extends Object {
  public static void main (String args[]){
    //Для моста JDBC-ODBC используйте
    //driverName = "sun.jdbc.odbc.JdbcOdbcDriver"
    String driverName = "JData2_0.sql.$Driver";
    try {
      Class.forName(driverName);
    } catch (ClassNotFoundException e) {
      System.out.println("Error creating class: "+e.getMessage());
    }
  }
}

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

Второй способ, которым DriverManager может найти драйвер, является цикл через все драйверы, имеющиеся в системном свойстве sql.drivers. Свойство sql.drivers является разделенным запятыми списком потенциальных драйверов. Этот список всегда проверяется прежде, чем класс загружается динамически, так что, если вы хотите использовать определенный драйвер, убедитесь, что свойство sql.drivers либо пустое, либо начинается с вашего драйвера.

Создание соединения

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

jdbc:<sub-protocol>:имя базы данных

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

Одной из причин введения под-протокола является соединение через ODBC. Если обращение к базе данных примера, с DSN pricing, выполняется через ODBC, URL может быть:

odbc:pricing

Это означает, что для соединения через JDBC URL должен быть:

jdbc:odbc:pricing

Реальное соединение создается ниже:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException; 
public class Pricing extends Object {
  public static void main (String args[]){
    //Для моста JDBC-ODBC используйте
    //driverName = "sun.jdbc.odbc.JdbcOdbcDriver"
    //и
    //connectURL = "jdbc:odbc:pricing"
    String driverName = "JData2_0.sql.$Driver";
    String connectURL = "jdbc:JDataConnect://127.0.0.1/pricing";
    Connection db = null; 
    try {
      Class.forName(driverName);
      db = DriverManager.getConnection(connectURL);
    } catch (ClassNotFoundException e) {
      System.out.println("Error creating class: "+e.getMessage());
    } catch (SQLException e) {
    System.out.println("Error creating connection: "+e.getMessage());
    }
  }
}

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

Закрытие соединения

Поскольку Statement и Connection являются объектами, Java будет включать их в сборку мусора, освобождая ресурсы базы данных, которые они занимают. Это может ввести вас в заблуждение и заставить думать, что вы не должны заботиться о закрытии этих объектов, но это не так.

Вполне возможно, что само Java-приложение имеет достаточно доступных ресурсов, что означает редкое выполнение сборки мусора. Возможно также, что хотя Java-приложение имеет достаточно ресурсов, ресурсы базы данных ограничены. Много ресурсов базы данных Могут быть забраны объектами Java, которые легко могут быть закрыты приложением.

Важно быть уверенным, что эти объекты закрыты, независимо от того, были или не были ошибки, так что добавим блок finally к уже имеющемуся блоку try-catch:

    ...
    Connection db = null;
    try {
      Class.forName(driverName);
      db = DriverManager.getConnection(connectURL);
    } catch (ClassNotFoundException e) {
      System.out.println("Error creating class: "+e.getMessage());
    } catch (SQLException e) {
      System.out.println("Error creating connection: "+e.getMessage());
    } finally {
      System.out.println("Closing connections...");
      try {
        db.close();
      } catch (SQLException e) {
        System.out.println("Can't close connection.");
      }
    }
  }
}

Смешно, но сам метод close()может генерировать SQLException, так что для него нужен свой блок try-catch.

Раздел 3. Выборка информации из базы данных при помощи JDBC

Анатомия оператора SELECT

Если вы имеете соединение с базой данных, то приложение может начать процесс выборки данных. В базах данных SQL данные (обычно) выбираются при помощи оператора SELECT. Оператор SELECT имеет несколько основных частей. Например:

SELECT product_id, product_name 
  FROM products 
  WHERE product_id < 1000 
  ORDER BY product_id

Этот оператор может быть разобран так:

Оператор SELECT может также группировать строки и возвращать агрегатные значения. Например, следующий оператор возвращает общий доход от всех товаров, которые обеспечили более $1000 дохода 15 сентября 2001г.:

SELECT product_id, sum(quantity*price) as revenue 
  FROM orders 
  WHERE order_date = '9/15/01' 
  GROUP BY product_id 
  HAVING revenue > 1000

Оператор SELECT выполняется объектом Statement.

Создание оператора

Создание объекта Statement очевидно; просто используйте метод createStatement() класса Connection, при этом убедитесь, что вылавливается возможное SQLException. Большинство классов, используемых в этом разделе, генерирует SQLException, так что в остальная часть выполняется в блоке try-catch.

...
import java.sql.Statement; 
public class Pricing extends Object {
    ...
    } catch (SQLException e) {
      System.out.println("Error creating connection: "+e.getMessage());
    }
    // Создание объекта Statement, используемого для выполнения SQL-оператора
    Statement statement = null;
    try {
      statement = db.createStatement();
    } catch (SQLException e) {
      System.out.println("SQL Error: "+e.getMessage());
    } finally {
      System.out.println("Closing connections...");
      try {
        db.close();
      } catch (SQLException e) {
        System.out.println("Can't close connection.");
      }
    }
  }
}

Это похоже на создание PreparedStatements и на Вызов хранимых процедур при помощи CallableStatement.

Выполнение оператора

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

...
import java.sql.ResultSet; 
public class Pricing extends Object {
  ...
  // Создание объекта Statement, используемого для выполнения SQL-оператора
  Statement statement = null;
  //Создание объекта ResultSet, который хранит выбранные данные
  ResultSet resultset = null; 
  try {
    statement = db.createStatement();
    //Выполнение запроса для заполнения ResultSet
    resultset = statement.executeQuery("SELECT * FROM products");
  } catch (SQLException e) {
    System.out.println("SQL Error: "+e.getMessage());
  } finally {
  ...

Если есть проблема с выполнением SQL-оператор, например, обращение к несуществующей таблице, приложение генерирует SQLException. Иначе приложение продолжает выполняться, независимо от того, были или нет найдены какие-либо данные.

Проверка данных

Когда ResultSet создается, он имеет "указатель", который ссылается на относительную позицию в наборе данных.

Непосредственно после возврата ResultSet (даже, если таблица пустая), этот указатель находится "перед" первой строкой. Чтобы получить первую строку данных, приложение вызывает метод next(). Метод возвращает значение типа boolean, показывающее, существует ли строка на новой позиции. Если не найдены данные, next() возвращает false.

  ...
    resultset = statement.executeQuery("select * from products");
    //Проверка наличия данных помещением курсора на первую запись if there 
    if (resultset.next()) {
      System.out.println("Data exists.");
    } else {
      System.out.println("No data exists.");
    }
  } catch (SQLException e) {
  ...

Похожий прием мы используем позже при Переборе данных.

Типы данных

Если вы установили, что данные имеются, вы можете выбрать их, используя методы getXXX()класса ResultSet. На самом деле нет метода с именем getXXX(), но есть целая группа методов, предназначенных для этого, потому что ResultSet может возвращать данные многих типов. Например, ResultSet может возвращать в приложение число (при помощи таких методов, как getDouble(),getInt() и getFloat()); String (при помощи getString()) или массив (при помощи getArray()). Большие единицы данных могут выбираться как InputStream (при помощи getBinaryStream()) или Reader (при помощи getCharacterStream()).

Важно помнить, что Java может точно соответствовать тирам данных, выбираемым из большинства баз данных, и что только некоторые типы данных могут быть выбраны как нечто отличное от того, что они есть. Например, getString() может выбирать данные, которые начинают свою жизнь как любой тип базы данных, но значения даты и времени могут выбираться только при помощи getDate() (или getTime(), getObject() или getString()).

Выборка данных по имени

Вы можете выбирать сами данные двумя способами: по имени и по индексу. Для выборки по имени используйте один из ранее обсуждавшихся методов getXXX(). Они принимают аргументы в форма int или String. В Выборке данных по индексу вы узнаете подробности, пока же приложение будет выбирать поля по имени столбца.

В конечном счете все эти данные предназначены для включения в XML-файл, где все они будут текстом, так что используйте для всех значений getString():

  ...
  if (resultset.next()) {
    //Вывод данных обращением к столбцам ResultSet по именам
    System.out.print(resultset.getString("product_id")+"|");
    System.out.print(resultset.getString("product_name")+" | ");
    System.out.print(resultset.getString("base_price")+" | ");
    System.out.print(resultset.getString("size")+" | ");
    System.out.print(resultset.getString("unit")+" | ");
    System.out.print(resultset.getString("lower")+" | ");
    System.out.print(resultset.getString("upper")+" | ");
    System.out.println(resultset.getString("unit_price"));
  } else {
    System.out.println("No data exists.");
  }
...

Компиляция и выполнение этого приложения покажет первую строку данных - ценовую информацию для онлайнового гастронома.

Перебор данных

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

Приложение осуществляет это, продвижением на следующую строку и выводом данных, а затем продвижением опять на следующую строку. Когда указатель продвигается за последнюю строку, next() возвращает false, и цикл заканчивается.

  ...
  resultset = statement.executeQuery("select * from products");
  //Цикл, пока существует слебующая запись
  while (resultset.next()) {
    //Вывод данных обращением к столбцам ResultSet по именам
    System.out.print(resultset.getString("product_id")+" | ");
    System.out.print(resultset.getString("product_name")+" | ");
    System.out.print(resultset.getString("base_price")+" | ");
    System.out.print(resultset.getString("size")+" | ");
    System.out.print(resultset.getString("unit")+" | ");
    System.out.print(resultset.getString("lower")+" | ");
    System.out.print(resultset.getString("upper")+" | ");
    System.out.println(resultset.getString("unit_price"));
    }
  } catch (SQLException e) {
  ...

Выполнение этого приложения вернет все строки таблицы.

Выборка данных по индексу

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

Первый столбец имеет индекс 1, второй - 2 и так далее. Зная, что есть восемь столбцов, мы перепишем предыдущий пример так:

...
  while (resultset.next()) {
    for (int i=1; i <= 8; i++) {
      //Вывод каждого столбца по его индексу
      System.out.print(resultset.getString(i)+" | ");
    }
    //Перевод строки в конце строки
    System.out.println("");
  }
  ...

Использование этого приема делает процесс выборки данных обобщенным и переносимым в другие похожие задачи и/или приложения.

Обобщенная выборка

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

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

ResultSetMetaData может использоваться для вывода всей информации о записи даже, если вся информация, которую он имеет - это сам ResultSet.

...
import java.sql.ResultSetMetaData; 
public class Pricing extends Object {
  ...
  Statement statement = null;
  ResultSet resultset = null;
  //Создание объекта ResultSetMetaData, который будет хранить
  //информацию о ResultSet
  ResultSetMetaData resultmetadata = null; 
  try {
    statement = db.createStatement();
    resultset = statement.executeQuery("select * from products");
    //Получение информации о ResultSet 
    resultmetadata = resultset.getMetaData();
    //Определение числа столбцов в ResultSet
    int numCols = resultmetadata.getColumnCount();
    while (resultset.next()) {
      for (int i=1; i <= numCols; i++) {
        //Для каждого индекса столбца определение имени столбца
        String colName = resultmetadata.getColumnName(i); 
        //Получение значения в столбце
        String colVal = resultset.getString(i); 
        //Вывод имени и значения
        System.out.println(colName+"="+colVal); 
      }
      //Перевод строки в конце строки
      System.out.println(" ");
    }
  ...

Выполнение приложения теперь даст нам не только данные, но также и имена столбцов, которые вам нужны, чтобы создать XML-документ для данных:

Опции ResultSet

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

Свойства устанавливаются на Statement, который в конечном счете создает ResultSet:

Statement statement = db.createStatement(
  ResultSet.TYPE_SCROLL_INSENSITIVE,
  ResultSet.FETCH_FORWARD
  );

Заметьте, что передача многих свойств в метод ResultSet может повлиять на производительность вашего приложения.

PreparedStatements

Одним из способов увеличения производительности вашего приложения является применение PreparedStatement.

PreparedStatement похож на Statement, за исключением того, что он создается с SQL-оператором в качестве параметра. В большинстве случаев SQL-оператор прекомпилируется (или, как минимум, кешируется) базой данных. Например, оператор SELECT может быть прекомпилирован, если приложение структурировано так, как это:

...
import java.sql.PreparedStatement; 
public class Pricing extends Object {
  ...
  //Создание PreparedStatement
  PreparedStatement statement = null;
  ResultSet resultset = null;
  ResultSetMetaData resultmetadata = null;
  try {
    //Компиляция или кеширование SQL в базе данных и подготовка его к выполнению
    statement = db.prepareStatement("select * from products");
    //Выполнение SQL для заполнения ResultSet
    resultset = statement.executeQuery();
    //Получение информации о ResultSet 
    resultmetadata = resultset.getMetaData();
  ...

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

Установка параметров IN

В большинстве случаев, если Statement или PreparedStatement будет использоваться повторно, каждое использование имеет небольшое отличие от предыдущего, такое как различный диапазон записей. В такой ситуации вам нужны параметры IN (входные). Например, чтобы выбирать спектр product_id, определяемый во время выполнения, используйте:

  ...
  statement = db.prepareStatement("select * from products where "+
      "product_id < ? and product_id > ? ");
  statement.setInt(1, 5);
  statement.setInt(2, 10); 
  resultset = statement.executeQuery();
  ...

Методы setXXX() соответствуют методам getXXX(), за исключением того, что они включают в себя номер параметра и значение, в которое устанавливается параметр. Например, setInt(2, 10) заменяет второй ? в операторе на целое число 10, так что оператор выполнятся, как если бы он был написан так:

select * from products where product_id < 5 and product_id > 10

Вы можете использовать тот же прием при вызове хранимых процедур при помощи CallableStatement.

Вызов хранимых процедур при помощи CallableStatement

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

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

Класс CallableStatement расширяет PreparedStatement и позволяет разработчику задать параметры для запроса к базе данных. CallableStatement затем возвращает ResultSet (или ResultSet'ы).

Создание CallableStatement во многом похоже на создание PreparedStatement, за исключение того, что вместо SQL-оператора используется оператор вызова. Оператор вызова затем транслируется драйвером в естественный вызов.

statement = db.prepareCall("{call product_listing }");

Замечание: Одним из различий между CallableStatement и PreparedStatement a является то, что CallableStatement может также иметь параметры OUT (выходные) вдобавок к обычно создаваемому ResultSet.

Обнаружение неопределенных значений

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

Все это хорошо, но это затрудняет построение XML-документов, так как пустые значения могут породить проблемы. Чтобы решить эту проблему, вы можете использовать метод wasNull() для обнаружения того, что определенная часть данных - null, и, возможно, заменить ее на что-то еще. Этот пример показывает замену неопределенных значений на слова "and up".

  ...
  while (resultset.next()) {
    //Вывод каждой строки
    for (int i=1; i <= numCols; i++) {
      //Для каждого столбца получение имени и значения
      String colName = resultmetadata.getColumnName(i);
      String colVal = resultset.getString(i);
      //Определение, не дало ли последнее обращение к столбцу null
      if (resultset.wasNull()) {
        colVal = "and up";
      }
      //Вывод информации
      System.out.println(colName+"="+colVal);
    }
    System.out.println(" ");
  }
  ...

Заметьте, что у wasNull() нет аргумента. Метод работает над последним выбранным из ResultSet столбцом, так что перед ним должен быть вызван getXXX().

Сейчас приложение выбирает соответствующие данные и имена столбцов. Вы готовы начать строить XML-документ.

Раздел 4. Установка отображения

Как работает отображение

Целью данного учебника является показать, как создать XML-документ из данных базы данных, используя файл отображения. Другими словами, файл отображения определяет, какие данные выбираются и как они в конечном счете представляются в XML-файле. Одним из способов сделать это является создание временного XML-документа из данных, как они выбраны из базы данных, а затем преобразование их в новый формат в соответствии с файлом отображения.

Файл отображения определяет, какие данные выделяются, имя и структуру нового файла и какие данные куда записываются.

Структура

Файл отображения содержит несколько порций информации:

Файл отображения

Конечный файл отображения такой:

<?xml version="1.0"?>
  <mapping>
    <data sql="select * from products" />
    <root name="pricingInfo" rowName="product">
      <element name="description">
        <attribute name="product_number">product_id</attribute>
        <content>product_name</content>
      </element>
      <element name="quantity">
        <content>lower</content>
      </element>
      <element name="size">
        <content>size</content>
      </element>
      <element name="sizeUnit">
        <content>unit</content>
      </element>
      <element name="quantityPrice">
        <content>unit_price</content>
      </element>
    </root>
  </mapping>

Раздел 5. Создание XML-документа из результатов SQL

Алгоритм

Процесс создания нового XML-документа следующий:

  1. Разбор файл отображения для получения необходимой информации, включая данные, которые должны быть выбраны.
  2. Выборка исходного запроса. Это позволяет динамически выбирать данные на основании файла отображения.
  3. Сохранение данных в объекте Document. Из этого временный документ будет затем возможно вытянуть данные для создания целевого документа в соответствии с отображением.
  4. Выборка отображения данных, чтобы сделать доступной ее для приложения.
  5. Перебор исходных данных. Каждая строка данных анализируется и отображается на новую структуру.
  6. Выборка отображения элементов. Файл отображения определяет, какие данные вытаскиваются из временного документа и в каком порядке.
  7. Добавление элемента в новый документ. Когда данные выбраны, добавляем их к новому документу под новыми именами.
  8. Добавление атрибутов в новый документ. Наконец, добавляем атрибуты к соответствующим элементам.

Разбор файла отображения

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

...
import org.w3c.dom.Document;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; 
public class Pricing extends Object {
  public static void main (String args[]){
    //Создание объекта Document 
    Document mapDoc = null;
    try {
      //Создание DocumentBuilderFactory
      DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
      // Создание DocumentBuilder
      DocumentBuilder docbuilder = dbfactory.newDocumentBuilder();
      //Разбор файла для создания Document
      mapDoc = docbuilder.parse("mapping.xml");
    } catch (Exception e) {
      System.out.println("Problem creating document: "+e.getMessage());
    }
  //Для моста JDBC-ODBC используйте
  //driverName = "sun.jdbc.odbc.JdbcOdbcDriver"
  //и
  //connectURL = "jdbc:odbc:pricing"
  String driverName = "JData2_0.sql.$Driver";
  String connectURL = "jdbc:JDataConnect://127.0.0.1/pricing";
  Connection db = null;
...

Выборка исходного запроса

Далее выберите исходный запрос, записанный в атрибуте sql элемента data.

...
import org.w3c.dom.Element;
import org.w3c.dom.Node; 
...
    System.out.println("Problem creating document: "+e.getMessage());
  }
  //Выборка корневого элемента
  Element mapRoot = mapDoc.getDocumentElement();
  //Выборка (только) элемента данных и преобразование его в Element
  Node dataNode = mapRoot.getElementsByTagName("data").item(0);
  Element dataElement = (Element)dataNode;
  //Выборка оператора sql 
  String sql = dataElement.getAttribute("sql");
  //Вывод SQL-оператора
  System.out.println(sql); 
  //Для моста JDBC-ODBC используйте
  //driverName = "sun.jdbc.odbc.JdbcOdbcDriver"
  //и
  //connectURL = "jdbc:odbc:pricing"
  String driverName = "JData2_0.sql.$Driver";
  String connectURL = "jdbc:JDataConnect://127.0.0.1/pricing";
  Connection db = null;
...

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

Наконец, преобразуйте Node в Element, так что значение Attribute теперь доступно.

Удалите предыдущие операторы вывода и запустите приложение, чтобы показать на выходе SQL-оператор.

Сохранение данных в объекте Document

Если данные успешно выбраны из базы данных, они записываются во временный Document. Общий метод состоит в создании элемента row для каждой строки данных, в котором каждый столбец представляется как элемент с именем этого столбца, а сами данные как содержимое элемента.

...
  public static void main (String args[]){
    Document mapDoc = null;
    //Определение нового объекта Document 
    Document dataDoc = null; 
    try {
      //Создание DocumentBuilderFactory
      DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
      //Создание DocumentBuilder
      DocumentBuilder docbuilder = dbfactory.newDocumentBuilder();
      //Разбор файла для создания Document
      mapDoc = docbuilder.parse("mapping.xml");
      // Создание нового экземпляра Document 
      dataDoc = docbuilder.newDocument();
    } catch (Exception e) {
      System.out.println("Problem creating document: "+e.getMessage());
    }
...
    ResultSetMetaData resultmetadata = null;
    // Создание нового элемента с именем "data"
    Element dataRoot = dataDoc.createElement("data");
    try {
      statement = db.createStatement();
      resultset = statement.executeQuery("select * from products");
      resultmetadata = resultset.getMetaData();
      int numCols = resultmetadata.getColumnCount();
      while (resultset.next()) {
        //Для каждой строки данных
        // Создание нового элемента с именем "row"
        Element rowEl = dataDoc.createElement("row");
        for (int i=1; i <= numCols; i++) {
          // Для каждого столбца, выборка имени и данных
          String colName = resultmetadata.getColumnName(i);
          String colVal = resultset.getString(i);
          //Если нет данных, добавить "and up"
          if (resultset.wasNull()) {
            colVal = "and up";
          }
          // Создание нового элемента с тем же именем, что и у столбца
          Element dataEl = dataDoc.createElement(colName);
          //Добавление данных к новому элементу
          dataEl.appendChild(dataDoc.createTextNode(colVal));
          // Добавление нового элемента к строке
          rowEl.appendChild(dataEl); 
        }
        //Добавление строки к корневому элементу
        dataRoot.appendChild(rowEl); 
      }
    } catch (SQLException e) {
      System.out.println("SQL Error: "+e.getMessage());
    } finally {
      System.out.println("Closing connections...");
      try {
        db.close();
      } catch (SQLException e) {
        System.out.println("Can't close connection.");
      }
    }
    //Добавление корневого элемента к документу
    dataDoc.appendChild(dataRoot); 
  }
}

Применительно к примеру, приведенному выше, сначала создаем пустой документ вместе с корневым элементом, data. Для каждой строки из базы данных создаем элемент row, а для каждого столбца создаем отдельный элемент и добавляем его к строке. Наконец, добавляем каждый элемент row к корню, а корень - к Document.

Выборка отображения данных

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

    ...
    import org.w3c.dom.NodeList; 
    ...
    dataDoc.appendChild(dataRoot);
    //Выборка корневого элемента (называвемого "root")
    Element newRootInfo = (Element)mapRoot.getElementsByTagName("root").item(0);
    // Выборка информации о корне и строке
    String newRootName = newRootInfo.getAttribute("name");
    String newRowName = newRootInfo.getAttribute("rowName");
    // Выборка информации об элементе, встраиваемом в новый документ
    NodeList newNodesMap = mapRoot.getElementsByTagName("element");
  }
}

Вооруженные этой информацией, вы можете строить новый Document.

Перебор исходных данных

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

...
    NodeList newNodesMap = mapRoot.getElementsByTagName("element");
    //Выборка всех строк в старом документе
    NodeList oldRows = dataRoot.getElementsByTagName("row");
    for (int i=0; i < oldRows.getLength(); i++){
      //Выборка каждой строки
      Element thisRow = (Element)oldRows.item(i);
    }
  ...

Выборка отображения элементов

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

...
  for (int i=0; i < oldRows.getLength(); i++){
    //Выборка каждой строки
    Element thisRow = (Element)oldRows.item(i);
    for (int j=0; j < newNodesMap.getLength(); j++) {
    //Для каждого узла в новом отображении, выборка информации
    //Сначала новая информация...
    Element thisElement = (Element)newNodesMap.item(j);
    String newElementName = thisElement.getAttribute("name");
    //Затем старая информация
    Element oldElement = (Element)thisElement.getElementsByTagName("content").item(0);
    String oldField = oldElement.getFirstChild().getNodeValue();
    }
  }
  ...

Для каждого элемента в newNodesMap приложение выбирает новое имя элемента и старое имя элемента для выборки.

Добавление элементов к новому документу

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

...
  public static void main (String args[]){
  Document mapDoc = null;
  Document dataDoc = null;
  //Создание нового Document
  Document newDoc = null; 
  try {
    // Создание DocumentBuilderFactory
    DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
    // Создание DocumentBuilder
    DocumentBuilder docbuilder = dbfactory.newDocumentBuilder();
    //Разбор файла для создания Document
    mapDoc = docbuilder.parse("mapping.xml");
    //Создание нового объекта Document 
    dataDoc = docbuilder.newDocument();
    // Создание нового объекта Document
    newDoc = docbuilder.newDocument();
  } catch (Exception e) {
    System.out.println("Problem creating document: "+e.getMessage());
  }
...
    //Выборка корневого элемента
    Element newRootInfo = (Element)mapRoot.getElementsByTagName("root").item(0);
    // Выборка корня и информации строки
    String newRootName = newRootInfo.getAttribute("name");
    String newRowName = newRootInfo.getAttribute("rowName");
    // Выборка информации об элементе, встраиваемом в новый документ
    NodeList newNodesMap = mapRoot.getElementsByTagName("element");
    //Создание конечного корневого элемента с именем из файла отображения
    Element newRootElement = newDoc.createElement(newRootName); 
    NodeList oldRows = dataRoot.getElementsByTagName("row");
    for (int i=0; i < oldRows.getLength(); i++){
      //Для каждой из исходных строк
      Element thisRow = (Element)oldRows.item(i);
      //Создание новой строки
      Element newRow = newDoc.createElement(newRowName); 
      for (int j=0; j < newNodesMap.getLength(); j++) {
        //Получение информации отображения для каждого столбца
        Element thisElement = (Element)newNodesMap.item(j);
        String newElementName = thisElement.getAttribute("name");
        Element oldElement = (Element)thisElement.getElementsByTagName("content").item(0);
        String oldField = oldElement.getFirstChild().getNodeValue();
        //Получение исходных значений на основе информации отображения
        Element oldValueElement = (Element)thisRow.getElementsByTagName(oldField).item(0);
        String oldValue = oldValueElement.getFirstChild().getNodeValue();
        //Создание нового элемента
        Element newElement = newDoc.createElement(newElementName);
        newElement.appendChild(newDoc.createTextNode(oldValue));
        //Добавление нового элемента к новой строке
        newRow.appendChild(newElement); 
      }
      //Добавление новой строки к корню
      newRootElement.appendChild(newRow); 
    }
    //Добавление нового корня к документу
    newDoc.appendChild(newRootElement); 
  }
}

Сначала создаем новый Document, затем создаем новый корневой элемент, который принимает имя из информации отображения. Для каждой строки временного Document создаем элемент для новой строки, используя newRowName, заданное в отображении.

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

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

Наконец, добавляем новую строку к корню, а корень - к Document. Осталось только добавить любые атрибуты.

Добавление атрибутов к новому документу

Новый Document почти закончен. Вы добавили новые элементы, но не атрибуты, которые могут быть заданы. Добавление их похоже на добавление самих новых элементов. Атрибуты не обязательно есть, но для элемента может быть более одного атрибута, это должно быть учтено в коде. Самый простой способ осуществить это - выбрать элементы attribute в NodeList, а затем перебрать список и рассмотреть каждый элемент. Для каждого атрибута определяем имя поля из исходного Document и имя в новом Document.

...
    Element newElement = newDoc.createElement(newElementName);
    newElement.appendChild(newDoc.createTextNode(oldValue));
    //Выборка списка элементов
    NodeList newAttributes = thisElement.getElementsByTagName("attribute");
    for (int k=0; k < newAttributes.getLength(); k++) {
      //Для каждого нового атрибута
      //Получение информации отображения
      Element thisAttribute = (Element)newAttributes.item(k);
      String oldAttributeField = thisAttribute.getFirstChild().getNodeValue();
      String newAttributeName = thisAttribute.getAttribute("name");
      //Получение исходного значения
      oldValueElement = (Element)thisRow.getElementsByTagName(oldAttributeField).item(0);
      String oldAttributeValue = oldValueElement.getFirstChild().getNodeValue();
      //Создание нового атрибута
      newElement.setAttribute(newAttributeName, oldAttributeValue);
    }
    //Добавление элемента к новой строке
    newRow.appendChild(newElement);
  }
  //Добавление новой строки к корню
  newRootElement.appendChild(newRow);
...

Окончательный документ

В конце этого процесса newDoc содержит старую информацию в новом формате. Теперь он может быть использован другим приложением или преобразован при помощи XSLT или другого средства.

<pricingInfo>
  <product>
    <description product_number="1">Filet Mignon</description>
    <quantity>1</quantity>
    <size>1</size>
    <sizeUnit>item</sizeUnit>
    <quantityPrice>40</quantityPrice>
  </product>
  <product>
    <description product_number="2">Filet Mignon</description>
    <quantity>11</quantity>
    <size>1</size>
    <sizeUnit>item</sizeUnit>
    <quantityPrice>30</quantityPrice>
  </product>
  <product>
    <description product_number="3">Filet Mignon</description>
    <quantity>101</quantity>
    <size>1</size>
    <sizeUnit>item</sizeUnit>
    <quantityPrice>20</quantityPrice>
  </product>
  <product>
    <description product_number="4">Prime Rib</description>
    <quantity>1</quantity>
    <size>1</size>
    <sizeUnit>lb</sizeUnit>
    <quantityPrice>20</quantityPrice>
  </product>
  <product>
    <description product_number="5">Prime Rib</description>
    <quantity>101</quantity>
    <size>1</size>
    <sizeUnit>lb</sizeUnit>
    <quantityPrice>15</quantityPrice>
  </product>
...

Раздел 6. Итоги выборки данных при помощи JDBC

Резюме

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

Структура целевого XML-файла определяется XML файл отображения. В учебнике также рассматриваются подробности чтения файла отображения и использования его для преобразования временного XML-файл в нужную структуру.

Ресурсы

И JDBC, и XML являются мощными инструментами, каждый в своем роде. Места, где можно найти дополнительную информацию, такие:

Информация по XML

Информация по JDBC

Что можно выгрузить


КаталогИндекс раздела


КаталогИндекс раздела