Содержание


Связывание с данными с помощью Castor

Часть 4. Связывание Java-объектов с базами данных SQL

Использование Castor для связывания Java-объектов и хранилищ данных SQL

Comments

Серия контента:

Этот контент является частью # из серии # статей: Связывание с данными с помощью Castor

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Связывание с данными с помощью Castor

Следите за выходом новых статей этой серии.

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

Как правило, большинство программистов, слыша термин связывание с данными, представляют себе связывание с XML-данными. Не замечая этой маленькой разницы (XML), разработчики часто проходят мимо широкого круга возможностей, особенно при использовании API Castor, так как в этом случае XML представляет собой лишь частный случай связывания. В дополнение к нему Castor позволяет связывать Java-объекты со строками в базах данных SQL. Эта возможность получила название связывание с SQL-данными.

Определение связывания с SQL-данными

Несмотря на то что термин связывание с SQL-данными может звучать непривычно, в нем нет ничего сложного. Лучше всего рассматривать его в свете того, что обозначает более привычный термин связывание с XML-данными. В последнем случае подразумевается процесс отображения данных в XML-документе (который как правило хранится в виде набора элементов и атрибутов) на свойства классов объектной модели в Java. Для преобразования данных из одного представления в другое и обратно используются маршаллер и демаршаллер. Первый принимает на вход данные Java-объектов и сохраняет их внутри документа XML, а второй извлекает данные из документа XML и загружает их в свойства классов объектной модели в Java.

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

Важность связывания с SQL-данными

Сразу после своего появления Java представляла собой фактически игрушечный язык программирования, в основном благодаря очень простому API, а также ориентированности на графические приложения (помните AWT?). Поворотным моментом в процессе развития Java сыграло появление интерфейса для соединения с базами данных JDBC (Java Database Connectivity), при помощи которого стало возможным сохранять информацию в хранилищах данных SQL. Единственной проблемой, которая остается актуальной и по сей день, была громоздкость JDBC. Он не очень сложен, но в большинстве приложений его использование требует немалых дополнительных усилий.

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

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

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

Отображение по-прежнему необходимо

При работе со связыванием с SQL-данным особенно важна возможность описания сопоставления Java-объектов и SQL-схем, не будучи при этом жестко привязанным к конкретным именам в Java или SQL. Структура реляционной базы данных как правило отличается от объектной модели Java еще более радикально, чем XML-схема. Таблицы содержат наборы записей, в то время как объекты представляют собой единичные элементы данных (например, они могут соответствовать строкам). При этом необходимо преобразовывать и сохранять связи между объектами, так же как и связи между таблицами. Однако в объектной модели нет ничего аналогичного оператору join для связей типа "один-ко-многим" и тем более для "многие-ко-многим".

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

Это то же самое, что JDO, не так ли?

Это не совсем так. Скорее некая разновидность JDO. Здесь необходимы пояснения.

Sun выпустила спецификацию, известную под аббревиатурой JDO (Java Data Objects – объекты данных Java). В документах JSR 12 и JSR 243 (запросы на спецификацию Java) был описан специфический подход к связыванию с SQL-данными (хотя сам термин "связывание с SQL-данными" там не употреблялся). Если вы обратитесь к документации по JDO, а затем перечитаете введение к данной статье, то, возможно, вам покажется, что это одно и то же. Однако Castor-реализация JDO (официально Castor называется именно так, что добавляет путаницы) отличается от реализации Sun. Более того, она вообще не имеет никакого отношения к JDO от Sun за тем исключением, что обе преследуют одну и ту же цель.

Это легко может привести разработчиков в замешательство, поэтому повторим еще раз: используя Castor, вы работаете не со стандартными API Sun. Однако это не настолько плохо, как может показаться. Разработчики Castor стараются перенести множество возможностей, предоставляемых API Castor, в API Sun для связывания с данными и JDO. Таким образом, несмотря на то что сам Castor не является стандартным API, многие его функции, в том числе API, могут перекочевать в стандартизированный API JDO.

Подготовка среды для запуска примеров

При использовании связывания с XML-данными двумя полярными представлениями являются объектная модель Java и XML-документ (а точнее, XML-схема, определяющая формат документа). В случае SQL-данных объектная модель Java остается без изменений, но в качестве второго представления выступает SQL-схема в виде одной или нескольких таблиц с колонками. Ниже приведены примеры достаточно простых объектных моделей и SQL-схем.

Далее мы будем работать с той же объектной моделью, описывающей книги и их авторов, что и в предыдущих статьях серии. Класс Book приведен в листинге 1.

Листинг 1. Класс Book
package ibm.xml.castor;

import java.util.LinkedList;
import java.util.List;

public class Book {

  /** ISBN-код книги */
  private String isbn;
  /** Заголовок книги */
  private String title;
  /** Список авторов */
  private List<Author> authors;

  public Book() { }

  public Book(String isbn, String title, List<Author> authors) {
    this.isbn = isbn;
    this.title = title;
    this.authors = authors;
  }

  public Book(String isbn, String title, Author author) {
    this.isbn = isbn;
    this.title = title;
    this.authors = new LinkedList<Author>();
    authors.add(author);
  }

  public void setIsbn(String isbn) {
    this.isbn = isbn;
  }

  public String getIsbn() {
    return isbn;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getTitle() {
    return title;
  }

  public void setAuthors(List<Author> authors) {
    this.authors = authors;
  }

  public List<Author> getAuthors() {
    return authors;
  }

  public void addAuthor(Author author) {
    authors.add(author);
  }
}

Код класса Author показан в листинге 2.

Класс 2. Класс для представления авторов
package ibm.xml.castor;

public class Author {

  private String firstName, lastName;

  public Author() { }

  public Author(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public String getFirstName() {
    return firstName;
  }
  
  public String getLastName() {
    return lastName;
  }

}

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

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

Создание схемы базы данных SQL для запуска примеров

В некотором смысле схему базы данных SQL часто легче сопоставить с объектной моделью Java, чем XML-схему, особенно если она не очень сложна (как в нашем примере). Далее мы рассмотрим две таблицы: dw_books и dw_authors.

Легче сначала создать таблицу dw_authors, потому что на нее ссылается таблица dw_books. Таблица должна состоять из трех колонок: идентификаторы автора, его имени и фамилии. Если вы собираетесь запускать примеры в процессе чтения статьи, то создайте таблицу, запустив скрипт MySQL из листинга 3.

Листинг 3. Таблица для хранения авторов
CREATE TABLE 'dw_authors' (
'id' INT NOT NULL ,
'first_name' VARCHAR( 50 ) NOT NULL ,
'last_name' VARCHAR( 50 ) NOT NULL ,
PRIMARY KEY ( 'id' ) ,
INDEX ( 'first_name' , 'last_name' )
);

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

Далее создадим таблицу dw_books. Она также довольно проста, ее определение на языке определения данных DDL (языке, используемом для создания SQL-объектов) показано в листинге 4.

Листинг 4. Фрагмент DDL для создания таблицы dw_books
CREATE TABLE 'dw_books' (
'isbn' VARCHAR( 13 ) NOT NULL ,
'title' VARCHAR( 200 ) NOT NULL ,
PRIMARY KEY ( 'isbn' ) ,
INDEX ( 'title' )
);

В конечном счете, данные таблицы необходимо будет связать. В этот момент становится очевидной следующая проблема: одна книга может быть написана несколькими авторами, а один человек может быть автором нескольких книг. Другими словами, таблицы dw_books и dw_authors должны быть связаны отношением типа «многие-ко-многим». Это означает, что необходима дополнительная таблица для хранения такого рода связей.

В мире SQL подобная ситуация встречается довольно часто. Необходимо создать таблицу, каждая строка которой будет содержать идентификатор автора и ISBN-код книги. Определение таблицы показано в листинге 5.

Листинг 5. Таблица для хранения отношения типа "многие-ко-многим" между книгами и авторами
CREATE TABLE 'dw_books_authors' (
'book_isbn' VARCHAR( 13 ) NOT NULL ,
'author_id' INT NOT NULL ,
PRIMARY KEY ( 'book_isbn' , 'author_id' )
);

О ссылочной целостности

Ссылочная целостность – это термин, обозначающий поддержание базы данных в согласованном состоянии, исключающем некорректные или неиспользуемые данные, а также обеспечивающем возможность соединения таблиц в запросах. В данном контексте это означает, что при удалении книги должны также быть удалены все строки в таблице dw_books_authors table, которые содержат ISBN удаляемой книги. Ни одна запись, относящаяся к удаленной книге, не должна сохраниться в базе данных. То же самое относится и к авторам: после удаления автора должны быть также удалены все строки, содержащие его ID.

Для обеспечения подобной согласованности используются внешние ключи. В данном случае колонка book_isbn таблицы dw_books_authors является внешним ключом, связывающим ее с таблицей dw_books через поле isbn. Аналогичный внешний ключ построен на колонках author_id и id в таблицах dw_books_authors и dw_authors соответственно. Таким образом, обеспечивается ссылочная целостность в этих двух таблицах.

Однако ссылочная целостность по-разному реализуется в разных СУБД. В частности, она не полностью поддерживается во многих версиях MySQL. Поэтому, чтобы упростить изложение материала, мы не будем использовать внешние ключи. Если у вас есть желание их использовать, то убедитесь в том, что они поддерживаются, обратившись к документации по вашей СУБД, либо спросите у вашего администратора базы данных (DBA).

Подготовка к связыванию с данными SQL

Даже если вы ранее применяли Castor для связывания с XML-данными, вам скорее всего придется сделать некоторые изменения в программном окружении, чтобы начать использовать связывание с SQL-данными.

Добавление файлов JDO Castor в classpath приложения

В первую очередь необходимо добавить классы JDO Castor в classpath нашего приложения. Если вы раньше выполнили все действия по установке Castor, описанные в первой статье серии (ссылка приведена в разделе Ресурсы), то у вас уже должны быть загружены все JAR-файлы Castor. Однако возможно, что в classpath не содержатся специальные JAR-файлы, относящиеся к JDO. В этом случае найдите в каталоге установки Castor файл castor-1.1.2.1-jdo.jar и добавьте его в classpath (учтите, что у вас может быть более поздняя версия файла).

Кроме этого файла в classpath также должны присутствовать те, которые вы скорее всего добавили ранее, а именно: castor-1.1.2.1.jar, castor-1.1.2.1-xml.jar, а также JAR-файлы Xerces и Commons Logging. Если вы используете сервер приложений или IDE для запуска примеров, то убедитесь, что соответствующие classpath были также изменены аналогичным образом.

Добавление драйвера JDBC

Для подключения к базе данных SQL необходим JDBC-драйвер для соответствующей СУБД. Драйверы для таких СУБД как IBM DB2®, Informix®, Oracle, MySQL, PostgreSQL и т.д. распространяются бесплатно. Если у вас уже есть приложение, работающее с базами данных, то, вероятно, соответствующий драйвер был установлен ранее. Если же вы впервые собираетесь использовать JDBC, то обратитесь к разделу Ресурсы, в котором приведены ссылки на JDBC-драйверы от DataDirect, которые успешно работают с базами данных DB2, Informix и некоторыми другими. Если вы используете другую СУБД, то нужный драйвер можно легко найти в Интернете при помощи поиска в Google. Загрузите JAR-файл драйвера и добавьте его в classpath.

В большинстве примеров к этой статье будет использоваться MySQL. Для этого нам понадобится JAR mysql-connector-java-3.0.17-ga-bin.jar, который можно загрузить бесплатно с сайта MySQL (обратитесь к разделу Ресурсы за более подробной информацией).

Отображение Java-класса в таблицу SQL

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

Сопоставление класса и таблицы

Вначале необходимо описать отображение класса Author в таблицу dw_authors. При этом соответствие между полями класса и колонками таблицы должно выглядеть, как показано в таблице 1.

Таблица 1. Сопоставление полей класса Author и колонок таблицы dw_authors

Имя свойства в JavaНазвание колонки в SQL
firstNamefirst_name
lastNamelast_name

Как видите, пример очень прост, но существует одна очевидная проблема, заключающаяся в том, что в таблице dw_authors присутствует колонка id. Более того, она обязательна, потому что выступает в роли первичного ключа. Поэтому придется добавить соответствующее поле в класс Author, как показано в листинге 6.

Листинг 6. Добавление идентификатора (поле ID) в класс Author
package ibm.xml.castor;

public class Author {

  private String firstName, lastName;
  private int id;

  public Author() { }

  public Author(int id, String firstName, String lastName) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public void setId(int id) {
    this.id = id;
  }

  public int getId() {
    return id;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public String getFirstName() {
    return firstName;
  }
  
  public String getLastName() {
    return lastName;
  }

}

В таблице 2 показан обновленный вариант сопоставления полей экземпляров Author и колонок в таблице dw_authors.

Таблица 2. Добавление поля ID в отображение

Имя свойства в JavaНазвание колонки в SQL
idid
firstNamefirst_name
lastNamelast_name

Описание отображения для Castor

Теперь осталось объяснить Castor, как отображать поля Java-объектов на колонки таблицы базы данных. Для этого используются файлы отображения, схожие с теми, которые обсуждались в третьей статье серии. Файл для сохранения экземпляров Author в таблице dw_authors показан в листинге 7.

Листинг 7. Отображение класса Author в таблицу dw_authors
<mapping>
  <class name="ibm.xml.castor.Author" identity="id">
    <map-to table="dw_authors" />
    <field name="id" type="int">
      <sql name="id" type="integer" />
    </field>
    <field name="firstName" type="string">
      <sql name="first_name" type="varchar" />
    </field>
    <field name="lastName" type="string">
      <sql name="last_name" type="varchar" />
    </field>
  </class>
</mapping>

Файл не представляет собой ничего особенного по сравнению с отображениями объектов Java в документы XML. Сохраните его под именем sql-mapping.xml. Этого достаточно для Castor чтобы трансформировать объекты Java в строки в таблицах и обратно.

Атрибут identity

Единственным новым элементом по сравнению с файлами, описывающими отображение между Java и XML, являет атрибут identity, который содержит имя колонки, являющейся первичным ключом в таблице dw_authors. Скорее всего раньше вам не приходилось использовать данный атрибут, так как в случае XML он необязателен, или Castor вычисляет его значение автоматически. Однако при связывании с данными SQL этот атрибут необходим для уникальной идентификации объектов. В случае Author для этого служит поле id.

Конфигурирование Castor для доступа к базе данных

Если бы речь шла о связывании с XML-данными, то все уже было бы готово к программированию маршаллинга (преобразования из Java в XML) и демаршаллинга (из XML в Java). Однако в случае SQL появляется дополнительный компонент, с которым необходимо взаимодействовать – база данных. Необходимо указать Castor где она находится, как к ней подсоединиться и какой файл отображения использовать. Для этих целей служит конфигурационный файл, обычно называемый наподобие jdo-conf.xml. При работе с MySQL можно использовать файл, показанный в листинге 8.

Листинг 8. Конфигурирование Castor для соединения с базой данных MySQL
<?xml version="1.0" ?>
<!DOCTYPE jdo-conf PUBLIC "-//EXOLAB/Castor JDO Configuration DTD Version 1.0//EN"
                          "http://castor.org/jdo-conf.dtd">
<jdo-conf>
  <database name="YOUR-DATABASE-NAME" engine="mysql">
    <driver class-name="com.mysql.jdbc.Driver"
            url="jdbc:mysql://YOUR-DATABASE-SERVER-HOSTNAME/YOUR-DATABASE-NAME">
      <param name="user" value="YOUR-DATABASE-USERNAME"/>
      <param name="password" value="YOUR-DATABASE-PASSWORD"/>
    </driver>
    <mapping href="sql-mapping.xml"/>
  </database>
  <transaction-demarcation mode="local"/>
</jdo-conf>

Добавление, поиск и удаление записей

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

Листинге 9. Использование связывания с данными на примере экземпляра Author
import ibm.xml.castor.Author;

import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;

public class SQLTester {
  public static void main(String args[]) {
    try {
      JDOManager.loadConfiguration("jdo-conf.xml");
      JDOManager jdoManager = JDOManager.createInstance("bmclaugh");

      Database database = jdoManager.getDatabase();
      database.begin();
      Author author = new Author(1001, "Joseph", "Conrad");
      database.create(author);

      Author lookup = (Author)database.load(Author.class,
                                    new Integer(1001));
      System.out.println("Located author is named " +
        author.getFirstName() + " " + author.getLastName());
      database.remove(lookup);
      database.commit();
      database.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Далее мы рассмотрим эти операции поочередно.

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

JDOManager.loadConfiguration("jdo-conf.xml");
JDOManager jdoManager = JDOManager.createInstance("bmclaugh");
Database database = jdoManager.getDatabase();
database.begin();

Главной задачей является получение ссылки на экземпляр Database, через который можно создавать, искать и удалять записи. Как только ссылка на этот объект получена, можно создать новый объект класса Author и сохранить его в базе данных следующим образом:

Author author = new Author(1001, "Joseph", "Conrad");
database.create(author);

В момент вызова метода create() Castor использует файл отображения (sql-mapping.xml), который содержит всю необходимую информацию о сохранении объекта, переданного в данный метод, в таблице SQL. Затем информация объекта сохраняется в базе данных, и ее можно использовать по вашему усмотрению.

Далее вы можете выполнять поиск объектов по их идентификаторам. Здесь свою роль начинает играть упомянутый выше атрибут identity.

Author lookup = (Author)database.load(Author.class,
                                    new Integer(1001));
System.out.println("Located author is named " +
  author.getFirstName() + " " + author.getLastName());

Для поиска служит метод load(), который принимает в качестве аргументов тип класса для искомых объектов, а также идентификатор объекта. Метод возвращает объект в виде нового экземпляра класса, который вы можете использовать как и любой другой Java-объект. При создании и наполнении объекта данными вновь используется отображение, описанное в файле sql-mapping.xml.

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

database.remove(lookup);

В конце необходимо подтвердить транзакцию и закрыть соединение с базой данных.

database.commit();
database.close();

Добавление отношений

Как видите, в связывании единичного класса с таблицей нет ничего сложного. Однако на практике как правило встречаются более интересные ситуации, например, работа с объектной моделью Java, включающей в себя экземпляры Book и Author, связанные отношением.

Описание базового отображения книги на таблицу SQL

Как и в случае с авторами, необходимо описать отображение свойств класса Book при сохранении его экземпляров в базе данных. В листинге 10 выделены строки, которые необходимо добавить в файл sql-mapping.xml для определения соответствия между базовыми свойствами класса Book и колонками в таблице.

Листинг 10. Добавление класса Book в файл отображения
<mapping>
  <class name="ibm.xml.castor.Book" identity="isbn">
    <map-to table="dw_books"/>
    <field name="isbn" type="string">
      <sql name="isbn" type="varchar" />
    </field>
    <field name="title" type="string">
      <sql name="title" type="varchar" />
    </field>
  </class>

  <class name="ibm.xml.castor.Author" identity="id">
    <map-to table="dw_authors" />
    <field name="id" type="int">
      <sql name="id" type="integer" />
    </field>
    <field name="firstName" type="string">
      <sql name="first_name" type="varchar" />
    </field>
    <field name="lastName" type="string">
      <sql name="last_name" type="varchar" />
    </field>
  </class>
</mapping>

Как видите, эти строки выглядят достаточно предсказуемо. Они описывают соответствие полей класса Book (isbn и title) и колонок таблицы dw_books.

Описание отношения типа "многие-ко-многим"

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

  • Каждая книга имеет уникальный код ISBN.
  • Каждый автор имеет уникальный идентификатор (ID).
  • Если автор участвовал в написании книги, то это отражается в строке таблицы dw_books_authors. Строка содержит ISBN книги и ID автора.
  • Одна книга может быть написана несколькими авторами. В этом случае таблица dw_books_authors будет содержать несколько строк с одинаковым ISBN книги, но разными ID авторов.
  • Один человек может быть автором нескольких книг. В этом случае в таблице dw_books_authors будут храниться несколько строк с одним ID автора, но разными ISBN-кодами книг.

Сначала рассмотрим это отношение со стороны класса Book. Каждый экземпляр Book должен хранить список авторов в специальном свойстве authors. То же самое верно и для авторов: если свойство authors экземпляра Book содержит список объектов, то они должны храниться в виде отдельных экземпляров (авторов) в соответствии со своими ID. Данное свойство (authors) необходимо отобразить на таблицу dw_books_authors.

Для этого нам понадобится еще один элемент field при описании отображения (листинг 11).

Листинг 11. Дополнительный элемент field для отображения свойства authors класса Book
<field name="authors" type="ibm.xml.castor.Author"
       collection="arraylist">
</field>

До этого момента вы не узнали ничего нового. Мы просто описали отображение свойства authors, при этом типом свойства является класс, а не примитивный тип вроде string или int. Для указания того, что значением свойства является коллекция, используется атрибут collection (collection="arraylist").

Однако далее необходимо указать поле для хранения ID авторов, а также нужную таблицу, потому что информация об авторах никогда не помещается в таблицу dw_books. В листинге 12 показано, как задать имя таблицы для отношения типа "многие-ко-многим" (dw_books_authors), а также колонку для хранения ISBN книги в этой таблице.

Листинг 12. Использование элемента sql для отображения отношения типа "многие-ко-многим"
<field name="authors" type="ibm.xml.castor.Author"
       collection="arraylist">
  <sql name="author_id"
       many-table="dw_books_authors"
       many-key="book_isbn" />
</field>

Внимательно разберитесь с этим моментом. Главное помните, что данный элемент описывает отображение свойства authors экземпляров класса Book. В качестве отображаемых значений выступают экземпляры Author, идентифицируемые при помощи свойства id, которому необходимо поставить в соответствие колонку author_id. Однако эта колонка находится не в таблице dw_books, поэтому используется атрибут many-table, указывающий на нужную таблицу dw_books_authors. Наконец, атрибут many-key говорит о том, что в данной таблице нужно также сохранять ISBN книги.

В листинге 13 показан файл sql-mapping.xml целиком, включая фрагмент, отвечающий за отображение отношения.

Листинг 13. Отображение свойства authors класса Book (отношения типа "многие-ко-многим")
<mapping>
  <class name="ibm.xml.castor.Book" identity="isbn">
    <map-to table="dw_books"/>
    <field name="isbn" type="string">
      <sql name="isbn" type="varchar" />
    </field>
    <field name="title" type="string">
      <sql name="title" type="varchar" />
    </field>
    <field name="authors" type="ibm.xml.castor.Author"
           collection="arraylist">
      <sql name="author_id"
           many-table="dw_books_authors"
           many-key="book_isbn" />
    </field>
  </class>

  <class name="ibm.xml.castor.Author" identity="id">
    <map-to table="dw_authors" />
    <field name="id" type="int">
      <sql name="id" type="integer" />
    </field>
    <field name="firstName" type="string">
      <sql name="first_name" type="varchar" />
    </field>
    <field name="lastName" type="string">
      <sql name="last_name" type="varchar" />
    </field>
  </class>
</mapping>

Тестирование отображения

В листинге 14 показана обновленная версия программы SQLTester, которая создает экземпляр Author и написанную им книгу (экземпляр Book).

Листинг 14. Сохранение нового автора и книги
import java.util.Iterator;

import ibm.xml.castor.Author;
import ibm.xml.castor.Book;

import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;

public class SQLTester {
  public static void main(String args[]) {
    try {
      JDOManager.loadConfiguration("jdo-conf.xml");
      JDOManager jdoManager = JDOManager.createInstance("bmclaugh");

      Database database = jdoManager.getDatabase();
      database.begin();
      Author author = new Author(1001, "Joseph", "Conrad");
      Book book = new Book("1892295490", "Heart of Darkness", author);
      database.create(author);
      database.create(book);
      database.commit();

      database.begin();
      Book lookup = (Book)database.load(Book.class, "1892295490");
      System.out.println("Located book is named " + lookup.getTitle());
      System.out.println("Authors:");
      for (Iterator i = lookup.getAuthors().iterator(); i.hasNext(); ) {
        Author bookAuthor = (Author)i.next();
        System.out.println("  " + bookAuthor.getFirstName() + " " +
                                  bookAuthor.getLastName());
      }
      database.commit();
      database.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

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

      Database database = jdoManager.getDatabase();
      database.begin();
      Author author = new Author(1001, "Joseph", "Conrad");
      Book book = new Book("1892295490", "Heart of Darkness", author);
      database.create(author);
      database.create(book);
      database.commit();

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

Для того чтобы максимально приблизить тест к реальности мы сначала подтвердим транзакцию, а затем начнем новую, повторно вызвав метод database.begin()после сохранения книги и автора. Это гарантирует, что во второй части программы происходит доступ к данным, реально хранящимся в базе, а не в локальном кэше Castor.

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

Взгляните на содержимое таблиц и убедитесь, что оно отвечает вашим ожиданиям. Строка в таблице dw_books должна выглядеть примерно как показано в таблице 3.

Таблица 3. Содержимое таблицы dw_books после запуска SQLTester

isbntitle
1892295490Heart of Darkness

Содержимое таблицы dw_authors после вставки должно выглядеть как показано в таблице 4.

Таблица 4. Содержимое таблицы dw_authors после запуска SQLTester

idfirst_namelast_name
1001JosephConrad

Наконец, в таблице 5 показана строка, связывающая сохраненные экземпляры книги и ее автора. В каком-то смысле это наиболее важные данные из всего, что было показано выше. Очевидно, что если бы книги и авторов нельзя было связывать, то от хранения их в базе данных также было бы мало пользы.

Таблица 5. Содержимое таблицы dw_books_authors после запуска SQLTester

book_isbnauthor_id
18922954901001

Удаление данных

В листинге 15 показана маленькая программа, которая удаляет строки, вставленные при помощи SQLTester. По мере того как вы будете практиковаться в связывании с данными SQL, добавляя в базу новые книги и новых авторов (как мы вам настойчиво рекомендуем), вы можете обновлять этот класс для удаления вставленных строк. Разделение программ для вставки и удаления записей позволяет проверить содержимое базы данных после сохранения объектов. Это важно для усвоения материала, а также обнаружения любых ошибок, которые могут произойти.

Листинг 15. Удаление строк, добавленных в результате запуска SQLTester
import ibm.xml.castor.Author;
import ibm.xml.castor.Book;

import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;

public class SQLClean {
  public static void main(String args[]) {
    try {
      JDOManager.loadConfiguration("jdo-conf.xml");
      JDOManager jdoManager = JDOManager.createInstance("bmclaugh");
      Database database = jdoManager.getDatabase();
      database.begin();
      try {
        Book book = (Book)database.load(Book.class, "1892295490");
        database.remove(book);
      } catch (org.exolab.castor.jdo.ObjectNotFoundException ignored) { }
      try {
        Author author = (Author)database.load(Author.class, 1001);
        database.remove(author);
      } catch (org.exolab.castor.jdo.ObjectNotFoundException ignored) { }
      database.commit();
      database.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

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

Castor не сохраняет отношения по умолчанию

Одним из интересных моментов в коде SQLTester (см. листинг 14) является то, что экземпляры Author и Book должны оба явно сохраняться в базе данных. Если исключить один из вызовов метода create, то сохранен будет только один объект, при этом таблица dw_books_authors останется пустой.

С точки зрения программистов Java подобное поведение выглядит несколько нелогично. Экземпляры Book и Author связаны, не так ли? Можно ли сохранять книгу без автора? По умолчанию Castor поступает именно так. Однако существует возможность заставить Castor автоматически сохранять все подобные связи. Для этого достаточно выполнить следующее:

database.setAutoStore(true);

Данный метод необходимо вызывать до начала первой транзакции. Пример использования Castor в таком режиме показан в листинге 16, который является немного модифицированным вариантом листинга 14.

Листинг 16. Проверка автосохранения отношений
import java.util.Iterator;

import ibm.xml.castor.Author;
import ibm.xml.castor.Book;

import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;

public class SQLTester {
  public static void main(String args[]) {
    try {
      JDOManager.loadConfiguration("jdo-conf.xml");
      JDOManager jdoManager = JDOManager.createInstance("bmclaugh");

      Database database = jdoManager.getDatabase();
      database.setAutoStore(true);
      database.begin();
      Author author = new Author(1001, "Joseph", "Conrad");
      Book book = new Book("1892295490", "Heart of Darkness", author);
      // Мы можем сохранить только книгу. Castor автоматически сохранит автора
      // database.create(author);
      database.create(book);
      database.commit();

      database.begin();
      Book lookup = (Book)database.load(Book.class, "1892295490");
      System.out.println("Located book is named " + lookup.getTitle());
      System.out.println("Authors:");
      for (Iterator i = lookup.getAuthors().iterator(); i.hasNext(); ) {
        Author bookAuthor = (Author)i.next();
        System.out.println("  " + bookAuthor.getFirstName() + " " +
                                  bookAuthor.getLastName());
      }
      database.commit();
      database.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

Не забудьте запустить SQLClean для удаления ранее добавленных записей, а потом запустите показанный выше класс. Результаты должны совпадать с полученными ранее, однако в данном случае экономится один вызов database.create(). Это может выглядеть незначительно, но представьте себе ситуацию с сохранением сотен или даже тысяч книг вместе с авторами. В таком случае выгода становится весьма ощутимой.

Заключение

В отличие от технологии для связывания с XML-данными, которые в настоящее время достаточно стабильны и чаще дополняются, чем изменяются, JDO (причем как версия от Sun, так и Castor) все еще находятся в фазе активного развития. Однако это не означает, что их следует избегать, потому что использование связывания с SQL-данными и любой реализации JDO способно упростить процесс разработки приложений. Данную статью следует рассматривать скорее как отправную точку для изучения этих технологий, а не как безусловное руководство.

Продолжайте практиковаться в связывании с SQL-данными, чтобы понять, будет ли оно полезно в вашем приложении (практически во всех случаях ответ будет "да"). Затем попробуйте в деле обе версии JDO, от Sun и Castor, сравните и выберите ту, которая вам больше по душе. Несмотря на то, что Castor не является стандартной технологией, он более гибок. Кроме того, принимая во внимание сотрудничество разработчиков Castor с Sun, можно надеяться, что официальный стандарт в будущем впитает в себя многие возможности Castor.

А самое главное – совершенствуйте свое искусство программирования. Изучайте связывание с данными SQL, в частности то, как можно извлечь из него пользу, облегчить процесс разработки, а в конечном итоге – повысить качество вашей работы и создаваемых вами приложений. Попробуйте совместить связывание с XML и SQL-данными в одном приложении, так как это поможет вам понять общие принципы отображения. В итоге вы убедитесь в пользе данной технологии и, вероятно, практически полностью избавитесь от JDBC (при этом ни у кого не повернется язык критиковать вас за это).


Ресурсы для скачивания


Похожие темы

  • Оригинал статьи: Data binding with Castor, Part 4: Bind your Java objects to SQL databases (Брэт Маклафлин, developerWorks, апрель 2008 г.). (EN)
  • Прочитайте первую статью серии: "Связывание с данными с помощью Castor, часть 1: Установка и настройка Castor" (Брэт Маклафлин, developerWorks, ноябрь 2007 г.), в которой описывается весь процесс установки Castor на вашем компьютере, в том числе загрузка, инсталляция, настройка, конфигурирование, пути к файлам классов и много другое.
  • Во второй статье серии: "Связывание с данными с помощью Castor, часть 2: Маршаллинг и демаршаллинг в XML" (Брэт Маклафлин, developerWorks, декабрь 2007 г.) рассказывается о преобразовании Java-объектов к XML-представлению и их обратном восстановлении из XML. Статья также рассказывает о принципах работы Castor и о том, как создавать классы, совместимые с API Castor.
  • Прочитав третью статью: "Связывание с данными с помощью Castor, часть 3: Отображение между схемами" (Брэт Маклафлин, developerWorks, январь 2008 г.), вы узнаете о том, как при помощи Castor преобразовать данные, хранящиеся в громоздких или неудобных XML-документах, в экземпляры ваших классов Java. При этом вы не будете чувствовать неудобств, обусловленных именами элементов в XML или свойствами Java-классов.
  • Посетите Web-сайт Castor, который содержит множество ресурсов по данной технологии. (EN)
  • Обратитесь к документации Java по классам Castor. (EN)
  • В статье "Введение в JDO Castor" приводится отличное введение в объектно-реляционное отображение и инфраструктуру для связывания с данными. (EN)
  • Посетите страницу Java Data Objects на сайте Sub – главный ресурс, посвященный официальной версии JDO. (EN)
  • Ознакомьтесь с документом JSR 12 – первоначальной спецификацией Java-объектов данных (JDO 1.0). (EN)
  • Прочитайте документ JSR 243, представляющий собой спецификацию JDO 2.0, в которой были предприняты попытки упростить данную технологию, расширить область ее применения и стандартизировать поддержку баз данных. (EN)
  • Драйвер JDBC для MySQL: загрузите и узнайте больше о данном JDBC-драйвере на сайте MySQL.com. (EN)
  • Прочитайте достаточно давно написанную статью под названием "Начало работы с JDO в Castor" (Брюс Снайдер, Bruce Snyder, developerWorks, август 2002 г.). Некоторые конструкции языка, описанные в статье, устарели, но общие идеи по-прежнему актуальны. (EN)
  • Ознакомьтесь со статьей "Связывание с данными на практике" (Брэт Маклафлин, developerWorks, май 2004 г.), являющейся вводной в серии, посвященной JAXB – API для связывания с данными от Sun. (EN)
  • Книга "Java и XML, 3-е издание" (Брэт Маклафлин и Джастин Эдельсон, Justin Edelson, O'Reilly Media, Inc.) описывает все аспекты XML, сначала и до конца, включая вопросы связывания с данными и отображения. (EN)
  • В более ранней книге "Java и связывание с XML-данными" (Брэт Маклафлин, O'Reilly Media, Inc.) приводится информация о Castor и общих принципах связывания с данными. (EN)
  • Если вы хотели бы получить платную поддержку Castor, то ознакомьтесь со списком коммерческих услуг. (EN)
  • Загрузите бесплатную версию DB2 под названием DB2 Express-C, которая идеально подходит для экспериментов с JDO. IBM DB2 – это SQL-СУБД, с которой можно без проблем работать через JDO Castor и стандартный JDO от Sun. (EN)
  • Посетите страницу Informix на сайте IBM, посвященную серии промышленных серверных продуктов для управления базами данных. (EN)
  • JDBC-драйверы для DB2 и Informix: загрузите единый программный пакет с сайта DataDirect. (EN)
  • Сертификация по XML корпорации IBM: узнайте, как стать сертифицированным разработчиком IBM в области XML и связанных с ним технологий. (EN)
  • Обратитесь к технической библиотеке XML, содержащей множество статей, советов, руководств, стандартов и справочников IBM Redbooks. (EN)
  • Материалы на все темы, касающиеся XML, можно найти в разделе XML на сайте developerWorks.
  • Скачайте ознакомительные версии программного обеспечения IBM: Используйте в вашем следующем проекте ознакомительные версии ПО, которые можно скачать прямо с сайта IBM developerWorks. (EN)
  • Подкасты: следите за трансляцими выступлений технических экспертов IBM. (EN)

Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML, Технология Java
ArticleID=367536
ArticleTitle=Связывание с данными с помощью Castor: Часть 4. Связывание Java-объектов с базами данных SQL
publish-date=01302009