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


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

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

Содержание

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

 

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

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

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

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

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

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

Инструменты

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

Вы можете найти список из более, чем 150 драйверов JDBC на http://industry.java.sun.com/products/jdbc/drivers. (Если у вас есть драйвер ODBC, вы можете пропустить этот шаг и использовать мост JBDC-ODBC.) Этот учебник использует JDataConnect, доступный на http://www.j-netdirect.com/Downloads.htm.

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

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

Об авторе

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 ", вы можете перейти дальше к Файл orders.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.

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

create table orders (
  orderid numeric
  primary key,
  userid varchar(50),
  productid varchar(10),
  quantity numeric,
  unitprice numeric )
create table productOrders (
  productid varchar(10),
  quantity numeric,
  revenue 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 SaveOrders 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 в документации для вашего драйвера JDBC.

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

odbc:orders

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

jdbc:odbc:orders

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

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class SaveOrders extends Object {
  public static void main (String args[]){
    //Для моста JDBC-ODBC используйте
    //driverName = "sun.jdbc.odbc.JdbcOdbcDriver"
    //и
    //connectURL = "jdbc:odbc:orders"
    String driverName = "JData2_0.sql.$Driver";
    String connectURL = "jdbc:JDataConnect://127.0.0.1/orders";
    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. Чтение XML-файла

Файл orders.xml

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

<?xml version="1.0" encoding="UTF-8"?>
<orders>
  <order orderid="1">
    <customerid>12341</customerid>
    <status>pending</status>
    <product productid="SA15">
      <name>Silver Show Saddle, 16 inch</name>
      <price>825.00</price>
      <qty>1</qty>
    </product>
    <product productid="C49">
      <name>Premium Cinch</name>
      <price>49.00</price>
      <qty>1</qty>
    </product>
  </order>
  <order orderid="2">
    <customerid>251222</customerid>
    <status>pending</status>
    <product productid="WB78">
      <name>Winter Blanket (78 inch)</name>
      <price>20</price>
      <qty>10</qty>
    </product>
    <product productid="C49">
      <name>Premium Cinch</name>
      <price>49.00</price>
      <qty>5</qty>
    </product>
  </order>
</orders>

Разбор файла

Чтение XML-данных начинается с разбора файла и создания объекта Document:

...
import org.w3c.dom.Document;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; 
public class SaveOrders extends Object {
  public static void main (String args[]){
    //Determine the file location
    String ordersFile = "orders.xml";
    //Создание XML-документа
    Document ordersDoc = null;
    try {
      //Создание экземпляра парсера и разбор файла
      DocumentBuilderFactory docbuilderfactory =   
            DocumentBuilderFactory.newInstance();
      DocumentBuilder docbuilder = 
            docbuilderfactory.newDocumentBuilder();
      ordersDoc = docbuilder.parse(ordersFile);
    } catch (Exception e) {
      System.out.println("Cannot read the orders file: "+e.getMessage());
    }
    Connection db = null;
...

Этот пример показывает создание объекта Document для XML-документа при помощи Java API for XML Processing (JAXP). Сначала создайте DocumentBuilderFactory, затем используйте его для создания DocumentBuilder. Этот DocumentBuilder и является парсером, который берет файл orders.xml и читает каждый элемент для создания объекта Document.

После создания Document, выполните перебор данных в цикле.

Перебор элементов

Прежде, чем вы сможете построить приложение, которое читает XML-файл на основе файла отображения, вам нужно сначала прочитать XML без файла отображения.

В данном примере элемент order содержит каждый заказ, а элемент product содержит каждую составляющую в заказе. Чтобы обратиться к этим данным, конструируется цикл:

...
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
import org.w3c.dom.Element; 
public class SaveOrders extends Object {
  ...
    Connection db = null;
    try {
      Class.forName(driver);
      db = DriverManager.getConnection(connectURL);
      //Получить корневой элемент и все элементы заказов
      Element ordersRoot = ordersDoc.getDocumentElement();
      NodeList orders = ordersRoot.getElementsByTagName("order");
      for (int i = 0; i < orders.getLength(); i++) {
        //Для каждого заказа получить элемент order 
        Element thisOrder = (Element)orders.item(i);
        //Цикл по товарам в заказе
        NodeList products = thisOrder.getElementsByTagName("product");
        for (int j=0; j < products.getLength(); j++) {
        }
      }
    } catch (ClassNotFoundException e) {
    System.out.println("Error creating class: "+e.getMessage());
...

Чтобы выбрать информацию из Document, вам нужно выбрать корневой элемент, который содержит все данные. Если вы имеете этот элемент, приложение может выбирать данные по имени элемента, используя getElementsByTagName(). Этот метод возвращает NodeList, который может быть использован для обращения к каждому элементу.

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

Далее выведите данные.

Выборка данных

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

...
      Element ordersRoot = ordersDoc.getDocumentElement();
      NodeList orders = ordersRoot.getElementsByTagName("order");
      for (int i = 0; i < orders.getLength(); i++) {
      //Для каждого заказа получить элемент order 
          Element thisOrder = (Element)orders.item(i);
          //Получение информации заказа
          String thisOrderid = thisOrder.getAttribute("orderid");
          System.out.println("Order: " + thisOrderid);
          //Получение информации о покупателе
          String thisCustomerid = thisOrder.getElementsByTagName("customerid")
                  .item(0)
                  .getFirstChild().getNodeValue();
          System.out.println("Customer: " + thisCustomerid); 
          //Цикл по товарам в заказе
          NodeList products = thisOrder.getElementsByTagName("product");
          for (int j=0; j < products.getLength(); j++) {
              //Выборка каждого товара
              Element thisProduct = (Element)products.item(j);
              // Получение информации о товаре для 
              // атрибутов и дочерних элементов
              String thisProductid = thisProduct.getAttribute("productid");
              System.out.println(" Product: " + thisProductid);
              String priceStr = thisProduct.getElementsByTagName("price")
                      .item(0)
                      .getFirstChild().getNodeValue();
              System.out.println(" Price: " + priceStr);
              String qtyStr = thisProduct.getElementsByTagName("qty")
                      .item(0)
                      .getFirstChild().getNodeValue();
              System.out.println(" Quantity: " + qtyStr);
              System.out.println();
          }
      }
  } catch (ClassNotFoundException e) {
    System.out.println("Error creating class: "+e.getMessage());
...

Сначала выберите атрибут orderid из элемента order. Это значение просто возвращается методом getAttribute().

Выборка значений дочерних элементов чуть более сложна. Сначала выберите NodeList всех дочерних элементов с желаемыми именами, такими как customerid и productid. Таким образом вы обходите все потенциальные текстовые узлы. Но поскольку есть только один нужный для нас потомок, выбирайте item(0). Это обеспечивает нужные элементы. Чтобы получить действительное значение, нужно значение текстового узла первого потомка элемента.

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

 

Раздел 4. Изменение базы данных при помощи SQL

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

Наиболее очевидным способом добавить любые данные в базу данных является применение оператора SQL. Наиболее общий объект, используемый для выполнения SQL-оператора на базе данных - Statement.

Объект Statement создается Connection:

...
import java.sql.Statement;
public class SaveOrders extends Object {
  ...  
    Connection db = null;
    try {
      Class.forName(className);
      db = DriverManager.getConnection(connectURL);
      //Создание объекта Statement 
      Statement statement = db.createStatement();
      Element ordersRoot = ordersDoc.getDocumentElement();
...

Statement выполняет SQL-оператор INSERT.

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

Вставка данных при помощи SQL предполагает использование оператора INSERT. Рассмотрим такой пример:

insert into orders (orderid, userid) values (2333, 'nchase')

Вы можете использовать объект Statement, чтобы выполнить этот оператор.

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

В общем случае для выполнения SQL-оператора при помощи объекта Statement используется один из четырех методов:

В данном учебнике будет использоваться executeUpdate():

    ...
        String qtyStr = thisProduct.getElementsByTagName("qty")
            .item(0)
            .getFirstChild()
            .getNodeValue();
        //Создание и выполнение SQL-оператора
        String sqlTxt = "insert into orders "+
        "(orderid, userid, productid, unitprice, quantity) "+
        "values ("+thisOrderid+", "+thisCustomerid+", '"+
        thisProductid+"', "+priceStr+", "+qtyStr+")";
        int results = statement.executeUpdate(sqlTxt); 
      }
    }
  } catch (ClassNotFoundException e) {
...

Создайте оператор INSERT на базе таблицы orders и значений, взятых из файла orders.xml.

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

 

Раздел 5. Использование XML-файла отображения

Структура файла отображения

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

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

Файл отображения может быть весьма сложным, так что в данном учебнике мы сделаем определенные допущения:

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

Файл отображения: информация базы данных

Файл отображения должен различать два разных типа информации: информацию базы данных и информацию XML-файла. Это может быть сделано при помощи двух дочерних элементов корня. Первый имеет дело с информацией базы данных:

<?xml version="1.0"?>
<map>
<!-- Для моста JDBC-ODBC используйте
driver = "sun.jdbc.odbc.JdbcOdbcDriver"
и
url = "jdbc:odbc:orders" -->
  <database url="jdbc:JDataConnect://127.0.0.1/orders"
          driver="JData2_0.sql.$Driver">
    <order_table>orders</order_table>
    <order_orderid>orderid</order_orderid>
    <order_customer>userid</order_customer>
    <order_productid>productid</order_productid>
    <order_quantity>quantity</order_quantity>
    <order_price>unitprice</order_price>
    <products_table>productOrders</products_table>
    <products_productid>productid</products_productid>
    <products_quantity>quantity</products_quantity>
    <products_total>revenue</products_total>
  </database>
...

Элемент database содержит информация о URL базы данных и об используемом драйвере. Это может быть для вас полезным, если вы не уверены в том, какой вид базы данных будет использован для хранения информации. Добавление ее в файл отображения предохраняет вас от необходимости перекомпиляции приложения при изменении баз данных.

Столбцы базы данных и таблицы будут представляться как переменные String, поскольку они будут использоваться только для построения строк SQL. В файле отображения перечислены все переменные и таблицы или столбцы, которым они соответствуют в базе данных. Заметьте, что в существующих таблицах, orders и products есть общие имена столбцов, но файл отображения делает это неважным.

Файл отображения также задает информацию XML.

Файл отображения: информация XML

XML-порция файла содержит несколько больше информации; данные могут быть в элементах или в атрибутах:

...
  <xmlfile url="orders.xml">
    <order>order</order>
    <orderid type="attribute">orderid</orderid>
    <customer type="element">customerid</customer>
    <products>product</products>
    <productid type="attribute">productid</productid>
    <quantity type="element">qty</quantity>
    <unitprice type="element">price</unitprice>
  </xmlfile>
</map>

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

Выборка основное информации файла

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

Данный пример показывает, как выбрать информацию файла заказов:

...
  //Создание элементов для использования их в методах
  static Element databaseEl = null;
  static Element xmlfileEl = null; 
  public static void main (String args[]){
    //Обявление элементов отображения и документа
    String mapFile = "map.xml";
    Document mapDoc = null;
    Element mapRoot = null; 
    Document ordersDoc = null;
    try {
      DocumentBuilderFactory docbuilderfactory =     
            DocumentBuilderFactory.newInstance();
      DocumentBuilder docbuilder = 
            docbuilderfactory.newDocumentBuilder();
      //Разбор файла отображения и получение корневого элемента
      mapDoc = docbuilder.parse(отображениеFile);
      mapRoot = mapDoc.getDocumentElement();
      //Выборка базы данных и элементов и информации XML-файла 
      databaseEl = (Element)mapRoot.getElementsByTagName("database").item(0);
      xmlfileEl = (Element)mapRoot.getElementsByTagName("xmlfile").item(0); 
      System.out.println("Reading orders file...");
      String ordersFile = xmlfileEl.getAttribute("url");
      ordersDoc = docbuilder.parse(ordersFile);
    } catch (Exception e) {
      System.out.println("Cannot read the orders file:"+e.getMessage());
    }
...

Первое, что нужно отметить, что databaseEl, который хранит главный элемент отображения база данных, и xmlfileEl, который хранит главный элемент отображения XML-файла, объявлены как static вне метода main, так что их можно использовать в методах, создаваемых позже.

В предыдущих примерах ordersFile была первой главной переменной, устанавливаемой приложением. Теперь такой переменной является mapFile, которая указывает на действительный файл отображения, где может быть найдено значение ordersFile. Вам также понадобится создать основные XML-структуры, такие как mapRoot, вне блока try-catch, поскольку они тоже понадобятся вам позже.

В блоке try-catch сначала DocumentBuilder разбирает файл отображения, а не файл заказов, и заполняет два главных элемента отображение, databaseEl и xmlfileEl. Вы затем можете использовать xmlfileEl, чтобы определить имя файла заказов и наконец разобрать его.

Вам нужна также информация базы данных.

Выборка информации базы данных

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

...
    } catch (Exception e) {
      System.out.println("Cannot read the orders file:"+e.getMessage());
    }
    String driverName = databaseEl.getAttribute("driver");
    String connectURL = databaseEl.getAttribute("url");
    Connection db = null;
    try {
      Class.forName(driverName);
      db = DriverManager.getConnection(connectURL);
      //Создание объекта Statement 
      Statement statement = db.createStatement();
      Element ordersRoot = ordersDoc.getDocumentElement();
      NodeList orders = ordersRoot.getElementsByTagName("order");
...

Заметьте, что в динамической загрузке класса есть одно преимущество. Если вы меняете драйверы JDBC, вам не нужна перекомпиляция; только измените файл отображения.

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

Использование переменных базы данных

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

...
public class SaveOrders extends Object {
  static Element databaseEl = null;
  static Element xmlfileEl = null;
  private static String getDatabaseInfo (String varname) {
    //Выборока текстового узла заданного элемента
    String tempStr = databaseEl.getElementsByTagName(varname)
            .item(0)
            .getFirstChild().getNodeValue();
    return tempStr;
  }
  public static void main (String args[]){
...
    } catch (Exception e) {
      System.out.println("Cannot read the orders file:"+e.getMessage());
    }
    String driverName = databaseEl.getAttribute("driver");
    String connectURL = databaseEl.getAttribute("url");
    //Выборка имен столбцов
    String order_table = getDatabaseInfo("order_table");
    String order_orderid = getDatabaseInfo("order_orderid");
    String order_customer = getDatabaseInfo("order_customer");
    String order_productid = getDatabaseInfo("order_productid");
    String order_quantity = getDatabaseInfo("order_quantity");
    String order_price = getDatabaseInfo("order_price");
    String products_table = getDatabaseInfo("products_table");
    String products_productid = getDatabaseInfo("products_productid");
    String products_quantity = getDatabaseInfo("products_quantity");
    String products_total = getDatabaseInfo("products_total");
    Connection db = null;
    try {
...

Метод getDatabaseInfo() просто получает дочерний элемент с заданным именем и возвращает значение его дочернего текстового узла. Используйте этот метод для заполнения других переменных базы данных, чтобы они могли быть использованы в приложении.

Чтобы использовать переменные, просто замените жестко закодированные их эквиваленты в SQL-операторе:

...
    String qtyStr = thisProduct.getElementsByTagName("qty")
            .item(0)
            .getFirstChild().getNodeValue();
    //Создание и выполнение SQL-оператора
    String sqlTxt = "insert into "+order_table+
            " ("+order_orderid+", "+order_customer+", "+
            order_productid+", "+order_price+", "+order_quantity+") "+
            "values ("+thisOrderid+", "+thisCustomerid+", '"+
            thisProductid+"', "+priceStr+", "+qtyStr+")";
    int results = statement.executeUpdate(sqlTxt);
...

Здесь обрабатываются имена столбцов. Имена столбцов уже поступили из orders.xml, но они еще не выбраны на основании файла отображения. Это немного сложнее.

Использование основных переменных XML

Получение основных переменных XML-файла отображение достаточно просто благодаря допущениям, сделанным выше. Общая структура документа предопределена, так что все, что вам нужно, чтобы начать, это имена элементов, хранящих заказы и товары. Эта информация имеется в файле отображения как текст в элементах order и product. Это все делает основные отображения легкими в использовании:

...
    try {
      Class.forName(driverName);
      db = DriverManager.getConnection(connectURL);
      Statement statement = db.createStatement();
      //Получение имен элементов product и order 
      String orderElName = xmlfileEl.getElementsByTagName("order").item(0)
            .getFirstChild().getNodeValue();
      String productElName = xmlfileEl.getElementsByTagName("products").item(0)
            .getFirstChild().getNodeValue();
      //Получение корневого элемента и всех элементов order
      Element ordersRoot = ordersDoc.getDocumentElement();
      NodeList orders = ordersRoot.getElementsByTagName(orderElName);
      for (int i = 0; i < orders.getLength(); i++) {
        //Для каждого заказа получить элемент order
        Element thisOrder = (Element)orders.item(i);
        //Получение информации заказа
        String thisOrderid = thisOrder.getAttribute("orderid");
        System.out.println("Order: " + thisOrderid);
        //Получение информации покупателя
        String thisCustomerid = thisOrder.getElementsByTagName("customerid")
              .item(0)
              .getFirstChild().getNodeValue();
        System.out.println("Customer: " + thisCustomerid);
        //Перебор всех товаров для заказа
        NodeList products = thisOrder.getElementsByTagName(productElName);
        for (int j=0; j < products.getLength(); j++) {
          Element thisProduct = (Element)products.item(j);
          String thisProductid = thisProduct.getAttribute("productid");
...

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

Использование элементов отображения

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

Сама логика принимает во внимание тот факт, что данные, которые вы ищете, могут находиться и в элементах, и в атрибутах:

...
public class SaveOrders extends Object {
...
  private static String getDatabaseInfo (String varname) {
    String tempStr = databaseEl.getElementsByTagName(varname).item(0)
          .getFirstChild().getNodeValue();
    return tempStr;
  }
  private static String getXMLInfo (Element start, String varname) {
    //Получение заданного элемента и типа
    Element mapEl = (Element)xmlfileEl.getElementsByTagName(varname).item(0);
    String type = mapEl.getAttribute("type");
    String mappedName = mapEl.getFirstChild().getNodeValue();
    //Определение, возвращается ли атрибут или текстовый узел
    String tempStr = null;
    if (type.equals("attribute")) {
      tempStr = start.getAttribute(mappedName);
    } else if (type.equals("element")) {
      tempStr = start.getElementsByTagName(mappedName)
            .item(0)
            .getFirstChild().getNodeValue();
    }
    return tempStr;
  }
  public static void main (String args[]){
...
    Element ordersRoot = ordersDoc.getDocumentElement();
    NodeList orders = ordersRoot.getElementsByTagName(orderElName);
    for (int i = 0; i < orders.getLength(); i++) {
      Element thisOrder = (Element)orders.item(i);
      //Получение информации заказа и покупателя
      String thisOrderid = getXMLInfo(thisOrder, "orderid");
      String thisCustomerid = getXMLInfo(thisOrder, "customer");
      NodeList products = thisOrder.getElementsByTagName(productElName);
      for (int j=0; j < products.getLength(); j++) {
        //Выборка всех товаров
        Element thisProduct = (Element)products.item(j);
        //Получение информации товара
        String thisProductid = getXMLInfo(thisProduct, "productid");
        String priceStr = getXMLInfo(thisProduct, "unitprice");
        String qtyStr = getXMLInfo(thisProduct, "quantity");
        //Создание и выполнение SQL-оператора
        String sqlTxt = "insert into "+order_table+
              " ("+order_orderid+", "+order_customer+", "+
              order_productid+", "+order_price+", "+order_quantity+") "+
              "values ("+thisOrderid+", "+thisCustomerid+", '"+
              thisProductid+"', "+priceStr+", "+qtyStr+")";
...

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

Теперь вы должны сделать операцию типовой. Используя файл отображения, вы можете изменить имена таблиц и столбцов или даже самой базы данных. Возможно также изменить структуру файла orders.xml. Пока вы модифицируете map.xml, приложение будет по-прежнему работать так, как ожидается.

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

 

Раздел 6. Использование транзакций и пакетных операций

Что такое транзакция?

Теперь, когда приложение собрано, есть несколько относительно простых вещей, которые могут сделать его более надежным.

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

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

Обычно Statement автоматически фиксирует каждый SQL-оператор, но эти установки можно изменить, используя объект Connection. Определенные действия, такие как изменение структуры базы данных или закрытие объекта Statement, также будут фиксировать транзакцию немедленно - даже без выполнения метода commit().

Вы можете использовать транзакции для создания приложения, которое будет включать все INSERT'ы (если в них нет проблем) или ни один из них (если что-то не так).

Группирование вставок в транзакцию

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

...
    Connection db = null;
    //Установка флага ошибки
    boolean dbErrors = false; 
    try {
...
    } catch (ClassNotFoundException e) {
      System.out.println("Error creating class: "+e.getMessage());
    } catch (SQLException e) {
      System.out.println("Error creating connection: "+e.getMessage());
      //Произошла ошибка, устанавливается флаг ошибки
      dbErrors = true; 
    } finally {
      System.out.println("Closing connections...");
      try {
        //Определяется, были ли ошибки и, соответственно
        //фиксируется или откатывается транзакция
        if (dbErrors) {
          db.rollback();
        } else {
          db.commit();
        }
      db.close();
      } catch (SQLException e) {
        System.out.println("Can't close connection.");
      }
...

Чтобы отслеживать, есть ли ошибки, создайте переменную типа boolean вне блока try-catch-finally и установите ее в false. Если все операторы завершились без ошибок, приложение получит блок finally с boolean-переменной, все еще установленной в false. В этом случае транзакция может быть зафиксирована. Если, с другой стороны, произошли ошибки, блок catch установит dbErrors в true, и приложение откатит все операции.

Вы можете также включить и другие операции для принятия решения о фиксации транзакции.

Включение в транзакцию файловых операций

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

Фактически, вы можете установить приложение так, что любая ошибка приведет к откату:

...
    } catch (SQLException e) {
      System.out.println("Error creating connection: "+e.getMessage());
      //Произошла ошибка, устанавливается флаг ошибки
      dbErrors = true;
    } catch (Exception e) {
      //Display the error message and set the error flag for any errors
      System.out.println(e.getMessage());
      dbErrors = true; 
    } finally {
      System.out.println("Closing connections...");
      try {
        //Определяется, были ли ошибки и, соответственно
        //фиксируется или откатывается транзакция
        if (dbErrors) {
          db.rollback();
        } else {
          db.commit();
        }
...

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

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

Проверка поддержки транзакций

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

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

...
import java.sql.DatabaseMetaData; 
public class SaveOrders extends Object {
...
    Connection db = null;
    DatabaseMetaData dbmeta = null; 
    //Установка флага ошибок
    boolean dbErrors = false;
    try {
      Class.forName(driverName);
      db = DriverManager.getConnection(connectURL);
      //Получение метаданных базы данных
      dbmeta = db.getMetaData();
      //Если транзакции поддерживаются, выключить autocommit
      if (dbmeta.supportsTransactions()) {
        db.setAutoCommit(false);
      }
      Statement statement = db.createStatement();
      Element ordersRoot = ordersDoc.getDocumentElement();
      NodeList orders = ordersRoot.getElementsByTagName(orderElName);
...
    } catch (Exception e) {
      System.out.println(e.getMessage());
      dbErrors = true;
    } finally {
      System.out.println("Closing connections...");
      try {
        //Определяется, были ли ошибки и, соответственно,
        //фиксируется или откатывается транзакция
        if (dbmeta.supportsTransactions()) {
          if (dbErrors) {
            db.rollback();
          } else {
            db.commit();
          }
        }
        db.close();
      } catch (SQLException e) {
        System.out.println("Can't close connection.");
      }
...

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

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

Использование пакетных изменений

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

...
    Element ordersRoot = ordersDoc.getDocumentElement();
    NodeList orders = ordersRoot.getElementsByTagName(orderElName);
    for (int i = 0; i < orders.getLength(); i++) {
      Element thisOrder = (Element)orders.item(i);
...
      NodeList products = thisOrder.getElementsByTagName(productElName);
      for (int j=0; j < products.getLength(); j++) {
...
        //Если транзакции поддерживаются, добавить в пакет.
        //Иначе выполнить оператор
        if (dbmeta.supportsBatchUpdates()) {
          statement.addBatch(sqlTxt);
        } else {
          int results = statement.executeUpdate(sqlTxt);
        }
      }
      //Если есть пакет, выполнить его
      if (dbmeta.supportsBatchUpdates()) {
        System.out.println("Executing batch ...");
        statement.executeBatch();
      }
    }
  } catch (ClassNotFoundException e) {
...

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

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

 

Раздел 7. Изменяемые ResultSet'ы

Использование ResultSet

Чтобы собрать воедино информацию заказов, вам нужно интергировать ее с информацией, которая уже существует в базе данных, а не просто создавать и выполнять SQL-операторы. Одним из способов делать это являются изменяемые ResultSet'ы.

ResultSet является объектом, который возвращает запрос к базе данных. Это структура данных, позволяющая вам перебирать ее в цикле, обращаясь к данным по индексу или по имени столбца. Например, вы можете перебирать в цикле содержимое таблицы товаров:

    ResultSet resultset = statement.executeQuery("select * from productorders");
    while (resultset.next()) {
      System.out.print(resultset.getString("productid"));
      System.out.print("|");
      System.out.print(resultset.getString("quantity"));
      System.out.print("|");
      System.out.println(resultset.getString("revenue"));
    }

Ресурсах есть ссылка на углубленный учебник по использованию только-читаемых ResultSet с XML.)

Данный фрагмент кода демонстрирует шаги, необходимые для использования ResultSet. Сначала создайте его заполнением его результатами запроса. Затем перебирайте в цикле строки и выводите результаты. Используйте метод next() для продвижения на следующую запись; "указатель" исходно устанавливается перед первой записью, так что, когда вы вызываете next() в первый раз, он переходит на первую запись (ели она есть). Пока next()находит следующую запись, он будет возвращать true.

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

По-настоящему мощность ResultSet проявляется, когда вы непосредственно изменяете базу данных.

Подготовка Statement для изменяемого ResultSet

Созданный с установками по умолчанию, ResultSet является объектом одноразовым, только-читаемым, читаемым только вперед. Вы получаете одну порцию данных и, если она вам нужна опять, вы должны опять запросить базу данных. Однако не обязательно следовать именно таким путем. Устанавливая параметры объекта Statement, вы можете управлять теми ResultSet'ами, которые он вырабатывает. Например:

...
    Class.forName(driverName);
    db = DriverManager.getConnection(connectURL);
    Statement statement = 
        db.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
            ResultSet.CONCUR_UPDATABLE);
    String orderElName = xmlfileEl.getElementsByTagName("order").item(0)
          .getFirstChild().getNodeValue();
...

Этот Statement теперь будет вырабатывать ResultSet'ы, которые могут быть изменены и которые будут подхватывать изменения, сделанные другими пользователями базы данных. Вы также можете двигаться по этому ResultSet вперед и назад.

Первый параметр задает тип ResultSet. Варианты такие:

Второй параметр устанавливает признак, определяющий, может ли ResultSet быть изменен. Варианты такие:

Замтьте, что если вы только изменили ResultSet, то данные в базе данных еще не обязательно отразили эти изменения.

Изменение ResultSet

Создание и изменение ResultSet многом похоже на создание и изменение ResultSet в предыдущем примере:

...
import java.sql.ResultSet;
public class SaveOrders extends Object {
...
            int results = statement.executeUpdate(sqlTxt);
          }
          //Выборка данного товара из таблицы productOrders 
          String updateSqlTxt = "select * from productOrders where productid = '"+
                thisProductid +"'";
          ResultSet updateRs = statement.executeQuery(updateSqlTxt);
          //Получение нового количества и изменение ResultSet
          int oldQty = updateRs.getInt("quantity");
          updateRs.updateInt("quantity", getSum(oldQty, qtyStr));
          double oldRev = updateRs.getDouble("revenue");
          updateRs.updateInt("revenue", getRev(oldRev, qtyStr, priceStr));
          //Отсылка изменений в базу данных
          updateRs.updateRow();
        }
      }
    } catch (ClassNotFoundException e) {
...

Создайте ResultSet, как обычно, но вместо чтения столбцов при помощи таких методов, как getString и getInt, измените поля ResultSet при помощи таких методов, как updateString и updateInt (в зависимости от типа в базе данных). Первым параметром является имя поля (хотя вы можете использовать также и индекс поля), а вторым - устанавливаемое значение. (getSum() и getRev() просто выполняют преобразования типа и математические вычисления.)

Эти изменения не воздействуют на базу данных, пока не будет вызван метод updateRow(). Они, однако, воздействуют на значения, хранимые в ResultSet. (Вы можете также отменить изменения прежде, чем они будут посланы в базу данных, при помощи cancelRowUpdates().)

Теперь вы можете легко применить информацию отображения.

Отображение изменений

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

...
import java.sql.ResultSet;
public class SaveOrders extends Object {
...
          int results = statement.executeUpdate(sqlTxt);
          //Выборка данного товара из таблицы productOrders
          String updateSqlTxt = "select * from " + products_table
                + " where " + products_productid
                + " = '" + thisProductid + "'";
          ResultSet updateRs = statement.executeQuery(updateSqlTxt);
          //Получение нового количества и изменение ResultSet
          int oldQty = updateRs.getInt(products_quantity);
          updateRs.updateInt(products_quantity, getSum(oldQty, qtyStr));
          double oldRev = updateRs.getDouble(products_total);
          updateRs.updateDouble(products_total, getSum(oldRev, qtyStr, priceStr));
          //Отсылка изменений в базу данных
          updateRs.updateRow();
        }
      }
    } catch (ClassNotFoundException e) {
...

В данном случае SQL-оператор является единственным местом, где строки должны комбинироваться сложным способом. Переменные, представляющие имена столбцов просто указываются в методе updateXXX(). Остается, однако, серьезная проблема: что если запись вообще не существует?

Вставка новой записи

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

Данный пример показывает, как проверить существование текущей записи и как создать новую, если проверка показала, что ее нет:

...
    String updateSqlTxt = "select * from " + products_table
          + " where " + products_productid
          + " = '" + thisProductid + "'";
    ResultSet updateRs = statement.executeQuery(updateSqlTxt);
    if (updateRs.next()) {
      //Получение нового количества и модификация ResultSet
      int oldQty = updateRs.getInt(products_quantity);
      updateRs.updateInt(products_quantity, getSum(oldQty, qtyStr));
      double oldRev = updateRs.getDouble(products_total);
      updateRs.updateDouble(products_total, getRev(oldRev, qtyStr, priceStr));
      //Отсылка изменений в базу данных
      updateRs.updateRow();
    } else {
      //Создание новой строки вставки
      updateRs.moveToInsertRow();
      //Добавление данных в строку вставки
      updateRs.updateInt(products_productid, getSum(0, thisProductid));
      updateRs.updateInt(products_quantity, getSum(0, qtyStr));
      updateRs.updateDouble(products_total, getRev(0, qtyStr, priceStr));
      //Отсылка новой строки в базу данных
      updateRs.insertRow();
    }
...

Как и при модификации, никакие изменения в базе данных на самом деле при вызове методов updateXXX не производятся. Только когда вы выполняете insertRow(),база данных получает новые данные. (В данном случае getSum() и getRev использованы для преобразования тиров.)

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

 

Раздел 8. Итоги по вставке данных при помощи JDBC/XML

Резюме

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

Данные вводятся в базу данных на основе внешнего XML-файла отображения, который определяет имена базы данных, таблиц, столбцов и имена элементов и атрибутов в XML-файле, хранящем данные, вставляемые в базу данных.

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

Ресурсы

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

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

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

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


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